diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index b04f4260c3bb87a985fb44cc330c085c0b39e879..6cb1b34997ed39b72eacf8889552650366453344 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -16,12 +16,22 @@ jobs: steps: - uses: actions/checkout@v3.3.0 + if: github.event_name != 'pull_request_target' with: fetch-depth: 0 submodules: true - - name: Checkout HEAD + - uses: actions/checkout@v3.3.0 if: github.event_name == 'pull_request_target' - run: git checkout ${{ github.head_ref }} + with: + fetch-depth: 0 + submodules: true + ref: "refs/pull/${{ github.event.number }}/merge" + - name: Checkout actual HEAD + if: github.event_name == 'pull_request_target' + id: rev + run: | + echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT + git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3) - name: Install pnpm uses: pnpm/action-setup@v2 with: @@ -68,7 +78,7 @@ jobs: if: github.event_name == 'pull_request_target' id: chromatic_pull_request run: | - DIFF="${{ github.base_ref }} HEAD" + DIFF="${{ steps.rev.outputs.base }} HEAD" if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then DIFF="HEAD" fi @@ -76,7 +86,11 @@ jobs: if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then echo "skip=true" >> $GITHUB_OUTPUT fi - pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER") + BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}" + if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then + BRANCH="${{ github.event.pull_request.head.ref }}" + fi + pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER") env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Notify that Chromatic detects changes @@ -91,18 +105,6 @@ jobs: commit_sha: context.sha, body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).' }) - - name: Notify that Chromatic will skip testing - uses: actions/github-script@v6.4.0 - if: github.event_name == 'pull_request_target' && steps.chromatic_pull_request.outputs.skip == 'true' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).' - }) - name: Upload Artifacts uses: actions/upload-artifact@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8594c42d10569531d1a12b1faa15ad760b301b9f..a0630065669ae1a98a12ad45d96892ffa402aac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,44 @@ --> +## 13.13.0 + +### General +- カスタム絵文å—ã”ã¨ã«ãれをリアクションã¨ã—ã¦ä½¿ãˆã‚‹ãƒãƒ¼ãƒ«ã‚’è¨å®šã§ãるよã†ã« +- カスタム絵文å—ã”ã¨ã«é€£åˆã™ã‚‹ã‹ã©ã†ã‹è¨å®šã§ãるよã†ã« +- カスタム絵文å—ã”ã¨ã«ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–フラグをè¨å®šã§ãるよã†ã« +- センシティブãªã‚«ã‚¹ã‚¿ãƒ 絵文å—ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’å—ã‘入れãªã„è¨å®šãŒå¯èƒ½ã« +- タイムラインã«ãƒ•ã‚©ãƒã‚¤ãƒ¼ã®è¡Œã£ãŸä»–人ã¸ã®ãƒªãƒ—ライをå«ã‚ã‚‹ã‹ã©ã†ã‹ã®è¨å®šã‚’アカウントã«ä¿å˜ã™ã‚‹ã®ã‚’ã‚„ã‚るよã†ã« + - 今後ã¯API呼ã³å‡ºã—時ãŠã‚ˆã³ã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°æŽ¥ç¶šæ™‚ã«è¨å®šã™ã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã™ +- リストを公開ã§ãるよã†ã«ãªã‚Šã¾ã—㟠+ +### Client +- リアクションã®å–り消ã—/変更時ã«ç¢ºèªãƒ€ã‚¤ã‚¢ãƒã‚°ã‚’出ã™ã‚ˆã†ã« +- é–‹ç™ºè€…ãƒ¢ãƒ¼ãƒ‰ã‚’è¿½åŠ +- AiScriptã‚’0.13.3ã«æ›´æ–° +- Deck UIを使用ã—ã¦ã„ã‚‹å ´åˆã€`/`以外ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸéš›ã«Zen UIã§è¡¨ç¤ºã™ã‚‹ã‚ˆã†ã« + - メインカラムをè¨ç½®ã—ã¦ã„ãªã„å ´åˆã®å•é¡Œã‚’解決 +- ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°ã®ãƒŽãƒ¼ãƒˆä¸€è¦§ãƒšãƒ¼ã‚¸ã‹ã‚‰ã€ãã®ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°ã§æŠ•ç¨¿ã™ã‚‹ãƒœã‚¿ãƒ³ã‚’è¿½åŠ +- アカウントåˆæœŸè¨å®šã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ã«æˆ»ã‚‹ãƒœã‚¿ãƒ³ã‚’è¿½åŠ +- アカウントã®åˆæœŸè¨å®šã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ã«ã‚ã¨ã§ãƒœã‚¿ãƒ³ã‚’è¿½åŠ +- サーãƒãƒ¼ã«ã‚«ã‚¹ã‚¿ãƒ 絵文å—ã®ç¨®é¡žãŒå¤šã„å ´åˆã®ãƒ‘フォーマンスã®æ”¹å–„ +- Fix: URLプレビューã§æƒ…å ±ãŒå–å¾—ã§ããªã‹ã£ãŸéš›ã®æŒ™å‹•ã‚’ä¿®æ£ +- Fix: Safariã€Firefoxã§ã®æ–°è¦ç™»éŒ²æ™‚ã€ãƒ‘スワードマãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã«ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒç™»éŒ²ã•ã‚Œã¦ã„ãŸæŒ™å‹•ã‚’ä¿®æ£ +- Fix: ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインãŒç„¡åŠ¹ã§ã‚‚投稿ãŒæµã‚Œã¦ã—ã¾ã†å•é¡Œã®ä¿®æ£ +- Fix: ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインã«ã¦å…¨ã¦ã®æŠ•ç¨¿ãŒæµã‚Œã¦ã—ã¾ã†å•é¡Œã®ä¿®æ£ +- Fix: 「アクセストークンã®ç®¡ç†ã€ç”»é¢ã§ã‚¢ãƒ—リã®æƒ…å ±ãŒè¡¨ç¤ºã•ã‚Œãªã„å•é¡Œã®ä¿®æ£ +- Fix: Firefoxã«ãŠã‘る絵文å—ピッカーã®Tabã‚ーフォーカスå•é¡Œã®ä¿®æ£ +- Fix: フォãƒãƒ¼ãƒœã‚¿ãƒ³ãŒãƒ†ãƒ¼ãƒžã®ã‚«ãƒ©ãƒ¼ã‚¹ã‚ームã«ã‚ˆã£ã¦è¦–èªæ€§ãŒæ‚ªããªã‚‹å•é¡Œã‚’ä¿®æ£ + - æ–°ã—ã„プãƒãƒ‘ティ `fgOnWhite` ãŒè¿½åŠ ã•ã‚Œã¾ã—㟠+ +### Server +- bullã‚’bull-mqã«ã‚¢ãƒƒãƒ—グレードã—ã€ã‚¸ãƒ§ãƒ–ã‚ューã®ãƒ‘フォーマンスを改善 +- ストリーミングã®ãƒ‘フォーマンスを改善 +- Fix: 無効化ã•ã‚ŒãŸã‚¢ãƒ³ãƒ†ãƒŠã«ã‚¢ã‚¯ã‚»ã‚¹ãŒã‚ã£ãŸéš›ã«å†åº¦æœ‰åŠ¹åŒ–ã™ã‚‹ã‚ˆã†ã« +- Fix: ãŠçŸ¥ã‚‰ã›ã®ç”»åƒURLを空ã«ã§ããªã„å•é¡Œã‚’ä¿®æ£ +- Fix: i/notificationsã®sinceIdãŒæ©Ÿèƒ½ã—ãªã„å•é¡Œã‚’ä¿®æ£ +- Fix: pageã®ãƒ”ン留ã‚を解除ã™ã‚‹ã“ã¨ãŒã§ããªã„å•é¡Œã‚’ä¿®æ£ + ## 13.12.2 ## NOTE @@ -87,6 +125,7 @@ Meilisearchã®è¨å®šã«`index`ãŒå¿…è¦ã«ãªã‚Šã¾ã—ãŸã€‚値ã¯Misskeyサー * ç”»åƒãŒå…¨ã¦éš ã‚ŒãŸçŠ¶æ…‹ã§è¡¨ç¤ºã•ã‚Œã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã™ - 閲覧注æ„è¨å®šã•ã‚ŒãŸç”»åƒã¯è¡¨ç¤ºã—ãŸçŠ¶æ…‹ã§ã‚‚ãã‚ŒãŒé–²è¦§æ³¨æ„ã ã¨åˆ†ã‹ã‚‹è¡¨ç¤ºã‚’ã™ã‚‹ã‚ˆã†ã« - モデレーターã¯ãƒŽãƒ¼ãƒˆã«æ·»ä»˜ã•ã‚ŒãŸç”»åƒä¸Šã‹ã‚‰ç›´æŽ¥NSFWè¨å®šã§ãるよã†ã« +- 1æžšã ã‘ã®ãƒ¡ãƒ‡ã‚£ã‚¢ãƒªã‚¹ãƒˆã®ç”»åƒã®ã‚¢ã‚¹ãƒšã‚¯ãƒˆæ¯”ã‚’ç”»åƒã«å¿œã˜ã¦ç¸¦é•·ã«ã™ã‚‹ã‚ˆã†ã« - プãƒãƒ•ã‚£ãƒ¼ãƒ«è¨å®šã€Œè¿½åŠ æƒ…å ±ã€ã®é …ç›®ã®å‰Šé™¤ã¨ä¸¦ã³æ›¿ãˆãŒã§ãるよã†ã« - æ–°ã—ã„å®Ÿç¸¾ã‚’è¿½åŠ - AiScriptã‚’0.13.2ã«æ›´æ–° @@ -334,6 +373,7 @@ Meilisearchã®è¨å®šã«`index`ãŒå¿…è¦ã«ãªã‚Šã¾ã—ãŸã€‚値ã¯Misskeyサー - アンテナã§CWも検索対象ã«ã™ã‚‹ã‚ˆã†ã« - ノートã®æ“作部をホãƒãƒ¼æ™‚ã®ã¿è¡¨ç¤ºã™ã‚‹ã‚ªãƒ—ã‚·ãƒ§ãƒ³ã‚’è¿½åŠ - ã‚µã‚¦ãƒ³ãƒ‰ã‚’è¿½åŠ +- enhance(client): MFMã®x2, scale, positionãŒå«ã¾ã‚Œã¦ã„ãŸã‚‰ãƒŽãƒ¼ãƒˆã‚’ãŸãŸã‚€ã‚ˆã†ã« - サーãƒãƒ¼ã®ãƒ‘フォーマンスを改善 ### Bugfixes diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 9185be344cb77afd2a68d407ca7a34cc9bf742a7..827a326d1855cebffa3e86dc15d9fb29d07352d4 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -21,6 +21,8 @@ import './commands' Cypress.on('uncaught:exception', (err, runnable) => { if ([ + 'The source image cannot be decoded', + // Chrome 'ResizeObserver loop limit exceeded', diff --git a/docs/DONATORS.md b/docs/DONATORS.md deleted file mode 100644 index 9da5c1a94e4bb7b4072918e2293bc8c776a389d5..0000000000000000000000000000000000000000 --- a/docs/DONATORS.md +++ /dev/null @@ -1,25 +0,0 @@ -DONATORS -======== -The list of people who have sent donation for Misskey. - -(In random order, honorific titles are omitted.) - -* らãµã -* 俺様 -* ãªãŽã†ã‚Š -* スルメ https://surume.tk/ -* è— -* 音船 https://otofune.me/ -* aqz https://misskey.xyz/aqz -* kotodu "虚無創作ä¸" -* Maya Minatsuki -* Knzk https://knzk.me/@Knzk -* ãã˜ã‚Šã‚ã•ã³ https://knzk.me/@y -* NCLS https://knzk.me/@imncls] -* ã“ã˜ã¾ @skoji@sandbox.skoji.jp - -:heart: Thanks for donating, guys! - ---- - -If your name is missing, please contact us! diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index bfca086f5ce6058fcd58b27c1baeac448e19d65f..5d0fd201b301f74ce3f3bc6ad3b7931edda48f19 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -2,6 +2,7 @@ _lang_: "العربية" headlineMisskey: "شبكة مرتبطة بالملاØظات" introMisskey: "اهلا بك! ميسكي هو منصة تدوين مصغر لا مركزية ومÙتوØØ© المصدر.\nيمكنك مشاركة \"ملاØظات\" عن ما يجري Øولك، وإخبار الجميع عن Ù†Ùسك 📡\nØªØ³Ù…Ø Ù„Ùƒ \"الانÙعالات\" بتعبير عن شعورك Øول ملاØظات الآخرين ðŸ‘\nاكتش٠عالمًا جديدًا 🚀" +poweredByMisskeyDescription: "{name} هو Ø¥Øدى الخÙدمات التي تستخدم المنصة Ù…ÙتوØØ© المصدر <b>ميسكي</b> (يشار إليه كمثيل ميسكي)" monthAndDay: "{day}/{month}" search: "البØØ«" notifications: "الإشعارات" @@ -49,6 +50,7 @@ deleteAndEdit: "إزالة وإعادة الصياغة" deleteAndEditConfirm: "أمتأكد من Øذ٠الملاØظة؟ ستÙقد كل مشاركاتها، والتÙاعلات، والردود عليها." addToList: "أضÙÙ‡ إلى قائمة" sendMessage: "أرسل رسالة" +copyRSS: "انسخ رابط RSS" copyUsername: "انسخ اسم المستخدم" searchUser: "ابØØ« عن مستخدمين" reply: "رد" @@ -102,6 +104,8 @@ renoted: "Ø£Ùعيد نشره" cantRenote: "لا يمكن إعادة نشر الملاØظة" cantReRenote: "لا يمكنك إعادة نشر ملاØظة معاد نشرها" quote: "اقتبس" +inChannelRenote: "إعادة نشر ÙÙŠ قناة" +inChannelQuote: "اقتباس ÙÙŠ قناة" pinnedNote: "ملاØظة مدبسة" pinned: "دبّسها على الصÙØØ© الشخصية" you: "أنت" @@ -119,6 +123,8 @@ unmarkAsSensitive: "ألغ تعيينه كمØتوى Øساس" enterFileName: "ادخل اسم الملÙ" mute: "اكتم" unmute: "إلغاء الكتم" +renoteMute: "اكتم إعادة النشر" +renoteUnmute: "ارÙع الكتم عن إعادة النشر" block: "اØجب" unblock: "إلغاء الØجب" suspend: "علÙÙ‚" @@ -141,6 +147,7 @@ emojiUrl: "رابط الإيموجي" addEmoji: "إضاÙØ© إيموجي" settingGuide: "الإعدادات المستØسنة" cacheRemoteFiles: "خزن مؤقتا الملÙات البعيدة" +cacheRemoteFilesDescription: "إذا عÙطل هذا الإعداد، ستÙØمل الملÙات من المثيل البعيد، هذا سيقلل من المساØØ© المستغلة على القرص لكن سيزيد Øجم تدÙÙ‚ البيانات وهذا لأن الصور المصغرة لن تولّد." flagAsBot: "علّمه ÙƒØساب آلي" flagAsBotDescription: "Ùعّل هذا الخيار إذا كان هذا الØساب ÙŠÙدار عبر برمجية. إذا ÙÙعل Ùسيكون بمثابة علامة للمطورين الآخرين لتجنب سلاسل لا متناهية من التÙاعل بين Øسابات الآلية وضبط أنظمة ميسكي للتعامل مع هذا الØساب كآلي." flagAsCat: "علّم هذا الØساب ÙƒØساب قط" @@ -253,14 +260,15 @@ startMessaging: "ابدأ Ù…Øادثة" nUsersRead: "قرأه {n}" agreeTo: "اواÙÙ‚ على {0}" agree: "أقبل" +agreeBelow: "أقبل ما يلي" basicNotesBeforeCreateAccount: "ملاØظات مهمة" termsOfService: "شروط الخدمة" start: "البداية" home: "الرئيسي" remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد." activity: "النشاط" -images: "الصور" -image: "الصور" +images: "صور" +image: "صور" birthday: "تاريخ الميلاد" yearsOld: "{age} سنة" registeredDate: "انضم ÙÙŠ" @@ -362,6 +370,7 @@ antennaExcludeKeywords: "الكلمات المÙتاØية المستثناة" antennaKeywordsDescription: "اÙصل بينهم بمساÙØ© لاستخدام معامل \"Ùˆ\" أو بسطر لاستخدام معامل \"أو\"" notifyAntenna: "نبهني بصول ملاØظات جديدة" withFileAntenna: "ملاØظات تØوي ملÙات Ùقط" +enableServiceworker: "Ùعّل إرسال الإشعارات للمتصÙØ" antennaUsersDescription: "اكتب اسم مستخدم لكل سطر" caseSensitive: "Øساسية Øالة الأØرÙ" withReplies: "بالردود" @@ -391,6 +400,7 @@ moderation: "الإشراÙ" nUsersMentioned: "{n} مستخدمين Ø£Ùشير إليهم" securityKey: "Ù…ÙØªØ§Ø Ø§Ù„Ø£Ù…Ø§Ù†" lastUsed: "آخر استخدام" +lastUsedAt: "آخر استخدام: {t}" unregister: "إلغاء التسجيل" passwordLessLogin: "Ù„Ùج Ù…ÙÙ† دون كلمة سرية" resetPassword: "أعد تعيين كلمتك السرية" @@ -441,6 +451,7 @@ or: "أو" language: "اللغة" uiLanguage: "لغة واجهة المستخدم" aboutX: "عن {x}" +emojiStyle: "نمط الوجوه التعبيرية" noHistory: "السجل Ùارغ" signinHistory: "تاريخ تسجيل الدخول" doing: "انتظر Ù„Øظة" @@ -451,6 +462,7 @@ createAccount: "أنشئ Øسابًا" existingAccount: "الØسابات الموجودة" regenerate: "أعÙد التوليد" fontSize: "Øجم الخط" +limitTo: "سقÙه٠لـ{x}" noFollowRequests: "ليس لديك طلبات متابعة معلقة" openImageInNewTab: "Ø¥ÙØªØ Ø§Ù„ØµÙˆØ±Ø© بصÙØØ© جديدة" dashboard: "لوØØ© التØكم" @@ -479,6 +491,7 @@ objectStorageUseProxyDesc: "عطل هذا الخيار إذا لم ترد است serverLogs: "سجلات الخادم" deleteAll: "Øذ٠الكل" showFixedPostForm: "أظهر نموذج الكتابة ÙÙŠ أعلى الصÙØØ©" +showFixedPostFormInChannel: "أظهر نموذج الكتابة ÙÙŠ أعلى الخط الزمني (قنوات)" newNoteRecived: "هناك ملاØظات جديدة" sounds: "الرنات" sound: "الرنات" @@ -514,6 +527,7 @@ userSilenced: "ÙƒÙتم هذا المستخدم." yourAccountSuspendedTitle: "هذا الØساب معلق" yourAccountSuspendedDescription: "عÙلق الØساب بسبب انتهاك شروط خدمة المثيل Ùˆ ما شابه. إذا أردت معرÙØ© التÙصيل تواصل مع مدير المثيل. رجاءً لا تنشئ Øساب جديد." accountDeleted: "ØÙذ٠الØساب" +accountDeletedDescription: "ØÙذ٠هذا الØساب." menu: "القائمة" divider: "Ùاصل" addItem: "إضاÙØ© عنصر" @@ -539,6 +553,7 @@ author: "الكاتب" leaveConfirm: "لديك تغييرات غير Ù…ØÙوظة. أتريد المتابعة دون ØÙظها؟" manage: "إدارة " plugins: "الإضاÙات" +preferencesBackups: "النÙسخ الاØتياطية للإعدادات" useFullReactionPicker: "استخدم الØجم الكامل لمنتقي التÙاعلات" width: "العرض" height: "الإرتÙاع" @@ -648,6 +663,7 @@ contact: "التواصل" useSystemFont: "استخدم الخط الاÙتراضية للنظام" clips: "مشابك" experimentalFeatures: "ميّزات اختبارية" +experimental: "اختباري" developer: "المطور" makeExplorable: "أظهر الØساب ÙÙŠ صÙØØ© \"استكشاÙ\"" makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر Øسابك ÙÙŠ صÙØØ© \"استكشاÙ\"" @@ -669,6 +685,7 @@ accentColor: "طابع لوني" textColor: "لون النص" saveAs: "اØÙظ كـ..." advanced: "متقدم" +advancedSettings: "إعدادات متقدمة" value: "القيمة" createdAt: "Ø£Ùنشئ ÙÙŠ" updatedAt: "ØÙدّث ÙÙŠ" @@ -733,6 +750,7 @@ popularPosts: "المشاركات المتداولة" shareWithNote: "شاركه ÙÙŠ ملاØظة" ads: "الإعلانات" expiration: "ينتهي استطلاع الرأي ÙÙŠ" +startingperiod: "ابدأ" memo: "تذكير" priority: "الأولوية" high: "عالية" @@ -763,6 +781,7 @@ lastCommunication: "آخر تواصل" resolved: "عولج" unresolved: "لم يعالج" breakFollow: "إلغاء الاشتراك" +breakFollowConfirm: "أمتأكد من إزالة المتابÙع ØŸ" itsOn: "Ù…Ùعّل" itsOff: "معطّل" emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل" @@ -777,6 +796,7 @@ muteThread: "اكتم النقاش" unmuteThread: "ارÙع الكتم عن النقاش" ffVisibility: "مرئية المتابÙعين/المتابَعين" ffVisibilityDescription: "ÙŠØ³Ù…Ø Ù„Ùƒ بتØديد من يمكنهم رؤية متابÙعيك ومتابَعيك." +continueThread: "اعرض بقية النقاش" deleteAccountConfirm: "سيØØ°Ù Øسابك نهائيًا، أتريد المتابعة؟" incorrectPassword: "كلمة السر خاطئة." voteConfirm: "متيقّÙÙ† من تصويتك لـ {choice}ØŸ" @@ -798,25 +818,127 @@ tenMinutes: "10 دقائق" oneHour: "ساعة" oneDay: "يوم" oneWeek: "أسبوع" +oneMonth: "شهر" failedToFetchAccountInformation: "تعذر جلب معلومات الØساب" +cropNo: "استخدمها كما هي" file: "الملÙات" +recentNHours: "آخر {n} ساعة" +recentNDays: "آخر {n} أيام" +noEmailServerWarning: "خادم البريد غير مضبوط." +thereIsUnresolvedAbuseReportWarning: "توجد بلاغات غير معالجة." +recommended: "مقترØ" +driveCapOverrideLabel: "غيّر Øجم قرص التخزين لهذا المستخدم" +driveCapOverrideCaption: "أعد الØجم إلى القيمة الاÙتراضية بإدخال 0 أو أقل." +requireAdminForView: "لاستعراض هذه الصÙØØ© وجب عليك الولوج كمدير." +typeToConfirm: "أدخل {x} للتأكيد" +deleteAccount: "اØذ٠الØساب" +document: "التوثيق" +numberOfPageCache: "عدد الصÙØات المخزنة مؤقتًا" +logoutConfirm: "أتريد الخروج؟" +lastActiveDate: "آخر استخدام" +statusbar: "شريط الØالة" +pleaseSelect: "Øدد خيارًا" reverse: "اقلب" colored: "ملوّن" label: "التسمية" +type: "نوع" +speed: "سرعة" +slow: "بطيء" +fast: "سريع" +sensitiveMediaDetection: "التعر٠على المØتوى الØساس" localOnly: "المØلي Ùقط" +failedToUpload: "Ùشل الرÙع" +cannotUploadBecauseInappropriate: "تعذر رÙع المل٠لوجود Ù…Øتوى Øساس Ùيه." +cannotUploadBecauseNoFreeSpace: "تعذر رÙع المل٠لنقص مساØØ© التخزين." +cannotUploadBecauseExceedsFileSizeLimit: "تعذر رÙع المل٠بسبب تجاوز Øجمه للØد المسموØ" +beta: "بيتا" +navbar: "شريط التنقل" +shuffle: "خلط" account: "الØسابات" +move: "أنقل" +pushNotification: "إرسال الإشعارات" +subscribePushNotification: "Ùعّل إرسال الإشعارات" +unsubscribePushNotification: "عطل إرسال الإشعارات" +pushNotificationAlreadySubscribed: "إرسال الإشعارات Ù…Ùعل سلÙًا" +pushNotificationNotSupported: "متصÙØÙƒ لا يدعم إرسال الإشعارات أو المثيل لا يدعمها." +sendPushNotificationReadMessage: "اØذ٠الإشعارات Ùور قراءتها" +sendPushNotificationReadMessageCaption: "هذا قد يزيد من معدل استهلاك الطاقة لجهازك." +caption: "التعليق التوضيØÙŠ" +tools: "أدوات" cannotLoad: "تعذر التØميل" like: "أعجبني" +unlike: "ألغ٠الإعجاب" show: "المظهر" +neverShow: "لا تظهره مجددًا" +didYouLikeMisskey: "هل أعجبك ميسكي؟" +roles: "الأدوار" +role: "الدور" +noRole: "لم ÙŠÙعثر على دور" +normalUser: "مستخدم عادي" +undefined: "غير معرّÙ" color: "اللون" +manageCustomEmojis: "إدارة الإيموجي المخصصة" +cannotPerformTemporary: "غير Ù…ØªØ§Ø Ù…Ø¤Ù‚ØªØ§Ù‹" +permissionDeniedError: "رÙÙضة العملية" +preset: "إعدادات مسبقة" +selectFromPresets: "اختر من الإعدادات المسبقة" +achievements: "الإنجازات" +gotInvalidResponseError: "استجابة غير متوقعة من الخادم" +gotInvalidResponseErrorDescription: "يتعذر الوصول إلى الخادم أوأنه ÙŠÙصان، رجاءً Øاول لاØقًا." +thisPostMayBeAnnoying: "هذا قد يزعج الآخرين." +thisPostMayBeAnnoyingHome: "أنشر ÙÙŠ الخط الزمني الرئيس" +thisPostMayBeAnnoyingCancel: "ألغÙ" +internalServerError: "خطأ داخلي ÙÙŠ الخادم" +internalServerErrorDescription: "واجه الخادم خطأ غي متوقع." +copyErrorInfo: "انسخ تÙاصيل الخطأ" +joinThisServer: "سجل ÙÙŠ هذا المثيل" +exploreOtherServers: "اعثر على مثيل آخر" +disableFederationOk: "عطّل" +invitationRequiredToRegister: "هذا المثيل للمدعوين Ùقط. لتسجيل Ùيه تØتاج رمزًا صالØًا." +postToTheChannel: "انشر ÙÙŠ قناة" +cannotBeChangedLater: "لا يمكن تغييره لاØقًا." +reactionAcceptance: "قبول التÙاعلات" +rolesAssignedToMe: "الأدوار المسندة إلي" +resetPasswordConfirm: "هل تريد إعادة تعيين كلمة السر؟" +noteIdOrUrl: "معر٠الملاØظة أو رابطها" +video: "Ùيديو" +videos: "Ùيديوهات" +accountMigration: "ترØيل الØساب" +accountMoved: "نقل هذا المستخدم Øسابه:" +accountMovedShort: "رÙØÙ„ هذا الØساب." +operationForbidden: "عملية ممنوعة" +forceShowAds: "أظهر الإعلانات التجارية دائما" +vertical: "عمودي" horizontal: "جانبي" +position: "الموضع" +serverRules: "قوانين الخادم" +pleaseConfirmBelowBeforeSignup: "رجاءً واÙÙ‚ على ما يلي قبل التسجيل." +pleaseAgreeAllToContinue: "للمتابعة واÙÙ‚ على الØقول أعلاه." +continue: "متابعة" +preservedUsernames: "أسماء المستخدمين المØجوزة" +preservedUsernamesDescription: "قائمة بأسماء المستخدمين المØجوزة كلٌ ÙÙŠ سطر. لن ÙŠÙقبل التسجيل بهذه الأسماء وستبقى Ù…Øصورة على التسجيل اليدوي بواسطة المديرين. لن يتأثر المستخدمون الذين يملكون هذه الأسماء سلÙًا." +archive: "الأرشيÙ" youFollowing: "متابَع" +options: "خيارات" _role: + new: "دور جديد" + edit: "Øرر الأدوار" + name: "اسم الدور" + description: "وص٠الدور" + permission: "أذونات الدور" + assignTarget: "نوع الإسناد" + options: "خيارات" + policies: "السياسة العامة" priority: "الأولوية" _priority: low: "منخÙضة" middle: "متوسط" high: "عالية" + _options: + canManageCustomEmojis: "إدارة الإيموجي المخصصة" + _condition: + isLocal: "مستخدم Ù…Øلي" + isRemote: "مستخدم بعيد" _emailUnavailable: used: "هذا البريد الإلكتروني مستخدم" format: "صيغة البريد الإلكتروني غير صالØØ©" @@ -1064,6 +1186,7 @@ _widgets: onlineUsers: "المتّصلون" jobQueue: "قائمة الانتظار" serverMetric: "Ø¥Øصائيات الخادم" + userList: "قائمة المستخدمين" _userList: chooseList: "اختر قائمة" _cw: @@ -1127,6 +1250,7 @@ _profile: changeBanner: "غيّر اللاÙتة" _exportOrImport: allNotes: "كل الملاØظات" + favoritedNotes: " الملاØظات المÙضلة" followingList: "المتابَعون" muteList: "المستخدمون المكتومون" blockingList: "المستخدمون المØجوبون" @@ -1145,6 +1269,8 @@ _charts: notesTotal: "إجمالي الملاØظات" filesIncDec: "تباين عدد الملÙات" filesTotal: "العدد الإجمالي للملÙات" + storageUsageIncDec: "التباين ÙÙŠ استغلال مساØØ© التخزين" + storageUsageTotal: "اجمالي مساØØ© التخزين المستغلة" _instanceCharts: requests: "الطلبات" users: "تباين عدد المستخدمين" @@ -1205,7 +1331,7 @@ _pages: text: "نص" textarea: "Øقل نصي" section: "قسم" - image: "الصور" + image: "صور" button: "زرّ" note: "ملاØظة مضمّنة" _note: @@ -1264,3 +1390,5 @@ _deck: _webhookSettings: name: "الإسم" active: "Ù…Ùعّل" + _events: + reaction: "عند تلقي تÙاعل" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 843470cf46eb61a19a2c4393658b258f9a8cc439..c4c12cb1aa8cc4c5ab8c097859426da743ce1213 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -52,6 +52,8 @@ addToList: "Zu Liste hinzufügen" sendMessage: "Nachricht senden" copyRSS: "RSS kopieren" copyUsername: "Benutzernamen kopieren" +copyUserId: "Benutzer-ID kopieren" +copyNoteId: "Notiz-ID kopieren" searchUser: "Nach einem Benutzer suchen" reply: "Antworten" loadMore: "Mehr laden" @@ -790,6 +792,7 @@ noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert. noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert." configure: "Konfigurieren" postToGallery: "Neuen Galeriebeitrag erstellen" +postToHashtag: "Mit diesem Hashtag senden" gallery: "Galerie" recentPosts: "Neue Beiträge" popularPosts: "Beliebte Beiträge" @@ -823,6 +826,7 @@ translatedFrom: "Aus {x} übersetzt" accountDeletionInProgress: "Die Löschung deines Benutzerkontos ist momentan in Bearbeitung." usernameInfo: "Ein Name, durch den dein Benutzerkonto auf diesem Server identifiziert werden kann. Du kannst das Alphabet (a~z, A~Z), Ziffern (0~9) oder Unterstriche (_) verwenden. Benutzernamen können später nicht geändert werden." aiChanMode: "Ai-Modus" +devMode: "Entwicklermodus" keepCw: "Inhaltswarnungen beibehalten" pubSub: "Pub/Sub Benutzerkonten" lastCommunication: "Letzte Kommunikation" @@ -832,6 +836,8 @@ breakFollow: "Follower entfernen" breakFollowConfirm: "Diesen Follower wirklich entfernen?" itsOn: "Eingeschaltet" itsOff: "Ausgeschaltet" +on: "An" +off: "Aus" emailRequiredForSignup: "Angabe einer Email-Adresse als benötigt markieren" unread: "Ungelesen" filter: "Filter" @@ -986,6 +992,8 @@ cannotBeChangedLater: "Kann später nicht mehr geändert werden." reactionAcceptance: "Reaktionsannahme" likeOnly: "Nur \"Gefällt mir\"" likeOnlyForRemote: "Nur \"Gefällt mir\" für fremde Instanzen" +nonSensitiveOnly: "Keine Sensitiven" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Keine Sensitiven (Nur \"Gefällt mir\" von fremden Instanzen)" rolesAssignedToMe: "Mir zugewiesene Rollen" resetPasswordConfirm: "Wirklich Passwort zurücksetzen?" sensitiveWords: "Sensible Wörter" @@ -1043,6 +1051,17 @@ preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich." options: "Optionen" specifyUser: "Spezifischer Benutzer" +failedToPreviewUrl: "Vorschau nicht anzeigbar" +update: "Aktualisieren" +rolesThatCanBeUsedThisEmojiAsReaction: "Rollen, die dieses Emoji als Reaktion verwenden können" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Sind keine Rollen angegeben, kann jeder dieses Emoji als Reaktion verwenden." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Diese Rollen müssen öffentlich sein." +cancelReactionConfirm: "Möchtest du deine Reaktion wirklich löschen?" +changeReactionConfirm: "Möchtest du deine Reaktion wirklich ändern?" +later: "Später" +goToMisskey: "Zu Misskey" +additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher" +installed: "Installiert" _initialAccountSetting: accountCreated: "Dein Konto wurde erfolgreich erstellt!" letsStartAccountSetup: "Lass uns nun dein Konto einrichten." @@ -1057,6 +1076,7 @@ _initialAccountSetting: haveFun: "Viel Spaß mit {name}!" ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest." skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?" + laterAreYouSure: "Die Kontoeinrichtung wirklich später erledigen?" _serverRules: description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." _accountMigration: diff --git a/locales/en-US.yml b/locales/en-US.yml index 3ea2313b2f4e26ad975359f0f0fa4e05fc247aa8..0f1c7c89fee6c9bfa3a4ff4d89173d83e4f1602a 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -52,6 +52,8 @@ addToList: "Add to list" sendMessage: "Send a message" copyRSS: "Copy RSS" copyUsername: "Copy username" +copyUserId: "Copy user ID" +copyNoteId: "Copy note ID" searchUser: "Search for a user" reply: "Reply" loadMore: "Load more" @@ -790,6 +792,7 @@ noMaintainerInformationWarning: "Maintainer information is not configured." noBotProtectionWarning: "Bot protection is not configured." configure: "Configure" postToGallery: "Create new gallery post" +postToHashtag: "Post to this hashtag" gallery: "Gallery" recentPosts: "Recent posts" popularPosts: "Popular posts" @@ -823,6 +826,7 @@ translatedFrom: "Translated from {x}" accountDeletionInProgress: "Account deletion is currently in progress" usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later." aiChanMode: "Ai Mode" +devMode: "Developer mode" keepCw: "Keep content warnings" pubSub: "Pub/Sub Accounts" lastCommunication: "Last communication" @@ -832,6 +836,8 @@ breakFollow: "Remove follower" breakFollowConfirm: "Really remove this follower?" itsOn: "Enabled" itsOff: "Disabled" +on: "On" +off: "Off" emailRequiredForSignup: "Require email address for sign-up" unread: "Unread" filter: "Filter" @@ -986,6 +992,8 @@ cannotBeChangedLater: "This cannot be changed later." reactionAcceptance: "Reaction Acceptance" likeOnly: "Only likes" likeOnlyForRemote: "Only likes for remote instances" +nonSensitiveOnly: "Non-sensitive only" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non-sensitive only (Only likes from remote)" rolesAssignedToMe: "Roles assigned to me" resetPasswordConfirm: "Really reset your password?" sensitiveWords: "Sensitive words" @@ -1043,6 +1051,17 @@ preventAiLearning: "Reject usage in Machine Learning (Generative AI)" preventAiLearningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (Predictive / Generative AI) data sets. This is achieved by adding a \"noai\" HTML-Response flag to the respective content. A complete prevention can however not be achieved through this flag, as it may simply be ignored." options: "Options" specifyUser: "Specific user" +failedToPreviewUrl: "Could not preview" +update: "Update" +rolesThatCanBeUsedThisEmojiAsReaction: "Roles that can use this emoji as reaction" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "If no roles are specified, anyone can use this emoji as reaction." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "These roles must be public." +cancelReactionConfirm: "Really delete your reaction?" +changeReactionConfirm: "Really change your reaction?" +later: "Later" +goToMisskey: "To Misskey" +additionalEmojiDictionary: "Additional emoji dictionaries" +installed: "Installed" _initialAccountSetting: accountCreated: "Your account was successfully created!" letsStartAccountSetup: "For starters, let's set up your profile." @@ -1057,6 +1076,7 @@ _initialAccountSetting: haveFun: "Enjoy {name}!" ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}." skipAreYouSure: "Really skip profile setup?" + laterAreYouSure: "Really do profile setup later?" _serverRules: description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended." _accountMigration: diff --git a/locales/es-ES.yml b/locales/es-ES.yml index b043ecf3cf5c7d4421d89142831d864669a79bc0..a5dd18e3fc6e4cc8039acd5ac2f7fd7f2e34c5af 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -20,6 +20,7 @@ noNotes: "No hay notas" noNotifications: "No hay notificaciones" instance: "Instancia" settings: "Configuración" +notificationSettings: "Configurar las notificaciones" basicSettings: "Configuración Básica" otherSettings: "Configuración avanzada" openInWindow: "Abrir en una ventana" @@ -262,8 +263,10 @@ noMoreHistory: "El historial se ha acabado" startMessaging: "Iniciar chat" nUsersRead: "LeÃdo por {n} personas" agreeTo: "De acuerdo con {0}" +agree: "De acuerdo." agreeBelow: "Estoy de acuerdo con lo siguiente" basicNotesBeforeCreateAccount: "Notas básicas" +termsOfService: "Términos y condiciones" start: "Comenzar" home: "Inicio" remoteUserCaution: "Para el usuario remoto, la información está incompleta" @@ -473,6 +476,8 @@ createAccount: "Crear cuenta" existingAccount: "Cuenta existente" regenerate: "Regenerar" fontSize: "Tamaño de la letra" +mediaListWithOneImageAppearance: "Altura de la lista de medios con una sola imagen." +limitTo: "{x} hasta un máximo de" noFollowRequests: "No hay solicitudes de seguimiento" openImageInNewTab: "Abrir imagen en nueva pestaña" dashboard: "Panel de control" @@ -555,6 +560,7 @@ accountDeletedDescription: "Esta cuenta ha sido borrada." menu: "Menú" divider: "Divisor" addItem: "Agregar elemento" +rearrange: "Ordenar" relays: "Relés" addRelay: "Agregar relé" inboxUrl: "Inbox URL" @@ -698,6 +704,8 @@ contact: "Contacto" useSystemFont: "Utilizar la tipografÃa por defecto del sistema" clips: "Clip" experimentalFeatures: "CaracterÃsticas experimentales" +experimental: "Función experimental" +thisIsExperimentalFeature: "Se trata de una función experimental. Las especificaciones pueden cambiar o puede que no funcione correctamente." developer: "Desarrolladores" makeExplorable: "Hacer visible la cuenta en \"Explorar\"" makeExplorableDescription: "Si desactiva esta opción, su cuenta no aparecerá en la sección \"Explorar\"." @@ -991,9 +999,12 @@ largeNoteReactions: "Agrandar las reacciones de las notas" noteIdOrUrl: "ID o URL de la nota" accountMigration: "Migración de cuenta" accountMoved: "Este usuario se ha mudado a una nueva cuenta:" +accountMovedShort: "Esta cuenta ha sido migrada." horizontal: "Horizontal" youFollowing: "Siguiendo" options: "Opción" +_initialAccountSetting: + accountCreated: "¡La cuenta ha sido creada!" _accountMigration: moveFrom: "Trasladar de otra cuenta a ésta" moveFromLabel: "Cuenta desde la que se realiza el traslado:" @@ -1174,6 +1185,8 @@ _achievements: _client30min: title: "Un descansito" description: "30 minutos dedicados a Misskey" + _client60min: + title: "Viendo mucho Misskey." _noteDeletedWithin1min: title: "Ah... Mejor no..." description: "Borrar una nota antes que de pase 1 minuto" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index d8ac41c925ba191a3691a4727fcfe475897aede5..173380805c34fe79c97e66743021764a13599be6 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -908,12 +908,14 @@ neverShow: "Ne plus afficher" remindMeLater: "Peut-être plus tard" roles: "Rôles" role: "Rôles" +noRole: "Aucun rôle" normalUser: "Simple utilisateur·rice" assign: "Attribuer" color: "Couleur" manageCustomEmojis: "Gestion des émojis personnalisés" preset: "Préréglage" selectFromPresets: "Sélectionner à partir des préréglages" +thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes." thisPostMayBeAnnoyingCancel: "Annuler" license: "Licence" video: "Vidéo" @@ -921,6 +923,7 @@ videos: "Vidéos" dataSaver: "Économiseur de données" accountMigration: "Migration de compte" accountMoved: "Cet·te utilisateur·rice a migré son compte vers :" +addMemo: "Ajouter un mémo" notificationDisplay: "Style des notifications" leftTop: "En haut à gauche" rightTop: "En haut à droite" @@ -935,6 +938,8 @@ _achievements: _notes1: description: "Publiez votre première note" flavor: "Passez un bon moment avec Misskey !" + _notes100: + title: "Beaucoup de notes" _notes100000: title: "ALL YOUR NOTE ARE BELONG TO US" _login3: @@ -985,6 +990,8 @@ _achievements: title: "Joyeux Anniversaire !" _loggedInOnNewYearsDay: title: "Bonne année !" + _cookieClicked: + flavor: "Attendez une minute, vous êtes sur le mauvais site web ?" _role: assignTarget: "Attribuer" priority: "Priorité" diff --git a/locales/generateDTS.js b/locales/generateDTS.js new file mode 100644 index 0000000000000000000000000000000000000000..5949aee7cd3bb37bef67199e8fe486886e54753b --- /dev/null +++ b/locales/generateDTS.js @@ -0,0 +1,72 @@ +const fs = require('fs'); +const yaml = require('js-yaml'); +const ts = require('typescript'); + +function createMembers(record) { + return Object.entries(record) + .map(([k, v]) => ts.factory.createPropertySignature( + undefined, + ts.factory.createStringLiteral(k), + undefined, + typeof v === 'string' + ? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) + : ts.factory.createTypeLiteralNode(createMembers(v)), + )); +} + +module.exports = function generateDTS() { + const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); + const members = createMembers(locale); + const elements = [ + ts.factory.createInterfaceDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier('Locale'), + undefined, + undefined, + members, + ), + ts.factory.createVariableStatement( + [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration( + ts.factory.createIdentifier('locales'), + undefined, + ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature( + undefined, + [ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier('lang'), + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + undefined, + )], + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('Locale'), + undefined, + ), + )]), + undefined, + )], + ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags, + ), + ), + ts.factory.createExportAssignment( + undefined, + true, + ts.factory.createIdentifier('locales'), + ), + ]; + const printed = ts.createPrinter({ + newLine: ts.NewLineKind.LineFeed, + }).printList( + ts.ListFormat.MultiLine, + ts.factory.createNodeArray(elements), + ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS), + ); + + fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */ +// This file is generated by locales/generateDTS.js +// Do not edit this file directly. +${printed}`, 'utf-8'); +} diff --git a/locales/id-ID.yml b/locales/id-ID.yml index df42697ccbc86bd98398c81b7e6bd11683e3ee6f..70217caa27bdab42494b5a776a76faac1918ccae 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -20,6 +20,7 @@ noNotes: "Tidak ada catatan" noNotifications: "Tidak ada pemberitahuan" instance: "Instansi" settings: "Pengaturan" +notificationSettings: "Atur Notifikasi" basicSettings: "Pengaturan umum" otherSettings: "Pengaturan lainnya" openInWindow: "Buka di jendela" @@ -261,8 +262,10 @@ noMoreHistory: "Tidak ada sejarah lagi" startMessaging: "Mulai mengirim pesan" nUsersRead: "Dibaca oleh {n}" agreeTo: "Saya setuju kepada {0}" +agree: "Setuju" agreeBelow: "Saya setuju dengan di bawah ini" basicNotesBeforeCreateAccount: "Catatan penting" +termsOfService: "Syarat dan ketentuan" start: "Mulai" home: "Beranda" remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal dari instansi luar." diff --git a/locales/index.d.ts b/locales/index.d.ts index fe3edb445b8c7889ef50eba66c0304342beba5dd..7047f42eff4983c2b7ddee224368490920081fa6 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1,3 +1,2150 @@ -declare const locales: { [lang: string]: any }; - +/* eslint-disable */ +// This file is generated by locales/generateDTS.js +// Do not edit this file directly. +export interface Locale { + "_lang_": string; + "headlineMisskey": string; + "introMisskey": string; + "poweredByMisskeyDescription": string; + "monthAndDay": string; + "search": string; + "notifications": string; + "username": string; + "password": string; + "forgotPassword": string; + "fetchingAsApObject": string; + "ok": string; + "gotIt": string; + "cancel": string; + "noThankYou": string; + "enterUsername": string; + "renotedBy": string; + "noNotes": string; + "noNotifications": string; + "instance": string; + "settings": string; + "notificationSettings": string; + "basicSettings": string; + "otherSettings": string; + "openInWindow": string; + "profile": string; + "timeline": string; + "noAccountDescription": string; + "login": string; + "loggingIn": string; + "logout": string; + "signup": string; + "uploading": string; + "save": string; + "users": string; + "addUser": string; + "favorite": string; + "favorites": string; + "unfavorite": string; + "favorited": string; + "alreadyFavorited": string; + "cantFavorite": string; + "pin": string; + "unpin": string; + "copyContent": string; + "copyLink": string; + "delete": string; + "deleteAndEdit": string; + "deleteAndEditConfirm": string; + "addToList": string; + "sendMessage": string; + "copyRSS": string; + "copyUsername": string; + "copyUserId": string; + "copyNoteId": string; + "searchUser": string; + "reply": string; + "loadMore": string; + "showMore": string; + "showLess": string; + "youGotNewFollower": string; + "receiveFollowRequest": string; + "followRequestAccepted": string; + "mention": string; + "mentions": string; + "directNotes": string; + "importAndExport": string; + "import": string; + "export": string; + "files": string; + "download": string; + "driveFileDeleteConfirm": string; + "unfollowConfirm": string; + "exportRequested": string; + "importRequested": string; + "lists": string; + "noLists": string; + "note": string; + "notes": string; + "following": string; + "followers": string; + "followsYou": string; + "createList": string; + "manageLists": string; + "error": string; + "somethingHappened": string; + "retry": string; + "pageLoadError": string; + "pageLoadErrorDescription": string; + "serverIsDead": string; + "youShouldUpgradeClient": string; + "enterListName": string; + "privacy": string; + "makeFollowManuallyApprove": string; + "defaultNoteVisibility": string; + "follow": string; + "followRequest": string; + "followRequests": string; + "unfollow": string; + "followRequestPending": string; + "enterEmoji": string; + "renote": string; + "unrenote": string; + "renoted": string; + "cantRenote": string; + "cantReRenote": string; + "quote": string; + "inChannelRenote": string; + "inChannelQuote": string; + "pinnedNote": string; + "pinned": string; + "you": string; + "clickToShow": string; + "sensitive": string; + "add": string; + "reaction": string; + "reactions": string; + "reactionSetting": string; + "reactionSettingDescription2": string; + "rememberNoteVisibility": string; + "attachCancel": string; + "markAsSensitive": string; + "unmarkAsSensitive": string; + "enterFileName": string; + "mute": string; + "unmute": string; + "renoteMute": string; + "renoteUnmute": string; + "block": string; + "unblock": string; + "suspend": string; + "unsuspend": string; + "blockConfirm": string; + "unblockConfirm": string; + "suspendConfirm": string; + "unsuspendConfirm": string; + "selectList": string; + "selectChannel": string; + "selectAntenna": string; + "selectWidget": string; + "editWidgets": string; + "editWidgetsExit": string; + "customEmojis": string; + "emoji": string; + "emojis": string; + "emojiName": string; + "emojiUrl": string; + "addEmoji": string; + "settingGuide": string; + "cacheRemoteFiles": string; + "cacheRemoteFilesDescription": string; + "flagAsBot": string; + "flagAsBotDescription": string; + "flagAsCat": string; + "flagAsCatDescription": string; + "flagShowTimelineReplies": string; + "flagShowTimelineRepliesDescription": string; + "autoAcceptFollowed": string; + "addAccount": string; + "reloadAccountsList": string; + "loginFailed": string; + "showOnRemote": string; + "general": string; + "wallpaper": string; + "setWallpaper": string; + "removeWallpaper": string; + "searchWith": string; + "youHaveNoLists": string; + "followConfirm": string; + "proxyAccount": string; + "proxyAccountDescription": string; + "host": string; + "selectUser": string; + "recipient": string; + "annotation": string; + "federation": string; + "instances": string; + "registeredAt": string; + "latestRequestReceivedAt": string; + "latestStatus": string; + "storageUsage": string; + "charts": string; + "perHour": string; + "perDay": string; + "stopActivityDelivery": string; + "blockThisInstance": string; + "operations": string; + "software": string; + "version": string; + "metadata": string; + "withNFiles": string; + "monitor": string; + "jobQueue": string; + "cpuAndMemory": string; + "network": string; + "disk": string; + "instanceInfo": string; + "statistics": string; + "clearQueue": string; + "clearQueueConfirmTitle": string; + "clearQueueConfirmText": string; + "clearCachedFiles": string; + "clearCachedFilesConfirm": string; + "blockedInstances": string; + "blockedInstancesDescription": string; + "muteAndBlock": string; + "mutedUsers": string; + "blockedUsers": string; + "noUsers": string; + "editProfile": string; + "noteDeleteConfirm": string; + "pinLimitExceeded": string; + "intro": string; + "done": string; + "processing": string; + "preview": string; + "default": string; + "defaultValueIs": string; + "noCustomEmojis": string; + "noJobs": string; + "federating": string; + "blocked": string; + "suspended": string; + "all": string; + "subscribing": string; + "publishing": string; + "notResponding": string; + "instanceFollowing": string; + "instanceFollowers": string; + "instanceUsers": string; + "changePassword": string; + "security": string; + "retypedNotMatch": string; + "currentPassword": string; + "newPassword": string; + "newPasswordRetype": string; + "attachFile": string; + "more": string; + "featured": string; + "usernameOrUserId": string; + "noSuchUser": string; + "lookup": string; + "announcements": string; + "imageUrl": string; + "remove": string; + "removed": string; + "removeAreYouSure": string; + "deleteAreYouSure": string; + "resetAreYouSure": string; + "saved": string; + "messaging": string; + "upload": string; + "keepOriginalUploading": string; + "keepOriginalUploadingDescription": string; + "fromDrive": string; + "fromUrl": string; + "uploadFromUrl": string; + "uploadFromUrlDescription": string; + "uploadFromUrlRequested": string; + "uploadFromUrlMayTakeTime": string; + "explore": string; + "messageRead": string; + "noMoreHistory": string; + "startMessaging": string; + "nUsersRead": string; + "agreeTo": string; + "agree": string; + "agreeBelow": string; + "basicNotesBeforeCreateAccount": string; + "termsOfService": string; + "start": string; + "home": string; + "remoteUserCaution": string; + "activity": string; + "images": string; + "image": string; + "birthday": string; + "yearsOld": string; + "registeredDate": string; + "location": string; + "theme": string; + "themeForLightMode": string; + "themeForDarkMode": string; + "light": string; + "dark": string; + "lightThemes": string; + "darkThemes": string; + "syncDeviceDarkMode": string; + "drive": string; + "fileName": string; + "selectFile": string; + "selectFiles": string; + "selectFolder": string; + "selectFolders": string; + "renameFile": string; + "folderName": string; + "createFolder": string; + "renameFolder": string; + "deleteFolder": string; + "addFile": string; + "emptyDrive": string; + "emptyFolder": string; + "unableToDelete": string; + "inputNewFileName": string; + "inputNewDescription": string; + "inputNewFolderName": string; + "circularReferenceFolder": string; + "hasChildFilesOrFolders": string; + "copyUrl": string; + "rename": string; + "avatar": string; + "banner": string; + "nsfw": string; + "whenServerDisconnected": string; + "disconnectedFromServer": string; + "reload": string; + "doNothing": string; + "reloadConfirm": string; + "watch": string; + "unwatch": string; + "accept": string; + "reject": string; + "normal": string; + "instanceName": string; + "instanceDescription": string; + "maintainerName": string; + "maintainerEmail": string; + "tosUrl": string; + "thisYear": string; + "thisMonth": string; + "today": string; + "dayX": string; + "monthX": string; + "yearX": string; + "pages": string; + "integration": string; + "connectService": string; + "disconnectService": string; + "enableLocalTimeline": string; + "enableGlobalTimeline": string; + "disablingTimelinesInfo": string; + "registration": string; + "enableRegistration": string; + "invite": string; + "driveCapacityPerLocalAccount": string; + "driveCapacityPerRemoteAccount": string; + "inMb": string; + "iconUrl": string; + "bannerUrl": string; + "backgroundImageUrl": string; + "basicInfo": string; + "pinnedUsers": string; + "pinnedUsersDescription": string; + "pinnedPages": string; + "pinnedPagesDescription": string; + "pinnedClipId": string; + "pinnedNotes": string; + "hcaptcha": string; + "enableHcaptcha": string; + "hcaptchaSiteKey": string; + "hcaptchaSecretKey": string; + "recaptcha": string; + "enableRecaptcha": string; + "recaptchaSiteKey": string; + "recaptchaSecretKey": string; + "turnstile": string; + "enableTurnstile": string; + "turnstileSiteKey": string; + "turnstileSecretKey": string; + "avoidMultiCaptchaConfirm": string; + "antennas": string; + "manageAntennas": string; + "name": string; + "antennaSource": string; + "antennaKeywords": string; + "antennaExcludeKeywords": string; + "antennaKeywordsDescription": string; + "notifyAntenna": string; + "withFileAntenna": string; + "enableServiceworker": string; + "antennaUsersDescription": string; + "caseSensitive": string; + "withReplies": string; + "connectedTo": string; + "notesAndReplies": string; + "withFiles": string; + "silence": string; + "silenceConfirm": string; + "unsilence": string; + "unsilenceConfirm": string; + "popularUsers": string; + "recentlyUpdatedUsers": string; + "recentlyRegisteredUsers": string; + "recentlyDiscoveredUsers": string; + "exploreUsersCount": string; + "exploreFediverse": string; + "popularTags": string; + "userList": string; + "about": string; + "aboutMisskey": string; + "administrator": string; + "token": string; + "2fa": string; + "totp": string; + "totpDescription": string; + "moderator": string; + "moderation": string; + "nUsersMentioned": string; + "securityKeyAndPasskey": string; + "securityKey": string; + "lastUsed": string; + "lastUsedAt": string; + "unregister": string; + "passwordLessLogin": string; + "passwordLessLoginDescription": string; + "resetPassword": string; + "newPasswordIs": string; + "reduceUiAnimation": string; + "share": string; + "notFound": string; + "notFoundDescription": string; + "uploadFolder": string; + "cacheClear": string; + "markAsReadAllNotifications": string; + "markAsReadAllUnreadNotes": string; + "markAsReadAllTalkMessages": string; + "help": string; + "inputMessageHere": string; + "close": string; + "invites": string; + "members": string; + "transfer": string; + "title": string; + "text": string; + "enable": string; + "next": string; + "retype": string; + "noteOf": string; + "quoteAttached": string; + "quoteQuestion": string; + "noMessagesYet": string; + "newMessageExists": string; + "onlyOneFileCanBeAttached": string; + "signinRequired": string; + "invitations": string; + "invitationCode": string; + "checking": string; + "available": string; + "unavailable": string; + "usernameInvalidFormat": string; + "tooShort": string; + "tooLong": string; + "weakPassword": string; + "normalPassword": string; + "strongPassword": string; + "passwordMatched": string; + "passwordNotMatched": string; + "signinWith": string; + "signinFailed": string; + "or": string; + "language": string; + "uiLanguage": string; + "aboutX": string; + "emojiStyle": string; + "native": string; + "disableDrawer": string; + "showNoteActionsOnlyHover": string; + "noHistory": string; + "signinHistory": string; + "enableAdvancedMfm": string; + "enableAnimatedMfm": string; + "doing": string; + "category": string; + "tags": string; + "docSource": string; + "createAccount": string; + "existingAccount": string; + "regenerate": string; + "fontSize": string; + "mediaListWithOneImageAppearance": string; + "limitTo": string; + "noFollowRequests": string; + "openImageInNewTab": string; + "dashboard": string; + "local": string; + "remote": string; + "total": string; + "weekOverWeekChanges": string; + "dayOverDayChanges": string; + "appearance": string; + "clientSettings": string; + "accountSettings": string; + "promotion": string; + "promote": string; + "numberOfDays": string; + "hideThisNote": string; + "showFeaturedNotesInTimeline": string; + "objectStorage": string; + "useObjectStorage": string; + "objectStorageBaseUrl": string; + "objectStorageBaseUrlDesc": string; + "objectStorageBucket": string; + "objectStorageBucketDesc": string; + "objectStoragePrefix": string; + "objectStoragePrefixDesc": string; + "objectStorageEndpoint": string; + "objectStorageEndpointDesc": string; + "objectStorageRegion": string; + "objectStorageRegionDesc": string; + "objectStorageUseSSL": string; + "objectStorageUseSSLDesc": string; + "objectStorageUseProxy": string; + "objectStorageUseProxyDesc": string; + "objectStorageSetPublicRead": string; + "s3ForcePathStyleDesc": string; + "serverLogs": string; + "deleteAll": string; + "showFixedPostForm": string; + "showFixedPostFormInChannel": string; + "newNoteRecived": string; + "sounds": string; + "sound": string; + "listen": string; + "none": string; + "showInPage": string; + "popout": string; + "volume": string; + "masterVolume": string; + "details": string; + "chooseEmoji": string; + "unableToProcess": string; + "recentUsed": string; + "install": string; + "uninstall": string; + "installedApps": string; + "nothing": string; + "installedDate": string; + "lastUsedDate": string; + "state": string; + "sort": string; + "ascendingOrder": string; + "descendingOrder": string; + "scratchpad": string; + "scratchpadDescription": string; + "output": string; + "script": string; + "disablePagesScript": string; + "updateRemoteUser": string; + "deleteAllFiles": string; + "deleteAllFilesConfirm": string; + "removeAllFollowing": string; + "removeAllFollowingDescription": string; + "userSuspended": string; + "userSilenced": string; + "yourAccountSuspendedTitle": string; + "yourAccountSuspendedDescription": string; + "tokenRevoked": string; + "tokenRevokedDescription": string; + "accountDeleted": string; + "accountDeletedDescription": string; + "menu": string; + "divider": string; + "addItem": string; + "rearrange": string; + "relays": string; + "addRelay": string; + "inboxUrl": string; + "addedRelays": string; + "serviceworkerInfo": string; + "deletedNote": string; + "invisibleNote": string; + "enableInfiniteScroll": string; + "visibility": string; + "poll": string; + "useCw": string; + "enablePlayer": string; + "disablePlayer": string; + "expandTweet": string; + "themeEditor": string; + "description": string; + "describeFile": string; + "enterFileDescription": string; + "author": string; + "leaveConfirm": string; + "manage": string; + "plugins": string; + "preferencesBackups": string; + "deck": string; + "undeck": string; + "useBlurEffectForModal": string; + "useFullReactionPicker": string; + "width": string; + "height": string; + "large": string; + "medium": string; + "small": string; + "generateAccessToken": string; + "permission": string; + "enableAll": string; + "disableAll": string; + "tokenRequested": string; + "pluginTokenRequestedDescription": string; + "notificationType": string; + "edit": string; + "emailServer": string; + "enableEmail": string; + "emailConfigInfo": string; + "email": string; + "emailAddress": string; + "smtpConfig": string; + "smtpHost": string; + "smtpPort": string; + "smtpUser": string; + "smtpPass": string; + "emptyToDisableSmtpAuth": string; + "smtpSecure": string; + "smtpSecureInfo": string; + "testEmail": string; + "wordMute": string; + "regexpError": string; + "regexpErrorDescription": string; + "instanceMute": string; + "userSaysSomething": string; + "makeActive": string; + "display": string; + "copy": string; + "metrics": string; + "overview": string; + "logs": string; + "delayed": string; + "database": string; + "channel": string; + "create": string; + "notificationSetting": string; + "notificationSettingDesc": string; + "useGlobalSetting": string; + "useGlobalSettingDesc": string; + "other": string; + "regenerateLoginToken": string; + "regenerateLoginTokenDescription": string; + "setMultipleBySeparatingWithSpace": string; + "fileIdOrUrl": string; + "behavior": string; + "sample": string; + "abuseReports": string; + "reportAbuse": string; + "reportAbuseOf": string; + "fillAbuseReportDescription": string; + "abuseReported": string; + "reporter": string; + "reporteeOrigin": string; + "reporterOrigin": string; + "forwardReport": string; + "forwardReportIsAnonymous": string; + "send": string; + "abuseMarkAsResolved": string; + "openInNewTab": string; + "openInSideView": string; + "defaultNavigationBehaviour": string; + "editTheseSettingsMayBreakAccount": string; + "instanceTicker": string; + "waitingFor": string; + "random": string; + "system": string; + "switchUi": string; + "desktop": string; + "clip": string; + "createNew": string; + "optional": string; + "createNewClip": string; + "unclip": string; + "confirmToUnclipAlreadyClippedNote": string; + "public": string; + "i18nInfo": string; + "manageAccessTokens": string; + "accountInfo": string; + "notesCount": string; + "repliesCount": string; + "renotesCount": string; + "repliedCount": string; + "renotedCount": string; + "followingCount": string; + "followersCount": string; + "sentReactionsCount": string; + "receivedReactionsCount": string; + "pollVotesCount": string; + "pollVotedCount": string; + "yes": string; + "no": string; + "driveFilesCount": string; + "driveUsage": string; + "noCrawle": string; + "noCrawleDescription": string; + "lockedAccountInfo": string; + "alwaysMarkSensitive": string; + "loadRawImages": string; + "disableShowingAnimatedImages": string; + "verificationEmailSent": string; + "notSet": string; + "emailVerified": string; + "noteFavoritesCount": string; + "pageLikesCount": string; + "pageLikedCount": string; + "contact": string; + "useSystemFont": string; + "clips": string; + "experimentalFeatures": string; + "experimental": string; + "thisIsExperimentalFeature": string; + "developer": string; + "makeExplorable": string; + "makeExplorableDescription": string; + "showGapBetweenNotesInTimeline": string; + "duplicate": string; + "left": string; + "center": string; + "wide": string; + "narrow": string; + "reloadToApplySetting": string; + "needReloadToApply": string; + "showTitlebar": string; + "clearCache": string; + "onlineUsersCount": string; + "nUsers": string; + "nNotes": string; + "sendErrorReports": string; + "sendErrorReportsDescription": string; + "myTheme": string; + "backgroundColor": string; + "accentColor": string; + "textColor": string; + "saveAs": string; + "advanced": string; + "advancedSettings": string; + "value": string; + "createdAt": string; + "updatedAt": string; + "saveConfirm": string; + "deleteConfirm": string; + "invalidValue": string; + "registry": string; + "closeAccount": string; + "currentVersion": string; + "latestVersion": string; + "youAreRunningUpToDateClient": string; + "newVersionOfClientAvailable": string; + "usageAmount": string; + "capacity": string; + "inUse": string; + "editCode": string; + "apply": string; + "receiveAnnouncementFromInstance": string; + "emailNotification": string; + "publish": string; + "inChannelSearch": string; + "useReactionPickerForContextMenu": string; + "typingUsers": string; + "jumpToSpecifiedDate": string; + "showingPastTimeline": string; + "clear": string; + "markAllAsRead": string; + "goBack": string; + "unlikeConfirm": string; + "fullView": string; + "quitFullView": string; + "addDescription": string; + "userPagePinTip": string; + "notSpecifiedMentionWarning": string; + "info": string; + "userInfo": string; + "unknown": string; + "onlineStatus": string; + "hideOnlineStatus": string; + "hideOnlineStatusDescription": string; + "online": string; + "active": string; + "offline": string; + "notRecommended": string; + "botProtection": string; + "instanceBlocking": string; + "selectAccount": string; + "switchAccount": string; + "enabled": string; + "disabled": string; + "quickAction": string; + "user": string; + "administration": string; + "accounts": string; + "switch": string; + "noMaintainerInformationWarning": string; + "noBotProtectionWarning": string; + "configure": string; + "postToGallery": string; + "postToHashtag": string; + "gallery": string; + "recentPosts": string; + "popularPosts": string; + "shareWithNote": string; + "ads": string; + "expiration": string; + "startingperiod": string; + "memo": string; + "priority": string; + "high": string; + "middle": string; + "low": string; + "emailNotConfiguredWarning": string; + "ratio": string; + "previewNoteText": string; + "customCss": string; + "customCssWarn": string; + "global": string; + "squareAvatars": string; + "sent": string; + "received": string; + "searchResult": string; + "hashtags": string; + "troubleshooting": string; + "useBlurEffect": string; + "learnMore": string; + "misskeyUpdated": string; + "whatIsNew": string; + "translate": string; + "translatedFrom": string; + "accountDeletionInProgress": string; + "usernameInfo": string; + "aiChanMode": string; + "devMode": string; + "keepCw": string; + "pubSub": string; + "lastCommunication": string; + "resolved": string; + "unresolved": string; + "breakFollow": string; + "breakFollowConfirm": string; + "itsOn": string; + "itsOff": string; + "on": string; + "off": string; + "emailRequiredForSignup": string; + "unread": string; + "filter": string; + "controlPanel": string; + "manageAccounts": string; + "makeReactionsPublic": string; + "makeReactionsPublicDescription": string; + "classic": string; + "muteThread": string; + "unmuteThread": string; + "ffVisibility": string; + "ffVisibilityDescription": string; + "continueThread": string; + "deleteAccountConfirm": string; + "incorrectPassword": string; + "voteConfirm": string; + "hide": string; + "useDrawerReactionPickerForMobile": string; + "welcomeBackWithName": string; + "clickToFinishEmailVerification": string; + "overridedDeviceKind": string; + "smartphone": string; + "tablet": string; + "auto": string; + "themeColor": string; + "size": string; + "numberOfColumn": string; + "searchByGoogle": string; + "instanceDefaultLightTheme": string; + "instanceDefaultDarkTheme": string; + "instanceDefaultThemeDescription": string; + "mutePeriod": string; + "period": string; + "indefinitely": string; + "tenMinutes": string; + "oneHour": string; + "oneDay": string; + "oneWeek": string; + "oneMonth": string; + "reflectMayTakeTime": string; + "failedToFetchAccountInformation": string; + "rateLimitExceeded": string; + "cropImage": string; + "cropImageAsk": string; + "cropYes": string; + "cropNo": string; + "file": string; + "recentNHours": string; + "recentNDays": string; + "noEmailServerWarning": string; + "thereIsUnresolvedAbuseReportWarning": string; + "recommended": string; + "check": string; + "driveCapOverrideLabel": string; + "driveCapOverrideCaption": string; + "requireAdminForView": string; + "isSystemAccount": string; + "typeToConfirm": string; + "deleteAccount": string; + "document": string; + "numberOfPageCache": string; + "numberOfPageCacheDescription": string; + "logoutConfirm": string; + "lastActiveDate": string; + "statusbar": string; + "pleaseSelect": string; + "reverse": string; + "colored": string; + "refreshInterval": string; + "label": string; + "type": string; + "speed": string; + "slow": string; + "fast": string; + "sensitiveMediaDetection": string; + "localOnly": string; + "remoteOnly": string; + "failedToUpload": string; + "cannotUploadBecauseInappropriate": string; + "cannotUploadBecauseNoFreeSpace": string; + "cannotUploadBecauseExceedsFileSizeLimit": string; + "beta": string; + "enableAutoSensitive": string; + "enableAutoSensitiveDescription": string; + "activeEmailValidationDescription": string; + "navbar": string; + "shuffle": string; + "account": string; + "move": string; + "pushNotification": string; + "subscribePushNotification": string; + "unsubscribePushNotification": string; + "pushNotificationAlreadySubscribed": string; + "pushNotificationNotSupported": string; + "sendPushNotificationReadMessage": string; + "sendPushNotificationReadMessageCaption": string; + "windowMaximize": string; + "windowMinimize": string; + "windowRestore": string; + "caption": string; + "loggedInAsBot": string; + "tools": string; + "cannotLoad": string; + "numberOfProfileView": string; + "like": string; + "unlike": string; + "numberOfLikes": string; + "show": string; + "neverShow": string; + "remindMeLater": string; + "didYouLikeMisskey": string; + "pleaseDonate": string; + "roles": string; + "role": string; + "noRole": string; + "normalUser": string; + "undefined": string; + "assign": string; + "unassign": string; + "color": string; + "manageCustomEmojis": string; + "youCannotCreateAnymore": string; + "cannotPerformTemporary": string; + "cannotPerformTemporaryDescription": string; + "invalidParamError": string; + "invalidParamErrorDescription": string; + "permissionDeniedError": string; + "permissionDeniedErrorDescription": string; + "preset": string; + "selectFromPresets": string; + "achievements": string; + "gotInvalidResponseError": string; + "gotInvalidResponseErrorDescription": string; + "thisPostMayBeAnnoying": string; + "thisPostMayBeAnnoyingHome": string; + "thisPostMayBeAnnoyingCancel": string; + "thisPostMayBeAnnoyingIgnore": string; + "collapseRenotes": string; + "internalServerError": string; + "internalServerErrorDescription": string; + "copyErrorInfo": string; + "joinThisServer": string; + "exploreOtherServers": string; + "letsLookAtTimeline": string; + "disableFederationConfirm": string; + "disableFederationConfirmWarn": string; + "disableFederationOk": string; + "invitationRequiredToRegister": string; + "emailNotSupported": string; + "postToTheChannel": string; + "cannotBeChangedLater": string; + "reactionAcceptance": string; + "likeOnly": string; + "likeOnlyForRemote": string; + "nonSensitiveOnly": string; + "nonSensitiveOnlyForLocalLikeOnlyForRemote": string; + "rolesAssignedToMe": string; + "resetPasswordConfirm": string; + "sensitiveWords": string; + "sensitiveWordsDescription": string; + "sensitiveWordsDescription2": string; + "notesSearchNotAvailable": string; + "license": string; + "unfavoriteConfirm": string; + "myClips": string; + "drivecleaner": string; + "retryAllQueuesNow": string; + "retryAllQueuesConfirmTitle": string; + "retryAllQueuesConfirmText": string; + "enableChartsForRemoteUser": string; + "enableChartsForFederatedInstances": string; + "showClipButtonInNoteFooter": string; + "largeNoteReactions": string; + "noteIdOrUrl": string; + "video": string; + "videos": string; + "dataSaver": string; + "accountMigration": string; + "accountMoved": string; + "accountMovedShort": string; + "operationForbidden": string; + "forceShowAds": string; + "addMemo": string; + "editMemo": string; + "reactionsList": string; + "renotesList": string; + "notificationDisplay": string; + "leftTop": string; + "rightTop": string; + "leftBottom": string; + "rightBottom": string; + "stackAxis": string; + "vertical": string; + "horizontal": string; + "position": string; + "serverRules": string; + "pleaseConfirmBelowBeforeSignup": string; + "pleaseAgreeAllToContinue": string; + "continue": string; + "preservedUsernames": string; + "preservedUsernamesDescription": string; + "createNoteFromTheFile": string; + "archive": string; + "channelArchiveConfirmTitle": string; + "channelArchiveConfirmDescription": string; + "thisChannelArchived": string; + "displayOfNote": string; + "initialAccountSetting": string; + "youFollowing": string; + "preventAiLearning": string; + "preventAiLearningDescription": string; + "options": string; + "specifyUser": string; + "failedToPreviewUrl": string; + "update": string; + "rolesThatCanBeUsedThisEmojiAsReaction": string; + "rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription": string; + "rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn": string; + "cancelReactionConfirm": string; + "changeReactionConfirm": string; + "later": string; + "goToMisskey": string; + "additionalEmojiDictionary": string; + "installed": string; + "_initialAccountSetting": { + "accountCreated": string; + "letsStartAccountSetup": string; + "letsFillYourProfile": string; + "profileSetting": string; + "privacySetting": string; + "theseSettingsCanEditLater": string; + "youCanEditMoreSettingsInSettingsPageLater": string; + "followUsers": string; + "pushNotificationDescription": string; + "initialAccountSettingCompleted": string; + "haveFun": string; + "ifYouNeedLearnMore": string; + "skipAreYouSure": string; + "laterAreYouSure": string; + }; + "_serverRules": { + "description": string; + }; + "_accountMigration": { + "moveFrom": string; + "moveFromSub": string; + "moveFromLabel": string; + "moveFromDescription": string; + "moveTo": string; + "moveToLabel": string; + "moveCannotBeUndone": string; + "moveAccountDescription": string; + "moveAccountHowTo": string; + "startMigration": string; + "migrationConfirm": string; + "movedAndCannotBeUndone": string; + "postMigrationNote": string; + "movedTo": string; + }; + "_achievements": { + "earnedAt": string; + "_types": { + "_notes1": { + "title": string; + "description": string; + "flavor": string; + }; + "_notes10": { + "title": string; + "description": string; + }; + "_notes100": { + "title": string; + "description": string; + }; + "_notes500": { + "title": string; + "description": string; + }; + "_notes1000": { + "title": string; + "description": string; + }; + "_notes5000": { + "title": string; + "description": string; + }; + "_notes10000": { + "title": string; + "description": string; + }; + "_notes20000": { + "title": string; + "description": string; + }; + "_notes30000": { + "title": string; + "description": string; + }; + "_notes40000": { + "title": string; + "description": string; + }; + "_notes50000": { + "title": string; + "description": string; + }; + "_notes60000": { + "title": string; + "description": string; + }; + "_notes70000": { + "title": string; + "description": string; + }; + "_notes80000": { + "title": string; + "description": string; + }; + "_notes90000": { + "title": string; + "description": string; + }; + "_notes100000": { + "title": string; + "description": string; + "flavor": string; + }; + "_login3": { + "title": string; + "description": string; + "flavor": string; + }; + "_login7": { + "title": string; + "description": string; + "flavor": string; + }; + "_login15": { + "title": string; + "description": string; + }; + "_login30": { + "title": string; + "description": string; + }; + "_login60": { + "title": string; + "description": string; + }; + "_login100": { + "title": string; + "description": string; + "flavor": string; + }; + "_login200": { + "title": string; + "description": string; + }; + "_login300": { + "title": string; + "description": string; + }; + "_login400": { + "title": string; + "description": string; + }; + "_login500": { + "title": string; + "description": string; + "flavor": string; + }; + "_login600": { + "title": string; + "description": string; + }; + "_login700": { + "title": string; + "description": string; + }; + "_login800": { + "title": string; + "description": string; + }; + "_login900": { + "title": string; + "description": string; + }; + "_login1000": { + "title": string; + "description": string; + "flavor": string; + }; + "_noteClipped1": { + "title": string; + "description": string; + }; + "_noteFavorited1": { + "title": string; + "description": string; + }; + "_myNoteFavorited1": { + "title": string; + "description": string; + }; + "_profileFilled": { + "title": string; + "description": string; + }; + "_markedAsCat": { + "title": string; + "description": string; + "flavor": string; + }; + "_following1": { + "title": string; + "description": string; + }; + "_following10": { + "title": string; + "description": string; + }; + "_following50": { + "title": string; + "description": string; + }; + "_following100": { + "title": string; + "description": string; + }; + "_following300": { + "title": string; + "description": string; + }; + "_followers1": { + "title": string; + "description": string; + }; + "_followers10": { + "title": string; + "description": string; + }; + "_followers50": { + "title": string; + "description": string; + }; + "_followers100": { + "title": string; + "description": string; + }; + "_followers300": { + "title": string; + "description": string; + }; + "_followers500": { + "title": string; + "description": string; + }; + "_followers1000": { + "title": string; + "description": string; + }; + "_collectAchievements30": { + "title": string; + "description": string; + }; + "_viewAchievements3min": { + "title": string; + "description": string; + }; + "_iLoveMisskey": { + "title": string; + "description": string; + "flavor": string; + }; + "_foundTreasure": { + "title": string; + "description": string; + }; + "_client30min": { + "title": string; + "description": string; + }; + "_client60min": { + "title": string; + "description": string; + }; + "_noteDeletedWithin1min": { + "title": string; + "description": string; + }; + "_postedAtLateNight": { + "title": string; + "description": string; + "flavor": string; + }; + "_postedAt0min0sec": { + "title": string; + "description": string; + "flavor": string; + }; + "_selfQuote": { + "title": string; + "description": string; + }; + "_htl20npm": { + "title": string; + "description": string; + }; + "_viewInstanceChart": { + "title": string; + "description": string; + }; + "_outputHelloWorldOnScratchpad": { + "title": string; + "description": string; + }; + "_open3windows": { + "title": string; + "description": string; + }; + "_driveFolderCircularReference": { + "title": string; + "description": string; + }; + "_reactWithoutRead": { + "title": string; + "description": string; + }; + "_clickedClickHere": { + "title": string; + "description": string; + }; + "_justPlainLucky": { + "title": string; + "description": string; + }; + "_setNameToSyuilo": { + "title": string; + "description": string; + }; + "_passedSinceAccountCreated1": { + "title": string; + "description": string; + }; + "_passedSinceAccountCreated2": { + "title": string; + "description": string; + }; + "_passedSinceAccountCreated3": { + "title": string; + "description": string; + }; + "_loggedInOnBirthday": { + "title": string; + "description": string; + }; + "_loggedInOnNewYearsDay": { + "title": string; + "description": string; + "flavor": string; + }; + "_cookieClicked": { + "title": string; + "description": string; + "flavor": string; + }; + "_brainDiver": { + "title": string; + "description": string; + "flavor": string; + }; + }; + }; + "_role": { + "new": string; + "edit": string; + "name": string; + "description": string; + "permission": string; + "descriptionOfPermission": string; + "assignTarget": string; + "descriptionOfAssignTarget": string; + "manual": string; + "conditional": string; + "condition": string; + "isConditionalRole": string; + "isPublic": string; + "descriptionOfIsPublic": string; + "options": string; + "policies": string; + "baseRole": string; + "useBaseValue": string; + "chooseRoleToAssign": string; + "iconUrl": string; + "asBadge": string; + "descriptionOfAsBadge": string; + "isExplorable": string; + "descriptionOfIsExplorable": string; + "displayOrder": string; + "descriptionOfDisplayOrder": string; + "canEditMembersByModerator": string; + "descriptionOfCanEditMembersByModerator": string; + "priority": string; + "_priority": { + "low": string; + "middle": string; + "high": string; + }; + "_options": { + "gtlAvailable": string; + "ltlAvailable": string; + "canPublicNote": string; + "canInvite": string; + "canManageCustomEmojis": string; + "driveCapacity": string; + "alwaysMarkNsfw": string; + "pinMax": string; + "antennaMax": string; + "wordMuteMax": string; + "webhookMax": string; + "clipMax": string; + "noteEachClipsMax": string; + "userListMax": string; + "userEachUserListsMax": string; + "rateLimitFactor": string; + "descriptionOfRateLimitFactor": string; + "canHideAds": string; + "canSearchNotes": string; + }; + "_condition": { + "isLocal": string; + "isRemote": string; + "createdLessThan": string; + "createdMoreThan": string; + "followersLessThanOrEq": string; + "followersMoreThanOrEq": string; + "followingLessThanOrEq": string; + "followingMoreThanOrEq": string; + "notesLessThanOrEq": string; + "notesMoreThanOrEq": string; + "and": string; + "or": string; + "not": string; + }; + }; + "_sensitiveMediaDetection": { + "description": string; + "sensitivity": string; + "sensitivityDescription": string; + "setSensitiveFlagAutomatically": string; + "setSensitiveFlagAutomaticallyDescription": string; + "analyzeVideos": string; + "analyzeVideosDescription": string; + }; + "_emailUnavailable": { + "used": string; + "format": string; + "disposable": string; + "mx": string; + "smtp": string; + }; + "_ffVisibility": { + "public": string; + "followers": string; + "private": string; + }; + "_signup": { + "almostThere": string; + "emailAddressInfo": string; + "emailSent": string; + }; + "_accountDelete": { + "accountDelete": string; + "mayTakeTime": string; + "sendEmail": string; + "requestAccountDelete": string; + "started": string; + "inProgress": string; + }; + "_ad": { + "back": string; + "reduceFrequencyOfThisAd": string; + "hide": string; + }; + "_forgotPassword": { + "enterEmail": string; + "ifNoEmail": string; + "contactAdmin": string; + }; + "_gallery": { + "my": string; + "liked": string; + "like": string; + "unlike": string; + }; + "_email": { + "_follow": { + "title": string; + }; + "_receiveFollowRequest": { + "title": string; + }; + }; + "_plugin": { + "install": string; + "installWarn": string; + "manage": string; + }; + "_preferencesBackups": { + "list": string; + "saveNew": string; + "loadFile": string; + "apply": string; + "save": string; + "inputName": string; + "cannotSave": string; + "nameAlreadyExists": string; + "applyConfirm": string; + "saveConfirm": string; + "deleteConfirm": string; + "renameConfirm": string; + "noBackups": string; + "createdAt": string; + "updatedAt": string; + "cannotLoad": string; + "invalidFile": string; + }; + "_registry": { + "scope": string; + "key": string; + "keys": string; + "domain": string; + "createKey": string; + }; + "_aboutMisskey": { + "about": string; + "contributors": string; + "allContributors": string; + "source": string; + "translation": string; + "donate": string; + "morePatrons": string; + "patrons": string; + }; + "_nsfw": { + "respect": string; + "ignore": string; + "force": string; + }; + "_instanceTicker": { + "none": string; + "remote": string; + "always": string; + }; + "_serverDisconnectedBehavior": { + "reload": string; + "dialog": string; + "quiet": string; + }; + "_channel": { + "create": string; + "edit": string; + "setBanner": string; + "removeBanner": string; + "featured": string; + "owned": string; + "following": string; + "usersCount": string; + "notesCount": string; + "nameAndDescription": string; + "nameOnly": string; + }; + "_menuDisplay": { + "sideFull": string; + "sideIcon": string; + "top": string; + "hide": string; + }; + "_wordMute": { + "muteWords": string; + "muteWordsDescription": string; + "muteWordsDescription2": string; + "softDescription": string; + "hardDescription": string; + "soft": string; + "hard": string; + "mutedNotes": string; + }; + "_instanceMute": { + "instanceMuteDescription": string; + "instanceMuteDescription2": string; + "title": string; + "heading": string; + }; + "_theme": { + "explore": string; + "install": string; + "manage": string; + "code": string; + "description": string; + "installed": string; + "installedThemes": string; + "builtinThemes": string; + "alreadyInstalled": string; + "invalid": string; + "make": string; + "base": string; + "addConstant": string; + "constant": string; + "defaultValue": string; + "color": string; + "refProp": string; + "refConst": string; + "key": string; + "func": string; + "funcKind": string; + "argument": string; + "basedProp": string; + "alpha": string; + "darken": string; + "lighten": string; + "inputConstantName": string; + "importInfo": string; + "deleteConstantConfirm": string; + "keys": { + "accent": string; + "bg": string; + "fg": string; + "focus": string; + "indicator": string; + "panel": string; + "shadow": string; + "header": string; + "navBg": string; + "navFg": string; + "navHoverFg": string; + "navActive": string; + "navIndicator": string; + "link": string; + "hashtag": string; + "mention": string; + "mentionMe": string; + "renote": string; + "modalBg": string; + "divider": string; + "scrollbarHandle": string; + "scrollbarHandleHover": string; + "dateLabelFg": string; + "infoBg": string; + "infoFg": string; + "infoWarnBg": string; + "infoWarnFg": string; + "cwBg": string; + "cwFg": string; + "cwHoverBg": string; + "toastBg": string; + "toastFg": string; + "buttonBg": string; + "buttonHoverBg": string; + "inputBorder": string; + "listItemHoverBg": string; + "driveFolderBg": string; + "wallpaperOverlay": string; + "badge": string; + "messageBg": string; + "accentDarken": string; + "accentLighten": string; + "fgHighlighted": string; + }; + }; + "_sfx": { + "note": string; + "noteMy": string; + "notification": string; + "chat": string; + "chatBg": string; + "antenna": string; + "channel": string; + }; + "_ago": { + "future": string; + "justNow": string; + "secondsAgo": string; + "minutesAgo": string; + "hoursAgo": string; + "daysAgo": string; + "weeksAgo": string; + "monthsAgo": string; + "yearsAgo": string; + "invalid": string; + }; + "_time": { + "second": string; + "minute": string; + "hour": string; + "day": string; + }; + "_timelineTutorial": { + "title": string; + "step1_1": string; + "step1_2": string; + "step2_1": string; + "step2_2": string; + "step3_1": string; + "step3_2": string; + "step4_1": string; + "step4_2": string; + }; + "_2fa": { + "alreadyRegistered": string; + "registerTOTP": string; + "passwordToTOTP": string; + "step1": string; + "step2": string; + "step2Click": string; + "step2Url": string; + "step3Title": string; + "step3": string; + "step4": string; + "securityKeyNotSupported": string; + "registerTOTPBeforeKey": string; + "securityKeyInfo": string; + "chromePasskeyNotSupported": string; + "registerSecurityKey": string; + "securityKeyName": string; + "tapSecurityKey": string; + "removeKey": string; + "removeKeyConfirm": string; + "whyTOTPOnlyRenew": string; + "renewTOTP": string; + "renewTOTPConfirm": string; + "renewTOTPOk": string; + "renewTOTPCancel": string; + }; + "_permissions": { + "read:account": string; + "write:account": string; + "read:blocks": string; + "write:blocks": string; + "read:drive": string; + "write:drive": string; + "read:favorites": string; + "write:favorites": string; + "read:following": string; + "write:following": string; + "read:messaging": string; + "write:messaging": string; + "read:mutes": string; + "write:mutes": string; + "write:notes": string; + "read:notifications": string; + "write:notifications": string; + "read:reactions": string; + "write:reactions": string; + "write:votes": string; + "read:pages": string; + "write:pages": string; + "read:page-likes": string; + "write:page-likes": string; + "read:user-groups": string; + "write:user-groups": string; + "read:channels": string; + "write:channels": string; + "read:gallery": string; + "write:gallery": string; + "read:gallery-likes": string; + "write:gallery-likes": string; + }; + "_auth": { + "shareAccessTitle": string; + "shareAccess": string; + "shareAccessAsk": string; + "permission": string; + "permissionAsk": string; + "pleaseGoBack": string; + "callback": string; + "denied": string; + "pleaseLogin": string; + }; + "_antennaSources": { + "all": string; + "homeTimeline": string; + "users": string; + "userList": string; + }; + "_weekday": { + "sunday": string; + "monday": string; + "tuesday": string; + "wednesday": string; + "thursday": string; + "friday": string; + "saturday": string; + }; + "_widgets": { + "profile": string; + "instanceInfo": string; + "memo": string; + "notifications": string; + "timeline": string; + "calendar": string; + "trends": string; + "clock": string; + "rss": string; + "rssTicker": string; + "activity": string; + "photos": string; + "digitalClock": string; + "unixClock": string; + "federation": string; + "instanceCloud": string; + "postForm": string; + "slideshow": string; + "button": string; + "onlineUsers": string; + "jobQueue": string; + "serverMetric": string; + "aiscript": string; + "aiscriptApp": string; + "aichan": string; + "userList": string; + "_userList": { + "chooseList": string; + }; + "clicker": string; + }; + "_cw": { + "hide": string; + "show": string; + "chars": string; + "files": string; + }; + "_poll": { + "noOnlyOneChoice": string; + "choiceN": string; + "noMore": string; + "canMultipleVote": string; + "expiration": string; + "infinite": string; + "at": string; + "after": string; + "deadlineDate": string; + "deadlineTime": string; + "duration": string; + "votesCount": string; + "totalVotes": string; + "vote": string; + "showResult": string; + "voted": string; + "closed": string; + "remainingDays": string; + "remainingHours": string; + "remainingMinutes": string; + "remainingSeconds": string; + }; + "_visibility": { + "public": string; + "publicDescription": string; + "home": string; + "homeDescription": string; + "followers": string; + "followersDescription": string; + "specified": string; + "specifiedDescription": string; + "disableFederation": string; + "disableFederationDescription": string; + }; + "_postForm": { + "replyPlaceholder": string; + "quotePlaceholder": string; + "channelPlaceholder": string; + "_placeholders": { + "a": string; + "b": string; + "c": string; + "d": string; + "e": string; + "f": string; + }; + }; + "_profile": { + "name": string; + "username": string; + "description": string; + "youCanIncludeHashtags": string; + "metadata": string; + "metadataEdit": string; + "metadataDescription": string; + "metadataLabel": string; + "metadataContent": string; + "changeAvatar": string; + "changeBanner": string; + }; + "_exportOrImport": { + "allNotes": string; + "favoritedNotes": string; + "followingList": string; + "muteList": string; + "blockingList": string; + "userLists": string; + "excludeMutingUsers": string; + "excludeInactiveUsers": string; + }; + "_charts": { + "federation": string; + "apRequest": string; + "usersIncDec": string; + "usersTotal": string; + "activeUsers": string; + "notesIncDec": string; + "localNotesIncDec": string; + "remoteNotesIncDec": string; + "notesTotal": string; + "filesIncDec": string; + "filesTotal": string; + "storageUsageIncDec": string; + "storageUsageTotal": string; + }; + "_instanceCharts": { + "requests": string; + "users": string; + "usersTotal": string; + "notes": string; + "notesTotal": string; + "ff": string; + "ffTotal": string; + "cacheSize": string; + "cacheSizeTotal": string; + "files": string; + "filesTotal": string; + }; + "_timelines": { + "home": string; + "local": string; + "social": string; + "global": string; + }; + "_play": { + "new": string; + "edit": string; + "created": string; + "updated": string; + "deleted": string; + "pageSetting": string; + "editThisPage": string; + "viewSource": string; + "my": string; + "liked": string; + "featured": string; + "title": string; + "script": string; + "summary": string; + }; + "_pages": { + "newPage": string; + "editPage": string; + "readPage": string; + "created": string; + "updated": string; + "deleted": string; + "pageSetting": string; + "nameAlreadyExists": string; + "invalidNameTitle": string; + "invalidNameText": string; + "editThisPage": string; + "viewSource": string; + "viewPage": string; + "like": string; + "unlike": string; + "my": string; + "liked": string; + "featured": string; + "inspector": string; + "contents": string; + "content": string; + "variables": string; + "title": string; + "url": string; + "summary": string; + "alignCenter": string; + "hideTitleWhenPinned": string; + "font": string; + "fontSerif": string; + "fontSansSerif": string; + "eyeCatchingImageSet": string; + "eyeCatchingImageRemove": string; + "chooseBlock": string; + "selectType": string; + "contentBlocks": string; + "inputBlocks": string; + "specialBlocks": string; + "blocks": { + "text": string; + "textarea": string; + "section": string; + "image": string; + "button": string; + "note": string; + "_note": { + "id": string; + "idDescription": string; + "detailed": string; + }; + }; + }; + "_relayStatus": { + "requesting": string; + "accepted": string; + "rejected": string; + }; + "_notification": { + "fileUploaded": string; + "youGotMention": string; + "youGotReply": string; + "youGotQuote": string; + "youRenoted": string; + "youWereFollowed": string; + "youReceivedFollowRequest": string; + "yourFollowRequestAccepted": string; + "pollEnded": string; + "unreadAntennaNote": string; + "emptyPushNotificationMessage": string; + "achievementEarned": string; + "_types": { + "all": string; + "follow": string; + "mention": string; + "reply": string; + "renote": string; + "quote": string; + "reaction": string; + "pollEnded": string; + "receiveFollowRequest": string; + "followRequestAccepted": string; + "achievementEarned": string; + "app": string; + }; + "_actions": { + "followBack": string; + "reply": string; + "renote": string; + }; + }; + "_deck": { + "alwaysShowMainColumn": string; + "columnAlign": string; + "addColumn": string; + "configureColumn": string; + "swapLeft": string; + "swapRight": string; + "swapUp": string; + "swapDown": string; + "stackLeft": string; + "popRight": string; + "profile": string; + "newProfile": string; + "deleteProfile": string; + "introduction": string; + "introduction2": string; + "widgetsIntroduction": string; + "_columns": { + "main": string; + "widgets": string; + "notifications": string; + "tl": string; + "antenna": string; + "list": string; + "channel": string; + "mentions": string; + "direct": string; + "roleTimeline": string; + }; + }; + "_dialog": { + "charactersExceeded": string; + "charactersBelow": string; + }; + "_disabledTimeline": { + "title": string; + "description": string; + }; + "_drivecleaner": { + "orderBySizeDesc": string; + "orderByCreatedAtAsc": string; + }; + "_webhookSettings": { + "createWebhook": string; + "name": string; + "secret": string; + "events": string; + "active": string; + "_events": { + "follow": string; + "followed": string; + "note": string; + "reply": string; + "renote": string; + "reaction": string; + "mention": string; + }; + }; +} +declare const locales: { + [lang: string]: Locale; +}; export = locales; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0b7108fe6d820f185737ff257c600ccaeb41cca2..fcba3fb8223f5a5a909bd60e26a972b48cbb6f01 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -52,6 +52,8 @@ addToList: "リストã«è¿½åŠ " sendMessage: "メッセージをé€ä¿¡" copyRSS: "RSSをコピー" copyUsername: "ユーザーåをコピー" +copyUserId: "ユーザーIDをコピー" +copyNoteId: "ノートIDをコピー" searchUser: "ユーザーを検索" reply: "返信" loadMore: "ã‚‚ã£ã¨è¦‹ã‚‹" @@ -790,6 +792,7 @@ noMaintainerInformationWarning: "管ç†è€…æƒ…å ±ãŒè¨å®šã•ã‚Œã¦ã„ã¾ã›ã‚“ noBotProtectionWarning: "Botプãƒãƒ†ã‚¯ã‚·ãƒ§ãƒ³ãŒè¨å®šã•ã‚Œã¦ã„ã¾ã›ã‚“。" configure: "è¨å®šã™ã‚‹" postToGallery: "ギャラリーã¸æŠ•ç¨¿" +postToHashtag: "ã“ã®ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°ã§æŠ•ç¨¿" gallery: "ギャラリー" recentPosts: "最近ã®æŠ•ç¨¿" popularPosts: "人気ã®æŠ•ç¨¿" @@ -823,6 +826,7 @@ translatedFrom: "{x}ã‹ã‚‰ç¿»è¨³" accountDeletionInProgress: "アカウントã®å‰Šé™¤ãŒé€²è¡Œä¸ã§ã™" usernameInfo: "サーãƒãƒ¼ä¸Šã§ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’一æ„ã«è˜åˆ¥ã™ã‚‹ãŸã‚ã®åå‰ã€‚アルファベット(a~z, A~Z)ã€æ•°å—(0~9)ã€ãŠã‚ˆã³ã‚¢ãƒ³ãƒ€ãƒ¼ãƒãƒ¼(_)ãŒä½¿ç”¨ã§ãã¾ã™ã€‚ユーザーåã¯å¾Œã‹ã‚‰å¤‰æ›´ã™ã‚‹ã“ã¨ã¯å‡ºæ¥ã¾ã›ã‚“。" aiChanMode: "è—モード" +devMode: "開発者モード" keepCw: "CWã‚’ç¶æŒã™ã‚‹" pubSub: "Pub/Subã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ" lastCommunication: "ç›´è¿‘ã®é€šä¿¡" @@ -987,7 +991,9 @@ postToTheChannel: "ãƒãƒ£ãƒ³ãƒãƒ«ã«æŠ•ç¨¿" cannotBeChangedLater: "後ã‹ã‚‰å¤‰æ›´ã§ãã¾ã›ã‚“。" reactionAcceptance: "リアクションã®å—ã‘入れ" likeOnly: "ã„ã„ãã®ã¿" -likeOnlyForRemote: "リモートã‹ã‚‰ã¯ã„ã„ãã®ã¿" +likeOnlyForRemote: "全㦠(リモートã¯ã„ã„ãã®ã¿)" +nonSensitiveOnly: "éžã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã®ã¿" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "éžã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã®ã¿ (リモートã¯ã„ã„ãã®ã¿)" rolesAssignedToMe: "自分ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚ŒãŸãƒãƒ¼ãƒ«" resetPasswordConfirm: "パスワードリセットã—ã¾ã™ã‹ï¼Ÿ" sensitiveWords: "センシティブワード" @@ -1045,6 +1051,17 @@ preventAiLearning: "生æˆAIã«ã‚ˆã‚‹å¦ç¿’ã‚’æ‹’å¦" preventAiLearningDescription: "外部ã®æ–‡ç« 生æˆAIã‚„ç”»åƒç”ŸæˆAIã«å¯¾ã—ã¦ã€æŠ•ç¨¿ã—ãŸãƒŽãƒ¼ãƒˆã‚„ç”»åƒãªã©ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã‚’å¦ç¿’ã®å¯¾è±¡ã«ã—ãªã„よã†ã«è¦æ±‚ã—ã¾ã™ã€‚ã“ã‚Œã¯noaiフラグをHTMLレスãƒãƒ³ã‚¹ã«å«ã‚ã‚‹ã“ã¨ã«ã‚ˆã£ã¦å®Ÿç¾ã•ã‚Œã¾ã™ãŒã€ã“ã®è¦æ±‚ã«å¾“ã†ã‹ã¯ãã®AI次第ã§ã‚ã‚‹ãŸã‚ã€å¦ç¿’を完全ã«é˜²æ¢ã™ã‚‹ã‚‚ã®ã§ã¯ã‚ã‚Šã¾ã›ã‚“。" options: "オプション" specifyUser: "ユーザー指定" +failedToPreviewUrl: "プレビューã§ãã¾ã›ã‚“" +update: "æ›´æ–°" +rolesThatCanBeUsedThisEmojiAsReaction: "リアクションã¨ã—ã¦ä½¿ãˆã‚‹ãƒãƒ¼ãƒ«" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ãƒãƒ¼ãƒ«ã®æŒ‡å®šãŒä¸€ã¤ã‚‚ãªã„å ´åˆã€èª°ã§ã‚‚リアクションã¨ã—ã¦ä½¿ãˆã¾ã™ã€‚" +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ãƒãƒ¼ãƒ«ã¯å…¬é–‹ãƒãƒ¼ãƒ«ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚" +cancelReactionConfirm: "リアクションをå–り消ã—ã¾ã™ã‹ï¼Ÿ" +changeReactionConfirm: "リアクションを変更ã—ã¾ã™ã‹ï¼Ÿ" +later: "ã‚ã¨ã§" +goToMisskey: "Misskeyã¸" +additionalEmojiDictionary: "絵文å—ã®è¿½åŠ 辞書" +installed: "インストール済ã¿" _initialAccountSetting: accountCreated: "アカウントã®ä½œæˆãŒå®Œäº†ã—ã¾ã—ãŸï¼" @@ -1060,6 +1077,7 @@ _initialAccountSetting: haveFun: "{name}ã‚’ãŠæ¥½ã—ã¿ãã ã•ã„ï¼" ifYouNeedLearnMore: "{name}(Misskey)ã®ä½¿ã„æ–¹ãªã©ã‚’詳ã—ã知るã«ã¯{link}ã‚’ã”覧ãã ã•ã„。" skipAreYouSure: "åˆæœŸè¨å®šã‚’スã‚ップã—ã¾ã™ã‹ï¼Ÿ" + laterAreYouSure: "åˆæœŸè¨å®šã‚’ã‚ã¨ã§ã‚„ã‚Šç›´ã—ã¾ã™ã‹ï¼Ÿ" _serverRules: description: "æ–°è¦ç™»éŒ²å‰ã«è¡¨ç¤ºã™ã‚‹ã€ã‚µãƒ¼ãƒãƒ¼ã®ç°¡æ½”ãªãƒ«ãƒ¼ãƒ«ã‚’è¨å®šã—ã¾ã™ã€‚内容ã¯åˆ©ç”¨è¦ç´„ã®è¦ç´„ã¨ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index d09f75155dd90ac1fec4458e4fcaf4699ce3c7d1..652814ca9861d69bc16c4a6ec0ebe6b0298f94b9 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -8,7 +8,7 @@ search: "探ã™" notifications: "通知" username: "ユーザーå" password: "パスワード" -forgotPassword: "パスワード忘れã¦ã‚‚ã†ãŸ" +forgotPassword: "パスワード忘れãŸã‚“?" fetchingAsApObject: "今ã¡ã¨é€£åˆã«ç…§ä¼šã—ã¨ã‚‹ã§" ok: "ãˆãˆã§" gotIt: "ã»ã„" @@ -47,11 +47,13 @@ copyContent: "内容をコピー" copyLink: "リンクをコピー" delete: "ã»ã‹ã™" deleteAndEdit: "ã»ã‹ã—ã¦ç›´ã™" -deleteAndEditConfirm: "ã“ã®ãƒŽãƒ¼ãƒˆã‚’ã»ã‹ã—ã¦ã‚‚ã£ã‹ã„ç›´ã™ï¼Ÿã“ã®ãƒŽãƒ¼ãƒˆã¸ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€Renoteã€è¿”信も全部消ãˆã‚‹ã‚“ã‚„ã‘ã©ãã‚Œã§ã‚‚ãˆãˆã‚“?" +deleteAndEditConfirm: "ã“ã®ãƒŽãƒ¼ãƒˆã‚’ã»ã‹ã—ã¦ã‚‚ã£ã‹ã„ç›´ã™ï¼Ÿã“ã®ãƒŽãƒ¼ãƒˆã¸ã®ãƒ„ッコミã€Renoteã€è¿”信も全部消ãˆã‚‹ã‚“ã‚„ã‘ã©ãã‚Œã§ã‚‚ãˆãˆã‚“?" addToList: "リストã«å…¥ã‚ŒãŸã‚‹" sendMessage: "メッセージをé€ã‚‹" copyRSS: "RSSをコピー" copyUsername: "ユーザーåをコピー" +copyUserId: "ユーザーIDをコピー" +copyNoteId: "ノートIDをコピー" searchUser: "ユーザーを検索" reply: "返事" loadMore: "ã¾ã ã¾ã ã‚ã‚‹ã§ï¼" @@ -1043,6 +1045,10 @@ preventAiLearning: "生æˆAIã®å¦ç¿’ã«ä½¿ã‚ã‚“ã¨ã„ã¦" preventAiLearningDescription: "ä»–ã®æ–‡ç« 生æˆAIã¨ã‹ç”»åƒç”ŸæˆAIã«ã€æŠ•ç¨¿ã—ãŸãƒŽãƒ¼ãƒˆã¨ã‹ç”»åƒãªã‚“ã‹ã‚’å‹æ‰‹ã«ä½¿ã‚んよã†ã«é ¼ã‚€ã§ã€‚具体的ã«ã¯noaiフラグをHTMLレスãƒãƒ³ã‚¹ã«å«ã‚ã‚‹ã‚“ã‚„ã‘ã©ã€ã“ã‚Œèžã„ã¦ãれるんã¯AIã®æ°—分次第やã‹ã‚‰ã€ä½¿ã‚れるå¯èƒ½æ€§ã‚‚ã¡ã‚‡ã£ã¨ã¯ã‚ã‚‹ãªã€‚" options: "オプション" specifyUser: "ユーザー指定" +rolesThatCanBeUsedThisEmojiAsReaction: "ツッコミã¨ã—ã¦ä½¿ãˆã‚‹ãƒãƒ¼ãƒ«" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ãƒãƒ¼ãƒ«ãŒä¸€å€‹ã‚‚指定ã•ã‚Œã¦ã¸ã‚“ã‹ã£ãŸã‚‰ã€èª°ã§ã‚‚ツッコミã¨ã—ã¦ä½¿ãˆã‚‹ã§ã€‚" +cancelReactionConfirm: "ツッコむんをやã£ã±ã‚„ã‚ã‚‹ã‹ï¼Ÿ" +changeReactionConfirm: "ツッコミを別ã®ã«å¤‰ãˆã‚‹ã‹ï¼Ÿ" _initialAccountSetting: accountCreated: "アカウント作り終ã‚ã£ãŸã§ã€‚" letsStartAccountSetup: "アカウントã®åˆæœŸè¨å®šã‚’ã—よã‹ã€‚" @@ -1614,7 +1620,7 @@ _timelineTutorial: step2_2: "最åˆã®ãƒŽãƒ¼ãƒˆã¯ã€è‡ªå·±ç´¹ä»‹ã¨ã‹ã€Œ{name}始ã‚ã¦ã¿ãŸã‚“ã‚„ã€ã¨ã‹ãŒãˆãˆã¨æ€ã†ã§ã€‚" step3_1: "投稿ã§ããŸï¼Ÿ" step3_2: "ã‚ã‚“ãŸã®ãƒŽãƒ¼ãƒˆãŒã‚¿ã‚¤ãƒ ラインã«å‡ºã¦ããŸã‚‰æˆåŠŸã‚„。" - step4_1: "ノートã«ã¯ã€ã€Œãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€ã‚’付ã‘れるã§ã€‚" + step4_1: "ノートã«ã¯ã€ã€Œãƒ„ッコミã€ã‚’付ã‘れるã§ã€‚" step4_2: "ツッコむんやã£ãŸã‚‰ã€ãƒŽãƒ¼ãƒˆã®ã€Œ+ã€ãƒžãƒ¼ã‚¯ã‚’押ã—ã¦ã€å¥½ããªçµµæ–‡å—ã‚’é¸ã¶ã§ã€‚" _2fa: alreadyRegistered: "ã‚‚ã†è¨å®šçµ‚ã‚ã£ã¨ã‚‹ã‚。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 39574d3321b9d83846430a556b69b32895bbc74e..fd46eef1ff4a5f908d5f479c5ac4430690e3b938 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -52,6 +52,8 @@ addToList: "ë¦¬ìŠ¤íŠ¸ì— ì¶”ê°€" sendMessage: "메시지 보내기" copyRSS: "RSS 복사" copyUsername: "ìœ ì €ëª… 복사" +copyUserId: "ìœ ì € ID 복사" +copyNoteId: "노트 ID 복사" searchUser: "ì‚¬ìš©ìž ê²€ìƒ‰" reply: "답글" loadMore: "ë” ë³´ê¸°" @@ -505,7 +507,7 @@ objectStoragePrefixDesc: "ì´ Prefix ì˜ ë””ë ‰í† ë¦¬ ì•„ëž˜ì— íŒŒì¼ì´ ì €ìž¥ objectStorageEndpoint: "Endpoint" objectStorageEndpointDesc: "AWS S3ì˜ ê²½ìš° 공란, 다른 ì„œë¹„ìŠ¤ì˜ ê²½ìš° ê° ì„œë¹„ìŠ¤ì˜ ê°€ì´ë“œì— 맞게 endpoint를 ì„¤ì •í•´ì£¼ì„¸ìš”. '<host>' í˜¹ì€ '<host>:<port>' 와 ê°™ì´ ì§€ì •í•©ë‹ˆë‹¤." objectStorageRegion: "Region" -objectStorageRegionDesc: "'xx-east-1'와 ê°™ì´ regionì„ ì§€ì •í•´ì£¼ì„¸ìš”. 사용하는 ì„œë¹„ìŠ¤ì— region ê°œë…ì´ ì—†ëŠ” 경우, 비워 ë‘거나 'us-east-1'으로 ì„¤ì •í•´ 주세요." +objectStorageRegionDesc: "'xx-east-1'와 ê°™ì´ regionì„ ì§€ì •í•´ 주세요. 사용하는 ì„œë¹„ìŠ¤ì— region ê°œë…ì´ ì—†ëŠ” 경우 'us-east-1'으로 ì„¤ì •í•´ 주세요. AWS ì„¤ì • íŒŒì¼ ë˜ëŠ” 환경 변수를 ì°¸ì¡°í• ê²½ìš°ì—는 비워주세요." objectStorageUseSSL: "SSL 사용" objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF ë¡œ ì„¤ì •í•´ 주세요" objectStorageUseProxy: "ì—°ê²°ì— í”„ë¡ì‹œë¥¼ 사용" @@ -790,6 +792,7 @@ noMaintainerInformationWarning: "ê´€ë¦¬ìž ì •ë³´ê°€ ì„¤ì •ë˜ì–´ 있지 않습 noBotProtectionWarning: "Bot ë°©ì–´ê°€ ì„¤ì •ë˜ì–´ 있지 않습니다." configure: "ì„¤ì •í•˜ê¸°" postToGallery: "ê°¤ëŸ¬ë¦¬ì— ì—…ë¡œë“œ" +postToHashtag: "ì´ í•´ì‹œíƒœê·¸ì— ê²Œì‹œ" gallery: "갤러리" recentPosts: "최근 í¬ìŠ¤íŠ¸" popularPosts: "ì¸ê¸° í¬ìŠ¤íŠ¸" @@ -823,6 +826,7 @@ translatedFrom: "{x}ì—ì„œ 번ì—" accountDeletionInProgress: "ê³„ì • ì‚ì œ ìž‘ì—…ì„ ì§„í–‰í•˜ê³ ìžˆìŠµë‹ˆë‹¤" usernameInfo: "서버ìƒì—ì„œ ê³„ì •ì„ ì‹ë³„하기 위한 ì´ë¦„. 알파벳(a~z, A~Z), 숫ìž(0~9) ë° ì–¸ë”ë°”(_)를 ì‚¬ìš©í• ìˆ˜ 있습니다. 사용ìžëª…ì€ ë‚˜ì¤‘ì— ë³€ê²½í• ìˆ˜ 없습니다." aiChanMode: "ì•„ì´ ëª¨ë“œ" +devMode: "ê°œë°œìž ëª¨ë“œ" keepCw: "CW ìœ ì§€í•˜ê¸°" pubSub: "Pub/Sub ê³„ì •" lastCommunication: "마지막 í†µì‹ " @@ -830,8 +834,10 @@ resolved: "í•´ê²°ë¨" unresolved: "í•´ê²°ë˜ì§€ ì•ŠìŒ" breakFollow: "팔로워 í•´ì œ" breakFollowConfirm: "팔로우를 í•´ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" -itsOn: "켜ì§" -itsOff: "꺼ì§" +itsOn: "ì¼œì ¸ 있습니다" +itsOff: "êº¼ì ¸ 있습니다" +on: "켜ì§" +off: "꺼ì§" emailRequiredForSignup: "ê°€ìž…í• ë•Œ ì´ë©”ì¼ ì£¼ì†Œ ìž…ë ¥ì„ í•„ìˆ˜ë¡œ 하기" unread: "ì½ì§€ ì•ŠìŒ" filter: "í•„í„°" @@ -864,7 +870,7 @@ instanceDefaultLightTheme: "서버 기본 ë¼ì´íŠ¸ 테마" instanceDefaultDarkTheme: "서버 기본 ë‹¤í¬ í…Œë§ˆ" instanceDefaultThemeDescription: "ê°ì²´ 형ì‹ì˜ 테마 코드를 ìž…ë ¥í•´ 주세요." mutePeriod: "ë®¤íŠ¸í• ê¸°ê°„" -period: "투표 기한" +period: "기간" indefinitely: "무기한" tenMinutes: "10분" oneHour: "1시간" @@ -986,10 +992,13 @@ cannotBeChangedLater: "ë‚˜ì¤‘ì— ë³€ê²½í• ìˆ˜ 없습니다." reactionAcceptance: "리액션 ìˆ˜ì‹ " likeOnly: "좋아요만 받기" likeOnlyForRemote: "리모트ì—서는 좋아요만 받기" +nonSensitiveOnly: "열람 주ì˜ë¡œ ì„¤ì •ë˜ì§€ ì•Šì•˜ì„ ë•Œë§Œ 받기" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "열람 주ì˜ë¡œ ì„¤ì •ë˜ì§€ ì•Šì•˜ì„ ë•Œë§Œ 받기 (리모트ì—서는 좋아요만 받기)" rolesAssignedToMe: "나ì—게 í• ë‹¹ëœ ì—í• " resetPasswordConfirm: "비밀번호를 ìž¬ì„¤ì •í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" sensitiveWords: "민ê°í•œ 단어" sensitiveWordsDescription: "ì„¤ì •í•œ 단어가 í¬í•¨ëœ ë…¸íŠ¸ì˜ ê³µê°œ 범위를 '홈'으로 ê°•ì œí•©ë‹ˆë‹¤. 개행으로 구분하여 여러 개를 ì§€ì •í• ìˆ˜ 있습니다." +sensitiveWordsDescription2: "공백으로 구분하면 AND ì§€ì •ì´ ë˜ë©°, 키워드를 슬래시로 둘러싸면 ì •ê·œ 표현ì‹ì´ ë©ë‹ˆë‹¤." notesSearchNotAvailable: "노트 ê²€ìƒ‰ì„ ì´ìš©í•˜ì‹¤ 수 없습니다." license: "ë¼ì´ì„ 스" unfavoriteConfirm: "ì¦ê²¨ì°¾ê¸°ë¥¼ í•´ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" @@ -1038,31 +1047,47 @@ thisChannelArchived: "ì´ ì±„ë„ì€ ì•„ì¹´ì´ë¸Œë˜ì—ˆìŠµë‹ˆë‹¤." displayOfNote: "노트 표시" initialAccountSetting: "초기 ì„¤ì •" youFollowing: "팔로잉" +preventAiLearning: "기계학습(ìƒì„±í˜• AI)ìœ¼ë¡œì˜ ì‚¬ìš©ì„ ê±°ë¶€" +preventAiLearningDescription: "ì™¸ë¶€ì˜ ë¬¸ìž¥ ìƒì„± AI나 ì´ë¯¸ì§€ ìƒì„± AIì— ëŒ€í•´ ì œì¶œí•œ 노트나 ì´ë¯¸ì§€ ë“±ì˜ ì½˜í…ì¸ ë¥¼ í•™ìŠµì˜ ëŒ€ìƒìœ¼ë¡œ 사용하지 ì•Šë„ë¡ ìš”êµ¬í•©ë‹ˆë‹¤. 다만, ì´ ìš”êµ¬ì‚¬í•ì„ 지킬 ì˜ë¬´ëŠ” 없기 ë•Œë¬¸ì— í•™ìŠµì„ ì™„ì „ížˆ 방지하는 ê²ƒì€ ì•„ë‹™ë‹ˆë‹¤." options: "옵션" +specifyUser: "ì‚¬ìš©ìž ì§€ì •" +failedToPreviewUrl: "미리 ë³¼ 수 ì—†ìŒ" +update: "ì—…ë°ì´íŠ¸" +rolesThatCanBeUsedThisEmojiAsReaction: "ì´ ì´ëª¨ì§€ë¥¼ 리액션으로 ì‚¬ìš©í• ìˆ˜ 있는 ì—í• " +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ì—í• ì„ ì§€ì •í•˜ì§€ 않으면, 누구나 ì´ ì´ëª¨ì§€ë¥¼ 리액션으로 ì‚¬ìš©í• ìˆ˜ 있습니다." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ì—í• ì€ ê³µê°œë¡œ ì„¤ì •ë˜ì–´ 있어야 합니다." +cancelReactionConfirm: "ë¦¬ì•¡ì…˜ì„ ì·¨ì†Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" +changeReactionConfirm: "ë¦¬ì•¡ì…˜ì„ ë³€ê²½í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" +later: "나중ì—" +goToMisskey: "Misskeyë¡œ" +additionalEmojiDictionary: "ì´ëª¨ì§€ 추가 ì‚¬ì „" +installed: "설치ë¨" _initialAccountSetting: accountCreated: "ê³„ì • ìƒì„±ì´ 완료ë˜ì—ˆìŠµë‹ˆë‹¤!" letsStartAccountSetup: "ê³„ì •ì˜ ì´ˆê¸° ì„¤ì •ì„ ì§„í–‰í•©ë‹ˆë‹¤." letsFillYourProfile: "ìš°ì„ ë‚˜ì˜ í”„ë¡œí•„ì„ ì„¤ì •í•´ ë³´ì•„ìš”." profileSetting: "프로필 ì„¤ì •" + privacySetting: "프ë¼ì´ë²„ì‹œ ì„¤ì •" theseSettingsCanEditLater: "ì´ ì„¤ì •ë“¤ì€ ë‚˜ì¤‘ì—ë„ ë³€ê²½í• ìˆ˜ 있습니다." - youCanEditMoreSettingsInSettingsPageLater: "ì´ ì™¸ì—ë„ 'ì„¤ì •' 페ì´ì§€ì—ì„œ 다양한 ì„¤ì •ì„ ë‚˜ì˜ ìž…ë§›ì— ë§›ê²Œ ì¡°ì ˆí• ìˆ˜ 있습니다. ê¼ í™•ì¸í•´ 보세요!" + youCanEditMoreSettingsInSettingsPageLater: "ì´ ì™¸ì—ë„ 'ì„¤ì •' 페ì´ì§€ì—ì„œ 다양한 ì„¤ì •ì„ ë‚˜ì˜ ìž…ë§›ì— ë§žê²Œ ì¡°ì ˆí• ìˆ˜ 있습니다. ê¼ í™•ì¸í•´ 보세요!" followUsers: "관심사가 맞는 ìœ ì €ë¥¼ 팔로우하여 타임ë¼ì¸ì„ 가꾸어 봅시다." pushNotificationDescription: "푸시 ì•Œë¦¼ì„ í™œì„±í™”í•˜ë©´ {name}ì˜ ì•Œë¦¼ì„ ë‚˜ì˜ ê¸°ê¸°ì—ì„œ 받아볼 수 있게 ë©ë‹ˆë‹¤." initialAccountSettingCompleted: "초기 ì„¤ì •ì„ ëª¨ë‘ ë§ˆì³¤ìŠµë‹ˆë‹¤!" haveFun: "{name}와 함께 ì¦ê±°ìš´ 시간 보내세요!" ifYouNeedLearnMore: "{name}(Misskey)ì˜ ì‚¬ìš© ë°©ë²•ì— ëŒ€í•´ ìžì„¸ížˆ ì•Œì•„ë³´ë ¤ë©´ {link}를 ì°¸ê³ í•´ 주세요." - skipAreYouSure: "초기 ì„¤ì •ì„ ë„˜ê¸°ì‹œê² ìŠµë‹ˆê¹Œ?" + skipAreYouSure: "초기 ì„¤ì •ì„ ì¤‘ë‹¨í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + laterAreYouSure: "초기 ì„¤ì •ì„ ë‚˜ì¤‘ì— ì§„í–‰í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" _serverRules: description: "íšŒì› ê°€ìž… ì´ì „ì— ê°„ë‹¨í•˜ê²Œ í‘œì‹œí• ì„œë²„ 규칙입니다. ì´ìš© ì•½ê´€ì˜ ìš”ì•½ìœ¼ë¡œ 구성하는 ê²ƒì„ ì¶”ì²œí•©ë‹ˆë‹¤." _accountMigration: moveFrom: "다른 ê³„ì •ì—ì„œ ì´ ê³„ì •ìœ¼ë¡œ ì´ì‚¬" moveFromSub: "다른 ê³„ì •ì— ëŒ€í•œ 별ì¹ì„ ìƒì„±" - moveFromLabel: "기존 ê³„ì •:" + moveFromLabel: "기존 ê³„ì • #{n}" moveFromDescription: "다른 ê³„ì •ì—ì„œ ì´ ê³„ì •ìœ¼ë¡œ 팔로워를 ê°€ì ¸ì˜¤ë ¤ë©´, ìš°ì„ ì—¬ê¸°ì—ì„œ 별ì¹ì„ ì§€ì •í•´ì•¼ 합니다. 반드시 ì´ì‚¬í•˜ê¸° ì „ì— ì§€ì •í•´ì•¼ 합니다! 기존 ê³„ì •ì„ ë‹¤ìŒê³¼ ê°™ì€ í˜•ì‹ìœ¼ë¡œ ìž…ë ¥í•´ 주ì‹ì‹œì˜¤: @person@instance.com" moveTo: "ì´ ê³„ì •ì—ì„œ 다른 ê³„ì •ìœ¼ë¡œ ì´ì‚¬" moveToLabel: "ì´ì‚¬í• ê³„ì •:" moveCannotBeUndone: "í•œ 번 ì´ì‚¬í•˜ë©´, ë‘ ë²ˆ 다시 ë˜ëŒë¦´ 수 없습니다." - moveAccountDescription: "ì´ ìž‘ì—…ì€ ì·¨ì†Œí• ìˆ˜ 없습니다. ë¨¼ì € ì´ì‚¬í• ê³„ì •ì—ì„œ ì´ ê³„ì •ì— ëŒ€í•œ 별ì¹ì„ ì§€ì •í•˜ì˜€ëŠ”ì§€ 다시 í•œ 번 확ì¸í•´ 주ì‹ì‹œì˜¤. 별ì¹ì„ ì§€ì •í•œ 다ìŒ, ì´ì‚¬í• ê³„ì •ì„ ë‹¤ìŒê³¼ ê°™ì€ í˜•ì‹ìœ¼ë¡œ ìž…ë ¥í•´ 주ì‹ì‹œì˜¤: @person@instance.com" + moveAccountDescription: "새 ê³„ì •ìœ¼ë¡œ ì´ì „합니다.\n ・팔로워가 새 ê³„ì •ì„ ìžë™ìœ¼ë¡œ 팔로우 합니다\nã€€ãƒ»ì´ ê³„ì •ì—ì„œ 팔로우는 ëª¨ë‘ í•´ì œë©ë‹ˆë‹¤\nã€€ãƒ»ì´ ê³„ì •ìœ¼ë¡œëŠ” 노트 작성 ë“±ì„ í• ìˆ˜ 없게 ë©ë‹ˆë‹¤\n\n팔로워는 ìžë™ìœ¼ë¡œ ì´ì „ë˜ì§€ë§Œ, 팔로우는 수ë™ìœ¼ë¡œ 진행해야 합니다. ì´ì „하기 ì „ì— ì´ ê³„ì •ì—ì„œ 팔로우를 ë‚´ë³´ë‚´ê³ , ì´ì „ 후ì—는 즉시 ì´ì „í•œ ê³„ì •ì—ì„œ ê°€ì ¸ì˜¤ê¸°ë¥¼ 진행하ì‹ì‹œì˜¤.\në¦¬ìŠ¤íŠ¸ãƒ»ë®¤íŠ¸ãƒ»ì°¨ë‹¨ì— ëŒ€í•´ì„œë„ ë§ˆì°¬ê°€ì§€ì´ë¯€ë¡œ 수ë™ìœ¼ë¡œ ì´ì „해야 합니다.\n\n(ì´ ì„¤ëª…ì€ ì´ ì„œë²„(Misskey v13.12.0 ì´í›„)ì˜ ì‚¬ì–‘ìž…ë‹ˆë‹¤. Mastodon ë“±ì˜ ë‹¤ë¥¸ ActivityPub 소프트웨어ì—서는 ìž‘ë™ì´ 다를 수 있습니다.)" moveAccountHowTo: "ê³„ì •ì„ ì´ì‚¬í•˜ë ¤ë©´ ìš°ì„ ì´ì‚¬ê°ˆ ê³„ì •ì—ì„œ ì´ ê³„ì •ì— ëŒ€í•œ 별ì¹ì„ ì§€ì •í•´ì•¼ 합니다.\n별ì¹ì„ 작성한 다ìŒ, ì´ì‚¬ê°ˆ ê³„ì •ì„ ë‹¤ìŒê³¼ ê°™ì´ ìž…ë ¥í•˜ì‹ì‹œì˜¤:\n@username@server.example.com" startMigration: "ì´ì‚¬í•˜ê¸°" migrationConfirm: "ì •ë§ë¡œ ì´ ê³„ì •ì„ {account} 으로 ì´ì „í•˜ì‹œê² ìŠµë‹ˆê¹Œ? í•œ 번 ì´ì „í•œ 다ìŒì—는 ì·¨ì†Œí• ìˆ˜ 없으며, ë‘ ë²ˆ 다시 ì›ëž˜ ìƒíƒœë¡œ ë³µêµ¬í• ìˆ˜ 없습니다.\nì´ì‚¬í• ê³„ì •ì—ì„œ ê³„ì • 별ì¹ì„ ì§€ì •í•˜ì˜€ëŠ”ì§€ 다시 í•œ 번 확ì¸í•˜ì‹ì‹œì˜¤." @@ -1491,7 +1516,7 @@ _menuDisplay: hide: "숨기기" _wordMute: muteWords: "ë®¤íŠ¸í• ë‹¨ì–´" - muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 ORë¡œ ì§€ì •ë©ë‹ˆë‹¤ã€‚" + muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 ORë¡œ ì§€ì •ë©ë‹ˆë‹¤." muteWordsDescription2: "ì •ê·œ 표현ì‹ì„ ì‚¬ìš©í•˜ë ¤ë©´ 키워드를 빗금표(/)ë¡œ ê°ì‹¸ 주세요." softDescription: "ì§€ì •í•œ ì¡°ê±´ì˜ ë…¸íŠ¸ë¥¼ 타임ë¼ì¸ì—ì„œ 숨ê¹ë‹ˆë‹¤." hardDescription: "ì§€ì •í•œ ì¡°ê±´ì˜ ë…¸íŠ¸ë¥¼ 타임ë¼ì¸ì— 추가하지 않습니다. 타임ë¼ì¸ì— 추가ë˜ì§€ ì•Šì€ ë…¸íŠ¸ëŠ” ì¡°ê±´ì„ ë³€ê²½í•´ë„ í‘œì‹œë˜ì§€ 않습니다." diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 36c29295f63be196cd82bb3d81e9d75cac8b9fda..ec2900527b6540e567f09c1be3d6c98cf926ad66 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -1,21 +1,31 @@ --- _lang_: "Norsk BokmÃ¥l" +headlineMisskey: "Et nettverk forbundet med Notes" +introMisskey: "Velkommen! Misskey er en desentralisert mikrobloggtjeneste med Ã¥pen kildekode.\nOpprett \"Notes\" for Ã¥ dele tankene dine med alle rundt deg. 📡\nMed \"reaksjoner\" kan du ogsÃ¥ raskt gi uttrykk for hva du synes om alles Notes. ðŸ‘\nLa oss utforske en ny verden! 🚀" +monthAndDay: "{day}-{month}" search: "Søk" notifications: "Varsler" username: "Brukernavn" password: "Passord" forgotPassword: "Glemt passord" +fetchingAsApObject: "Henter fra Fediverse..." ok: "OK" gotIt: "Skjønner" cancel: "Avbryt" -noThankYou: "Avbryt" +noThankYou: "Ikke nÃ¥" +enterUsername: "Skriv inn brukernavn" +renotedBy: "Renotes av {user}" +noNotes: "Ingen Notes" noNotifications: "Ingen varsler" instance: "Server" settings: "Innstillinger" notificationSettings: "Varslingsinnstillinger" +basicSettings: "Grunnleggende innstillinger" otherSettings: "Andre innstillinger" +openInWindow: "Ã…pne i vindu" profile: "Profil" timeline: "Tidslinje" +noAccountDescription: "Denne brukeren har ikke skrevet sin biografi ennÃ¥." login: "Logg inn" loggingIn: "Logget inn" logout: "Logg ut" @@ -24,17 +34,21 @@ uploading: "Laster opp" save: "Lagre" users: "Brukere" addUser: "Legg til bruker" -favorite: "Favoritt" +favorite: "Legg til i favoritter" favorites: "Favoritter" -unfavorite: "Fjern favoritt" -pin: "Fest" -unpin: "Opphev festing" +unfavorite: "Fjern fra favoritter" +favorited: "Lagt til i favoritter." +alreadyFavorited: "Allerede lagt til i favoritter." +cantFavorite: "Kunne ikke legge til i favoritter." +pin: "Fest til profil" +unpin: "Fjern fra profil" copyContent: "Kopier innhold" copyLink: "Kopier lenke" delete: "Slett" deleteAndEdit: "Slett og rediger" +deleteAndEditConfirm: "Er du sikker pÃ¥ at du vil slette denne Noten og redigere den? Du vil miste alle reaksjoner, Renotes og svar pÃ¥ den." addToList: "Legg til i liste" -sendMessage: "Send melding" +sendMessage: "Send en melding" copyRSS: "Kopier RSS" copyUsername: "Kopier brukernavn" searchUser: "Søk brukere" @@ -42,140 +56,290 @@ reply: "Svar" loadMore: "Vis mer" showMore: "Vis mer" showLess: "Lukk" +youGotNewFollower: "fulgte deg" +followRequestAccepted: "Følgeforespørsel akseptert" +importAndExport: "Importer og eksporter" +import: "Importer" +export: "Eksporter" files: "Filer" download: "Nedlastinger" +driveFileDeleteConfirm: "Er du sikker pÃ¥ at du vil slette \"{name}\"? Det vil ogsÃ¥ forsvinne fra alt innhold som bruker det." +unfollowConfirm: "Er du sikker pÃ¥ at du vil slutte Ã¥ følge {name}?" +importRequested: "Du har bedt om import. Dette kan ta en stund." lists: "Lister" noLists: "Ingen lister" -following: "Følg" +note: "Note" +notes: "Notes" +following: "Følger" followers: "Følgere" followsYou: "Følger deg" createList: "Opprett liste" error: "Feil" +somethingHappened: "En feil har oppstÃ¥tt" retry: "Prøv igjen" pageLoadError: "Kunne ikke hente side." +serverIsDead: "Denne serveren svarer ikke. Vennligst vent en stund og prøv igjen." +enterListName: "Skriv inn et navn pÃ¥ listen" privacy: "Personvern" +defaultNoteVisibility: "Standard synlighet" follow: "Følg" followRequest: "Følgeforespørsel" followRequests: "Følgeforespørsel" unfollow: "Avfølg" followRequestPending: "Venter pÃ¥ godkjenning" +enterEmoji: "Skriv inn en emoji" +renote: "Renote" +renoted: "Renotet." +cantRenote: "Dette innlegget kan ikke renotes." +cantReRenote: "En Renote kan ikke renotes." quote: "Sitat" -pinned: "Fest" +inChannelRenote: "Renote kun for kanal" +inChannelQuote: "Sitat kun for kanal" +pinnedNote: "Festet Note" +pinned: "Fest til profil" you: "Du" clickToShow: "Klikk for Ã¥ vise" add: "Legg til" reaction: "Reaksjon" reactions: "Reaksjoner" +reactionSetting: "Reaksjoner som vises i reaksjonsvelgeren" +reactionSettingDescription2: "Dra for Ã¥ endre rekkefølgen, klikk for Ã¥ slette, trykk \"+\" for Ã¥ legge til." +rememberNoteVisibility: "Husk innstillingene for synlighet av Notes" +attachCancel: "Fjern vedlegg" +enterFileName: "Skriv inn filnavn" mute: "Skjul" unmute: "Vis" +renoteMute: "Skjul Renotes" +renoteUnmute: "Vis Renotes" block: "Blokker" unblock: "Opphev blokkering" -blockConfirm: "Blokker?" -selectList: "Velg liste" -selectChannel: "Velg kanal" +suspend: "Suspender" +blockConfirm: "Er du sikker pÃ¥ at du vil blokke denne kontoen?" +unblockConfirm: "Er du sikker pÃ¥ at du vil oppheve blokkeringen av denne kontoen?" +suspendConfirm: "Er du sikker pÃ¥ at du vil suspendere denne kontoen?" +selectList: "Velg en liste" +selectChannel: "Velg en kanal" +selectAntenna: "Velg en antenne" +selectWidget: "Velg en widget" +editWidgets: "Rediger widgeter" +editWidgetsExit: "Ferdig" emoji: "Emoji" emojis: "Emojier" addEmoji: "Legg til emoji" +settingGuide: "Anbefalte innstillinger" +flagAsBot: "Merk denne kontoen som en bot" +flagAsBotDescription: "Aktiver dette alternativet hvis denne kontoen styres av et program. Hvis det er aktivert, vil det fungere som et flagg for andre utviklere for Ã¥ forhindre endeløse interaksjonskjeder med andre roboter og justere Misskeys interne systemer til Ã¥ behandle denne kontoen som en bot." +flagAsCat: "Merk denne kontoen som en katt" +flagAsCatDescription: "Aktiver dette alternativet for Ã¥ merke denne kontoen som en katt." +flagShowTimelineReplies: "Vis svar i tidslinje" addAccount: "Legg til konto" -selectUser: "Velg bruker" -instances: "Server" +reloadAccountsList: "Last inn kontoliste pÃ¥ nytt" +loginFailed: "Kunne ikke logge inn" +general: "Generelt" +searchWith: "Søk: {q}" +youHaveNoLists: "Du har ingen lister" +followConfirm: "Er du sikker pÃ¥ at du vil følge {name}?" +host: "Vert" +selectUser: "Velg en bruker" +recipient: "Mottaker" +annotation: "Kommentarer" +federation: "Føderasjon" +instances: "Servere" registeredAt: "Registrerte seg" +latestRequestReceivedAt: "Siste forespørsel mottatt" +latestStatus: "Siste status" +charts: "Diagrammer" perHour: "Per time" perDay: "Per dag" +stopActivityDelivery: "Slutt Ã¥ sende aktiviteter" blockThisInstance: "Blokker denne serveren" +operations: "Operasjoner" +software: "Programvare" version: "Versjon" +metadata: "Metadata" +withNFiles: "{n} fil(er)" +network: "Nettverk" +instanceInfo: "Serverinformasjon" statistics: "Statistikk" clearQueue: "Tøm kø" -clearQueueConfirmTitle: "Vil du tømme kø?" +clearQueueConfirmTitle: "Er du sikker pÃ¥ at du vil tømme køen?" blockedInstances: "Blokkerte severe" +blockedInstancesDescription: "Skriv opp vertsnavnene til serverne du vil blokkere, atskilt med linjeskift. Serverne i listen vil ikke lenger kunne kommunisere med denne serveren." muteAndBlock: "Skjul og blokker" mutedUsers: "Skjulte brukere" blockedUsers: "Blokkerte brukere" +noUsers: "Det er ingen brukere" editProfile: "Rediger profil" +noteDeleteConfirm: "Er du sikker pÃ¥ at du vil slette denne Noten?" pinLimitExceeded: "Du kan ikke feste flere." -noCustomEmojis: "Ingen emoji" +intro: "Installasjonen av Misskey er ferdig! Vennligst opprett en administratorkonto." +done: "Ferdig" +default: "Standard" +defaultValueIs: "Standard: {value}" +noCustomEmojis: "Det er ingen emoji" +noJobs: "Det er ingen jobber" blocked: "Blokkert" +suspended: "Suspendert" all: "Alle" +notResponding: "Svarer ikke" changePassword: "Endre passord" security: "Sikkerhet" +retypedNotMatch: "Inngangene stemmer ikke overens." +currentPassword: "NÃ¥værende passord" newPassword: "Nytt passord" newPasswordRetype: "Nytt passord (gjenta)" +attachFile: "Legg ved filer" more: "Mer!" +noSuchUser: "Bruker ikke funnet" +announcements: "Kunngjøringer" remove: "Slett" -removed: "Slettet" +removed: "Vellykket slettet" +removeAreYouSure: "Er du sikker pÃ¥ at du vil fjerne \"{x}\"?" +deleteAreYouSure: "Er du sikker pÃ¥ at du vil slette \"{x}\"?" saved: "Lagret" upload: "Laste opp" +keepOriginalUploading: "Behold originalbildet" +fromUrl: "Fra URL" +uploadFromUrl: "Last opp fra en URL" +uploadFromUrlDescription: "URL til filen du vil laste opp" explore: "Utforsk" messageRead: "Lest" -agree: "Jeg godtar" +nUsersRead: "lest av {n}" +agreeTo: "Jeg godtar {0}" +agree: "Godta" +agreeBelow: "Jeg godtar følgende" +basicNotesBeforeCreateAccount: "Viktige merknader" +termsOfService: "VilkÃ¥r for bruk" home: "Hjem" +activity: "Aktivitet" images: "Bilder" -image: "Bilder" +image: "Bilde" birthday: "Bursdag" yearsOld: "{age} Ã¥r gammel" +theme: "Temaer" light: "Lys" dark: "Mørk" +lightThemes: "Lyse temaer" +darkThemes: "Mørke temaer" +syncDeviceDarkMode: "Synkroniser mørkmodus med enhetens innstillinger" fileName: "Filnavn" -selectFile: "Velg fil" -selectFiles: "Velg fil" -selectFolder: "Velg mappe" -selectFolders: "Velg mappe" +selectFile: "Velg en fil" +selectFiles: "Velg filer" +selectFolder: "Velg en mappe" +selectFolders: "Velg mapper" renameFile: "Endre filnavn" folderName: "Mappenavn" -createFolder: "Opprett mappe" +createFolder: "Opprett en mappe" renameFolder: "Endre mappenavn" -deleteFolder: "Slett mappe" -addFile: "Legg til fil" +deleteFolder: "Slett denne mappen" +addFile: "Legg til en fil" emptyFolder: "Denne mappen er tom" +unableToDelete: "Kan ikke slette" +inputNewFileName: "Skriv inn et nytt filnavn" +inputNewDescription: "Skriv inn ny bildetekst" +inputNewFolderName: "Skriv inn et nytt mappenavn" +circularReferenceFolder: "MÃ¥lmappen er en undermappe til mappen du ønsker Ã¥ flytte." +hasChildFilesOrFolders: "Siden denne mappen ikke er tom, kan den ikke slettes." copyUrl: "Kopier URL" rename: "Endre navn" -doNothing: "Gjør ingenting" +avatar: "Avatar" +banner: "Banner" +doNothing: "Ignorer" accept: "Tillatt" reject: "AvslÃ¥" instanceName: "Servernavn" -thisYear: "I Ã¥r" +instanceDescription: "Serverbeskrivelse" +thisYear: "Ã…r" +thisMonth: "MÃ¥ned" today: "I dag" +dayX: "{day}" +monthX: "{month}" +yearX: "{year}" pages: "Sider" -pinnedUsers: "Festete brukrere" -pinnedPages: "Festete sider" +integration: "Integrasjon" +enableLocalTimeline: "Aktiver lokal tidslinje" +enableGlobalTimeline: "Aktiver global tidslinje" +disablingTimelinesInfo: "Administratorer og Moderatorer vil alltid ha tilgang til alle tidslinjer, selv om de ikke er aktivert." +registration: "Registrer" +enableRegistration: "Aktiver registrering av nye brukere" +invite: "Inviter" +basicInfo: "Grunnleggende informasjon" +pinnedUsers: "Festede brukrere" +pinnedUsersDescription: "Liste over brukernavn atskilt med linjeskift som skal festes i \"Utforsk\" fanen." +pinnedPages: "Festede sider" +pinnedNotes: "Festet Note" hcaptcha: "hCaptcha" +enableHcaptcha: "Aktiver hCaptcha" recaptcha: "reCAPTCHA" +enableRecaptcha: "Aktiver reCAPTCHA" +turnstile: "Turnstile" +enableTurnstile: "Aktiver Turnstile" +antennas: "Antenner" name: "Navn" +antennaSource: "Antennekilde" +notifyAntenna: "Varsle om nye Notes" +withFileAntenna: "Bare Notes med filer" +notesAndReplies: "Notes og svar" popularUsers: "Populære brukere" exploreUsersCount: "Det finnes {count} brukere" +exploreFediverse: "Utforsk Fediverse" userList: "Lister" -about: "Infomasjon" +about: "Informasjon" aboutMisskey: "Om Misskey" +newPasswordIs: "Det nye passordet er \"{password}\"." share: "Del" +notFound: "Ikke funnet" +markAsReadAllNotifications: "Merk alle varsler som lest" +markAsReadAllUnreadNotes: "Merk alle Notes som lest" help: "Hjelp" +inputMessageHere: "Skriv inn melding her" close: "Lukk" +invites: "Inviter" members: "Medlemmer" +title: "Tittel" text: "Tekst" next: "Neste" retype: "Gjenta" +quoteAttached: "Sitat" +noMessagesYet: "Ingen meldinger ennÃ¥" +newMessageExists: "Det er nye meldinger" +onlyOneFileCanBeAttached: "Du kan bare legge ved én fil i en melding" +invitations: "Inviter" available: "Tilgjengelig" unavailable: "Utilgjengelig" tooShort: "For kort" tooLong: "For langt" +weakPassword: "Svakt passord" +normalPassword: "Gjennomsnittlig passord" +strongPassword: "Sterkt passord" +signinWith: "Logg inn med {x}" +signinFailed: "Kunne ikke logge inn. Det oppgitte brukernavnet eller passordet er feil." or: "eller" language: "SprÃ¥k" aboutX: "Om {x}" -category: "Kategorier" +category: "Kategori" createAccount: "Opprett konto" +openImageInNewTab: "Ã…pne bilder i ny fane" +clientSettings: "Klientinnstillinger" +accountSettings: "Kontoinnstillinger" objectStorageRegion: "Region" objectStorageUseSSL: "Bruk SSL" objectStorageUseProxy: "Bruk Proxy" deleteAll: "Slett alt" +newNoteRecived: "Det er nye Notes" listen: "Lytt" none: "Ingen" +volume: "Volum" chooseEmoji: "Velg emoji" recentUsed: "Sist brukte" install: "Installer" +uninstall: "Avinstaller" nothing: "Ingenting" deleteAllFiles: "Slett alle filer" -deleteAllFilesConfirm: "Vil du slette alle filer?" +deleteAllFilesConfirm: "Er du sikker pÃ¥ at du vil slette alle filer?" +userSuspended: "Denne brukeren har blitt suspendert." accountDeleted: "Kontoen blir slettet" -accountDeletedDescription: "Denne kontoen blir slettet" +accountDeletedDescription: "Denne kontoen har blitt slettet." menu: "Meny" poll: "Avstemning" description: "Beskrivelse" @@ -186,6 +350,7 @@ small: "Liten" notificationType: "Varseltype" edit: "Rediger" email: "E-post" +smtpHost: "Vert" smtpUser: "Brukernavn" smtpPass: "Passord" userSaysSomething: "{name} sa noe" @@ -201,16 +366,25 @@ reportAbuse: "Rappoter" send: "Send" openInNewTab: "Ã…pne i ny fane" waitingFor: "Venter pÃ¥ {x}" +random: "Tilfeldig" system: "System" +desktop: "Skrivebord" +i18nInfo: "Misskey oversettes til flere sprÃ¥k av frivillige. Du kan hjelpe til pÃ¥ {link}." followingCount: "Følger" followersCount: "Følgere" yes: "Ja" no: "Nei" +contact: "Kontakt" +developer: "Utvikler" +makeExplorable: "Gjør konto synlig i \"Utforsk\"" +makeExplorableDescription: "Hvis du slÃ¥r av dette, vises ikke kontoen din i \"Utforsk\" delen." left: "Venstre" +nNotes: "{n} Notes" saveAs: "Lagre som" value: "Verdi" deleteConfirm: "Vil du slette?" invalidValue: "Verdien er ugyldig." +closeAccount: "Avslutt konto" emailNotification: "E-postvarsler" inChannelSearch: "Søk i kanal" clear: "Tøm" @@ -224,16 +398,23 @@ accounts: "Kontoer" switch: "Bytt" gallery: "Galleri" ads: "Annonser" +memo: "Notat" high: "Høy" low: "Lav" -sent: "Send" +sent: "Sendt" +received: "Mottatt" learnMore: "Les mer" +misskeyUpdated: "Misskey har blitt oppdatert!" translate: "Oversett" +translatedFrom: "Oversatt fra {x}" unread: "Ulest" manageAccounts: "Administrer konto" classic: "Klassisk" muteThread: "Skjul denne trÃ¥den" unmuteThread: "Vis denne trÃ¥den" +continueThread: "Vis fortsettelse av trÃ¥den" +hide: "Skjul" +smartphone: "Smarttelefon" tablet: "Nettbrett" auto: "Automatisk" size: "Størrelse" @@ -249,10 +430,10 @@ check: "Sjekk" deleteAccount: "Slett konto" document: "Dokumenter" logoutConfirm: "Vil du logge ut?" -pleaseSelect: "Vennligst velg" +pleaseSelect: "Velg et alternativ" type: "Type" beta: "Beta" -account: "Kontoer" +account: "Konto" move: "Flytt" pushNotification: "Push-varsler" tools: "Verktøy" @@ -268,6 +449,7 @@ role: "Rolle" color: "Farge" youCannotCreateAnymore: "Du kan ikke opprette flere." cannotPerformTemporary: "Midlertidig utilgjengelig" +achievements: "Prestasjoner" thisPostMayBeAnnoyingCancel: "Avbryt" exploreOtherServers: "Utforsk andre severe" letsLookAtTimeline: "La oss se pÃ¥ tidslinje" @@ -283,6 +465,26 @@ _initialAccountSetting: theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." _achievements: _types: + _notes10: + title: "Noen Notes" + _notes100: + title: "Mange Notes" + _notes500: + title: "Dekket i Notes" + _notes1000: + title: "Et fjell av Notes" + _notes5000: + title: "Overfylte Notes" + _notes10000: + title: "Super Notes" + _notes20000: + title: "Trenger... mer... Notes..." + _notes30000: + title: "Notes Notes Notes!" + _notes40000: + title: "Note fabrikk" + _notes50000: + title: "Planet av Notes" _notes100000: flavor: "Du har jammen mye Ã¥ si." _noteFavorited1: @@ -311,11 +513,25 @@ _achievements: _justPlainLucky: title: "Rett og slett heldig" _setNameToSyuilo: - description: "Du har satt navnet ditt til \"syuilo\"" + description: "Du satte navnet ditt til \"syuilo\"" + _passedSinceAccountCreated1: + title: "Ett Ã¥rs jubileum" + description: "Det har gÃ¥tt ett Ã¥r siden kontoen din ble opprettet" + _passedSinceAccountCreated2: + title: "To Ã¥rs jubileum" + description: "Det har gÃ¥tt to Ã¥r siden kontoen din ble opprettet" + _passedSinceAccountCreated3: + title: "Tre Ã¥rs jubileum" + description: "Det har gÃ¥tt tre Ã¥r siden kontoen din ble opprettet" _loggedInOnBirthday: title: "Gratulerer med dagen" + description: "Du logget inn pÃ¥ bursdagen din" _loggedInOnNewYearsDay: title: "Godt nytt Ã¥r" + description: "Du logget inn pÃ¥ Ã¥rets første dag" + _cookieClicked: + description: "Du klikket pÃ¥ kjeksen" + flavor: "Er du pÃ¥ riktig nettsted?" _brainDiver: title: "Brain Diver" flavor: "Misskey-Misskey La-Tu-Ma" @@ -333,6 +549,9 @@ _ad: _gallery: like: "Liker!" unlike: "Liker ikke" +_email: + _follow: + title: "fulgte deg" _preferencesBackups: saveNew: "Lagre som ny" cannotSave: "Kunne ikke lagre" @@ -351,6 +570,8 @@ _channel: featured: "Populært" following: "Følger" nameAndDescription: "Navn og beskrivelse" +_menuDisplay: + hide: "Skjul" _wordMute: soft: "Myk" hard: "Hard" @@ -360,15 +581,17 @@ _theme: key: "Nøkkel" keys: link: "Lenke" + renote: "Renote" _sfx: + note: "Notes" notification: "Varsler" _ago: future: "Fremitid" justNow: "Akkurat nÃ¥" - secondsAgo: "{n} sekunder siden" - minutesAgo: "{n} minutter siden" - hoursAgo: "{n} timer siden" - daysAgo: "{n} dager siden" + secondsAgo: "{n}s siden" + minutesAgo: "{n}m siden" + hoursAgo: "{n}t siden" + daysAgo: "{n}d siden" weeksAgo: "{n} uker siden" monthsAgo: "{n} mÃ¥neder siden" yearsAgo: "{n} Ã¥r siden" @@ -380,6 +603,7 @@ _time: day: "Dager" _timelineTutorial: title: "Hvordan bruke Misskey" + step2_2: "Hva med Ã¥ skrive en selvpresentasjon, eller bare \"Hei {name}!\" hvis du ikke har lyst?" _2fa: renewTOTPCancel: "Avbryt" _weekday: @@ -392,18 +616,22 @@ _weekday: saturday: "Lørdag" _widgets: profile: "Profil" + instanceInfo: "Serverinformasjon" notifications: "Varsler" timeline: "Tidslinje" calendar: "Kalender" trends: "Populært" clock: "Klokke" + activity: "Aktivitet" photos: "Bilder" + federation: "Føderasjon" button: "Knapp" aiscriptApp: "AiScript App" userList: "Brukerliste" _userList: chooseList: "Velg liste" _cw: + hide: "Skjul" show: "Vis mer" _poll: noOnlyOneChoice: "Trenger minst to valger." @@ -424,6 +652,7 @@ _postForm: _profile: name: "Navn" username: "Brukernavn" + description: "Biografi" metadataContent: "Innhold" _exportOrImport: followingList: "Følg" @@ -431,6 +660,7 @@ _exportOrImport: blockingList: "Blokker" userLists: "Lister" _charts: + federation: "Føderasjon" filesIncDec: "Forskjell pÃ¥ antall filer" _instanceCharts: users: "Forskjell pÃ¥ antall brukere" @@ -442,14 +672,18 @@ _play: new: "Opprett Play" edit: "Rediger Play" featured: "Populært" + title: "Tittel" summary: "Beskrivelse" _pages: + invalidNameText: "Pass pÃ¥ at sidetittelen ikke er tom" like: "Liker" unlike: "Liker ikke" my: "Mine sider" featured: "Populært" contents: "Innhold" + title: "Tittel" url: "Side URL" + hideTitleWhenPinned: "Skjul sidetittel nÃ¥r festet til profil" fontSerif: "Serif" fontSansSerif: "Sans Serif" selectType: "Velg type" @@ -459,13 +693,18 @@ _pages: image: "Bilde" button: "Knapp" _notification: + youWereFollowed: "fulgte deg" + unreadAntennaNote: "Antenne {name}" + achievementEarned: "Prestasjon lÃ¥st opp" _types: - follow: "Følg" + follow: "Nye følgere" reply: "Svar" - quote: "Sitat" - reaction: "Reaksjon" + renote: "Renotes" + quote: "Sitater" + reaction: "Reaksjoner" _actions: reply: "Svar" + renote: "Renote" _deck: swapLeft: "Flytt til venstre" swapRight: "Flytt til høyre" @@ -477,6 +716,7 @@ _deck: _columns: notifications: "Varsler" tl: "Tidslinje" + antenna: "Antenner" list: "Lister" channel: "Kanaler" direct: "Direkte" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index a123ad726655487713de65fe02a0af3fa4df61bc..e92449fdb9aa015f43bdaea80ee601f0b5830269 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -2,7 +2,7 @@ _lang_: "РуÑÑкий" headlineMisskey: "Сеть, ÑÐ¿Ð»ÐµÑ‚Ñ‘Ð½Ð½Ð°Ñ Ð¸Ð· заметок" introMisskey: "Добро пожаловать! Misskey — Ñто децентрализованный ÑÐµÑ€Ð²Ð¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ð±Ð»Ð¾Ð³Ð¾Ð² Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом.\nПишите «заметки» — делитеÑÑŒ Ñо вÑеми проиÑходÑщим вокруг или раÑÑказывайте о Ñебе 📡\nСтавьте «реакции» — выражайте Ñвои чувÑтва и Ñмоции от заметок других ðŸ‘\nОткройте Ð´Ð»Ñ ÑÐµÐ±Ñ Ð½Ð¾Ð²Ñ‹Ð¹ мир 🚀" -poweredByMisskeyDescription: "{name} – один из инÑтанÑов (также называемый ÑкземплÑром Misskey), иÑпользующий платформу Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом <b>Misskey</b>." +poweredByMisskeyDescription: "{name} – ÑÐµÑ€Ð²Ð¸Ñ Ð½Ð° платформе Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом <b>Misskey</b>, называемый инÑтанÑом Misskey." monthAndDay: "{day}.{month}" search: "ПоиÑк" notifications: "УведомлениÑ" @@ -560,6 +560,7 @@ accountDeletedDescription: "Ðта ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ удалена" menu: "Меню" divider: "ЛиниÑ-разделитель" addItem: "Добавить Ñлемент" +rearrange: "Сортировать по" relays: "РетранÑлÑторы" addRelay: "Добавить ретранÑлÑтор" inboxUrl: "URL Ñщика входÑщих Ñообщений" @@ -648,8 +649,8 @@ abuseReported: "Жалоба отправлена. Большое ÑпаÑибо reporter: "Сообщивший" reporteeOrigin: "О ком Ñообщено" reporterOrigin: "Кто Ñообщил" -forwardReport: "Перенаправление отчета на инÑтант." -forwardReportIsAnonymous: "Удаленный инÑтант не Ñможет увидеть вашу информацию и будет отображатьÑÑ ÐºÐ°Ðº Ð°Ð½Ð¾Ð½Ð¸Ð¼Ð½Ð°Ñ ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ." +forwardReport: "Отправить жалобу на инÑÑ‚Ð°Ð½Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð°." +forwardReportIsAnonymous: "Жалоба на удалённый инÑÑ‚Ð°Ð½Ñ Ð±ÑƒÐ´ÐµÑ‚ отправлена анонимно. ВмеÑто ваших данных у Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ‚ÐµÐ»Ñ Ð±ÑƒÐ´ÐµÑ‚ отображена ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ." send: "Отправить" abuseMarkAsResolved: "Отметить жалобу как решённую" openInNewTab: "Открыть в новой вкладке" @@ -822,6 +823,7 @@ translatedFrom: "Перевод. Язык оригинала — {x}" accountDeletionInProgress: "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÑетÑÑ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ðµ учетной запиÑи" usernameInfo: "ИмÑ, которое отличает вашу учетную запиÑÑŒ от других на Ñтом Ñервере. Ð’Ñ‹ можете иÑпользовать алфавит (a~z, A~Z), цифры (0~9) или Ñимволы Ð¿Ð¾Ð´Ñ‡ÐµÑ€ÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (_). Имена пользователей не могут быть изменены позже." aiChanMode: "Режим Ðй" +devMode: "Режим разработчика" keepCw: "СохранÑйте ÐŸÑ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¾ Ñодержимом" pubSub: "Учётные запиÑи Pub/Sub" lastCommunication: "ПоÑледнее Ñообщение" @@ -913,8 +915,8 @@ cannotUploadBecauseInappropriate: "Файл не может быть загру cannotUploadBecauseNoFreeSpace: "Файл не может быть загружен, так как не оÑталоÑÑŒ меÑта на диÑке" cannotUploadBecauseExceedsFileSizeLimit: "Файл не может быть загружен, так как он превышает лимит размера файла." beta: "Бета" -enableAutoSensitive: "ÐвтоматичеÑкое определение NSFW" -enableAutoSensitiveDescription: "ЕÑли доÑтупно, иÑпользуйте машинное обучение Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой уÑтановки флага NSFW на ноÑителе. Даже еÑли Ñта Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð°, она может быть уÑтановлена ​​автоматичеÑки в завиÑимоÑти от инÑтанта." +enableAutoSensitive: "ÐвтоматичеÑкое определение Ñодержимого не Ð´Ð»Ñ Ð²Ñех" +enableAutoSensitiveDescription: "ПозволÑет определÑÑ‚ÑŒ наличие Ñодержимого не Ð´Ð»Ñ Ð²Ñех при помощи иÑкуÑÑтвенного интеллекта там, где Ñто возможно. Даже еÑли Ñту опцию отключить, она вÑÑ‘ равно может быть включена на веÑÑŒ инÑтанÑ." activeEmailValidationDescription: "ЕÑли включено, будет проводитьÑÑ Ð±Ð¾Ð»ÐµÐµ ÑÑ‚Ñ€Ð¾Ð³Ð°Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ° адреÑа Ñлектронной почты, в том чиÑле на то, что он дейÑтвительный и не временный. ЕÑли же отключено, то проверÑетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ корректноÑÑ‚ÑŒ напиÑÐ°Ð½Ð¸Ñ Ð°Ð´Ñ€ÐµÑа." navbar: "Панель навигации" shuffle: "Перемешать" @@ -989,6 +991,7 @@ rolesAssignedToMe: "Мои роли" resetPasswordConfirm: "СброÑить пароль?" sensitiveWords: "ЧувÑтвительные Ñлова" sensitiveWordsDescription: "УÑтановите общедоÑтупный диапазон заметки, Ñодержащей заданное Ñлово, на домашний. Можно Ñделать неÑколько наÑтроек, разделив их переноÑами Ñтрок." +sensitiveWordsDescription2: "Разделение пробелом Ñоздаёт Ñпецификацию AND, а разделение коÑой чертой Ñоздаёт регулÑрное выражение." notesSearchNotAvailable: "ПоиÑк заметок недоÑтупен" license: "ЛицензиÑ" unfavoriteConfirm: "Удалить избранное?" @@ -1004,6 +1007,7 @@ noteIdOrUrl: "ID или ÑÑылка на заметку" video: "Видео" videos: "Видео" dataSaver: "ÐÐºÐ¾Ð½Ð¾Ð¼Ð¸Ñ Ñ‚Ñ€Ð°Ñ„Ð¸ÐºÐ°" +renotesList: "РепоÑÑ‚Ñ‹" horizontal: "Сбоку" youFollowing: "ПодпиÑки" options: "ÐаÑтройки ролей" @@ -1178,6 +1182,9 @@ _achievements: _client30min: title: "Перерыв на обед" description: "Прошло 30 минут Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° запуÑка клиента" + _client60min: + title: "Ðе наглÑдетьÑÑ Ð½Ð° Misskey" + description: "Misskey был открыт 60 минут подрÑд" _noteDeletedWithin1min: title: "Ой, нет!" description: "Заметка удалена через минуту поÑле публикации" @@ -1280,6 +1287,7 @@ _role: canInvite: "Может Ñоздавать приглаÑительные коды" canManageCustomEmojis: "УправлÑÑ‚ÑŒ пользовательÑкими Ñмодзи" driveCapacity: "ДоÑтупное проÑтранÑтво на «диÑке»" + alwaysMarkNsfw: "Ð’Ñегда отмечать файлы как «не Ð´Ð»Ñ Ð²Ñех»" pinMax: "ДоÑтупное количеÑтво закреплённых заметок" antennaMax: "ДоÑтупное количеÑтво антенн" wordMuteMax: "ДоÑтупное количеÑтво знаков в ÑпиÑке ÑÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñлов" @@ -1307,7 +1315,7 @@ _sensitiveMediaDetection: description: "Машинное обучение может быть иÑпользовано Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкого Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ñ‡ÑƒÐ²Ñтвительных медиа Ð´Ð»Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ†Ð¸Ð¸. Ðагрузка на Ñервер увеличиваетÑÑ Ð½ÐµÐ·Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾." sensitivity: "ЧувÑтвительноÑÑ‚ÑŒ обнаружениÑ" sensitivityDescription: "Более Ð½Ð¸Ð·ÐºÐ°Ñ Ñ‡ÑƒÐ²ÑтвительноÑÑ‚ÑŒ уменьшает количеÑтво ложных Ñрабатываний (false positives). Повышение чувÑтвительноÑти уменьшает утечку при обнаружении (ложноотрицательные результаты)." - setSensitiveFlagAutomatically: "УÑтановить флаг NSFW" + setSensitiveFlagAutomatically: "Обозначить как не Ð´Ð»Ñ Ð²Ñех" setSensitiveFlagAutomaticallyDescription: "Даже еÑли Ñтот параметр отключен, результат оценки ÑохранÑетÑÑ Ð²Ð½ÑƒÑ‚Ñ€Ð¸ ÑиÑтемы." analyzeVideos: "Ðнализировать видео?" analyzeVideosDescription: "Ðнализируйте видео в дополнение к неподвижным изображениÑм. Ðагрузка на Ñервер немного увеличиваетÑÑ." @@ -1526,6 +1534,16 @@ _time: minute: "мин" hour: "ч" day: "Ñут" +_timelineTutorial: + title: "Как пользоватьÑÑ Misskey" + step1_1: "Ðто лицо Misskey, так Ð½Ð°Ð·Ñ‹Ð²Ð°ÐµÐ¼Ð°Ñ Ð»ÐµÐ½Ñ‚Ð°. Ваш инÑтанÑ, {name}, покажет тут вÑе опубликованные на нём заметки в хронологичеÑком порÑдке." + step1_2: "ЗдеÑÑŒ еÑÑ‚ÑŒ неÑколько лент. К примеру «перÑональнаÑ» лента отображает заметки тех, на кого вы подпиÑаны. Р«меÑтнаÑ» — заметки тех, кого приютил {name}." + step2_1: "Что ж, теперь Ñамое Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ¾Ð²Ð°Ñ‚ÑŒ заметку. ЕÑли нажать вверху Ñтраницы на изображение карандаша, поÑвитÑÑ Ñ„Ð¾Ñ€Ð¼Ð° Ð´Ð»Ñ Ñ‚ÐµÐºÑта." + step2_2: "Почему бы не напиÑать немного о Ñебе? Ðу, или Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ «Привет, {name}»?" + step3_1: "СправилиÑÑŒ Ñ Ð¿ÐµÑ€Ð²Ð¾Ð¹ заметкой?" + step3_2: "Отлично, теперь она должна поÑвитьÑÑ Ð² вашей ленте." + step4_1: "Рещё здеÑÑŒ можно делитьÑÑ Ñвоими реакциÑми на заметки." + step4_2: "Отмечайте реакции, Ð½Ð°Ð¶Ð¸Ð¼Ð°Ñ Ð½Ð° Ñимвол «+» под заметкой и Ð²Ñ‹Ð±Ð¸Ñ€Ð°Ñ Ð·Ð½Ð°Ñ‡Ð¾Ðº по душе." _2fa: alreadyRegistered: "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ ÑƒÐ¶Ðµ наÑтроена." registerTOTP: "Ðачните наÑтраивать приложение-аутентификатор" @@ -1866,6 +1884,9 @@ _deck: _dialog: charactersExceeded: "Превышено макÑимальное количеÑтво Ñимволов! У Ð²Ð°Ñ {current} / из {max}" charactersBelow: "Ðто ниже минимального количеÑтва Ñимволов! У Ð²Ð°Ñ {current} / из {min}" +_disabledTimeline: + title: "Лента отключена" + description: "Ваша Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ñ€Ð¾Ð»ÑŒ не позволÑет пользоватьÑÑ Ñтой лентой." _webhookSettings: name: "Ðазвание" active: "Вкл." diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 22f110ebadf6bb295750c0dcebf48ee0ede8974a..d8e68202d71badf76cb3f5dd930cfa0ba04e1b5e 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -560,6 +560,7 @@ accountDeletedDescription: "บัà¸à¸Šà¸µà¸™à¸µà¹‰à¸–ูà¸à¸¥à¸šà¹„ปà¹à¸¥ menu: "เมนู" divider: "ตัวà¹à¸šà¹ˆà¸‡" addItem: "เพิ่มรายà¸à¸²à¸£" +rearrange: "จัดใหม่" relays: "รีเลย์" addRelay: "เพิ่มรีเลย์" inboxUrl: "à¸à¸´à¸™à¸šà¹‡à¸à¸à¸‹à¹Œ URL" @@ -1030,6 +1031,7 @@ continue: "ดำเนินà¸à¸²à¸£à¸•à¹ˆà¸" preservedUsernames: "ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸—ี่สงวนไว้" preservedUsernamesDescription: "ลิสต์ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸—ี่จะสำรà¸à¸‡à¹‚ดยคั่นด้วยà¸à¸²à¸£à¹à¸šà¹ˆà¸‡à¸šà¸£à¸£à¸—ัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างà¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸šà¸±à¸à¸Šà¸µà¸•à¸²à¸¡à¸›à¸à¸•à¸´ บัà¸à¸Šà¸µà¸—ี่มีà¸à¸¢à¸¹à¹ˆà¹à¸¥à¹‰à¸§à¸™à¸±à¹‰à¸™à¹‚ดยใช้ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¸ˆà¸°à¹„ม่ได้รับผลà¸à¸£à¸°à¸—บà¸à¸°à¹„ร" createNoteFromTheFile: "เรียบเรียงโน้ตจาà¸à¹„ฟล์นี้" +archive: "เà¸à¹‡à¸šà¸–าวร" youFollowing: "ติดตามà¹à¸¥à¹‰à¸§" options: "ตัวเลืà¸à¸à¸šà¸—บาท" _serverRules: @@ -1329,6 +1331,7 @@ _role: canInvite: "สร้างรหัสเชิà¸à¸à¸´à¸™à¸ªà¹à¸•à¸™à¸‹à¹Œ" canManageCustomEmojis: "จัดà¸à¸²à¸£à¸à¸µà¹‚มจิà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เà¸à¸‡" driveCapacity: "ความจุขà¸à¸‡à¹„ดรฟ์" + alwaysMarkNsfw: "ทำเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¹„ฟล์ว่าเป็น NSFW เสมà¸" pinMax: "จà¹à¸²à¸™à¸§à¸™à¸ªà¸¹à¸‡à¸ªà¸¸à¸”ขà¸à¸‡à¹‚น้ตที่ปัà¸à¸«à¸¡à¸¸à¸”ไว้" antennaMax: "จำนวนสูงสุดขà¸à¸‡à¹€à¸ªà¸²à¸à¸²à¸à¸²à¸¨" wordMuteMax: "จำนวนà¸à¸±à¸à¸‚ระสูงสุดที่à¸à¸™à¸¸à¸à¸²à¸•à¹ƒà¸™à¸à¸²à¸£à¸›à¸´à¸”เสียงคำ" @@ -1580,6 +1583,12 @@ _time: minute: "นาที" hour: "ชั่วโมง" day: "วัน" +_timelineTutorial: + title: "วิธีใช้งาน Misskey" + step3_1: "เสร็จสิ้นà¸à¸²à¸£à¹‚พสต์โน้ตย่à¸à¹à¸£à¸à¸‚à¸à¸‡à¸„ุณà¹à¸¥à¹‰à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸‡à¸±à¹‰à¸™à¸«à¸£à¸?" + step3_2: "ไชโย! ตà¸à¸™à¸™à¸µà¹‰à¹‚น้ตย่à¸à¹à¸£à¸à¸‚à¸à¸‡à¸„ุณได้ปราà¸à¸à¸šà¸™à¹„ทม์ไลน์ขà¸à¸‡à¸„ุณà¹à¸¥à¹‰à¸§à¸™à¸°" + step4_1: "คุณยังสามารถà¹à¸™à¸š \"ปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²\" ไปà¸à¸±à¸šà¹‚น้ตได้à¸à¸µà¸à¸”้วยนะค่ะ" + step4_2: "หาà¸à¸•à¹‰à¸à¸‡à¸à¸²à¸£à¹à¸™à¸šà¸à¸²à¸£à¹à¸ªà¸”งความรู้สึภให้à¸à¸”เครื่à¸à¸‡à¸«à¸¡à¸²à¸¢ \"+\" บนโน้ตà¹à¸¥à¹‰à¸§à¹€à¸¥à¸·à¸à¸à¸à¸´à¹‚มจิที่คุณต้à¸à¸‡à¸à¸²à¸£à¹à¸ªà¸”งความรู้สึà¸à¸—ี่ตนเà¸à¸‡à¸Šà¸à¸šà¹„ด้เลย" _2fa: alreadyRegistered: "คุณได้ลงทะเบียนà¸à¸¸à¸›à¸à¸£à¸“์ยืนยันตัวตนà¹à¸šà¸š 2 ชั้นà¹à¸¥à¹‰à¸§" registerTOTP: "ลงทะเบียนà¹à¸à¸žà¸•à¸±à¸§à¸•à¸£à¸§à¸ˆà¸ªà¸à¸šà¸ªà¸´à¸—ธิ์" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 638cc1cf8a9242244657cbd613cc4276fb552c53..9c278ea751af9e0adac24cbf26310123a2cfecdb 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -52,6 +52,8 @@ addToList: "æ·»åŠ è‡³åˆ—è¡¨" sendMessage: "å‘é€" copyRSS: "å¤åˆ¶RSS" copyUsername: "å¤åˆ¶ç”¨æˆ·å" +copyUserId: "å¤åˆ¶ç”¨æˆ·ID" +copyNoteId: "å¤åˆ¶å¸–åID" searchUser: "æœç´¢ç”¨æˆ·" reply: "回å¤" loadMore: "查看更多" @@ -560,6 +562,7 @@ accountDeletedDescription: "æ¤å¸æˆ·å·²ç»è¢«åˆ 除。" menu: "èœå•" divider: "分割线" addItem: "æ·»åŠ é¡¹ç›®" +rearrange: "排åºæ–¹å¼" relays: "ä¸ç»§" addRelay: "æ·»åŠ ä¸ç»§" inboxUrl: "Inbox URL" @@ -789,6 +792,7 @@ noMaintainerInformationWarning: "管ç†äººå‘˜ä¿¡æ¯æœªè®¾ç½®ã€‚" noBotProtectionWarning: "Botä¿æŠ¤æœªè®¾ç½®ã€‚" configure: "设置" postToGallery: "å‘é€åˆ°å›¾åº“" +postToHashtag: "æŠ•ç¨¿åˆ°è¿™ä¸ªæ ‡ç¾" gallery: "图库" recentPosts: "最新å‘布" popularPosts: "çƒé—¨æŠ•ç¨¿" @@ -822,6 +826,7 @@ translatedFrom: "从 {x} 翻译" accountDeletionInProgress: "æ£åœ¨åˆ 除账户" usernameInfo: "在æœåŠ¡å™¨ä¸Šå”¯ä¸€æ ‡è¯†æ‚¨çš„å¸æˆ·çš„å称。您å¯ä»¥ä½¿ç”¨å—æ¯ (a ~ z, A ~ Z)ã€æ•°å— (0 ~ 9) 和下划线 (_)。用户å以åŽä¸èƒ½æ›´æ”¹ã€‚" aiChanMode: "å°è“模å¼" +devMode: "å¼€å‘者模å¼" keepCw: "回å¤æ—¶ç»´æŒéšè—内容" pubSub: "Pub/Sub账户" lastCommunication: "最近通信" @@ -831,6 +836,8 @@ breakFollow: "移除关注者" breakFollowConfirm: "ä½ æƒ³å–消关注å—?" itsOn: "已开å¯" itsOff: "已关é—" +on: "å¼€å¯" +off: "å…³é—" emailRequiredForSignup: "注册账户需è¦ç”µå邮件地å€" unread: "未读" filter: "ç›é€‰" @@ -985,10 +992,13 @@ cannotBeChangedLater: "之åŽä¸èƒ½å†æ›´æ”¹ã€‚" reactionAcceptance: "接å—表情回应" likeOnly: "仅点赞" likeOnlyForRemote: "远程仅点赞" +nonSensitiveOnly: "ä»…é™éžæ•æ„Ÿå†…容" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "ä»…é™éžæ•æ„Ÿå†…容(远程仅点赞)" rolesAssignedToMe: "指派给自己的角色" resetPasswordConfirm: "确定é‡ç½®å¯†ç ?" sensitiveWords: "æ•æ„Ÿè¯" sensitiveWordsDescription: "将包å«è®¾ç½®è¯çš„帖åçš„å¯è§èŒƒå›´è®¾ç½®ä¸ºé¦–页。å¯ä»¥é€šè¿‡ç”¨æ¢è¡Œç¬¦åˆ†éš”æ¥è®¾ç½®å¤šä¸ªã€‚" +sensitiveWordsDescription2: "ç”¨ç©ºæ ¼åˆ†å‰²å…³é”®è¯ä½œä¸ºANDæ ¼å¼ï¼Œç”¨æ–œçº¿åŒ…裹关键å—æ¥æž„æˆæ£åˆ™è¡¨è¾¾å¼ã€‚" notesSearchNotAvailable: "帖å检索ä¸å¯ç”¨" license: "许å¯ä¿¡æ¯" unfavoriteConfirm: "确定è¦å–消收è—å—?" @@ -1028,28 +1038,60 @@ pleaseConfirmBelowBeforeSignup: "在这个æœåŠ¡å™¨ä¸Šæ³¨å†Œè´¦å·å‰ï¼Œè¯·ç¡® pleaseAgreeAllToContinue: "必须全部勾选「åŒæ„ã€æ‰èƒ½å¤Ÿç»§ç»ã€‚" continue: "继ç»" preservedUsernames: "ä¿ç•™çš„用户å" +preservedUsernamesDescription: "列出需è¦ä¿ç•™çš„用户å,使用æ¢è¡Œæ¥ä½œä¸ºåˆ†å‰²ã€‚被指定的用户ååœ¨å»ºç«‹è´¦æˆ·æ—¶æ— æ³•ä½¿ç”¨ï¼Œä½†ç”±ç®¡ç†å‘˜æ‰€åˆ›å»ºçš„账户ä¸å—该é™åˆ¶ã€‚æ¤å¤–,现有的账户也ä¸ä¼šå—到影å“。" createNoteFromTheFile: "从文件创建帖å" +archive: "å½’æ¡£" +channelArchiveConfirmTitle: "è¦å°†{name}å½’æ¡£å—?" +channelArchiveConfirmDescription: "å½’æ¡£åŽï¼Œåœ¨é¢‘é“列表与æœç´¢ç»“æžœä¸ä¸ä¼šæ˜¾ç¤ºï¼Œä¹Ÿæ— 法å‘布新的贴文。" +thisChannelArchived: "该频é“已被归档。" +displayOfNote: "显示帖å" +initialAccountSetting: "åˆå§‹è®¾ç½®" youFollowing: "æ£åœ¨å…³æ³¨" +preventAiLearning: "æ‹’ç»æŽ¥å—生æˆå¼AIçš„å¦ä¹ " +preventAiLearningDescription: "è¦æ±‚æ–‡ç« ç”ŸæˆAI或图åƒç”ŸæˆAIä¸èƒ½å¤Ÿä»¥å‘布的帖å和图åƒç‰å†…容作为å¦ä¹ 对象。这是通过在HTMLå“应ä¸åŒ…å«noaiæ ‡å¿—æ¥å®žçŽ°çš„,这ä¸èƒ½å®Œå…¨é˜»æ¢AIå¦ä¹ ä½ çš„å‘布内容,并ä¸æ˜¯æ‰€æœ‰AI都会éµå®ˆè¿™ç±»è¯·æ±‚。" options: "选项" +specifyUser: "用户指定" +failedToPreviewUrl: "æ— æ³•é¢„è§ˆ" +update: "æ›´æ–°" +rolesThatCanBeUsedThisEmojiAsReaction: "å¯ä»¥ä½¿ç”¨è¡¨æƒ…作为回应的角色" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "在没有指定角色的情况下,任何人都å¯ä»¥ä½¿ç”¨è¡¨æƒ…作为回应。" +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "角色必须是公开的。" +cancelReactionConfirm: "è¦å–消回应å—?" +changeReactionConfirm: "è¦æ›´æ”¹å›žåº”å—?" +later: "一会å†è¯´" +goToMisskey: "去往Misskey" +installed: "已安装" _initialAccountSetting: accountCreated: "账户创建完æˆäº†ï¼" letsStartAccountSetup: "æ¥è¿›è¡Œå¸æˆ·çš„åˆå§‹è®¾ç½®å§ã€‚" letsFillYourProfile: "首先,æ¥è®¾å®šä½ 的个人档案å§ï¼" profileSetting: "个人资料设置" + privacySetting: "éšç§è®¾ç½®" theseSettingsCanEditLater: "也å¯ä»¥åœ¨ç¨åŽä¿®æ”¹è¿™é‡Œçš„设置。" + youCanEditMoreSettingsInSettingsPageLater: "还å¯ä»¥åœ¨ã€Œè®¾ç½®ã€é¡µé¢è¿›è¡Œå…¶å®ƒå„ç§è®¾ç½®ï¼Œç¨åŽå°±æ¥ç¡®è®¤ä¸€ä¸‹çœ‹çœ‹å§ã€‚" + followUsers: "ä¸ºäº†å»ºç«‹å±žäºŽä½ è‡ªå·±çš„æ—¶é—´çº¿ï¼Œè¯•ç€åŽ»å…³æ³¨ä½ 感兴趣的用户å§ã€‚" + pushNotificationDescription: "å¯ç”¨æŽ¨é€é€šçŸ¥çš„è¯ï¼Œå°±å¯ä»¥åœ¨è®¾å¤‡ä¸Šå—到æ¥è‡ª{name}的通知了。" + initialAccountSettingCompleted: "åˆå§‹è®¾å®šå·²ç»å®Œæˆäº†ï¼" + haveFun: "希望{name}在这里玩得开心ï¼" + ifYouNeedLearnMore: "关于{name}(Misskey)的使用方法,详è§{link}。" + skipAreYouSure: "è¦è·³è¿‡åˆå§‹è®¾ç½®å—?" + laterAreYouSure: "è¦ç¨åŽå†è¿›è¡Œåˆå§‹è®¾å®šå—?" _serverRules: description: "在新用户注册å‰æ˜¾ç¤ºæœåŠ¡å™¨çš„简å•è§„则。推è显示æœåŠ¡æ¡æ¬¾çš„主è¦å†…容。" _accountMigration: moveFrom: "从别的账å·è¿ç§»åˆ°æ¤è´¦æˆ·" + moveFromSub: "为å¦ä¸€ä¸ªè´¦æˆ·å»ºç«‹åˆ«å" moveFromLabel: "è¿ç§»å‰çš„账户" moveFromDescription: "如果è¿ç§»æ—¶éœ€è¦ç»§æ‰¿å…¶ä»–账户的关注者,请在æ¤åˆ›é€ 别å。æ¤æ“作需è¦åœ¨å®žè¡Œè¿ç§»ä¹‹å‰å®Œæˆï¼è¯·å¦‚已下输入需è¦è¿ç§»çš„账户:@person@instance.com" moveTo: "把这个账户è¿ç§»åˆ°æ–°çš„账户" moveToLabel: "è¿ç§»åŽçš„账户" moveCannotBeUndone: "一旦è¿ç§»è´¦æˆ·ï¼Œå°±æ— 法撤销。" moveAccountDescription: "æ¤æ“ä½œæ— æ³•å–消。请先确认您已在è¿ç§»åŽçš„账户上,为æ¤è´¦æˆ·åˆ›é€ 了别åã€‚åˆ›é€ åˆ«ååŽï¼Œè¯·å¦‚以下输入您的è¿ç§»åŽçš„账户:@person@instance.com" + moveAccountHowTo: "è¦è¿›è¡Œè´¦æˆ·è¿ç§»ï¼Œè¯·çŽ°åœ¨ç›®æ ‡è´¦æˆ·ä¸ä¸ºæ¤è´¦æˆ·å»ºç«‹ä¸€ä¸ªåˆ«å。\n建立别ååŽï¼Œè¯·åƒè¿™æ ·è¾“å…¥ç›®æ ‡è´¦æˆ·ï¼š@username@server.example.com" startMigration: "è¿ç§»" migrationConfirm: "确定è¦æŠŠæ¤è´¦æˆ·è¿ç§»åˆ°{account}å—?一旦确定åŽï¼Œæ¤æ“ä½œæ— æ³•å–消,æ¤è´¦æˆ·ä¹Ÿæ— 法以原æ¥çš„状æ€ä½¿ç”¨ã€‚\nåŒæ—¶ï¼Œè¯·ç¡®è®¤è¿ç§»åŽçš„è´¦æˆ·ï¼Œå·²åˆ›é€ åˆ«å。" movedAndCannotBeUndone: "该账户已被è¿ç§»ã€‚\nè¿ç§»æ“ä½œæ— æ³•æ’¤é”€ã€‚" + postMigrationNote: "这个账户的关注会在è¿ç§»æ“作åŽçš„24å°æ—¶åŽè§£é™¤ã€‚该账户的「关注ä¸ã€å’Œã€Œå…³æ³¨è€…ã€çš†ä¼šå˜ä¸º0。由于ä¸ä¼šè§£é™¤å…³æ³¨å…³ç³»ï¼Œä½ 的关注者ä»ç„¶å¯ä»¥ç»§ç»æŸ¥çœ‹è¯¥è´¦æˆ·å‘补给关注者的帖å。" movedTo: "è¿ç§»åŽçš„账户" _achievements: earnedAt: "è¾¾æˆæ—¶é—´" @@ -1331,6 +1373,7 @@ _role: canInvite: "å‘放æœåŠ¡å™¨é‚€è¯·ç " canManageCustomEmojis: "管ç†è‡ªå®šä¹‰è¡¨æƒ…符å·" driveCapacity: "网盘容é‡" + alwaysMarkNsfw: "æ€»æ˜¯å°†æ–‡ä»¶æ ‡è®°ä¸ºNSFW" pinMax: "帖å置顶数é‡é™åˆ¶" antennaMax: "å¯åˆ›å»ºçš„最大天线数é‡" wordMuteMax: "å±è”½è¯çš„å—æ•°é™åˆ¶" @@ -1583,8 +1626,15 @@ _time: hour: "å°æ—¶" day: "æ—¥" _timelineTutorial: + title: "Misskey的使用方法" + step1_1: "这个画é¢æ˜¯ã€Œæ—¶é—´çº¿ã€ã€‚{name}的投稿会按照帖åçš„å‘布时间顺åºæ¥æ˜¾ç¤ºã€‚" + step1_2: "时间线有许多ç§ç±»ï¼Œæ¯”如在「首页时间线ã€ä¸å±•çŽ°çš„æ˜¯ä½ å…³æ³¨çš„äººçš„è´´æ–‡ï¼›è€Œåœ¨ã€Œæœ¬åœ°æ—¶é—´çº¿ã€ä¸å±•çŽ°çš„是{name}里全部用户的贴文。" + step2_1: "那么接下æ¥ï¼Œè¯•ç€å†™ä¸€äº›ä»€ä¹ˆä¸œè¥¿æ¥å‘布å§ï¼ä½ å¯ä»¥é€šè¿‡ç‚¹å‡»å±å¹•ä¸Šçš„é“…ç¬”å›¾æ ‡æ¥æ‰“开投稿页é¢ã€‚" + step2_2: "第一次å‘布的帖å内容,建议包å«è‡ªæˆ‘介ç»ï¼Œä»¥åŠã€Œå¼€å§‹ä½¿ç”¨{name}了ã€ã€‚" step3_1: "将想说的è¯å‘出去了å—?" step3_2: "太棒了ï¼çŽ°åœ¨ä½ å¯ä»¥åœ¨ä½ 的时间线ä¸çœ‹åˆ°åˆšåˆšå‘布的帖å了。" + step4_1: "试ç€å¯¹å¸–å使用「回应ã€å§ï¼" + step4_2: "在他人的帖å上按下「+ã€å›¾æ ‡ï¼Œå³å¯é€‰æ‹©æƒ³è¦çš„表情æ¥è¿›è¡Œã€Œå›žåº”ã€ã€‚" _2fa: alreadyRegistered: "æ¤è®¾å¤‡å·²è¢«æ³¨å†Œ" registerTOTP: "开始设置认è¯åº”用" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index d2b42313a780ce9b2a6f164680ea3748a714226f..ef0baeef500e80f7c3fc363aea60e8683a8396b3 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -52,6 +52,8 @@ addToList: "åŠ å…¥è‡³æ¸…å–®" sendMessage: "發é€è¨Šæ¯" copyRSS: "複製RSS" copyUsername: "複製使用者å稱" +copyUserId: "複製使用者ID" +copyNoteId: "複製貼文ID" searchUser: "æœå°‹ä½¿ç”¨è€…" reply: "回覆" loadMore: "載入更多" @@ -790,6 +792,7 @@ noMaintainerInformationWarning: "尚未è¨å®šç®¡ç†å“¡ä¿¡æ¯ã€‚" noBotProtectionWarning: "尚未è¨å®šBot防è·ã€‚" configure: "è¨å®š" postToGallery: "發佈到相簿" +postToHashtag: "以æ¤ä¸»é¡Œæ¨™ç±¤ç™¼å¸ƒ" gallery: "相簿" recentPosts: "最新貼文" popularPosts: "熱門的貼文" @@ -823,6 +826,7 @@ translatedFrom: "從 {x} ç¿»è¯" accountDeletionInProgress: "æ£åœ¨åˆªé™¤å¸³æˆ¶" usernameInfo: "在伺æœå™¨ä¸Šæ‚¨çš„帳戶是唯一的è˜åˆ¥å稱。您å¯ä»¥ä½¿ç”¨å—æ¯ (a ~ z, A ~ Z)ã€æ•¸å— (0 ~ 9) 和下底線 (_)。之後帳戶å是ä¸èƒ½æ›´æ”¹çš„。" aiChanMode: "å°è—模å¼" +devMode: "開發者模å¼" keepCw: "ä¿æŒCW" pubSub: "Pub/Sub 帳戶" lastCommunication: "最近的通信" @@ -832,6 +836,8 @@ breakFollow: "解除追隨者" breakFollowConfirm: "確定è¦å–消被追隨嗎?" itsOn: "已開啟" itsOff: "已關閉" +on: "é–‹å•Ÿ" +off: "關閉" emailRequiredForSignup: "註冊帳戶需è¦é›»å郵件地å€" unread: "未讀" filter: "篩é¸" @@ -986,6 +992,8 @@ cannotBeChangedLater: "之後ä¸èƒ½è®Šæ›´ã€‚" reactionAcceptance: "接å—表情å應" likeOnly: "僅é™è®š" likeOnlyForRemote: "é 端僅é™è®š" +nonSensitiveOnly: "僅é™éžæ•æ„Ÿ" +nonSensitiveOnlyForLocalLikeOnlyForRemote: "僅é™éžæ•æ„Ÿï¼ˆé 端僅é™æŒ‰è®šï¼‰" rolesAssignedToMe: "指派給自己的角色" resetPasswordConfirm: "é‡è¨å¯†ç¢¼ï¼Ÿ" sensitiveWords: "æ•æ„Ÿè©ž" @@ -1039,14 +1047,27 @@ thisChannelArchived: "é€™å€‹é »é“已被å°å˜ã€‚" displayOfNote: "顯示貼文" initialAccountSetting: "åˆå§‹è¨å®š" youFollowing: "追隨ä¸" -preventAiLearning: "拒絕接å—產生å¼AIçš„å¸ç¿’" -preventAiLearningDescription: "è¦æ±‚å¤–éƒ¨çš„æ–‡ç« ç”¢ç”ŸAI或圖åƒç”¢ç”ŸAIä¸ä»¥ç™¼å¸ƒçš„貼文和圖åƒç‰å…§å®¹ç‚ºå¸ç¿’å°è±¡ã€‚這是é€éŽåœ¨HTML響應ä¸åŒ…å«noai旗標來實ç¾çš„,但ä¸èƒ½å®Œå…¨é˜²æ¢AIçš„å¸ç¿’ï¼Œå› ç‚ºé€™è¦çœ‹è©²AI是å¦éµå®ˆé€™å€‹è¦æ±‚。" +preventAiLearning: "拒絕接å—生æˆå¼AI的訓練" +preventAiLearningDescription: "è¦æ±‚å¤–éƒ¨çš„æ–‡ç« ç”Ÿæˆå¼AI或圖åƒç”Ÿæˆå¼AIä¸ä»¥ç™¼å¸ƒçš„貼文和圖åƒç‰å…§å®¹ç‚ºå¸ç¿’å°è±¡ã€‚這是é€éŽåœ¨HTML響應ä¸åŒ…å«noai旗標來實ç¾çš„,但ä¸èƒ½å®Œå…¨é˜²æ¢AIçš„å¸ç¿’ï¼Œå› ç‚ºé€™è¦çœ‹è©²AI是å¦éµå®ˆé€™å€‹è¦æ±‚。" options: "é¸é …" +specifyUser: "指定使用者" +failedToPreviewUrl: "無法é 覽" +update: "æ›´æ–°" +rolesThatCanBeUsedThisEmojiAsReaction: "å¯ä»¥ç•¶æˆå應使用的角色" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "如果是未指定角色的情æ³ï¼Œå‰‡ä»»ä½•äººéƒ½å¯ä»¥è¢«ç•¶æˆå應來使用。" +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "è§’è‰²å¿…é ˆæ˜¯å…¬é–‹çš„è§’è‰²ã€‚" +cancelReactionConfirm: "è¦å–消åšå‡ºçš„å應嗎?" +changeReactionConfirm: "è¦è®Šæ›´åšå‡ºçš„å應嗎?" +later: "ç¨å¾Œå†èªª" +goToMisskey: "å¾€Misskey" +additionalEmojiDictionary: "è¡¨æƒ…ç¬¦è™Ÿçš„é™„åŠ è¾å…¸" +installed: "已安è£" _initialAccountSetting: accountCreated: "帳戶已建立完æˆï¼" letsStartAccountSetup: "來進行帳戶的åˆå§‹è¨å®šå§ã€‚" letsFillYourProfile: "首先,來è¨å®šæ‚¨çš„個人檔案å§ã€‚" profileSetting: "個人檔案è¨å®š" + privacySetting: "éš±ç§è¨å®š" theseSettingsCanEditLater: "這裡的è¨å®šå¯ä»¥åœ¨ä¹‹å¾Œè®Šæ›´ã€‚" youCanEditMoreSettingsInSettingsPageLater: "除æ¤ä¹‹å¤–,還å¯ä»¥åœ¨ã€Œè¨å®šã€é é¢é€²è¡Œå„種è¨å®šã€‚之後請確èªçœ‹çœ‹ã€‚" followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者å§ã€‚" @@ -1055,6 +1076,7 @@ _initialAccountSetting: haveFun: "盡情享å—{name}å§ï¼" ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。" skipAreYouSure: "è¦ç•¥éŽåˆå§‹è¨å®šå—Žï¼Ÿ" + laterAreYouSure: "ç¨å¾Œå†é‡æ–°é€²è¡Œåˆå§‹è¨å®šå—Žï¼Ÿ" _serverRules: description: "è¨å®šä¼ºæœå™¨çš„ç°¡è¦è¦å‰‡ï¼Œåœ¨æ–°çš„註冊之å‰é¡¯ç¤ºã€‚建è°çš„內容是使用æ¢æ¬¾çš„摘è¦ã€‚" _accountMigration: diff --git a/package.json b/package.json index a21d7ab9e3c909688128ec74b283971bbe152194..8cf7d37f639f24fde39e49c663389ceeb12e1e1f 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "13.12.2", + "version": "13.13.0", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@8.3.1", + "packageManager": "pnpm@8.6.0", "workspaces": [ "packages/frontend", "packages/backend", @@ -51,16 +51,16 @@ "gulp-replace": "1.1.4", "gulp-terser": "2.1.0", "js-yaml": "4.1.0", - "typescript": "5.0.4" + "typescript": "5.1.3" }, "devDependencies": { "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", - "@typescript-eslint/eslint-plugin": "5.59.5", - "@typescript-eslint/parser": "5.59.5", + "@typescript-eslint/eslint-plugin": "5.59.8", + "@typescript-eslint/parser": "5.59.8", "cross-env": "7.0.3", - "cypress": "12.12.0", - "eslint": "8.40.0", + "cypress": "12.13.0", + "eslint": "8.41.0", "start-server-and-test": "2.0.0" }, "optionalDependencies": { diff --git a/packages/backend/migration/1683847157541-UserList.js b/packages/backend/migration/1683847157541-UserList.js new file mode 100644 index 0000000000000000000000000000000000000000..b50a50eed8818ddfeabd6f48e6a6057ceda6993a --- /dev/null +++ b/packages/backend/migration/1683847157541-UserList.js @@ -0,0 +1,13 @@ +export class UserList1683847157541 { + name = 'UserList1683847157541' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list" ADD "isPublic" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`CREATE INDEX "IDX_48a00f08598662b9ca540521eb" ON "user_list" ("isPublic") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_48a00f08598662b9ca540521eb"`); + await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "isPublic"`); + } +} diff --git a/packages/backend/migration/1683869758873-UserListFavorites.js b/packages/backend/migration/1683869758873-UserListFavorites.js new file mode 100644 index 0000000000000000000000000000000000000000..ac9c4c42b9a2f84ed8d938d00c10ddabf22af4a6 --- /dev/null +++ b/packages/backend/migration/1683869758873-UserListFavorites.js @@ -0,0 +1,19 @@ +export class UserListFavorites1683869758873 { + name = 'UserListFavorites1683869758873' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "user_list_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_c0974b21e18502a4c8178e09fe6" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_016f613dc4feb807e03e3e7da9" ON "user_list_favorite" ("userId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d6765a8c2a4c17c33f9d7f948b" ON "user_list_favorite" ("userId", "userListId") `); + await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_016f613dc4feb807e03e3e7da92" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2"`); + await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_016f613dc4feb807e03e3e7da92"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d6765a8c2a4c17c33f9d7f948b"`); + await queryRunner.query(`DROP INDEX "public"."IDX_016f613dc4feb807e03e3e7da9"`); + await queryRunner.query(`DROP TABLE "user_list_favorite"`); + } +} diff --git a/packages/backend/migration/1684206886988-remove-showTimelineReplies.js b/packages/backend/migration/1684206886988-remove-showTimelineReplies.js new file mode 100644 index 0000000000000000000000000000000000000000..690653bd7cb3afb72cdb23ffdbb9e889ab44c168 --- /dev/null +++ b/packages/backend/migration/1684206886988-remove-showTimelineReplies.js @@ -0,0 +1,11 @@ +export class RemoveShowTimelineReplies1684206886988 { + name = 'RemoveShowTimelineReplies1684206886988' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "showTimelineReplies"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "showTimelineReplies" boolean NOT NULL DEFAULT false`); + } +} diff --git a/packages/backend/migration/1684386446061-emoji-improve.js b/packages/backend/migration/1684386446061-emoji-improve.js new file mode 100644 index 0000000000000000000000000000000000000000..40b0a2bc5ec55c86a783b3e8d0c9e9ef10bb1e03 --- /dev/null +++ b/packages/backend/migration/1684386446061-emoji-improve.js @@ -0,0 +1,15 @@ +export class EmojiImprove1684386446061 { + name = 'EmojiImprove1684386446061' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" ADD "localOnly" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "emoji" ADD "isSensitive" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "emoji" ADD "roleIdsThatCanBeUsedThisEmojiAsReaction" character varying(128) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "roleIdsThatCanBeUsedThisEmojiAsReaction"`); + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "isSensitive"`); + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "localOnly"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 4bab4a7341855e5ebd27cd65ea00335b1b3ac3a2..56ecbc2eaf004277e14647682a67adc99e2be83e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -35,49 +35,52 @@ "@swc/core-win32-x64-msvc": "1.3.56", "@tensorflow/tfjs": "4.4.0", "@tensorflow/tfjs-node": "4.4.0", - "slacc-android-arm-eabi": "0.0.7", - "slacc-android-arm64": "0.0.7", - "slacc-darwin-arm64": "0.0.7", - "slacc-darwin-universal": "0.0.7", - "slacc-darwin-x64": "0.0.7", - "slacc-linux-arm-gnueabihf": "0.0.7", - "slacc-linux-arm64-gnu": "0.0.7", - "slacc-linux-arm64-musl": "0.0.7", - "slacc-linux-x64-gnu": "0.0.7", - "slacc-win32-arm64-msvc": "0.0.7", - "slacc-win32-x64-msvc": "0.0.7" + "bufferutil": "^4.0.7", + "slacc-android-arm-eabi": "0.0.9", + "slacc-android-arm64": "0.0.9", + "slacc-darwin-arm64": "0.0.9", + "slacc-darwin-universal": "0.0.9", + "slacc-darwin-x64": "0.0.9", + "slacc-freebsd-x64": "0.0.9", + "slacc-linux-arm-gnueabihf": "0.0.9", + "slacc-linux-arm64-gnu": "0.0.9", + "slacc-linux-arm64-musl": "0.0.9", + "slacc-linux-x64-gnu": "0.0.9", + "slacc-win32-arm64-msvc": "0.0.9", + "slacc-win32-x64-msvc": "0.0.9", + "utf-8-validate": "^6.0.3" }, "dependencies": { "@aws-sdk/client-s3": "3.321.1", "@aws-sdk/lib-storage": "3.321.1", "@aws-sdk/node-http-handler": "3.321.1", - "@bull-board/api": "5.1.2", - "@bull-board/fastify": "5.1.2", - "@bull-board/ui": "5.1.2", + "@bull-board/api": "5.2.0", + "@bull-board/fastify": "5.2.0", + "@bull-board/ui": "5.2.0", "@discordapp/twemoji": "14.1.2", "@fastify/accepts": "4.1.0", "@fastify/cookie": "8.3.0", - "@fastify/cors": "8.2.1", + "@fastify/cors": "8.3.0", "@fastify/http-proxy": "9.1.0", "@fastify/multipart": "7.6.0", - "@fastify/static": "6.10.1", + "@fastify/static": "6.10.2", "@fastify/view": "7.4.1", - "@nestjs/common": "9.4.0", - "@nestjs/core": "9.4.0", - "@nestjs/testing": "9.4.0", + "@nestjs/common": "9.4.2", + "@nestjs/core": "9.4.2", + "@nestjs/testing": "9.4.2", "@peertube/http-signature": "1.7.0", - "@sinonjs/fake-timers": "10.0.2", + "@sinonjs/fake-timers": "10.2.0", "@swc/cli": "0.1.62", - "@swc/core": "1.3.56", + "@swc/core": "1.3.61", "accepts": "1.3.8", "ajv": "8.12.0", "archiver": "5.3.1", "autwh": "0.1.0", "bcryptjs": "2.4.3", "blurhash": "2.0.5", - "bull": "4.10.4", + "bullmq": "3.15.0", "cacheable-lookup": "6.1.0", - "cbor": "8.1.0", + "cbor": "9.0.0", "chalk": "5.2.0", "chalk-template": "0.4.0", "chokidar": "3.5.3", @@ -93,30 +96,30 @@ "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", "got": "12.6.0", - "happy-dom": "9.16.0", + "happy-dom": "9.20.3", "hpagent": "1.2.0", "ioredis": "5.3.2", "ip-cidr": "3.1.0", "is-svg": "4.3.2", "js-yaml": "4.1.0", - "jsdom": "21.1.1", + "jsdom": "22.1.0", "json5": "2.2.3", - "jsonld": "8.1.1", - "meilisearch": "0.32.3", + "jsonld": "8.2.0", "jsrsasign": "10.8.6", + "meilisearch": "0.32.5", "mfm-js": "0.23.3", "mime-types": "2.1.35", "misskey-js": "workspace:*", "ms": "3.0.0-canary.1", "nested-property": "4.0.0", "node-fetch": "3.3.1", - "nodemailer": "6.9.2", + "nodemailer": "6.9.3", "nsfwjs": "2.4.2", "oauth": "0.10.0", "os-utils": "0.0.14", "otpauth": "9.1.2", "parse5": "7.1.2", - "pg": "8.10.0", + "pg": "8.11.0", "private-ip": "3.0.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -126,7 +129,7 @@ "qrcode": "1.5.3", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.18.0", + "re2": "1.19.0", "redis-lock": "0.1.4", "reflect-metadata": "0.1.13", "rename": "1.0.4", @@ -136,27 +139,26 @@ "s-age": "1.1.2", "sanitize-html": "2.10.0", "seedrandom": "3.0.5", - "semver": "7.5.0", + "semver": "7.5.1", "sharp": "0.32.1", "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", - "slacc": "0.0.7", + "slacc": "0.0.9", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "summaly": "github:misskey-dev/summaly", - "systeminformation": "5.17.12", + "systeminformation": "5.17.16", "tinycolor2": "1.6.0", "tmp": "0.2.1", "tsc-alias": "1.8.6", "tsconfig-paths": "4.2.0", "twemoji-parser": "14.0.0", "typeorm": "0.3.16", - "typescript": "5.0.4", + "typescript": "5.1.3", "ulid": "2.3.0", - "unzipper": "0.10.11", + "unzipper": "0.10.14", "uuid": "9.0.0", "vary": "1.1.2", "web-push": "3.6.1", - "websocket": "1.0.34", "ws": "8.13.0", "xev": "3.0.2" }, @@ -166,23 +168,22 @@ "@types/accepts": "1.3.5", "@types/archiver": "5.3.2", "@types/bcryptjs": "2.4.2", - "@types/bull": "4.10.0", "@types/cbor": "6.0.0", "@types/color-convert": "2.0.0", "@types/content-disposition": "0.5.5", "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.21", - "@types/jest": "29.5.1", + "@types/jest": "29.5.2", "@types/js-yaml": "4.0.5", "@types/jsdom": "21.1.1", "@types/jsonld": "1.5.8", "@types/jsrsasign": "10.5.8", "@types/mime-types": "2.1.1", - "@types/node": "20.1.3", + "@types/node": "20.2.5", "@types/node-fetch": "3.0.3", - "@types/nodemailer": "6.4.7", + "@types/nodemailer": "6.4.8", "@types/oauth": "0.9.1", - "@types/pg": "8.6.6", + "@types/pg": "8.10.1", "@types/pug": "2.0.6", "@types/punycode": "2.1.0", "@types/qrcode": "1.5.0", @@ -196,17 +197,17 @@ "@types/sinonjs__fake-timers": "8.1.2", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", - "@types/unzipper": "0.10.5", + "@types/unzipper": "0.10.6", "@types/uuid": "9.0.1", "@types/vary": "1.1.0", "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.59.5", - "@typescript-eslint/parser": "5.59.5", + "@typescript-eslint/eslint-plugin": "5.59.8", + "@typescript-eslint/parser": "5.59.8", "aws-sdk-client-mock": "2.1.1", "cross-env": "7.0.3", - "eslint": "8.40.0", + "eslint": "8.41.0", "eslint-plugin-import": "2.27.5", "execa": "6.1.0", "jest": "29.5.0", diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 5fb4e8ef3cf9880a2d078cda438d5b81372016f5..406e3192bb623d2eaa4bb5d598056fb23b03ef99 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -4,7 +4,7 @@ import * as Redis from 'ioredis'; import { DataSource } from 'typeorm'; import { MeiliSearch } from 'meilisearch'; import { DI } from './di-symbols.js'; -import { loadConfig } from './config.js'; +import { Config, loadConfig } from './config.js'; import { createPostgresDataSource } from './postgres.js'; import { RepositoryModule } from './models/RepositoryModule.js'; import type { Provider, OnApplicationShutdown } from '@nestjs/common'; @@ -25,7 +25,7 @@ const $db: Provider = { const $meilisearch: Provider = { provide: DI.meilisearch, - useFactory: (config) => { + useFactory: (config: Config) => { if (config.meilisearch) { return new MeiliSearch({ host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`, @@ -40,7 +40,7 @@ const $meilisearch: Provider = { const $redis: Provider = { provide: DI.redis, - useFactory: (config) => { + useFactory: (config: Config) => { return new Redis.Redis({ port: config.redis.port, host: config.redis.host, @@ -55,7 +55,7 @@ const $redis: Provider = { const $redisForPub: Provider = { provide: DI.redisForPub, - useFactory: (config) => { + useFactory: (config: Config) => { const redis = new Redis.Redis({ port: config.redisForPubsub.port, host: config.redisForPubsub.host, @@ -71,7 +71,7 @@ const $redisForPub: Provider = { const $redisForSub: Provider = { provide: DI.redisForSub, - useFactory: (config) => { + useFactory: (config: Config) => { const redis = new Redis.Redis({ port: config.redisForPubsub.port, host: config.redisForPubsub.host, @@ -100,7 +100,7 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redisForSub) private redisForSub: Redis.Redis, ) {} - async onApplicationShutdown(signal: string): Promise<void> { + public async dispose(): Promise<void> { if (process.env.NODE_ENV === 'test') { // XXX: // Shutting down the existing connections causes errors on Jest as @@ -116,4 +116,8 @@ export class GlobalModule implements OnApplicationShutdown { this.redisForSub.disconnect(), ]); } + + async onApplicationShutdown(signal: string): Promise<void> { + await this.dispose(); + } } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index c6e107538952df6e2b15b9ae32a7bc717f6899a2..9d1945e4d41b8ce2890fb94e2b6191e492f06e77 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -144,7 +144,7 @@ export function loadConfig() { const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json'); const clientManifest = clientManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8')) - : { 'src/init.ts': { file: 'src/init.ts' } }; + : { 'src/_boot_.ts': { file: 'src/_boot_.ts' } }; const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; const mixin = {} as Mixin; @@ -165,7 +165,7 @@ export function loadConfig() { mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`; mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; mixin.userAgent = `Misskey/${meta.version} (${config.url})`; - mixin.clientEntry = clientManifest['src/init.ts']; + mixin.clientEntry = clientManifest['src/_boot_.ts']; mixin.clientManifestExists = clientManifestExists; const externalMediaProxy = config.mediaProxy ? @@ -190,6 +190,6 @@ function tryCreateUrl(url: string) { try { return new URL(url); } catch (e) { - throw `url="${url}" is not a valid URL.`; + throw new Error(`url="${url}" is not a valid URL.`); } } diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 2d4226a32de374dd34d94f431e8995da4c55d296..d8df371916bdab8d75a7e1f41ed4935a0d3ca2aa 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -55,11 +55,6 @@ export class AntennaService implements OnApplicationShutdown { this.redisForSub.on('message', this.onRedisMessage); } - @bindThis - public onApplicationShutdown(signal?: string | undefined) { - this.redisForSub.off('message', this.onRedisMessage); - } - @bindThis private async onRedisMessage(_: string, data: string): Promise<void> { const obj = JSON.parse(data); @@ -196,4 +191,14 @@ export class AntennaService implements OnApplicationShutdown { return this.antennas; } + + @bindThis + public dispose(): void { + this.redisForSub.off('message', this.onRedisMessage); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index cf1e81ffc821648f3dd4b9ddd75bb152fb94a43c..de33e4c243de2357cc239393cc10762cc95390fa 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -166,7 +166,12 @@ export class CacheService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { this.redisForSub.off('message', this.onMessage); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 7aaa1b833fda5b20b1f4ea231982e5716d55a3eb..1a52a229c57f44609c9a928a25501eeffbc476b5 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -30,7 +30,7 @@ export class CaptchaService { }, { throwErrorWhenResponseNotOk: false }); if (!res.ok) { - throw `${res.status}`; + throw new Error(`${res.status}`); } return await res.json() as CaptchaResponse; @@ -39,48 +39,48 @@ export class CaptchaService { @bindThis public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> { if (response == null) { - throw 'recaptcha-failed: no response provided'; + throw new Error('recaptcha-failed: no response provided'); } const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => { - throw `recaptcha-request-failed: ${err}`; + throw new Error(`recaptcha-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw `recaptcha-failed: ${errorCodes}`; + throw new Error(`recaptcha-failed: ${errorCodes}`); } } @bindThis public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> { if (response == null) { - throw 'hcaptcha-failed: no response provided'; + throw new Error('hcaptcha-failed: no response provided'); } const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => { - throw `hcaptcha-request-failed: ${err}`; + throw new Error(`hcaptcha-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw `hcaptcha-failed: ${errorCodes}`; + throw new Error(`hcaptcha-failed: ${errorCodes}`); } } @bindThis public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> { if (response == null) { - throw 'turnstile-failed: no response provided'; + throw new Error('turnstile-failed: no response provided'); } const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => { - throw `turnstile-request-failed: ${err}`; + throw new Error(`turnstile-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw `turnstile-failed: ${errorCodes}`; + throw new Error(`turnstile-failed: ${errorCodes}`); } } } diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 93557ce617998ffc0b79bd35588fcb4eadba0b59..3499df38b763521cce5c7085888ed98d4ea9e7a1 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -7,7 +7,7 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Emoji } from '@/models/entities/Emoji.js'; -import type { EmojisRepository } from '@/models/index.js'; +import type { EmojisRepository, Role } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -15,6 +15,8 @@ import type { Config } from '@/config.js'; import { query } from '@/misc/prelude/url.js'; import type { Serialized } from '@/server/api/stream/types.js'; +const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/; + @Injectable() export class CustomEmojiService { private cache: MemoryKVCache<Emoji | null>; @@ -63,6 +65,9 @@ export class CustomEmojiService { aliases: string[]; host: string | null; license: string | null; + isSensitive: boolean; + localOnly: boolean; + roleIdsThatCanBeUsedThisEmojiAsReaction: Role['id'][]; }): Promise<Emoji> { const emoji = await this.emojisRepository.insert({ id: this.idService.genId(), @@ -75,6 +80,9 @@ export class CustomEmojiService { publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url, type: data.driveFile.webpublicType ?? data.driveFile.type, license: data.license, + isSensitive: data.isSensitive, + localOnly: data.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction, }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); if (data.host == null) { @@ -90,10 +98,14 @@ export class CustomEmojiService { @bindThis public async update(id: Emoji['id'], data: { + driveFile?: DriveFile; name?: string; category?: string | null; aliases?: string[]; license?: string | null; + isSensitive?: boolean; + localOnly?: boolean; + roleIdsThatCanBeUsedThisEmojiAsReaction?: Role['id'][]; }): Promise<void> { const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() }); @@ -105,6 +117,12 @@ export class CustomEmojiService { category: data.category, aliases: data.aliases, license: data.license, + isSensitive: data.isSensitive, + localOnly: data.localOnly, + originalUrl: data.driveFile != null ? data.driveFile.url : undefined, + publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined, + type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined, + roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined, }); this.localEmojisCache.refresh(); @@ -259,7 +277,7 @@ export class CustomEmojiService { @bindThis public parseEmojiStr(emojiName: string, noteUserHost: string | null) { - const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); + const match = emojiName.match(parseEmojiStrRegexp); if (!match) return { name: null, host: null }; const name = match[1]; diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 8103d5afe9725aefa9a26f8f625831803f39e715..9de633350bef4ce440fbf85bba413f3493e7d34f 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -116,14 +116,14 @@ export class FetchInstanceMetadataService { const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo') .catch(err => { if (err.statusCode === 404) { - throw 'No nodeinfo provided'; + throw new Error('No nodeinfo provided'); } else { throw err.statusCode ?? err.message; } }) as Record<string, unknown>; if (wellknown.links == null || !Array.isArray(wellknown.links)) { - throw 'No wellknown links'; + throw new Error('No wellknown links'); } const links = wellknown.links as any[]; @@ -134,7 +134,7 @@ export class FetchInstanceMetadataService { const link = lnik2_1 ?? lnik2_0 ?? lnik1_0; if (link == null) { - throw 'No nodeinfo link provided'; + throw new Error('No nodeinfo link provided'); } const info = await this.httpRequestService.getJson(link.href) diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 0b861be8d0475b55a8725ba7cb76d796f7491b5f..5acc9ad9adbd3d850db90ed90fb1550c4dee71ea 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -120,8 +120,13 @@ export class MetaService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { clearInterval(this.intervalId); this.redisForSub.off('message', this.onMessage); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 9b2d5dc0ff1ec7e3e364193709a6806c4f45dc39..dffee16e08ca28ceb4bddba1c61e4745cf199079 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -83,7 +83,7 @@ export class MfmService { if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) { text += txt; // メンション - } else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) { + } else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) { const part = txt.split('@'); if (part.length === 2 && href) { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 977c9052c0ac5b2155235eb722f2eb2450cacac9..1c8491bf573b11078ea5f8c54d8adc8254edc589 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -510,7 +510,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.poll && data.poll.expiresAt) { const delay = data.poll.expiresAt.getTime() - Date.now(); - this.queueService.endedPollNotificationQueue.add({ + this.queueService.endedPollNotificationQueue.add(note.id, { noteId: note.id, }, { delay, @@ -790,7 +790,13 @@ export class NoteCreateService implements OnApplicationShutdown { return mentionedUsers; } - onApplicationShutdown(signal?: string | undefined) { + @bindThis + public dispose(): void { this.#shutdownController.abort(); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 1129bd159cf934c2afa8667c147cd5c6475f4b58..e57e57d310168836043fb069ae6f8181a4c0f424 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -122,7 +122,13 @@ export class NoteReadService implements OnApplicationShutdown { } } - onApplicationShutdown(signal?: string | undefined): void { + @bindThis + public dispose(): void { this.#shutdownController.abort(); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index a245908c981cddeb371e9f902da5fcea60198fe8..ed47165f7b4c3c090b3eb3dab2f9ba66fcae00d0 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -152,7 +152,13 @@ export class NotificationService implements OnApplicationShutdown { */ } - onApplicationShutdown(signal?: string | undefined): void { + @bindThis + public dispose(): void { this.#shutdownController.abort(); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 0cee2076bf8d3d09ff175603f311f7194c8f601d..bf50a1cdedb6ecb4fd782e403d563b534a207b8b 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -208,7 +208,7 @@ export class QueryService { } @bindThis - public generateRepliesQuery(q: SelectQueryBuilder<any>, me?: Pick<User, 'id' | 'showTimelineReplies'> | null): void { + public generateRepliesQuery(q: SelectQueryBuilder<any>, withReplies: boolean, me?: Pick<User, 'id'> | null): void { if (me == null) { q.andWhere(new Brackets(qb => { qb .where('note.replyId IS NULL') // 返信ã§ã¯ãªã„ @@ -217,7 +217,7 @@ export class QueryService { .andWhere('note.replyUserId = note.userId'); })); })); - } else if (!me.showTimelineReplies) { + } else if (!withReplies) { q.andWhere(new Brackets(qb => { qb .where('note.replyId IS NULL') // 返信ã§ã¯ãªã„ .orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信ã ã‘ã©è‡ªåˆ†ã®ãƒŽãƒ¼ãƒˆã¸ã®è¿”ä¿¡ diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts index 1d7394777629e218c4526c7ec496c00bff07998c..3384ca457785c0805025ade46fdb706b156baa54 100644 --- a/packages/backend/src/core/QueueModule.ts +++ b/packages/backend/src/core/QueueModule.ts @@ -1,42 +1,11 @@ import { setTimeout } from 'node:timers/promises'; import { Inject, Module, OnApplicationShutdown } from '@nestjs/common'; -import Bull from 'bull'; +import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; +import { QUEUE, baseQueueOptions } from '@/queue/const.js'; import type { Provider } from '@nestjs/common'; -import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, DbJobMap } from '../queue/types.js'; - -function q<T>(config: Config, name: string, limitPerSec = -1) { - return new Bull<T>(name, { - redis: { - port: config.redisForJobQueue.port, - host: config.redisForJobQueue.host, - family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family, - password: config.redisForJobQueue.pass, - db: config.redisForJobQueue.db ?? 0, - }, - prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue` : 'queue', - limiter: limitPerSec > 0 ? { - max: limitPerSec, - duration: 1000, - } : undefined, - settings: { - backoffStrategies: { - apBackoff, - }, - }, - }); -} - -// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019 -function apBackoff(attemptsMade: number, err: Error) { - const baseDelay = 60 * 1000; // 1min - const maxBackoff = 8 * 60 * 60 * 1000; // 8hours - let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; - backoff = Math.min(backoff, maxBackoff); - backoff += Math.round(backoff * Math.random() * 0.2); - return backoff; -} +import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js'; export type SystemQueue = Bull.Queue<Record<string, unknown>>; export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>; @@ -49,49 +18,49 @@ export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>; const $system: Provider = { provide: 'queue:system', - useFactory: (config: Config) => q(config, 'system'), + useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)), inject: [DI.config], }; const $endedPollNotification: Provider = { provide: 'queue:endedPollNotification', - useFactory: (config: Config) => q(config, 'endedPollNotification'), + useFactory: (config: Config) => new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION)), inject: [DI.config], }; const $deliver: Provider = { provide: 'queue:deliver', - useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128), + useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)), inject: [DI.config], }; const $inbox: Provider = { provide: 'queue:inbox', - useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16), + useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX)), inject: [DI.config], }; const $db: Provider = { provide: 'queue:db', - useFactory: (config: Config) => q(config, 'db'), + useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)), inject: [DI.config], }; const $relationship: Provider = { provide: 'queue:relationship', - useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64), + useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP)), inject: [DI.config], }; const $objectStorage: Provider = { provide: 'queue:objectStorage', - useFactory: (config: Config) => q(config, 'objectStorage'), + useFactory: (config: Config) => new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE)), inject: [DI.config], }; const $webhookDeliver: Provider = { provide: 'queue:webhookDeliver', - useFactory: (config: Config) => q(config, 'webhookDeliver', 64), + useFactory: (config: Config) => new Bull.Queue(QUEUE.WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.WEBHOOK_DELIVER)), inject: [DI.config], }; @@ -131,7 +100,7 @@ export class QueueModule implements OnApplicationShutdown { @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, ) {} - async onApplicationShutdown(signal: string): Promise<void> { + public async dispose(): Promise<void> { if (process.env.NODE_ENV === 'test') { // XXX: // Shutting down the existing connections causes errors on Jest as @@ -151,4 +120,8 @@ export class QueueModule implements OnApplicationShutdown { this.webhookDeliverQueue.close(), ]); } + + async onApplicationShutdown(signal: string): Promise<void> { + await this.dispose(); + } } diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index b4ffffecc0a65371b057fdc568d97011138ecf06..2ae8a2b7548fbbea13cf069de1ddbdf258d143d1 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -1,6 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; import { v4 as uuid } from 'uuid'; -import Bull from 'bull'; import type { IActivity } from '@/core/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; @@ -11,6 +10,7 @@ import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; +import type * as Bull from 'bullmq'; @Injectable() export class QueueService { @@ -26,7 +26,43 @@ export class QueueService { @Inject('queue:relationship') public relationshipQueue: RelationshipQueue, @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue, - ) {} + ) { + this.systemQueue.add('tickCharts', { + }, { + repeat: { pattern: '55 * * * *' }, + removeOnComplete: true, + }); + + this.systemQueue.add('resyncCharts', { + }, { + repeat: { pattern: '0 0 * * *' }, + removeOnComplete: true, + }); + + this.systemQueue.add('cleanCharts', { + }, { + repeat: { pattern: '0 0 * * *' }, + removeOnComplete: true, + }); + + this.systemQueue.add('aggregateRetention', { + }, { + repeat: { pattern: '0 0 * * *' }, + removeOnComplete: true, + }); + + this.systemQueue.add('clean', { + }, { + repeat: { pattern: '0 0 * * *' }, + removeOnComplete: true, + }); + + this.systemQueue.add('checkExpiredMutings', { + }, { + repeat: { pattern: '*/5 * * * *' }, + removeOnComplete: true, + }); + } @bindThis public deliver(user: ThinUser, content: IActivity | null, to: string | null, isSharedInbox: boolean) { @@ -42,11 +78,10 @@ export class QueueService { isSharedInbox, }; - return this.deliverQueue.add(data, { + return this.deliverQueue.add(to, data, { attempts: this.config.deliverJobMaxAttempts ?? 12, - timeout: 1 * 60 * 1000, // 1min backoff: { - type: 'apBackoff', + type: 'custom', }, removeOnComplete: true, removeOnFail: true, @@ -60,11 +95,10 @@ export class QueueService { signature, }; - return this.inboxQueue.add(data, { + return this.inboxQueue.add('', data, { attempts: this.config.inboxJobMaxAttempts ?? 8, - timeout: 5 * 60 * 1000, // 5min backoff: { - type: 'apBackoff', + type: 'custom', }, removeOnComplete: true, removeOnFail: true, @@ -212,7 +246,7 @@ export class QueueService { private generateToDbJobData<T extends 'importFollowingToDb' | 'importBlockingToDb', D extends DbJobData<T>>(name: T, data: D): { name: string, data: D, - opts: Bull.JobOptions, + opts: Bull.JobsOptions, } { return { name, @@ -299,10 +333,10 @@ export class QueueService { } @bindThis - private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobOptions = {}): { + private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobsOptions = {}): { name: string, data: RelationshipJobData, - opts: Bull.JobOptions, + opts: Bull.JobsOptions, } { return { name, @@ -351,11 +385,10 @@ export class QueueService { eventId: uuid(), }; - return this.webhookDeliverQueue.add(data, { + return this.webhookDeliverQueue.add(webhook.id, data, { attempts: 4, - timeout: 1 * 60 * 1000, // 1min backoff: { - type: 'apBackoff', + type: 'custom', }, removeOnComplete: true, removeOnFail: true, @@ -367,11 +400,11 @@ export class QueueService { this.deliverQueue.once('cleaned', (jobs, status) => { //deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); }); - this.deliverQueue.clean(0, 'delayed'); + this.deliverQueue.clean(0, Infinity, 'delayed'); this.inboxQueue.once('cleaned', (jobs, status) => { //inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); }); - this.inboxQueue.clean(0, 'delayed'); + this.inboxQueue.clean(0, Infinity, 'delayed'); } } diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index a274b19e4b4b10095c23dcb8017312fa1bc26021..4b01b6af7e9b7e74b4a441ae5c62576bd7f0d182 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; +import { RoleService } from '@/core/RoleService.js'; const FALLBACK = 'â¤'; @@ -54,6 +55,9 @@ type DecodedReaction = { host?: string | null; }; +const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/; +const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/; + @Injectable() export class ReactionService { constructor( @@ -72,6 +76,7 @@ export class ReactionService { private utilityService: UtilityService, private metaService: MetaService, private customEmojiService: CustomEmojiService, + private roleService: RoleService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private userBlockingService: UserBlockingService, @@ -85,7 +90,7 @@ export class ReactionService { } @bindThis - public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string | null) { + public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, _reaction?: string | null) { // Check blocking if (note.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); @@ -99,10 +104,41 @@ export class ReactionService { throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.'); } - if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) { + let reaction = _reaction ?? FALLBACK; + + if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) { reaction = 'â¤ï¸'; - } else { - reaction = await this.toDbReaction(reaction, user.host); + } else if (_reaction) { + const custom = reaction.match(isCustomEmojiRegexp); + if (custom) { + const reacterHost = this.utilityService.toPunyNullable(user.host); + + const name = custom[1]; + const emoji = reacterHost == null + ? (await this.customEmojiService.localEmojisCache.fetch()).get(name) + : await this.emojisRepository.findOneBy({ + host: reacterHost, + name, + }); + + if (emoji) { + if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) { + reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`; + + // センシティブ + if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) { + reaction = FALLBACK; + } + } else { + // リアクションã¨ã—ã¦ä½¿ã†æ¨©é™ãŒãªã„ + reaction = FALLBACK; + } + } else { + reaction = FALLBACK; + } + } else { + reaction = this.normalize(reaction ?? null); + } } const record: NoteReaction = { @@ -288,11 +324,9 @@ export class ReactionService { } @bindThis - public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> { + public normalize(reaction: string | null): string { if (reaction == null) return FALLBACK; - reacterHost = this.utilityService.toPunyNullable(reacterHost); - // æ–‡å—列タイプã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’絵文å—ã«å¤‰æ› if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; @@ -306,25 +340,12 @@ export class ReactionService { return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, ''); } - const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/); - if (custom) { - const name = custom[1]; - const emoji = reacterHost == null - ? (await this.customEmojiService.localEmojisCache.fetch()).get(name) - : await this.emojisRepository.findOneBy({ - host: reacterHost, - name, - }); - - if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`; - } - return FALLBACK; } @bindThis public decodeReaction(str: string): DecodedReaction { - const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/); + const custom = str.match(decodeCustomEmojiRegexp); if (custom) { const name = custom[1]; diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 68087ccc3b34248fa38f626ade439747d5551cb3..40ae106662710270e54dc23ea8360368deac3270 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -306,6 +306,14 @@ export class RoleService implements OnApplicationShutdown { return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator); } + @bindThis + public async isExplorable(role: { id: Role['id']} | null): Promise<boolean> { + if (role == null) return false; + const check = await this.rolesRepository.findOneBy({ id: role.id }); + if (check == null) return false; + return check.isExplorable; + } + @bindThis public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> { const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); @@ -425,7 +433,12 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { this.redisForSub.off('message', this.onMessage); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index 3ee79906431f5d9aa9b05ba560d6ed435de25664..f58a6a10fcd8910a87271b18355a5554344dece8 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -16,6 +16,9 @@ type IWebFinger = { subject: string; }; +const urlRegex = /^https?:\/\//; +const mRegex = /^([^@]+)@(.*)/; + @Injectable() export class WebfingerService { constructor( @@ -35,12 +38,12 @@ export class WebfingerService { @bindThis private genUrl(query: string): string { - if (query.match(/^https?:\/\//)) { + if (query.match(urlRegex)) { const u = new URL(query); return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query }); } - const m = query.match(/^([^@]+)@(.*)/); + const m = query.match(mRegex); if (m) { const hostname = m[2]; const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true'; diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 57baade77707a60defcbdcb23a14e5326801761a..467755a0728c72fdf257afbec17bf615c2863d01 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -81,7 +81,12 @@ export class WebhookService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { this.redisForSub.off('message', this.onMessage); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 60e19bfca52f46eb194f15775f71067180077e83..d8b95ca4d1131e871f6498f53448789b2f8a7f17 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -277,7 +277,7 @@ export class ApRendererService { const name = reaction.replaceAll(':', ''); const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name); - if (emoji) object.tag = [this.renderEmoji(emoji)]; + if (emoji && !emoji.localOnly) object.tag = [this.renderEmoji(emoji)]; } return object; @@ -400,7 +400,7 @@ export class ApRendererService { })); const emojis = await this.getEmojis(note.emojis); - const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); + const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji)); const tag = [ ...hashtagTags, @@ -479,7 +479,7 @@ export class ApRendererService { } const emojis = await this.getEmojis(user.emojis); - const apemojis = emojis.map(emoji => this.renderEmoji(emoji)); + const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji)); const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts index 2dc1a410ac2f766996018cdcf5c1959ccd79cf36..20fe2a0a770b383055b534bf97423776d4f325df 100644 --- a/packages/backend/src/core/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -94,7 +94,7 @@ class LdSignature { @bindThis private getLoader() { return async (url: string): Promise<any> => { - if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`; + if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`); if (this.preLoad) { if (url in CONTEXTS) { @@ -126,7 +126,7 @@ class LdSignature { timeout: this.loderTimeout, }, { throwErrorWhenResponseNotOk: false }).then(res => { if (!res.ok) { - throw `${res.status} ${res.statusText}`; + throw new Error(`${res.status} ${res.statusText}`); } else { return res.json(); } diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 87a9db405f9d0fcc56deb27b915e3b8062bdae8a..76757f530a6d1dac75fc45a9cd90b9ac02a1d6e2 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -18,6 +18,7 @@ import { PollService } from '@/core/PollService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import { checkHttps } from '@/misc/check-https.js'; import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { ApLoggerService } from '../ApLoggerService.js'; @@ -32,7 +33,6 @@ import { ApQuestionService } from './ApQuestionService.js'; import { ApImageService } from './ApImageService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IPost } from '../type.js'; -import { checkHttps } from '@/misc/check-https.js'; @Injectable() export class ApNoteService { @@ -230,7 +230,7 @@ export class ApNoteService { quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x); if (!quote) { if (results.some(x => x.status === 'temperror')) { - throw 'quote resolve failed'; + throw new Error('quote resolve failed'); } } } @@ -311,7 +311,7 @@ export class ApNoteService { // ブãƒãƒƒã‚¯ã—ã¦ãŸã‚‰ä¸æ– const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw { statusCode: 451 }; + if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451); const unlock = await this.appLockService.getApLock(uri); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index eea1d1b8482825e01de66924013f1de9a3c99559..f52ebed107537bda65d3b90d29c6a2053452e9b5 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -32,6 +32,8 @@ import type { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import type { AccountMoveService } from '@/core/AccountMoveService.js'; +import { checkHttps } from '@/misc/check-https.js'; import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; import { extractApHashtags } from './tag.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -42,8 +44,6 @@ import type { ApLoggerService } from '../ApLoggerService.js'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import type { ApImageService } from './ApImageService.js'; import type { IActor, IObject } from '../type.js'; -import type { AccountMoveService } from '@/core/AccountMoveService.js'; -import { checkHttps } from '@/misc/check-https.js'; const nameLength = 128; const summaryLength = 2048; @@ -306,7 +306,6 @@ export class ApPersonService implements OnModuleInit { tags, isBot, isCat: (person as any).isCat === true, - showTimelineReplies: false, })) as RemoteUser; await transactionalEntityManager.save(new UserProfile({ @@ -696,7 +695,7 @@ export class ApPersonService implements OnModuleInit { if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) { return 'skip: dst.alsoKnownAs is empty'; } - if (!dst.alsoKnownAs?.includes(src.uri)) { + if (!dst.alsoKnownAs.includes(src.uri)) { return 'skip: alsoKnownAs does not include from.uri'; } diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 03e361265804d6a701b85b21aa0dcea167ce5a59..b0e9e534df5a3523f1badebae5e140321c90604f 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -60,7 +60,8 @@ export class ChartManagementService implements OnApplicationShutdown { }, 1000 * 60 * 20); } - async onApplicationShutdown(signal: string): Promise<void> { + @bindThis + public async dispose(): Promise<void> { clearInterval(this.saveIntervalId); if (process.env.NODE_ENV !== 'test') { await Promise.all( @@ -68,4 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown { ); } } + + @bindThis + async onApplicationShutdown(signal: string): Promise<void> { + await this.dispose(); + } } diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 3bad048bc095659fea0d7e6465d992116c1035be..4a18cd1b3b7e7fe8bbcc7f24fe6aea15ab0dd021 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -26,6 +26,8 @@ export class EmojiEntityService { category: emoji.category, // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚(publicUrlã¯stringãªã®ã§??ã¯ã ã‚) url: emoji.publicUrl || emoji.originalUrl, + isSensitive: emoji.isSensitive ? true : undefined, + roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined, }; } @@ -51,6 +53,9 @@ export class EmojiEntityService { // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚(publicUrlã¯stringãªã®ã§??ã¯ã ã‚) url: emoji.publicUrl || emoji.originalUrl, license: emoji.license, + isSensitive: emoji.isSensitive, + localOnly: emoji.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction, }; } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 7f61e1d6f323d16df042d4f894bb898d0ef0f7ad..bfd506ea868c938ea42a63d65e9581d093b5b25f 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -466,7 +466,6 @@ export class UserEntityService implements OnModuleInit { mutedInstances: profile!.mutedInstances, mutingNotificationTypes: profile!.mutingNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes, - showTimelineReplies: user.showTimelineReplies ?? falsy, achievements: profile!.achievements, loggedInDays: profile!.loggedInDates.length, policies: this.roleService.getUserPolicies(user.id), diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 2461cb2c122004fe4273f3d20213fbaa36d3c094..8628819278fe8919199db0042d73eae33acc677c 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -35,6 +35,7 @@ export class UserListEntityService { createdAt: userList.createdAt.toISOString(), name: userList.name, userIds: users.map(x => x.userId), + isPublic: userList.isPublic, }; } } diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts index 8cdfb703f1c3f15d1fb42cbf4e6ab19fa28c3b8d..f826d506254e2bf8a31bd702c84378c28aec7eb4 100644 --- a/packages/backend/src/daemons/JanitorService.ts +++ b/packages/backend/src/daemons/JanitorService.ts @@ -34,7 +34,12 @@ export class JanitorService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { clearInterval(this.intervalId); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index b717434e09c65ee2665b314fdb0c94eb2d333d9a..53a0d14cd78d70ad44a8f062e10345f58f0a5f92 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -1,7 +1,11 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import Xev from 'xev'; +import * as Bull from 'bullmq'; import { QueueService } from '@/core/QueueService.js'; import { bindThis } from '@/decorators.js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { QUEUE, baseQueueOptions } from '@/queue/const.js'; import type { OnApplicationShutdown } from '@nestjs/common'; const ev = new Xev(); @@ -13,6 +17,9 @@ export class QueueStatsService implements OnApplicationShutdown { private intervalId: NodeJS.Timer; constructor( + @Inject(DI.config) + private config: Config, + private queueService: QueueService, ) { } @@ -31,11 +38,14 @@ export class QueueStatsService implements OnApplicationShutdown { let activeDeliverJobs = 0; let activeInboxJobs = 0; - this.queueService.deliverQueue.on('global:active', () => { + const deliverQueueEvents = new Bull.QueueEvents(QUEUE.DELIVER, baseQueueOptions(this.config, QUEUE.DELIVER)); + const inboxQueueEvents = new Bull.QueueEvents(QUEUE.INBOX, baseQueueOptions(this.config, QUEUE.INBOX)); + + deliverQueueEvents.on('active', () => { activeDeliverJobs++; }); - this.queueService.inboxQueue.on('global:active', () => { + inboxQueueEvents.on('active', () => { activeInboxJobs++; }); @@ -71,9 +81,14 @@ export class QueueStatsService implements OnApplicationShutdown { this.intervalId = setInterval(tick, interval); } - + @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { clearInterval(this.intervalId); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index bb190cf60fa581d7240adb1f9e931bbbb42dba79..6cd71c0e2ae3901c44bfc2a8a443d8ba3580a190 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -63,9 +63,14 @@ export class ServerStatsService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { clearInterval(this.intervalId); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } // CPU STAT diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index c06c7a7159c2114d5718dce8986b895d66f00284..4a073f102fc88f09dfc6a83a9becda13eab50992 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -25,6 +25,7 @@ export const DI = { userSecurityKeysRepository: Symbol('userSecurityKeysRepository'), userPublickeysRepository: Symbol('userPublickeysRepository'), userListsRepository: Symbol('userListsRepository'), + userListFavoritesRepository: Symbol('userListFavoritesRepository'), userListJoiningsRepository: Symbol('userListJoiningsRepository'), userNotePiningsRepository: Symbol('userNotePiningsRepository'), userIpsRepository: Symbol('userIpsRepository'), diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts index 9e206ee98f7ae8d6a8c8c8624277200016e31684..f0cbc9900de0abe64d03425b9ce60125ceebaf44 100644 --- a/packages/backend/src/misc/id/aid.ts +++ b/packages/backend/src/misc/id/aid.ts @@ -21,7 +21,7 @@ function getNoise(): string { export function genAid(date: Date): string { const t = date.getTime(); - if (isNaN(t)) throw 'Failed to create AID: Invalid Date'; + if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date'); counter++; return getTime(t) + getNoise(); } diff --git a/packages/backend/src/misc/prelude/time.ts b/packages/backend/src/misc/prelude/time.ts index 34e8b6b17c56ef714a7ec50d29a863f498c1fe3d..b21978b186893c8adef27aabf771916801f8a7df 100644 --- a/packages/backend/src/misc/prelude/time.ts +++ b/packages/backend/src/misc/prelude/time.ts @@ -5,15 +5,16 @@ const dateTimeIntervals = { }; export function dateUTC(time: number[]): Date { - const d = time.length === 2 ? Date.UTC(time[0], time[1]) - : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) - : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) - : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) - : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) - : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) - : null; - - if (!d) throw 'wrong number of arguments'; + const d = + time.length === 2 ? Date.UTC(time[0], time[1]) + : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) + : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) + : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) + : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) + : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) + : null; + + if (!d) throw new Error('wrong number of arguments'); return new Date(d); } diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 588c98b58dd8a3baddeb88532cb531ffe4879fc6..4231acc0465baaa8933d6180a621bccb2db03fa0 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -112,6 +112,12 @@ const $userListsRepository: Provider = { inject: [DI.db], }; +const $userListFavoritesRepository: Provider = { + provide: DI.userListFavoritesRepository, + useFactory: (db: DataSource) => db.getRepository(UserListFavorite), + inject: [DI.db], +}; + const $userListJoiningsRepository: Provider = { provide: DI.userListJoiningsRepository, useFactory: (db: DataSource) => db.getRepository(UserListJoining), @@ -416,6 +422,7 @@ const $userMemosRepository: Provider = { $userSecurityKeysRepository, $userPublickeysRepository, $userListsRepository, + $userListFavoritesRepository, $userListJoiningsRepository, $userNotePiningsRepository, $userIpsRepository, @@ -483,6 +490,7 @@ const $userMemosRepository: Provider = { $userSecurityKeysRepository, $userPublickeysRepository, $userListsRepository, + $userListFavoritesRepository, $userListJoiningsRepository, $userNotePiningsRepository, $userIpsRepository, diff --git a/packages/backend/src/models/entities/Emoji.ts b/packages/backend/src/models/entities/Emoji.ts index dbb437d439a3a86e040485a5dd637ce4f229bc3c..8fd3e65f5e5dd14c23d113aacf5125198c2ef9de 100644 --- a/packages/backend/src/models/entities/Emoji.ts +++ b/packages/backend/src/models/entities/Emoji.ts @@ -60,4 +60,20 @@ export class Emoji { length: 1024, nullable: true, }) public license: string | null; + + @Column('boolean', { + default: false, + }) + public localOnly: boolean; + + @Column('boolean', { + default: false, + }) + public isSensitive: boolean; + + // TODO: 定期ジョブã§å˜åœ¨ã—ãªããªã£ãŸãƒãƒ¼ãƒ«IDを除去ã™ã‚‹ã‚ˆã†ã«ã™ã‚‹ + @Column('varchar', { + array: true, length: 128, default: '{}', + }) + public roleIdsThatCanBeUsedThisEmojiAsReaction: string[]; } diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts index df508b4dcaa635c5e53690259f0432346f152237..4f49a0595061e34e9d6d76cf156aeab8c9a2b974 100644 --- a/packages/backend/src/models/entities/Note.ts +++ b/packages/backend/src/models/entities/Note.ts @@ -90,7 +90,7 @@ export class Note { @Column('varchar', { length: 64, nullable: true, }) - public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null; + public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null; @Column('smallint', { default: 0, diff --git a/packages/backend/src/models/entities/User.ts b/packages/backend/src/models/entities/User.ts index 8e10f999b617e665a536a9b3688d808dd44bbdcf..6669890cf6775aab758ef7b3b6a69ea4167440e4 100644 --- a/packages/backend/src/models/entities/User.ts +++ b/packages/backend/src/models/entities/User.ts @@ -232,12 +232,6 @@ export class User { }) public followersUri: string | null; - @Column('boolean', { - default: false, - comment: 'Whether to show users replying to other users in the timeline.', - }) - public showTimelineReplies: boolean; - @Index({ unique: true }) @Column('char', { length: 16, nullable: true, unique: true, diff --git a/packages/backend/src/models/entities/UserList.ts b/packages/backend/src/models/entities/UserList.ts index b8a4b54d4c37bd284aa69d89728cdb3b3ad61025..94f3dc3cb320a9074985b42165417f168224cf63 100644 --- a/packages/backend/src/models/entities/UserList.ts +++ b/packages/backend/src/models/entities/UserList.ts @@ -19,6 +19,12 @@ export class UserList { }) public userId: User['id']; + @Index() + @Column('boolean', { + default: false, + }) + public isPublic: boolean; + @ManyToOne(type => User, { onDelete: 'CASCADE', }) diff --git a/packages/backend/src/models/entities/UserListFavorite.ts b/packages/backend/src/models/entities/UserListFavorite.ts new file mode 100644 index 0000000000000000000000000000000000000000..e57abb460a463f01b04dc41027e35915e9774dd2 --- /dev/null +++ b/packages/backend/src/models/entities/UserListFavorite.ts @@ -0,0 +1,33 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { id } from '../id.js'; +import { User } from './User.js'; +import { UserList } from './UserList.js'; + +@Entity() +@Index(['userId', 'userListId'], { unique: true }) +export class UserListFavorite { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone') + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: User | null; + + @Column(id()) + public userListId: UserList['id']; + + @ManyToOne(type => UserList, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public userList: UserList | null; +} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index b8ba28db9b58fc2a69c550957e681a1b407e9a63..4b230ab742161e8b19671d3a9331a05de9d0f15a 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -49,6 +49,7 @@ import { User } from '@/models/entities/User.js'; import { UserIp } from '@/models/entities/UserIp.js'; import { UserKeypair } from '@/models/entities/UserKeypair.js'; import { UserList } from '@/models/entities/UserList.js'; +import { UserListFavorite } from './entities/UserListFavorite.js'; import { UserListJoining } from '@/models/entities/UserListJoining.js'; import { UserNotePining } from '@/models/entities/UserNotePining.js'; import { UserPending } from '@/models/entities/UserPending.js'; @@ -117,6 +118,7 @@ export { UserIp, UserKeypair, UserList, + UserListFavorite, UserListJoining, UserNotePining, UserPending, @@ -184,6 +186,7 @@ export type UsersRepository = Repository<User>; export type UserIpsRepository = Repository<UserIp>; export type UserKeypairsRepository = Repository<UserKeypair>; export type UserListsRepository = Repository<UserList>; +export type UserListFavoritesRepository = Repository<UserListFavorite>; export type UserListJoiningsRepository = Repository<UserListJoining>; export type UserNotePiningsRepository = Repository<UserNotePining>; export type UserPendingsRepository = Repository<UserPending>; diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts index db4fd62cf6112b47cd4c8a6edc36ec4e566e0f0b..63f56e77cb83b648eae8436c82f723851ed3e447 100644 --- a/packages/backend/src/models/json-schema/emoji.ts +++ b/packages/backend/src/models/json-schema/emoji.ts @@ -22,6 +22,19 @@ export const packedEmojiSimpleSchema = { type: 'string', optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: true, nullable: false, + }, + roleIdsThatCanBeUsedThisEmojiAsReaction: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, }, } as const; @@ -63,5 +76,22 @@ export const packedEmojiDetailedSchema = { type: 'string', optional: false, nullable: true, }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + localOnly: { + type: 'boolean', + optional: false, nullable: false, + }, + roleIdsThatCanBeUsedThisEmojiAsReaction: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/user-list.ts b/packages/backend/src/models/json-schema/user-list.ts index 3ba5dc4a8a92e685cae4f6c99f550e4e7bd85123..1e620516e4b507fdce6917ec967940ad5d7f8670 100644 --- a/packages/backend/src/models/json-schema/user-list.ts +++ b/packages/backend/src/models/json-schema/user-list.ts @@ -25,5 +25,10 @@ export const packedUserListSchema = { format: 'id', }, }, + isPublic: { + type: 'boolean', + nullable: false, + optional: false, + }, }, } as const; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index f3d404e6c9b4f4197b57ef72b1d701deb34fb92f..488979c409db2e814670dc5d254ea44bafef2e59 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -57,6 +57,7 @@ import { User } from '@/models/entities/User.js'; import { UserIp } from '@/models/entities/UserIp.js'; import { UserKeypair } from '@/models/entities/UserKeypair.js'; import { UserList } from '@/models/entities/UserList.js'; +import { UserListFavorite } from '@/models/entities/UserListFavorite.js'; import { UserListJoining } from '@/models/entities/UserListJoining.js'; import { UserNotePining } from '@/models/entities/UserNotePining.js'; import { UserPending } from '@/models/entities/UserPending.js'; @@ -132,6 +133,7 @@ export const entities = [ UserKeypair, UserPublickey, UserList, + UserListFavorite, UserListJoining, UserNotePining, UserSecurityKey, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index dc025f988987ace5a3dd038f0e69af94c0deae4d..42f9c1af7d7a72a911f7b14a948723a954016ef3 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -1,10 +1,9 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; +import * as Bull from 'bullmq'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; -import { QueueService } from '@/core/QueueService.js'; import { bindThis } from '@/decorators.js'; -import { getJobInfo } from './get-job-info.js'; import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js'; import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; @@ -35,17 +34,51 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; +import { QUEUE, baseQueueOptions } from './const.js'; + +// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019 +function httpRelatedBackoff(attemptsMade: number) { + const baseDelay = 60 * 1000; // 1min + const maxBackoff = 8 * 60 * 60 * 1000; // 8hours + let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay; + backoff = Math.min(backoff, maxBackoff); + backoff += Math.round(backoff * Math.random() * 0.2); + return backoff; +} + +function getJobInfo(job: Bull.Job | undefined, increment = false): string { + if (job == null) return '-'; + + const age = Date.now() - job.timestamp; + + const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m` + : age > 10000 ? `${Math.floor(age / 1000)}s` + : `${age}ms`; + + // onActiveã¨ã‹onCompletedã®attemptsMadeãŒãªãœã‹0始ã¾ã‚Šãªã®ã§ã‚¤ãƒ³ã‚¯ãƒªãƒ¡ãƒ³ãƒˆã™ã‚‹ + const currentAttempts = job.attemptsMade + (increment ? 1 : 0); + const maxAttempts = job.opts ? job.opts.attempts : 0; + + return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`; +} @Injectable() -export class QueueProcessorService { +export class QueueProcessorService implements OnApplicationShutdown { private logger: Logger; + private systemQueueWorker: Bull.Worker; + private dbQueueWorker: Bull.Worker; + private deliverQueueWorker: Bull.Worker; + private inboxQueueWorker: Bull.Worker; + private webhookDeliverQueueWorker: Bull.Worker; + private relationshipQueueWorker: Bull.Worker; + private objectStorageQueueWorker: Bull.Worker; + private endedPollNotificationQueueWorker: Bull.Worker; constructor( @Inject(DI.config) private config: Config, private queueLoggerService: QueueLoggerService, - private queueService: QueueService, private webhookDeliverProcessorService: WebhookDeliverProcessorService, private endedPollNotificationProcessorService: EndedPollNotificationProcessorService, private deliverProcessorService: DeliverProcessorService, @@ -77,10 +110,7 @@ export class QueueProcessorService { private cleanProcessorService: CleanProcessorService, ) { this.logger = this.queueLoggerService.logger; - } - @bindThis - public start() { function renderError(e: Error): any { if (e) { // 何故ã‹eãŒundefinedã§æ¥ã‚‹ã“ã¨ãŒã‚ã‚‹ return { @@ -97,146 +127,232 @@ export class QueueProcessorService { } } + //#region system + this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => { + switch (job.name) { + case 'tickCharts': return this.tickChartsProcessorService.process(); + case 'resyncCharts': return this.resyncChartsProcessorService.process(); + case 'cleanCharts': return this.cleanChartsProcessorService.process(); + case 'aggregateRetention': return this.aggregateRetentionProcessorService.process(); + case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process(); + case 'clean': return this.cleanProcessorService.process(); + default: throw new Error(`unrecognized job type ${job.name} for system`); + } + }, { + ...baseQueueOptions(this.config, QUEUE.SYSTEM), + autorun: false, + }); + const systemLogger = this.logger.createSubLogger('system'); - const deliverLogger = this.logger.createSubLogger('deliver'); - const webhookLogger = this.logger.createSubLogger('webhook'); - const inboxLogger = this.logger.createSubLogger('inbox'); - const dbLogger = this.logger.createSubLogger('db'); - const relationshipLogger = this.logger.createSubLogger('relationship'); - const objectStorageLogger = this.logger.createSubLogger('objectStorage'); - this.queueService.systemQueue - .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`)) + this.systemQueueWorker .on('active', (job) => systemLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`)); + .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`)); + //#endregion + + //#region db + this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => { + switch (job.name) { + case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job); + case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job); + case 'exportNotes': return this.exportNotesProcessorService.process(job); + case 'exportFavorites': return this.exportFavoritesProcessorService.process(job); + case 'exportFollowing': return this.exportFollowingProcessorService.process(job); + case 'exportMuting': return this.exportMutingProcessorService.process(job); + case 'exportBlocking': return this.exportBlockingProcessorService.process(job); + case 'exportUserLists': return this.exportUserListsProcessorService.process(job); + case 'exportAntennas': return this.exportAntennasProcessorService.process(job); + case 'importFollowing': return this.importFollowingProcessorService.process(job); + case 'importFollowingToDb': return this.importFollowingProcessorService.processDb(job); + case 'importMuting': return this.importMutingProcessorService.process(job); + case 'importBlocking': return this.importBlockingProcessorService.process(job); + case 'importBlockingToDb': return this.importBlockingProcessorService.processDb(job); + case 'importUserLists': return this.importUserListsProcessorService.process(job); + case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job); + case 'importAntennas': return this.importAntennasProcessorService.process(job); + case 'deleteAccount': return this.deleteAccountProcessorService.process(job); + default: throw new Error(`unrecognized job type ${job.name} for db`); + } + }, { + ...baseQueueOptions(this.config, QUEUE.DB), + autorun: false, + }); + + const dbLogger = this.logger.createSubLogger('db'); + + this.dbQueueWorker + .on('active', (job) => dbLogger.debug(`active id=${job.id}`)) + .on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`)) + .on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`)); + //#endregion - this.queueService.deliverQueue - .on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`)) + //#region deliver + this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), { + ...baseQueueOptions(this.config, QUEUE.DELIVER), + autorun: false, + concurrency: this.config.deliverJobConcurrency ?? 128, + limiter: { + max: this.config.deliverJobPerSec ?? 128, + duration: 1000, + }, + settings: { + backoffStrategy: httpRelatedBackoff, + }, + }); + + const deliverLogger = this.logger.createSubLogger('deliver'); + + this.deliverQueueWorker .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) - .on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); + .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) + .on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`)); + //#endregion + + //#region inbox + this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), { + ...baseQueueOptions(this.config, QUEUE.INBOX), + autorun: false, + concurrency: this.config.inboxJobConcurrency ?? 16, + limiter: { + max: this.config.inboxJobPerSec ?? 16, + duration: 1000, + }, + settings: { + backoffStrategy: httpRelatedBackoff, + }, + }); - this.queueService.inboxQueue - .on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`)) + const inboxLogger = this.logger.createSubLogger('inbox'); + + this.inboxQueueWorker .on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`)) .on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) - .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`)); + .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`)); + //#endregion - this.queueService.dbQueue - .on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => dbLogger.debug(`active id=${job.id}`)) - .on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`)); - - this.queueService.relationshipQueue - .on('waiting', (jobId) => relationshipLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => relationshipLogger.debug(`active id=${job.id}`)) - .on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => relationshipLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => relationshipLogger.warn(`stalled id=${job.id}`)); + //#region webhook deliver + this.webhookDeliverQueueWorker = new Bull.Worker(QUEUE.WEBHOOK_DELIVER, (job) => this.webhookDeliverProcessorService.process(job), { + ...baseQueueOptions(this.config, QUEUE.WEBHOOK_DELIVER), + autorun: false, + concurrency: 64, + limiter: { + max: 64, + duration: 1000, + }, + settings: { + backoffStrategy: httpRelatedBackoff, + }, + }); - this.queueService.objectStorageQueue - .on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`)) - .on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); + const webhookLogger = this.logger.createSubLogger('webhook'); - this.queueService.webhookDeliverQueue - .on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`)) + this.webhookDeliverQueueWorker .on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) - .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); + .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) + .on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`)); + //#endregion - this.queueService.systemQueue.add('tickCharts', { + //#region relationship + this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => { + switch (job.name) { + case 'follow': return this.relationshipProcessorService.processFollow(job); + case 'unfollow': return this.relationshipProcessorService.processUnfollow(job); + case 'block': return this.relationshipProcessorService.processBlock(job); + case 'unblock': return this.relationshipProcessorService.processUnblock(job); + default: throw new Error(`unrecognized job type ${job.name} for relationship`); + } }, { - repeat: { cron: '55 * * * *' }, - removeOnComplete: true, + ...baseQueueOptions(this.config, QUEUE.RELATIONSHIP), + autorun: false, + concurrency: this.config.relashionshipJobConcurrency ?? 16, + limiter: { + max: this.config.relashionshipJobPerSec ?? 64, + duration: 1000, + }, }); - this.queueService.systemQueue.add('resyncCharts', { - }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, - }); + const relationshipLogger = this.logger.createSubLogger('relationship'); + + this.relationshipQueueWorker + .on('active', (job) => relationshipLogger.debug(`active id=${job.id}`)) + .on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`)) + .on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`)); + //#endregion - this.queueService.systemQueue.add('cleanCharts', { + //#region object storage + this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => { + switch (job.name) { + case 'deleteFile': return this.deleteFileProcessorService.process(job); + case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job); + default: throw new Error(`unrecognized job type ${job.name} for objectStorage`); + } }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, + ...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE), + autorun: false, + concurrency: 16, }); - this.queueService.systemQueue.add('aggregateRetention', { - }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, - }); + const objectStorageLogger = this.logger.createSubLogger('objectStorage'); - this.queueService.systemQueue.add('clean', { - }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, - }); + this.objectStorageQueueWorker + .on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`)) + .on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`)) + .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) })) + .on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`)); + //#endregion - this.queueService.systemQueue.add('checkExpiredMutings', { - }, { - repeat: { cron: '*/5 * * * *' }, - removeOnComplete: true, + //#region ended poll notification + this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => this.endedPollNotificationProcessorService.process(job), { + ...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION), + autorun: false, }); + //#endregion + } - this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job)); - this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job)); - this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done)); - this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job)); - - this.queueService.dbQueue.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done)); - this.queueService.dbQueue.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done)); - this.queueService.dbQueue.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done)); - this.queueService.dbQueue.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job)); - this.queueService.dbQueue.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done)); - this.queueService.dbQueue.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done)); - this.queueService.dbQueue.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job)); - this.queueService.dbQueue.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done)); - this.queueService.dbQueue.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done)); - this.queueService.dbQueue.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done)); - this.queueService.dbQueue.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job)); - - this.queueService.objectStorageQueue.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job)); - this.queueService.objectStorageQueue.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done)); - - { - const maxJobs = this.config.relashionshipJobConcurrency ?? 16; - this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job)); - this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job)); - this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job)); - this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job)); - } + @bindThis + public async start(): Promise<void> { + await Promise.all([ + this.systemQueueWorker.run(), + this.dbQueueWorker.run(), + this.deliverQueueWorker.run(), + this.inboxQueueWorker.run(), + this.webhookDeliverQueueWorker.run(), + this.relationshipQueueWorker.run(), + this.objectStorageQueueWorker.run(), + this.endedPollNotificationQueueWorker.run(), + ]); + } - this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done)); - this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done)); - this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done)); - this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done)); - this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done)); - this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done)); + @bindThis + public async stop(): Promise<void> { + await Promise.all([ + this.systemQueueWorker.close(), + this.dbQueueWorker.close(), + this.deliverQueueWorker.close(), + this.inboxQueueWorker.close(), + this.webhookDeliverQueueWorker.close(), + this.relationshipQueueWorker.close(), + this.objectStorageQueueWorker.close(), + this.endedPollNotificationQueueWorker.close(), + ]); + } + + @bindThis + public async onApplicationShutdown(signal?: string | undefined): Promise<void> { + await this.stop(); } } diff --git a/packages/backend/src/queue/const.ts b/packages/backend/src/queue/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..d240fe70e0c55d6eeed07910c1808c50c7e962d6 --- /dev/null +++ b/packages/backend/src/queue/const.ts @@ -0,0 +1,26 @@ +import { Config } from '@/config.js'; +import type * as Bull from 'bullmq'; + +export const QUEUE = { + DELIVER: 'deliver', + INBOX: 'inbox', + SYSTEM: 'system', + ENDED_POLL_NOTIFICATION: 'endedPollNotification', + DB: 'db', + RELATIONSHIP: 'relationship', + OBJECT_STORAGE: 'objectStorage', + WEBHOOK_DELIVER: 'webhookDeliver', +}; + +export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions { + return { + connection: { + port: config.redisForJobQueue.port, + host: config.redisForJobQueue.host, + family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family, + password: config.redisForJobQueue.pass, + db: config.redisForJobQueue.db ?? 0, + }, + prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`, + }; +} diff --git a/packages/backend/src/queue/get-job-info.ts b/packages/backend/src/queue/get-job-info.ts deleted file mode 100644 index d33e349c363ca4cd560e86ed903015ec0b6d8411..0000000000000000000000000000000000000000 --- a/packages/backend/src/queue/get-job-info.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Bull from 'bull'; - -export function getJobInfo(job: Bull.Job, increment = false) { - const age = Date.now() - job.timestamp; - - const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m` - : age > 10000 ? `${Math.floor(age / 1000)}s` - : `${age}ms`; - - // onActiveã¨ã‹onCompletedã®attemptsMadeãŒãªãœã‹0始ã¾ã‚Šãªã®ã§ã‚¤ãƒ³ã‚¯ãƒªãƒ¡ãƒ³ãƒˆã™ã‚‹ - const currentAttempts = job.attemptsMade + (increment ? 1 : 0); - const maxAttempts = job.opts ? job.opts.attempts : 0; - - return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`; -} diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts index e2720b4fe01e44699714462305f4b89e434b7d78..600ce0828f916c7933bcbe842e1569a8c3e884de 100644 --- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts +++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts @@ -9,7 +9,7 @@ import { deepClone } from '@/misc/clone.js'; import { IdService } from '@/core/IdService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class AggregateRetentionProcessorService { @@ -32,7 +32,7 @@ export class AggregateRetentionProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(): Promise<void> { this.logger.info('Aggregating retention...'); const now = new Date(); @@ -62,7 +62,6 @@ export class AggregateRetentionProcessorService { } catch (err) { if (isDuplicateKeyValueError(err)) { this.logger.succ('Skip because it has already been processed by another worker.'); - done(); return; } throw err; @@ -88,6 +87,5 @@ export class AggregateRetentionProcessorService { } this.logger.succ('Retention aggregated.'); - done(); } } diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 2476d71a5ebc8dd4aeef033a700f816791376a1b..c4ee212bab739fc3260ae6a045a6351aaf711a7e 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -7,7 +7,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class CheckExpiredMutingsProcessorService { @@ -27,7 +27,7 @@ export class CheckExpiredMutingsProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(): Promise<void> { this.logger.info('Checking expired mutings...'); const expired = await this.mutingsRepository.createQueryBuilder('muting') @@ -41,6 +41,5 @@ export class CheckExpiredMutingsProcessorService { } this.logger.succ('All expired mutings checked.'); - done(); } } diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index b458167042070c520dd2e4abf0f312d27845900a..22d7c1b4fb2d00cc622ebeacfbd6a930cd46256d 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class CleanChartsProcessorService { @@ -45,7 +45,7 @@ export class CleanChartsProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(): Promise<void> { this.logger.info('Clean charts...'); await Promise.all([ @@ -64,6 +64,5 @@ export class CleanChartsProcessorService { ]); this.logger.succ('All charts successfully cleaned.'); - done(); } } diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 1936e8df23eec5a76c598bae0e912731e9797b17..cefa6da5e946a2f029dfe659fa80e35efd046f9e 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -7,7 +7,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class CleanProcessorService { @@ -36,7 +36,7 @@ export class CleanProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(): Promise<void> { this.logger.info('Cleaning...'); this.userIpsRepository.delete({ @@ -72,6 +72,5 @@ export class CleanProcessorService { } this.logger.succ('Cleaned.'); - done(); } } diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 5a33c271889c2ef98168ef7f99e974d4fe44c54a..c54bf59ae4c2443c8121c06aa20d9f73ff8942cc 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -5,9 +5,9 @@ import type { DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; -import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; import { bindThis } from '@/decorators.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; @Injectable() export class CleanRemoteFilesProcessorService { @@ -27,7 +27,7 @@ export class CleanRemoteFilesProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(job: Bull.Job<Record<string, unknown>>): Promise<void> { this.logger.info('Deleting cached remote files...'); let deletedCount = 0; @@ -47,7 +47,7 @@ export class CleanRemoteFilesProcessorService { }); if (files.length === 0) { - job.progress(100); + job.updateProgress(100); break; } @@ -62,10 +62,9 @@ export class CleanRemoteFilesProcessorService { isLink: false, }); - job.progress(deletedCount / total); + job.updateProgress(deletedCount / total); } this.logger.succ('All cached remote files has been deleted.'); - done(); } } diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index e36a78de6aa2b77cc073650e14c7a1e63e47337d..39dd801af017e84b4c08e79f94e2a9fcc9fe238e 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -8,10 +8,10 @@ import { DriveService } from '@/core/DriveService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import { EmailService } from '@/core/EmailService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbUserDeleteJobData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteAccountProcessorService { diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 604497cf5425de9b1412af7cee6baa7fd1010875..6772c5dc7662545878594ef9f6899644ab707107 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -5,10 +5,10 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteDriveFilesProcessorService { @@ -31,12 +31,11 @@ export class DeleteDriveFilesProcessorService { } @bindThis - public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> { this.logger.info(`Deleting drive files of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -56,7 +55,7 @@ export class DeleteDriveFilesProcessorService { }); if (files.length === 0) { - job.progress(100); + job.updateProgress(100); break; } @@ -71,10 +70,9 @@ export class DeleteDriveFilesProcessorService { userId: user.id, }); - job.progress(deletedCount / total); + job.updateProgress(deletedCount / total); } this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); - done(); } } diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts index 2fb2f56f8def61f599605c89e743fc8f6bb4cb93..edf87bd921743dac725a4c1acbd21dbb9cdbc7f0 100644 --- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts @@ -3,10 +3,10 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { ObjectStorageFileJobData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class DeleteFileProcessorService { diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index f293bd4d7e016c5d33e4719f851dd27fc4a35730..406e9df850d8d3253483c58be608806b93810b02 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -16,7 +17,6 @@ import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; import type { DeliverJobData } from '../types.js'; @Injectable() @@ -121,15 +121,13 @@ export class DeliverProcessorService { isSuspended: true, }); }); - return `${host} is gone`; + throw new Bull.UnrecoverableError(`${host} is gone`); } - // HTTPステータスコード4xxã¯ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã‚¨ãƒ©ãƒ¼ã§ã‚ã‚Šã€ãã‚Œã¯ã¤ã¾ã‚Š - // 何回å†é€ã—ã¦ã‚‚æˆåŠŸã™ã‚‹ã“ã¨ã¯ãªã„ã¨ã„ã†ã“ã¨ãªã®ã§ã‚¨ãƒ©ãƒ¼ã«ã¯ã—ãªã„ã§ãŠã - return `${res.statusCode} ${res.statusMessage}`; + throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); } // 5xx etc. - throw `${res.statusCode} ${res.statusMessage}`; + throw new Error(`${res.statusCode} ${res.statusMessage}`); } else { // DNS error, socket error, timeout ... throw res; diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts index 501ed4090a7311e2023ed68535e27748139530fb..21501592f2cdc7f2db0ce545cb19ececf12b9b49 100644 --- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts +++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts @@ -6,7 +6,7 @@ import type Logger from '@/logger.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { EndedPollNotificationJobData } from '../types.js'; @Injectable() @@ -30,10 +30,9 @@ export class EndedPollNotificationProcessorService { } @bindThis - public async process(job: Bull.Job<EndedPollNotificationJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<EndedPollNotificationJobData>): Promise<void> { const note = await this.notesRepository.findOneBy({ id: job.data.noteId }); if (note == null || !note.hasPoll) { - done(); return; } @@ -51,7 +50,5 @@ export class EndedPollNotificationProcessorService { noteId: note.id, }); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index 894903e79b1073f351731652c512df3d218b3ac4..ac52325c8d998368d778a8b86057c8fef9725138 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -12,7 +12,7 @@ import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DBExportAntennasData } from '../types.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class ExportAntennasProcessorService { @@ -39,10 +39,9 @@ export class ExportAntennasProcessorService { } @bindThis - public async process(job: Bull.Job<DBExportAntennasData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DBExportAntennasData>): Promise<void> { const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } const [path, cleanup] = await createTemp(); @@ -96,7 +95,6 @@ export class ExportAntennasProcessorService { this.logger.succ('Exported to: ' + driveFile.id); } finally { cleanup(); - done(); } } } diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index c7b54070d6a786244e7401d81592b8244b75b7a9..eb758e162dfcaf0198a0e9c0f2d55e2635eb071a 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -9,10 +9,10 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ExportBlockingProcessorService { @@ -36,12 +36,11 @@ export class ExportBlockingProcessorService { } @bindThis - public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> { this.logger.info(`Exporting blocking of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -69,7 +68,7 @@ export class ExportBlockingProcessorService { }); if (blockings.length === 0) { - job.progress(100); + job.updateProgress(100); break; } @@ -99,7 +98,7 @@ export class ExportBlockingProcessorService { blockerId: user.id, }); - job.progress(exportedCount / total); + job.updateProgress(exportedCount / total); } stream.end(); @@ -112,7 +111,5 @@ export class ExportBlockingProcessorService { } finally { cleanup(); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index b50f373ef820d7a14f810bbfd94d4e0477c7e812..3203d9f3e5c9b888a5e6b677484b9f522e752ff7 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -13,7 +13,7 @@ import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class ExportCustomEmojisProcessorService { @@ -37,12 +37,11 @@ export class ExportCustomEmojisProcessorService { } @bindThis - public async process(job: Bull.Job, done: () => void): Promise<void> { + public async process(job: Bull.Job): Promise<void> { this.logger.info('Exporting custom emojis ...'); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -117,24 +116,26 @@ export class ExportCustomEmojisProcessorService { metaStream.end(); // Create archive - const [archivePath, archiveCleanup] = await createTemp(); - const archiveStream = fs.createWriteStream(archivePath); - const archive = archiver('zip', { - zlib: { level: 0 }, - }); - archiveStream.on('close', async () => { - this.logger.succ(`Exported to: ${archivePath}`); + await new Promise<void>(async (resolve) => { + const [archivePath, archiveCleanup] = await createTemp(); + const archiveStream = fs.createWriteStream(archivePath); + const archive = archiver('zip', { + zlib: { level: 0 }, + }); + archiveStream.on('close', async () => { + this.logger.succ(`Exported to: ${archivePath}`); - const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; - const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); + const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; + const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); - this.logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); - archiveCleanup(); - done(); + this.logger.succ(`Exported to: ${driveFile.id}`); + cleanup(); + archiveCleanup(); + resolve(); + }); + archive.pipe(archiveStream); + archive.directory(path, false); + archive.finalize(); }); - archive.pipe(archiveStream); - archive.directory(path, false); - archive.finalize(); } } diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index f2f2383a8837d8c036fbca4a41629e6881ac7497..76c38a6b8648417a72460b75773ad3a15bd63ed3 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js'; import type { Note } from '@/models/entities/Note.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @Injectable() @@ -42,12 +42,11 @@ export class ExportFavoritesProcessorService { } @bindThis - public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> { this.logger.info(`Exporting favorites of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -91,7 +90,7 @@ export class ExportFavoritesProcessorService { }) as (NoteFavorite & { note: Note & { user: User } })[]; if (favorites.length === 0) { - job.progress(100); + job.updateProgress(100); break; } @@ -112,7 +111,7 @@ export class ExportFavoritesProcessorService { userId: user.id, }); - job.progress(exportedFavoritesCount / total); + job.updateProgress(exportedFavoritesCount / total); } await write(']'); @@ -127,8 +126,6 @@ export class ExportFavoritesProcessorService { } finally { cleanup(); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index fa9c1ac1ea94b846fed0b205fae19d6a35bac0a4..8726cb14027832f5b29511789513305e1bf574b0 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -10,10 +10,10 @@ import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import type { Following } from '@/models/entities/Following.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbExportFollowingData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ExportFollowingProcessorService { @@ -40,12 +40,11 @@ export class ExportFollowingProcessorService { } @bindThis - public async process(job: Bull.Job<DbExportFollowingData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbExportFollowingData>): Promise<void> { this.logger.info(`Exporting following of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -116,7 +115,5 @@ export class ExportFollowingProcessorService { } finally { cleanup(); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index b14bf5f5b18af052e973a23f11e1a8d9beddb0bf..0f11a9e8435f7f3639082eee31ac778edc962325 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -9,10 +9,10 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ExportMutingProcessorService { @@ -39,12 +39,11 @@ export class ExportMutingProcessorService { } @bindThis - public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> { this.logger.info(`Exporting muting of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -73,7 +72,7 @@ export class ExportMutingProcessorService { }); if (mutes.length === 0) { - job.progress(100); + job.updateProgress(100); break; } @@ -103,7 +102,7 @@ export class ExportMutingProcessorService { muterId: user.id, }); - job.progress(exportedCount / total); + job.updateProgress(exportedCount / total); } stream.end(); @@ -116,7 +115,5 @@ export class ExportMutingProcessorService { } finally { cleanup(); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index e4f12ad101054ba6bc27df9e3911aad63937c591..24fb33188373b6975d6f964b0f558c2f1d26eefd 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js'; import type { Note } from '@/models/entities/Note.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @Injectable() @@ -39,12 +39,11 @@ export class ExportNotesProcessorService { } @bindThis - public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> { this.logger.info(`Exporting notes of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -87,7 +86,7 @@ export class ExportNotesProcessorService { }) as Note[]; if (notes.length === 0) { - job.progress(100); + job.updateProgress(100); break; } @@ -108,7 +107,7 @@ export class ExportNotesProcessorService { userId: user.id, }); - job.progress(exportedNotesCount / total); + job.updateProgress(exportedNotesCount / total); } await write(']'); @@ -123,8 +122,6 @@ export class ExportNotesProcessorService { } finally { cleanup(); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 54bde44044de147a1b6198e9a4efbf61437a296a..ec633580535a90333f1f086b61e80c8f8cc38df8 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -9,10 +9,10 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ExportUserListsProcessorService { @@ -39,12 +39,11 @@ export class ExportUserListsProcessorService { } @bindThis - public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> { this.logger.info(`Exporting user lists of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -92,7 +91,5 @@ export class ExportUserListsProcessorService { } finally { cleanup(); } - - done(); } } diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index d06131b8c81b80169f9413556b8beb8de5f2a01c..575cad69d5c49a7494200f777a2dc4a511eb2d00 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import { DBAntennaImportJobData } from '../types.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; const validate = new Ajv().compile({ type: 'object', @@ -59,7 +59,7 @@ export class ImportAntennasProcessorService { } @bindThis - public async process(job: Bull.Job<DBAntennaImportJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DBAntennaImportJobData>): Promise<void> { const now = new Date(); try { for (const antenna of job.data.antenna) { @@ -89,8 +89,6 @@ export class ImportAntennasProcessorService { } } catch (err: any) { this.logger.error(err); - } finally { - done(); } } } diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 3f075b02d20edb4f2995fa80644da89d30158566..2f1a9e5b03a10c9ba96b0458dafbddaa637f6370 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; -import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js'; import { bindThis } from '@/decorators.js'; import { QueueService } from '@/core/QueueService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js'; @Injectable() export class ImportBlockingProcessorService { @@ -34,12 +34,11 @@ export class ImportBlockingProcessorService { } @bindThis - public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> { this.logger.info(`Importing blocking of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -47,7 +46,6 @@ export class ImportBlockingProcessorService { id: job.data.fileId, }); if (file == null) { - done(); return; } @@ -56,7 +54,6 @@ export class ImportBlockingProcessorService { this.queueService.createImportBlockingToDbJob({ id: user.id }, targets); this.logger.succ('Import jobs created'); - done(); } @bindThis @@ -85,7 +82,7 @@ export class ImportBlockingProcessorService { } if (target == null) { - throw `Unable to resolve user: @${username}@${host}`; + throw new Error(`Unable to resolve user: @${username}@${host}`); } // skip myself diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index cf78d8330c961cbcc44c7ee2fbd46b76c009c834..d862567871570f06f15e51e469fcc9c3a81fbc4c 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -12,7 +12,7 @@ import { DriveService } from '@/core/DriveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; // TODO: åå‰è¡çªæ™‚ã®å‹•ä½œã‚’é¸ã¹ã‚‹ã‚ˆã†ã«ã™ã‚‹ @@ -45,14 +45,13 @@ export class ImportCustomEmojisProcessorService { } @bindThis - public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> { this.logger.info('Importing custom emojis ...'); const file = await this.driveFilesRepository.findOneBy({ id: job.data.fileId, }); if (file == null) { - done(); return; } @@ -107,13 +106,15 @@ export class ImportCustomEmojisProcessorService { aliases: emojiInfo.aliases, driveFile, license: emojiInfo.license, + isSensitive: emojiInfo.isSensitive, + localOnly: emojiInfo.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: [], }); } cleanup(); this.logger.succ('Imported'); - done(); }); unzipStream.pipe(extractor); this.logger.succ(`Unzipping to ${outputPath}`); diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index aa5cf12c509bb5f2cf42302624f2782258b56ae4..15bee9672eac441c3a2fcafe2ef47b9cc8b69c8c 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; -import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js'; import { bindThis } from '@/decorators.js'; import { QueueService } from '@/core/QueueService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js'; @Injectable() export class ImportFollowingProcessorService { @@ -34,12 +34,11 @@ export class ImportFollowingProcessorService { } @bindThis - public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> { this.logger.info(`Importing following of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -47,7 +46,6 @@ export class ImportFollowingProcessorService { id: job.data.fileId, }); if (file == null) { - done(); return; } @@ -56,7 +54,6 @@ export class ImportFollowingProcessorService { this.queueService.createImportFollowingToDbJob({ id: user.id }, targets); this.logger.succ('Import jobs created'); - done(); } @bindThis @@ -85,7 +82,7 @@ export class ImportFollowingProcessorService { } if (target == null) { - throw `Unable to resolve user: @${username}@${host}`; + throw new Error(`Unable to resolve user: @${username}@${host}`); } // skip myself diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 379994ee791b1907930daf8d12354515f434019b..723935cd311343e341c146e48fd26f0ca6f2b893 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -9,10 +9,10 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ImportMutingProcessorService { @@ -38,12 +38,11 @@ export class ImportMutingProcessorService { } @bindThis - public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> { this.logger.info(`Importing muting of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -51,7 +50,6 @@ export class ImportMutingProcessorService { id: job.data.fileId, }); if (file == null) { - done(); return; } @@ -83,7 +81,7 @@ export class ImportMutingProcessorService { } if (target == null) { - throw `cannot resolve user: @${username}@${host}`; + throw new Error(`cannot resolve user: @${username}@${host}`); } // skip myself @@ -98,6 +96,5 @@ export class ImportMutingProcessorService { } this.logger.succ('Imported'); - done(); } } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index c423863410febc3f28c2ffd332f8c1fc05325a70..824ee8157ab16fcbd9182fd1e0a72b2906233097 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -12,7 +12,7 @@ import { IdService } from '@/core/IdService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; @Injectable() @@ -46,12 +46,11 @@ export class ImportUserListsProcessorService { } @bindThis - public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> { + public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> { this.logger.info(`Importing user lists of ${job.data.user.id} ...`); const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); if (user == null) { - done(); return; } @@ -59,7 +58,6 @@ export class ImportUserListsProcessorService { id: job.data.fileId, }); if (file == null) { - done(); return; } @@ -109,6 +107,5 @@ export class ImportUserListsProcessorService { } this.logger.succ('Imported'); - done(); } } diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index ab8b1e9e22556d683f9e3b30c926858db22afc57..ce1d7aaa1bfcd9fcfd867f2f8b07db6fb5bc0bcd 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -1,8 +1,8 @@ import { URL } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import httpSignature from '@peertube/http-signature'; +import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; -import type { InstancesRepository, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { MetaService } from '@/core/MetaService.js'; @@ -23,10 +23,8 @@ import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; import type { InboxJobData } from '../types.js'; -// ユーザーã®inboxã«ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティãŒå±Šã„ãŸæ™‚ã®å‡¦ç† @Injectable() export class InboxProcessorService { private logger: Logger; @@ -35,12 +33,6 @@ export class InboxProcessorService { @Inject(DI.config) private config: Config, - @Inject(DI.instancesRepository) - private instancesRepository: InstancesRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - private utilityService: UtilityService, private metaService: MetaService, private apInboxService: ApInboxService, @@ -93,24 +85,24 @@ export class InboxProcessorService { try { authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor)); } catch (err) { - // 対象ãŒ4xxãªã‚‰ã‚¹ã‚ップ + // 対象ãŒ4xxãªã‚‰ã‚¹ã‚ップ if (err instanceof StatusError) { if (err.isClientError) { - return `skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`; + throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`); } - throw `Error in actor ${activity.actor} - ${err.statusCode ?? err}`; + throw new Error(`Error in actor ${activity.actor} - ${err.statusCode ?? err}`); } } } // ãã‚Œã§ã‚‚ã‚ã‹ã‚‰ãªã‘ã‚Œã°çµ‚了 if (authUser == null) { - return 'skip: failed to resolve user'; + throw new Bull.UnrecoverableError('skip: failed to resolve user'); } // publicKey ãŒãªãã¦ã‚‚終了 if (authUser.key == null) { - return 'skip: failed to resolve user publicKey'; + throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey'); } // HTTP-Signatureã®æ¤œè¨¼ @@ -118,10 +110,10 @@ export class InboxProcessorService { // ã¾ãŸã€signatureã®signerã¯ã€activity.actorã¨ä¸€è‡´ã™ã‚‹å¿…è¦ãŒã‚ã‚‹ if (!httpSignatureValidated || authUser.user.uri !== activity.actor) { - // 一致ã—ãªãã¦ã‚‚ã€ã§ã‚‚LD-SignatureãŒã‚ã‚Šãã†ãªã‚‰ãã£ã¡ã‚‚見る + // 一致ã—ãªãã¦ã‚‚ã€ã§ã‚‚LD-SignatureãŒã‚ã‚Šãã†ãªã‚‰ãã£ã¡ã‚‚見る if (activity.signature) { if (activity.signature.type !== 'RsaSignature2017') { - return `skip: unsupported LD-signature type ${activity.signature.type}`; + throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${activity.signature.type}`); } // activity.signature.creator: https://example.oom/users/user#main-key @@ -134,32 +126,32 @@ export class InboxProcessorService { // keyIdã‹ã‚‰LD-Signatureã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’å–å¾— authUser = await this.apDbResolverService.getAuthUserFromKeyId(activity.signature.creator); if (authUser == null) { - return 'skip: LD-Signatureã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå–å¾—ã§ãã¾ã›ã‚“ã§ã—ãŸ'; + throw new Bull.UnrecoverableError('skip: LD-Signatureã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå–å¾—ã§ãã¾ã›ã‚“ã§ã—ãŸ'); } if (authUser.key == null) { - return 'skip: LD-Signatureã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯publicKeyã‚’æŒã£ã¦ã„ã¾ã›ã‚“ã§ã—ãŸ'; + throw new Bull.UnrecoverableError('skip: LD-Signatureã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯publicKeyã‚’æŒã£ã¦ã„ã¾ã›ã‚“ã§ã—ãŸ'); } // LD-Signature検証 const ldSignature = this.ldSignatureService.use(); const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false); if (!verified) { - return 'skip: LD-Signatureã®æ¤œè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸ'; + throw new Bull.UnrecoverableError('skip: LD-Signatureã®æ¤œè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸ'); } // ã‚‚ã†ä¸€åº¦actorãƒã‚§ãƒƒã‚¯ if (authUser.user.uri !== activity.actor) { - return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`; + throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`); } // ブãƒãƒƒã‚¯ã—ã¦ãŸã‚‰ä¸æ– const ldHost = this.utilityService.extractDbHost(authUser.user.uri); if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) { - return `Blocked request: ${ldHost}`; + throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`); } } else { - return `skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`; + throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`); } } @@ -168,7 +160,7 @@ export class InboxProcessorService { const signerHost = this.utilityService.extractDbHost(authUser.user.uri!); const activityIdHost = this.utilityService.extractDbHost(activity.id); if (signerHost !== activityIdHost) { - return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`; + throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`); } } diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index ff454df455508464eb5b424c90593fe1edb7b35c..722260d94821b9facb85daf477d8dfc54eaea75d 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index e5840f3da8c84bc5a9f962bce18c9aa59bd10703..eab8e1e68d70da96dd89cbeb926a59c038e1c981 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -15,7 +15,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class ResyncChartsProcessorService { @@ -43,7 +43,7 @@ export class ResyncChartsProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(): Promise<void> { this.logger.info('Resync charts...'); // TODO: ユーザーã”ã¨ã®ãƒãƒ£ãƒ¼ãƒˆã‚‚æ›´æ–°ã™ã‚‹ @@ -55,6 +55,5 @@ export class ResyncChartsProcessorService { ]); this.logger.succ('All charts successfully resynced.'); - done(); } } diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index 7ff84c15a5c07640a5e2b25c69c53bf24705d4dd..f1696bf5677554223943b4873d54f834acf41c6d 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; +import type * as Bull from 'bullmq'; @Injectable() export class TickChartsProcessorService { @@ -45,7 +45,7 @@ export class TickChartsProcessorService { } @bindThis - public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> { + public async process(): Promise<void> { this.logger.info('Tick charts...'); await Promise.all([ @@ -64,6 +64,5 @@ export class TickChartsProcessorService { ]); this.logger.succ('All charts successfully ticked.'); - done(); } } diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index 84a5c21c4910fbccb8abdf42610da98b5166cf84..8b40c167496d1b52ab7e934477522558a6f7168f 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; import type { WebhooksRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -7,7 +8,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js'; import { StatusError } from '@/misc/status-error.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; -import type Bull from 'bull'; import type { WebhookDeliverJobData } from '../types.js'; @Injectable() @@ -66,11 +66,11 @@ export class WebhookDeliverProcessorService { if (res instanceof StatusError) { // 4xx if (res.isClientError) { - return `${res.statusCode} ${res.statusMessage}`; + throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); } // 5xx etc. - throw `${res.statusCode} ${res.statusMessage}`; + throw new Error(`${res.statusCode} ${res.statusMessage}`); } else { // DNS error, socket error, timeout ... throw res; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index e675d9cf1baf751e85e58f7aa082bbebce5d9f73..455acd1e47f4296312ce09141cb77a8e2bfe22f9 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -585,7 +585,7 @@ export class ActivityPubServerService { name: request.params.emoji, }); - if (emoji == null) { + if (emoji == null || emoji.localOnly) { reply.code(404); return; } diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 9257fee13e95c616bb0ab0935ae11af3801f6390..c3d45e4ad6932487af51f41e82e5a0a63bb0ec96 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -194,7 +194,7 @@ export class ServerService implements OnApplicationShutdown { fastify.register(this.clientServerService.createServer); - this.streamingApiServerService.attachStreamingApi(fastify.server); + this.streamingApiServerService.attach(fastify.server); fastify.server.on('error', err => { switch ((err as any).code) { @@ -222,7 +222,14 @@ export class ServerService implements OnApplicationShutdown { await fastify.ready(); } - async onApplicationShutdown(signal: string): Promise<void> { + @bindThis + public async dispose(): Promise<void> { + await this.streamingApiServerService.detach(); await this.#fastify.close(); } + + @bindThis + async onApplicationShutdown(signal: string): Promise<void> { + await this.dispose(); + } } diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index e3483c82c67d31fa59c02639927883c7eca56e3b..dad1a4132a955a609ecb1c206e9f56e67831397e 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -359,7 +359,12 @@ export class ApiCallService implements OnApplicationShutdown { } @bindThis - public onApplicationShutdown(signal?: string | undefined) { + public dispose(): void { clearInterval(this.userIpHistoriesClearIntervalId); } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index 6548c475b2dbfacf033c8060c3d2105225a7b76f..e23591d8765dfb0e9dd8c3720b5f97a3f2cfd4ec 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -36,7 +36,7 @@ export class AuthenticateService { } @bindThis - public async authenticate(token: string | null | undefined): Promise<[LocalUser | null | undefined, AccessToken | null | undefined]> { + public async authenticate(token: string | null | undefined): Promise<[LocalUser | null, AccessToken | null]> { if (token == null) { return [null, null]; } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index ee1aae5b6c787fead3660845c5aad2e8f5e20506..1e32e9988dfb269316556234547412ee33613c94 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -321,6 +321,9 @@ import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; import * as ep___users_lists_push from './endpoints/users/lists/push.js'; import * as ep___users_lists_show from './endpoints/users/lists/show.js'; import * as ep___users_lists_update from './endpoints/users/lists/update.js'; +import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; +import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; +import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js'; import * as ep___users_notes from './endpoints/users/notes.js'; import * as ep___users_pages from './endpoints/users/pages.js'; import * as ep___users_reactions from './endpoints/users/reactions.js'; @@ -659,6 +662,9 @@ const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default }; const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default }; const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default }; +const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default }; +const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default }; +const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default }; const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default }; const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default }; const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default }; @@ -1001,6 +1007,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_push, $users_lists_show, $users_lists_update, + $users_lists_favorite, + $users_lists_unfavorite, + $users_lists_create_from_public, $users_notes, $users_pages, $users_reactions, @@ -1335,6 +1344,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_push, $users_lists_show, $users_lists_update, + $users_lists_favorite, + $users_lists_unfavorite, + $users_lists_create_from_public, $users_notes, $users_pages, $users_reactions, diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 258e8de034195bb5aa00d02f4bc588ac841a5215..893dfe956e5057917f7d8c258552e1a9fb649f79 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -1,23 +1,27 @@ import { EventEmitter } from 'events'; import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import * as websocket from 'websocket'; +import * as WebSocket from 'ws'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js'; +import type { UsersRepository, AccessToken } from '@/models/index.js'; import type { Config } from '@/config.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; -import { AuthenticateService } from './AuthenticateService.js'; +import { LocalUser } from '@/models/entities/User'; +import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/index.js'; import { ChannelsService } from './stream/ChannelsService.js'; -import type { ParsedUrlQuery } from 'querystring'; import type * as http from 'node:http'; @Injectable() export class StreamingApiServerService { + #wss: WebSocket.WebSocketServer; + #connections = new Map<WebSocket.WebSocket, number>(); + #cleanConnectionsIntervalId: NodeJS.Timeout | null = null; + constructor( @Inject(DI.config) private config: Config, @@ -28,24 +32,6 @@ export class StreamingApiServerService { @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - @Inject(DI.mutingsRepository) - private mutingsRepository: MutingsRepository, - - @Inject(DI.renoteMutingsRepository) - private renoteMutingsRepository: RenoteMutingsRepository, - - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - - @Inject(DI.channelFollowingsRepository) - private channelFollowingsRepository: ChannelFollowingsRepository, - - @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - private cacheService: CacheService, private noteReadService: NoteReadService, private authenticateService: AuthenticateService, @@ -55,25 +41,65 @@ export class StreamingApiServerService { } @bindThis - public attachStreamingApi(server: http.Server) { - // Init websocket server - const ws = new websocket.server({ - httpServer: server, + public attach(server: http.Server): void { + this.#wss = new WebSocket.WebSocketServer({ + noServer: true, }); - ws.on('request', async (request) => { - const q = request.resourceURL.query as ParsedUrlQuery; + server.on('upgrade', async (request, socket, head) => { + if (request.url == null) { + socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); + socket.destroy(); + return; + } + + const q = new URL(request.url, `http://${request.headers.host}`).searchParams; + + let user: LocalUser | null = null; + let app: AccessToken | null = null; - // TODO: トークンãŒé–“é•ã£ã¦ã‚‹ãªã©ã—ã¦authenticateã«å¤±æ•—ã—ãŸã‚‰ - // コãƒã‚¯ã‚·ãƒ§ãƒ³åˆ‡æ–ã™ã‚‹ãªã‚Šã‚¨ãƒ©ãƒ¼ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸è¿”ã™ãªã‚Šã™ã‚‹ - // (ç¾çŠ¶ã¯ã‚¨ãƒ©ãƒ¼ãŒã‚ャッãƒã•ã‚Œã¦ãŠã‚‰ãšã‚µãƒ¼ãƒãƒ¼ã®ãƒã‚°ã«æµã‚Œã¦é‚ªé”ãªã®ã§) - const [user, miapp] = await this.authenticateService.authenticate(q.i as string); + try { + [user, app] = await this.authenticateService.authenticate(q.get('i')); + } catch (e) { + if (e instanceof AuthenticationError) { + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + } else { + socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n'); + } + socket.destroy(); + return; + } if (user?.isSuspended) { - request.reject(400); + socket.write('HTTP/1.1 403 Forbidden\r\n\r\n'); + socket.destroy(); return; } + const stream = new MainStreamConnection( + this.channelsService, + this.noteReadService, + this.notificationService, + this.cacheService, + user, app, + ); + + await stream.init(); + + this.#wss.handleUpgrade(request, socket, head, (ws) => { + this.#wss.emit('connection', ws, request, { + stream, user, app, + }); + }); + }); + + this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: { + stream: MainStreamConnection, + user: LocalUser | null; + app: AccessToken | null + }) => { + const { stream, user, app } = ctx; + const ev = new EventEmitter(); async function onRedisMessage(_: string, data: string): Promise<void> { @@ -83,21 +109,11 @@ export class StreamingApiServerService { this.redisForSub.on('message', onRedisMessage); - const main = new MainStreamConnection( - this.channelsService, - this.noteReadService, - this.notificationService, - this.cacheService, - ev, user, miapp, - ); + await stream.listen(ev, connection); - await main.init(); + this.#connections.set(connection, Date.now()); - const connection = request.accept(); - - main.init2(connection); - - const intervalId = user ? setInterval(() => { + const userUpdateIntervalId = user ? setInterval(() => { this.usersRepository.update(user.id, { lastActiveDate: new Date(), }); @@ -110,16 +126,38 @@ export class StreamingApiServerService { connection.once('close', () => { ev.removeAllListeners(); - main.dispose(); + stream.dispose(); this.redisForSub.off('message', onRedisMessage); - if (intervalId) clearInterval(intervalId); + if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); }); connection.on('message', async (data) => { - if (data.type === 'utf8' && data.utf8Data === 'ping') { + this.#connections.set(connection, Date.now()); + if (data.toString() === 'ping') { connection.send('pong'); } }); }); + + this.#cleanConnectionsIntervalId = setInterval(() => { + const now = Date.now(); + for (const [connection, lastActive] of this.#connections.entries()) { + if (now - lastActive > 1000 * 60 * 5) { + connection.terminate(); + this.#connections.delete(connection); + } + } + }, 1000 * 60 * 5); + } + + @bindThis + public detach(): Promise<void> { + if (this.#cleanConnectionsIntervalId) { + clearInterval(this.#cleanConnectionsIntervalId); + this.#cleanConnectionsIntervalId = null; + } + return new Promise((resolve) => { + this.#wss.close(() => resolve()); + }); } } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 09bd7cbff455ab12df430e88cc1ed61bd616a66e..7e678a64040728ea8eb09f4dc2fd3579fb1d5425 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -320,6 +320,9 @@ import * as ep___users_lists_list from './endpoints/users/lists/list.js'; import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; import * as ep___users_lists_push from './endpoints/users/lists/push.js'; import * as ep___users_lists_show from './endpoints/users/lists/show.js'; +import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; +import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; +import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js'; import * as ep___users_lists_update from './endpoints/users/lists/update.js'; import * as ep___users_notes from './endpoints/users/notes.js'; import * as ep___users_pages from './endpoints/users/pages.js'; @@ -656,7 +659,10 @@ const eps = [ ['users/lists/pull', ep___users_lists_pull], ['users/lists/push', ep___users_lists_push], ['users/lists/show', ep___users_lists_show], + ['users/lists/favorite', ep___users_lists_favorite], + ['users/lists/unfavorite', ep___users_lists_unfavorite], ['users/lists/update', ep___users_lists_update], + ['users/lists/create-from-public', ep___users_lists_create_from_public], ['users/notes', ep___users_notes], ['users/pages', ep___users_pages], ['users/reactions', ep___users_reactions], diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 2393c2441c3b7b5fcf434431e4b0122c5c6758b7..12db1f78fb689fca3f87aeb82fb4cc2d0f45a1db 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -25,7 +25,7 @@ export const paramDef = { id: { type: 'string', format: 'misskey:id' }, title: { type: 'string', minLength: 1 }, text: { type: 'string', minLength: 1 }, - imageUrl: { type: 'string', nullable: true, minLength: 1 }, + imageUrl: { type: 'string', nullable: true, minLength: 0 }, }, required: ['id', 'title', 'text', 'imageUrl'], } as const; @@ -46,7 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { updatedAt: new Date(), title: ps.title, text: ps.text, - imageUrl: ps.imageUrl, + /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空ã®æ–‡å—列ã®å ´åˆã€nullを渡ã™ã‚ˆã†ã«ã™ã‚‹ãŸã‚ */ + imageUrl: ps.imageUrl || null, }); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 2fb3e489e72f367084546fd84edb097a42de12df..509224e9c3f6cb131f12ab471587d46c2991516b 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -25,9 +25,24 @@ export const meta = { export const paramDef = { type: 'object', properties: { + name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, fileId: { type: 'string', format: 'misskey:id' }, + category: { + type: 'string', + nullable: true, + description: 'Use `null` to reset the category.', + }, + aliases: { type: 'array', items: { + type: 'string', + } }, + license: { type: 'string', nullable: true }, + isSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, + roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: { + type: 'string', + } }, }, - required: ['fileId'], + required: ['name', 'fileId'], } as const; // TODO: ãƒã‚¸ãƒƒã‚¯ã‚’サービスã«åˆ‡ã‚Šå‡ºã™ @@ -45,18 +60,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); - const name = driveFile.name.split('.')[0].match(/^[a-z0-9_]+$/) ? driveFile.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; - const emoji = await this.customEmojiService.add({ driveFile, - name, - category: null, - aliases: [], + name: ps.name, + category: ps.category ?? null, + aliases: ps.aliases ?? [], host: null, - license: null, + license: ps.license ?? null, + isSensitive: ps.isSensitive ?? false, + localOnly: ps.localOnly ?? false, + roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], }); this.moderationLogService.insertModerationLog(me, 'addEmoji', { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index f63348b60b79d9bdc79454460b2462121653e1e1..fb22bdc477e0a75894b9c3085fbbeb2087943feb 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,6 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; +import type { DriveFilesRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -15,6 +17,11 @@ export const meta = { code: 'NO_SUCH_EMOJI', id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8', }, + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: '14fb9fd9-0731-4e2f-aeb9-f09e4740333d', + }, sameNameEmojiExists: { message: 'Emoji that have same name already exists.', code: 'SAME_NAME_EMOJI_EXISTS', @@ -28,6 +35,7 @@ export const paramDef = { properties: { id: { type: 'string', format: 'misskey:id' }, name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, + fileId: { type: 'string', format: 'misskey:id' }, category: { type: 'string', nullable: true, @@ -37,6 +45,11 @@ export const paramDef = { type: 'string', } }, license: { type: 'string', nullable: true }, + isSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, + roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: { + type: 'string', + } }, }, required: ['id', 'name', 'aliases'], } as const; @@ -45,14 +58,28 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { + let driveFile; + + if (ps.fileId) { + driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); + } + await this.customEmojiService.update(ps.id, { + driveFile, name: ps.name, category: ps.category ?? null, aliases: ps.aliases, license: ps.license ?? null, + isSensitive: ps.isSensitive, + localOnly: ps.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction, }); }); } diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index f12738bd3a2d17f722dd6f5d8cd2027baf775283..f2d4aa8996ac09013f2d884059f732caf57b2a92 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { try { - if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; + if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only'); } catch { throw new ApiError(meta.errors.invalidUrl); } diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index dca0f443b7975ebec6420e7b5f2aa24c7d2dea16..e756a9b510c48759c67f91f841916b247056914c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -113,6 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } this.antennasRepository.update(antenna.id, { + isActive: true, lastUsedAt: new Date(), }); diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index cb2e661bfb2e3b6b3e32c2d21b6c10c499d98f4a..05842460cfb49b5f8ae9aa86ef38244135ca4836 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -55,7 +55,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchSession); } - // Generate access token const accessToken = secureRndstr(32, true); // Fetch exist access token @@ -65,7 +64,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); if (exist == null) { - // Lookup app const app = await this.appsRepository.findOneByOrFail({ id: session.appId }); // Generate Hash @@ -75,7 +73,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const now = new Date(); - // Insert access token doc await this.accessTokensRepository.insert({ id: this.idService.genId(), createdAt: now, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index ad33398da60fc8331b902ccd053d82e0bb254a32..e8985a9cd8343ffcfb780a77516d3e6d02948032 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -1,6 +1,6 @@ import { promisify } from 'node:util'; import bcrypt from 'bcryptjs'; -import * as cbor from 'cbor'; +import cbor from 'cbor'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 3361e5a4d3f54c79a14d5f8e4352f4db85222550..48fb03a8af122605a4e1c794aaac9f32e85fb2de 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -26,7 +26,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { const query = this.accessTokensRepository.createQueryBuilder('token') - .where('token.userId = :userId', { userId: me.id }); + .where('token.userId = :userId', { userId: me.id }) + .leftJoinAndSelect('token.app', 'app'); switch (ps.sort) { case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; @@ -40,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { return await Promise.all(tokens.map(token => ({ id: token.id, - name: token.name, + name: token.name ?? token.app?.name, createdAt: token.createdAt, lastUsedAt: token.lastUsedAt, permission: token.permission, diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index e141be764a795b483f00dd4f50a00a39f83c2b8d..f5662f4a0e819f876ca8620d9b84a45bbe32b3a2 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -91,18 +91,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][]; - const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdã«æŒ‡å®šã—ãŸã‚‚ã®ã‚‚å«ã¾ã‚Œã‚‹ãŸã‚+1 + const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdã«æŒ‡å®šã—ãŸã‚‚ã®ã‚‚å«ã¾ã‚Œã‚‹ãŸã‚+1 const notificationsRes = await this.redisClient.xrevrange( `notificationTimeline:${me.id}`, ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+', - '-', + ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : '-', 'COUNT', limit); if (notificationsRes.length === 0) { return []; } - let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId) as Notification[]; + let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as Notification[]; if (includeTypes && includeTypes.length > 0) { notifications = notifications.filter(notification => includeTypes.includes(notification.type)); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 74be00a8b8319a3b70f67a92bc0839eadc3e6404..8f5e6177c28f7b2e9909de055747735c6cd31044 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -141,13 +141,12 @@ export const paramDef = { preventAiLearning: { type: 'boolean' }, isBot: { type: 'boolean' }, isCat: { type: 'boolean' }, - showTimelineReplies: { type: 'boolean' }, injectFeaturedNote: { type: 'boolean' }, receiveAnnouncementEmail: { type: 'boolean' }, alwaysMarkNsfw: { type: 'boolean' }, autoSensitive: { type: 'boolean' }, ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] }, - pinnedPageId: { type: 'string', format: 'misskey:id' }, + pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true }, mutedWords: { type: 'array' }, mutedInstances: { type: 'array', items: { type: 'string', @@ -239,7 +238,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; - if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies; if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 584ea07c3bdb008e6e3542076f38c2033ebfefc9..53d724a9ddc073fa8305a27340e64d4b5e7b3a3c 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,5 +1,6 @@ import { IsNull, LessThanOrEqual, MoreThan } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; +import * as JSON5 from 'json5'; import type { AdsRepository, UsersRepository } from '@/models/index.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -292,8 +293,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, - defaultLightTheme: instance.defaultLightTheme, - defaultDarkTheme: instance.defaultDarkTheme, + // クライアントã®æ‰‹é–“を減らã™ãŸã‚ã‚らã‹ã˜ã‚JSONã«å¤‰æ›ã—ã¦ãŠã + defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null, + defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null, ads: ads.map(ad => ({ id: ad.id, url: ad.url, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 3f7f2cdecef3d4b2a76d4dd81be4271e52b2c056..96be5ed844dfe9c70277e7d5922244e5e7c8bb76 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -99,7 +99,7 @@ export const paramDef = { } }, cw: { type: 'string', nullable: true, maxLength: 100 }, localOnly: { type: 'boolean', default: false }, - reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null }, + reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, noExtractMentions: { type: 'boolean', default: false }, noExtractHashtags: { type: 'boolean', default: false }, noExtractEmojis: { type: 'boolean', default: false }, diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index c11c1eac407ba2f6f50bcfdcbe88803d0d91b464..88c1ca7f58c4e299afae82ffc4826f9d64b867ef 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -34,11 +34,8 @@ export const meta = { export const paramDef = { type: 'object', properties: { - withFiles: { - type: 'boolean', - default: false, - description: 'Only show notes that have attached files.', - }, + withFiles: { type: 'boolean', default: false }, + withReplies: { type: 'boolean', default: false }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, @@ -78,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - this.queryService.generateRepliesQuery(query, me); + this.queryService.generateRepliesQuery(query, ps.withReplies, me); if (me) { this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedNoteQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 89abd91c7e0576c4f610f1cfa32133a762238592..7a3581e6e441bdb57006315ccc155befe8a0e31a 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -46,11 +46,8 @@ export const paramDef = { includeMyRenotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true }, - withFiles: { - type: 'boolean', - default: false, - description: 'Only show notes that have attached files.', - }, + withFiles: { type: 'boolean', default: false }, + withReplies: { type: 'boolean', default: false }, }, required: [], } as const; @@ -98,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .setParameters(followingQuery.getParameters()); this.queryService.generateChannelQuery(query, me); - this.queryService.generateRepliesQuery(query, me); + this.queryService.generateRepliesQuery(query, ps.withReplies, me); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedNoteQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index afdafc7c55024aa79275bf87b6177fb55625c7ea..2ee549232c5395efe7097d94eb6969fb3b7cbb67 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -36,11 +36,8 @@ export const meta = { export const paramDef = { type: 'object', properties: { - withFiles: { - type: 'boolean', - default: false, - description: 'Only show notes that have attached files.', - }, + withFiles: { type: 'boolean', default: false }, + withReplies: { type: 'boolean', default: false }, fileType: { type: 'array', items: { type: 'string', } }, @@ -86,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('renote.user', 'renoteUser'); this.queryService.generateChannelQuery(query, me); - this.queryService.generateRepliesQuery(query, me); + this.queryService.generateRepliesQuery(query, ps.withReplies, me); this.queryService.generateVisibilityQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateMutedNoteQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 2956bf1cbd52b48ff428103c020d61b25e2c123b..742df0ca952e777a3bbfbecabb373767f3b2a5e1 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -82,14 +82,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { try { if (ps.tag) { - if (!safeForSql(normalizeForSearch(ps.tag))) throw 'Injection'; + if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection'); query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); } else { query.andWhere(new Brackets(qb => { for (const tags of ps.query!) { qb.orWhere(new Brackets(qb => { for (const tag of tags) { - if (!safeForSql(normalizeForSearch(tag))) throw 'Injection'; + if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection'); qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); } })); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index c6ee1e5c2bceef6e027e29b9b033cea79af244f1..e1f286439bca1b2650b7568ab5634e423f6b4fa9 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -35,11 +35,8 @@ export const paramDef = { includeMyRenotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true }, - withFiles: { - type: 'boolean', - default: false, - description: 'Only show notes that have attached files.', - }, + withFiles: { type: 'boolean', default: false }, + withReplies: { type: 'boolean', default: false }, }, required: [], } as const; @@ -84,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } this.queryService.generateChannelQuery(query, me); - this.queryService.generateRepliesQuery(query, me); + this.queryService.generateRepliesQuery(query, ps.withReplies, me); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedNoteQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index 4ced6d3ff175ac1f6325e3127644e712a6ba2c1a..1d4825f812eb0526e0864937c0fe48f0fcf2fdfb 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private redisClient: Redis.Redis, ) { super(meta, paramDef, async (ps, me) => { - if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; + if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test'); await redisClient.flushdb(); await resetDb(this.db); diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 6202c740f1a3e3099b3a6dcbbe693dd2b4625e58..42e36cb04a7e12bc5d7c3cc9bb25f01a923436d7 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -93,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const query = this.notesRepository.createQueryBuilder('note') .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .andWhere('(note.visibility = \'public\')') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts new file mode 100644 index 0000000000000000000000000000000000000000..8591e4ab965b20ddce2a2f453f89f98651449884 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -0,0 +1,148 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserList } from '@/models/entities/UserList.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { RoleService } from '@/core/RoleService.js'; +import { UserListService } from '@/core/UserListService.js'; + +export const meta = { + requireCredential: true, + prohibitMoved: true, + res: { + type: 'object', + optional: false, nullable: false, + ref: 'UserList', + }, + + errors: { + tooManyUserLists: { + message: 'You cannot create user list any more.', + code: 'TOO_MANY_USERLISTS', + id: 'e9c105b2-c595-47de-97fb-7f7c2c33e92f', + }, + noSuchList: { + message: 'No such list.', + code: 'NO_SUCH_LIST', + id: '9292f798-6175-4f7d-93f4-b6742279667d', + }, + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '13c457db-a8cb-4d88-b70a-211ceeeabb5f', + }, + + alreadyAdded: { + message: 'That user has already been added to that list.', + code: 'ALREADY_ADDED', + id: 'c3ad6fdb-692b-47ee-a455-7bd12c7af615', + }, + + youHaveBeenBlocked: { + message: 'You cannot push this user because you have been blocked by this user.', + code: 'YOU_HAVE_BEEN_BLOCKED', + id: 'a2497f2a-2389-439c-8626-5298540530f4', + }, + + tooManyUsers: { + message: 'You can not push users any more.', + code: 'TOO_MANY_USERS', + id: '1845ea77-38d1-426e-8e4e-8b83b24f5bd7', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 100 }, + listId: { type: 'string', format: 'misskey:id' }, + }, + required: ['name', 'listId'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + private userListService: UserListService, + private userListEntityService: UserListEntityService, + private idService: IdService, + private getterService: GetterService, + private roleService: RoleService, + ) { + super(meta, paramDef, async (ps, me) => { + const list = await this.userListsRepository.findOneBy({ + id: ps.listId, + isPublic: true, + }); + if (list === null) throw new ApiError(meta.errors.noSuchList); + const currentCount = await this.userListsRepository.countBy({ + userId: me.id, + }); + if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { + throw new ApiError(meta.errors.tooManyUserLists); + } + + const userList = await this.userListsRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + name: ps.name, + } as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); + + const users = (await this.userListJoiningsRepository.findBy({ + userListId: ps.listId, + })).map(x => x.userId); + + for (const user of users) { + const currentUser = await this.getterService.getUser(user).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + if (currentUser.id !== me.id) { + const block = await this.blockingsRepository.findOneBy({ + blockerId: currentUser.id, + blockeeId: me.id, + }); + if (block) { + throw new ApiError(meta.errors.youHaveBeenBlocked); + } + } + + const exist = await this.userListJoiningsRepository.findOneBy({ + userListId: userList.id, + userId: currentUser.id, + }); + + if (exist) { + throw new ApiError(meta.errors.alreadyAdded); + } + + try { + await this.userListService.push(currentUser, userList, me); + } catch (err) { + if (err instanceof UserListService.TooManyUsersError) { + throw new ApiError(meta.errors.tooManyUsers); + } + throw err; + } + } + return await this.userListEntityService.pack(userList); + }); + } +} + diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts new file mode 100644 index 0000000000000000000000000000000000000000..263852fde1ffe440596fb2ac6d3cff5c4d544abd --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -0,0 +1,70 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js'; +import { IdService } from '@/core/IdService.js'; +import { ApiError } from '@/server/api/error.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + requireCredential: true, + errors: { + noSuchList: { + message: 'No such user list.', + code: 'NO_SUCH_USER_LIST', + id: '7dbaf3cf-7b42-4b8f-b431-b3919e580dbe', + }, + + alreadyFavorited: { + message: 'The list has already been favorited.', + code: 'ALREADY_FAVORITED', + id: '6425bba0-985b-461e-af1b-518070e72081', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + listId: { type: 'string', format: 'misskey:id' }, + }, + required: ['listId'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor ( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListFavoritesRepository) + private userListFavoritesRepository: UserListFavoritesRepository, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + isPublic: true, + }); + + if (userList === null) { + throw new ApiError(meta.errors.noSuchList); + } + + const exist = await this.userListFavoritesRepository.findOneBy({ + userId: me.id, + userListId: ps.listId, + }); + + if (exist !== null) { + throw new ApiError(meta.errors.alreadyFavorited); + } + + await this.userListFavoritesRepository.insert({ + id: this.idService.genId(), + createdAt: new Date(), + userId: me.id, + userListId: ps.listId, + }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 2104c4377dd13dd2c11892e3f2dfccf0e1921e31..eab29944b2f31c3aacd21c41f9fc0497d72ff0c0 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,13 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository, UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { ApiError } from '@/server/api/error.js'; import { DI } from '@/di-symbols.js'; export const meta = { tags: ['lists', 'account'], - requireCredential: true, + requireCredential: false, kind: 'read:account', @@ -22,26 +23,58 @@ export const meta = { ref: 'UserList', }, }, + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: 'a8af4a82-0980-4cc4-a6af-8b0ffd54465e', + }, + remoteUser: { + message: 'Not allowed to load the remote user\'s list', + code: 'REMOTE_USER_NOT_ALLOWED', + id: '53858f1b-3315-4a01-81b7-db9b48d4b79a', + }, + invalidParam: { + message: 'Invalid param.', + code: 'INVALID_PARAM', + id: 'ab36de0e-29e9-48cb-9732-d82f1281620d', + }, + }, } as const; export const paramDef = { type: 'object', - properties: {}, + properties: { + userId: { type: 'string', format: 'misskey:id' }, + }, required: [], } as const; -// eslint-disable-next-line import/no-default-export -@Injectable() +@Injectable() // eslint-disable-next-line import/no-default-export export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, private userListEntityService: UserListEntityService, ) { super(meta, paramDef, async (ps, me) => { - const userLists = await this.userListsRepository.findBy({ + if (typeof ps.userId !== 'undefined') { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + if (user === null) throw new ApiError(meta.errors.noSuchUser); + if (user.host !== null) throw new ApiError(meta.errors.remoteUser); + } else if (me === null) { + throw new ApiError(meta.errors.invalidParam); + } + + const userLists = await this.userListsRepository.findBy(typeof ps.userId === 'undefined' && me !== null ? { userId: me.id, + } : { + userId: ps.userId, + isPublic: true, }); return await Promise.all(userLists.map(x => this.userListEntityService.pack(x))); diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 77f9cba808f949df5c3572db5b04fc24381af1fe..8077841c8ce15d6f9afbbfc1a1771e652cb6955a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/index.js'; +import type { UserListsRepository, UserListFavoritesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -8,7 +8,7 @@ import { ApiError } from '../../../error.js'; export const meta = { tags: ['lists', 'account'], - requireCredential: true, + requireCredential: false, kind: 'read:account', @@ -33,31 +33,54 @@ export const paramDef = { type: 'object', properties: { listId: { type: 'string', format: 'misskey:id' }, + forPublic: { type: 'boolean', default: false }, }, required: ['listId'], } as const; -// eslint-disable-next-line import/no-default-export -@Injectable() +@Injectable() // eslint-disable-next-line import/no-default-export export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, + @Inject(DI.userListFavoritesRepository) + private userListFavoritesRepository: UserListFavoritesRepository, + private userListEntityService: UserListEntityService, ) { super(meta, paramDef, async (ps, me) => { + const additionalProperties: Partial<{ likedCount: number, isLiked: boolean }> = {}; // Fetch the list - const userList = await this.userListsRepository.findOneBy({ + const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? { id: ps.listId, userId: me.id, + } : { + id: ps.listId, + isPublic: true, }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - return await this.userListEntityService.pack(userList); + if (ps.forPublic && userList.isPublic) { + additionalProperties.likedCount = await this.userListFavoritesRepository.countBy({ + userListId: ps.listId, + }); + if (me !== null) { + additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({ + userId: me.id, + userListId: ps.listId, + }) !== null); + } else { + additionalProperties.isLiked = false; + } + } + return { + ...await this.userListEntityService.pack(userList), + ...additionalProperties, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts new file mode 100644 index 0000000000000000000000000000000000000000..be8e317816c4062a54407740121c379e4ada6d5d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts @@ -0,0 +1,63 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js'; +import { ApiError } from '@/server/api/error.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + requireCredential: true, + errors: { + noSuchList: { + message: 'No such user list.', + code: 'NO_SUCH_USER_LIST', + id: 'baedb33e-76b8-4b0c-86a8-9375c0a7b94b', + }, + + notFavorited: { + message: 'You have not favorited the list.', + code: 'ALREADY_FAVORITED', + id: '835c4b27-463d-4cfa-969b-a9058678d465', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + listId: { type: 'string', format: 'misskey:id' }, + }, + required: ['listId'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor ( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListFavoritesRepository) + private userListFavoritesRepository: UserListFavoritesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + isPublic: true, + }); + + if (userList === null) { + throw new ApiError(meta.errors.noSuchList); + } + + const exist = await this.userListFavoritesRepository.findOneBy({ + userListId: ps.listId, + userId: me.id, + }); + + if (exist === null) { + throw new ApiError(meta.errors.notFavorited); + } + + await this.userListFavoritesRepository.delete({ id: exist.id }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 6453d7d9808ce2539ff4551e68db354b5cfa38dd..b0a95a2f2864326a6178764ed606f708bb48f80c 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -34,8 +34,9 @@ export const paramDef = { properties: { listId: { type: 'string', format: 'misskey:id' }, name: { type: 'string', minLength: 1, maxLength: 100 }, + isPublic: { type: 'boolean' }, }, - required: ['listId', 'name'], + required: ['listId'], } as const; // eslint-disable-next-line import/no-default-export @@ -48,7 +49,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private userListEntityService: UserListEntityService, ) { super(meta, paramDef, async (ps, me) => { - // Fetch the list const userList = await this.userListsRepository.findOneBy({ id: ps.listId, userId: me.id, @@ -60,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { await this.userListsRepository.update(userList.id, { name: ps.name, + isPublic: ps.isPublic, }); return await this.userListEntityService.pack(userList.id); diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 5454836fe116511a53ff2e9acbccca9c2fe21433..d3339072c1efce1b9c561fca1197996dbf987a2c 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -13,6 +13,7 @@ class GlobalTimelineChannel extends Channel { public readonly chName = 'globalTimeline'; public static shouldShare = true; public static requireCredential = false; + private withReplies: boolean; constructor( private metaService: MetaService, @@ -31,6 +32,8 @@ class GlobalTimelineChannel extends Channel { const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); if (!policies.gtlAvailable) return; + this.withReplies = params.withReplies as boolean; + // Subscribe events this.subscriber.on('notesStream', this.onNote); } @@ -54,7 +57,7 @@ class GlobalTimelineChannel extends Channel { } // 関係ãªã„返信ã¯é™¤å¤– - if (note.reply && !this.user!.showTimelineReplies) { + if (note.reply && !this.withReplies) { const reply = note.reply; // 「ãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€Œãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ãŒè¡Œã£ãŸè¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€ŒæŠ•ç¨¿è€…ã®æŠ•ç¨¿è€…自身ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã„å ´åˆ if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index ee874ad81e19aa10f926e44dc3734674e20edf51..1755aa94cf13545673d6f75ff58f79aa572b24f9 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -11,6 +11,7 @@ class HomeTimelineChannel extends Channel { public readonly chName = 'homeTimeline'; public static shouldShare = true; public static requireCredential = true; + private withReplies: boolean; constructor( private noteEntityService: NoteEntityService, @@ -24,6 +25,8 @@ class HomeTimelineChannel extends Channel { @bindThis public async init(params: any) { + this.withReplies = params.withReplies as boolean; + this.subscriber.on('notesStream', this.onNote); } @@ -63,7 +66,7 @@ class HomeTimelineChannel extends Channel { } // 関係ãªã„返信ã¯é™¤å¤– - if (note.reply && !this.user!.showTimelineReplies) { + if (note.reply && !this.withReplies) { const reply = note.reply; // 「ãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€Œãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ãŒè¡Œã£ãŸè¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€ŒæŠ•ç¨¿è€…ã®æŠ•ç¨¿è€…自身ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã„å ´åˆ if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 4f7b4e78b695bc984b43f56f6aa5f678208cfca4..5a33e13cf55b012579e165e9a55f6a7396246a0c 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -13,6 +13,7 @@ class HybridTimelineChannel extends Channel { public readonly chName = 'hybridTimeline'; public static shouldShare = true; public static requireCredential = true; + private withReplies: boolean; constructor( private metaService: MetaService, @@ -31,6 +32,8 @@ class HybridTimelineChannel extends Channel { const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); if (!policies.ltlAvailable) return; + this.withReplies = params.withReplies as boolean; + // Subscribe events this.subscriber.on('notesStream', this.onNote); } @@ -75,7 +78,7 @@ class HybridTimelineChannel extends Channel { if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return; // 関係ãªã„返信ã¯é™¤å¤– - if (note.reply && !this.user!.showTimelineReplies) { + if (note.reply && !this.withReplies) { const reply = note.reply; // 「ãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€Œãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ãŒè¡Œã£ãŸè¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€ŒæŠ•ç¨¿è€…ã®æŠ•ç¨¿è€…自身ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã„å ´åˆ if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 09b0005ac11421b748612ec4b1f2cd6c955ca54b..9ca4db8ced92bc0542530e89b6fa58d4da50dd40 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -12,6 +12,7 @@ class LocalTimelineChannel extends Channel { public readonly chName = 'localTimeline'; public static shouldShare = true; public static requireCredential = false; + private withReplies: boolean; constructor( private metaService: MetaService, @@ -30,6 +31,8 @@ class LocalTimelineChannel extends Channel { const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); if (!policies.ltlAvailable) return; + this.withReplies = params.withReplies as boolean; + // Subscribe events this.subscriber.on('notesStream', this.onNote); } @@ -54,7 +57,7 @@ class LocalTimelineChannel extends Channel { } // 関係ãªã„返信ã¯é™¤å¤– - if (note.reply && this.user && !this.user.showTimelineReplies) { + if (note.reply && this.user && !this.withReplies) { const reply = note.reply; // 「ãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€Œãƒãƒ£ãƒ³ãƒãƒ«æŽ¥ç¶šä¸»ãŒè¡Œã£ãŸè¿”ä¿¡ã€ã§ã‚‚ãªã‘ã‚Œã°ã€ã€ŒæŠ•ç¨¿è€…ã®æŠ•ç¨¿è€…自身ã¸ã®è¿”ä¿¡ã€ã§ã‚‚ãªã„å ´åˆ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return; diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index 9d106c8b2f9839024a5c35beec4a72d0b8f771c0..ab9c1aa0b52aad31406ff82ab399d97a67d3739e 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -5,15 +5,17 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import Channel from '../channel.js'; import { StreamMessages } from '../types.js'; +import { RoleService } from '@/core/RoleService.js'; class RoleTimelineChannel extends Channel { public readonly chName = 'roleTimeline'; public static shouldShare = false; public static requireCredential = false; private roleId: string; - + constructor( private noteEntityService: NoteEntityService, + private roleservice: RoleService, id: string, connection: Channel['connection'], @@ -34,6 +36,11 @@ class RoleTimelineChannel extends Channel { if (data.type === 'note') { const note = data.body; + if (!(await this.roleservice.isExplorable({ id: this.roleId }))) { + return; + } + if (note.visibility !== 'public') return; + // æµã‚Œã¦ããŸNoteãŒãƒŸãƒ¥ãƒ¼ãƒˆã—ã¦ã„るユーザーãŒé–¢ã‚ã‚‹ã‚‚ã®ã ã£ãŸã‚‰ç„¡è¦–ã™ã‚‹ if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // æµã‚Œã¦ããŸNoteãŒãƒ–ãƒãƒƒã‚¯ã•ã‚Œã¦ã„るユーザーãŒé–¢ã‚ã‚‹ã‚‚ã®ã ã£ãŸã‚‰ç„¡è¦–ã™ã‚‹ @@ -61,6 +68,7 @@ export class RoleTimelineChannelService { constructor( private noteEntityService: NoteEntityService, + private roleservice: RoleService, ) { } @@ -68,6 +76,7 @@ export class RoleTimelineChannelService { public create(id: string, connection: Channel['connection']): RoleTimelineChannel { return new RoleTimelineChannel( this.noteEntityService, + this.roleservice, id, connection, ); diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index a6f9145952db2bebe49a81e7bf76dd3c7c49ab79..8b1c2c09c974181fae099690e4a4c798bc9b0806 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,3 +1,4 @@ +import * as WebSocket from 'ws'; import type { User } from '@/models/entities/User.js'; import type { AccessToken } from '@/models/entities/AccessToken.js'; import type { Packed } from '@/misc/json-schema.js'; @@ -7,7 +8,6 @@ import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; import { UserProfile } from '@/models/index.js'; import type { ChannelsService } from './ChannelsService.js'; -import type * as websocket from 'websocket'; import type { EventEmitter } from 'events'; import type Channel from './channel.js'; import type { StreamEventEmitter, StreamMessages } from './types.js'; @@ -18,7 +18,7 @@ import type { StreamEventEmitter, StreamMessages } from './types.js'; export default class Connection { public user?: User; public token?: AccessToken; - private wsConnection: websocket.connection; + private wsConnection: WebSocket.WebSocket; public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; @@ -37,11 +37,9 @@ export default class Connection { private notificationService: NotificationService, private cacheService: CacheService, - subscriber: EventEmitter, user: User | null | undefined, token: AccessToken | null | undefined, ) { - this.subscriber = subscriber; if (user) this.user = user; if (token) this.token = token; } @@ -70,12 +68,16 @@ export default class Connection { if (this.user != null) { await this.fetch(); - this.fetchIntervalId = setInterval(this.fetch, 1000 * 10); + if (!this.fetchIntervalId) { + this.fetchIntervalId = setInterval(this.fetch, 1000 * 10); + } } } @bindThis - public async init2(wsConnection: websocket.connection) { + public async listen(subscriber: EventEmitter, wsConnection: WebSocket.WebSocket) { + this.subscriber = subscriber; + this.wsConnection = wsConnection; this.wsConnection.on('message', this.onWsConnectionMessage); @@ -88,14 +90,11 @@ export default class Connection { * クライアントã‹ã‚‰ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸å—信時 */ @bindThis - private async onWsConnectionMessage(data: websocket.Message) { - if (data.type !== 'utf8') return; - if (data.utf8Data == null) return; - + private async onWsConnectionMessage(data: WebSocket.RawData) { let obj: Record<string, any>; try { - obj = JSON.parse(data.utf8Data); + obj = JSON.parse(data.toString()); } catch (e) { return; } @@ -246,7 +245,7 @@ export default class Connection { const ch: Channel = channelService.create(id, this); this.channels.push(ch); - ch.init(params); + ch.init(params ?? {}); if (pong) { this.sendMessageToWs('connected', { diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index fd7f54da54cc554ad666473500718568c22a0f80..38ae8ad2e5b850d8ab8aea6d5d2ca9f12310e782 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -116,9 +116,9 @@ } } } - const colorSchema = localStorage.getItem('colorSchema'); - if (colorSchema) { - document.documentElement.style.setProperty('color-schema', colorSchema); + const colorScheme = localStorage.getItem('colorScheme'); + if (colorScheme) { + document.documentElement.style.setProperty('color-scheme', colorScheme); } //#endregion @@ -160,37 +160,41 @@ <path d="M12 9v2m0 4v.01"></path> <path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path> </svg> - <h1>An error has occurred!</h1> - <button class="button-big" onclick="location.reload();"> - <span class="button-label-big">Refresh</span> + <h1>Failed to load<br>èªã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸ</h1> + <button class="button-big" onclick="location.reload(true);"> + <span class="button-label-big">Reload / リãƒãƒ¼ãƒ‰</span> </button> - <p class="dont-worry">Don't worry, it's (probably) not your fault.</p> - <p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p> - <p>Update your os and browser.</p> - <p>Disable an adblocker.</p> - <a href="/flush"> - <button class="button-small"> - <span class="button-label-small">Clear preferences and cache</span> - </button> - </a> - <br> - <a href="/cli"> - <button class="button-small"> - <span class="button-label-small">Start the simple client</span> - </button> - </a> - <br> - <a href="/bios"> - <button class="button-small"> - <span class="button-label-small">Start the repair tool</span> - </button> - </a> + <p><b>The following actions may solve the problem. / 以下を行ã†ã¨è§£æ±ºã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚</b></p> + <p>Clear the browser cache / ブラウザã®ã‚ャッシュをクリアã™ã‚‹</p> + <p>Update your os and browser / ブラウザãŠã‚ˆã³OSを最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«æ›´æ–°ã™ã‚‹</p> + <p>Disable an adblocker / アドブãƒãƒƒã‚«ãƒ¼ã‚’無効ã«ã™ã‚‹</p> + <details style="color: #86b300;"> + <summary>Other options / ãã®ä»–ã®ã‚ªãƒ—ション</summary> + <a href="/flush"> + <button class="button-small"> + <span class="button-label-small">Clear preferences and cache</span> + </button> + </a> + <br> + <a href="/cli"> + <button class="button-small"> + <span class="button-label-small">Start the simple client</span> + </button> + </a> + <br> + <a href="/bios"> + <button class="button-small"> + <span class="button-label-small">Start the repair tool</span> + </button> + </a> + </details> <br> <div id="errors"></div> `; errorsElement = document.getElementById('errors'); } const detailsElement = document.createElement('details'); + detailsElement.id = 'errorInfo'; detailsElement.innerHTML = ` <br> <summary> @@ -247,7 +251,7 @@ .button-label-big { color: #222; font-weight: bold; - font-size: 20px; + font-size: 1.2em; padding: 12px; } @@ -267,11 +271,6 @@ font-size: 16px; } - .dont-worry, - #msg { - font-size: 18px; - } - .icon-warning { color: #dec340; height: 4rem; @@ -279,14 +278,15 @@ } h1 { - font-size: 32px; + font-size: 1.5em; + margin: 1em; } code { font-family: Fira, FiraCode, monospace; } - details { + #errorInfo { background: #333; margin-bottom: 2rem; padding: 0.5rem 1rem; @@ -296,16 +296,16 @@ margin: auto; } - summary { + #errorInfo summary { cursor: pointer; } - summary > * { + #errorInfo summary > * { display: inline; } @media screen and (max-width: 500px) { - details { + #errorInfo { width: 50%; } `) diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index cb5d05a403910528b38b53b111f34a2d12e023af..69b3f68e0524b34ead1077a00050d3921e126e0d 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -25,7 +25,6 @@ html meta(name='referrer' content='origin') meta(name='theme-color' content= themeColor || '#86b300') meta(name='theme-color-orig' content= themeColor || '#86b300') - meta(property='twitter:card' content='summary') meta(property='og:site_name' content= instanceName || 'Misskey') meta(name='viewport' content='width=device-width, initial-scale=1') link(rel='icon' href= icon || '/favicon.ico') @@ -36,7 +35,7 @@ html link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') //- https://github.com/misskey-dev/misskey/issues/9842 - link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.17.0') + link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0') link(rel='modulepreload' href=`/vite/${clientEntry.file}`) if !config.clientManifestExists @@ -59,6 +58,7 @@ html meta(property='og:title' content= title || 'Misskey') meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') meta(property='og:image' content= img) + meta(property='twitter:card' content='summary') style include ../style.css diff --git a/packages/backend/src/server/web/views/channel.pug b/packages/backend/src/server/web/views/channel.pug index 486f0ecc47bc2cc1b5ec963b2dd627dcc1da62f0..c514025e0b40fc934c5a82c4b019bad02126e34e 100644 --- a/packages/backend/src/server/web/views/channel.pug +++ b/packages/backend/src/server/web/views/channel.pug @@ -16,3 +16,4 @@ block og meta(property='og:description' content= channel.description) meta(property='og:url' content= url) meta(property='og:image' content= channel.bannerUrl) + meta(property='twitter:card' content='summary') diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug index 74dc62f1e7a191437f7fc4ca507f14bc9f11c2df..5a0018803a631383c28afa0ffe1b667d6eeaf625 100644 --- a/packages/backend/src/server/web/views/clip.pug +++ b/packages/backend/src/server/web/views/clip.pug @@ -17,6 +17,7 @@ block og meta(property='og:description' content= clip.description) meta(property='og:url' content= url) meta(property='og:image' content= avatarUrl) + meta(property='twitter:card' content='summary') block meta if profile.noCrawle diff --git a/packages/backend/src/server/web/views/flash.pug b/packages/backend/src/server/web/views/flash.pug index 5594fcdfbfdff95a898a11f3039acb3d5e9f6a01..1549aa7906109aa2e0a97378a32208896f47eedb 100644 --- a/packages/backend/src/server/web/views/flash.pug +++ b/packages/backend/src/server/web/views/flash.pug @@ -17,6 +17,7 @@ block og meta(property='og:description' content= flash.summary) meta(property='og:url' content= url) meta(property='og:image' content= avatarUrl) + meta(property='twitter:card' content='summary') block meta if profile.noCrawle diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug index 10f2d269bcdb720c1779711481a99c424d70841d..a458d7f8c7d6a2a82e6329e716c6717e77298d6c 100644 --- a/packages/backend/src/server/web/views/gallery-post.pug +++ b/packages/backend/src/server/web/views/gallery-post.pug @@ -17,6 +17,7 @@ block og meta(property='og:description' content= post.description) meta(property='og:url' content= url) meta(property='og:image' content= post.files[0].thumbnailUrl) + meta(property='twitter:card' content='summary_large_image') block meta if user.host || profile.noCrawle diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index badfcccd61291efea7ad64c07316afc2701b0a68..874c48c60285c760f9c674af8232916e7f682b2d 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -5,6 +5,8 @@ block vars - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const url = `${config.url}/notes/${note.id}`; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; + - const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.type.isSensitive) + - const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.type.isSensitive) block title = `${title} | ${instanceName}` @@ -17,7 +19,19 @@ block og meta(property='og:title' content= title) meta(property='og:description' content= summary) meta(property='og:url' content= url) - meta(property='og:image' content= avatarUrl) + if video + meta(property='og:video:url' content= video.url) + meta(property='og:video:secure_url' content= video.url) + meta(property='og:video:type' content= video.type) + // FIXME: add width and height + // FIXME: add embed player for Twitter + if image + meta(property='twitter:card' content='summary_large_image') + meta(property='og:image' content= image.url) + else + meta(property='twitter:card' content='summary') + meta(property='og:image' content= avatarUrl) + block meta if user.host || isRenote || profile.noCrawle diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug index ddffc361c888816ede27b99ffae0435aa5086d43..08bb08ffe7e12c419189b22783e36660193c5cfa 100644 --- a/packages/backend/src/server/web/views/page.pug +++ b/packages/backend/src/server/web/views/page.pug @@ -17,6 +17,7 @@ block og meta(property='og:description' content= page.summary) meta(property='og:url' content= url) meta(property='og:image' content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl) + meta(property='twitter:card' content= page.eyeCatchingImage ? 'summary_large_image' : 'summary') block meta if profile.noCrawle diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug index f4c83aa89d386e1bd6528876dc6fac1a1875d15a..83d57349a64dc79a326c1efa1705b2f944d6fa14 100644 --- a/packages/backend/src/server/web/views/user.pug +++ b/packages/backend/src/server/web/views/user.pug @@ -16,6 +16,7 @@ block og meta(property='og:description' content= profile.description) meta(property='og:url' content= url) meta(property='og:image' content= avatarUrl) + meta(property='twitter:card' content='summary') block meta if user.host || profile.noCrawle diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index 0addb430c970f3194284680af2379df87d10ed1b..5da997f28bb9ce8a7e3266ba233620d05ad0daec 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -2,7 +2,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as crypto from 'node:crypto'; -import * as cbor from 'cbor'; +import cbor from 'cbor'; import * as OTPAuth from 'otpauth'; import { loadConfig } from '../../src/config.js'; import { signup, api, post, react, startServer, waitFire } from '../utils.js'; diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd3b09f85ae2aba387a872f52fdcd8aaa08e63a4 --- /dev/null +++ b/packages/backend/test/e2e/antennas.ts @@ -0,0 +1,653 @@ +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import { inspect } from 'node:util'; +import { DEFAULT_POLICIES } from '@/core/RoleService.js'; +import type { Packed } from '@/misc/json-schema.js'; +import { + signup, + post, + userList, + page, + role, + startServer, + api, + successfulApiCall, + failedApiCall, + uploadFile, + testPaginationConsistency, +} from '../utils.js'; +import type * as misskey from 'misskey-js'; +import type { INestApplicationContext } from '@nestjs/common'; + +const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => { + return selector(a).localeCompare(selector(b)); +}; + +describe('アンテナ', () => { + // エンティティã¨ã—ã¦ã®ã‚¢ãƒ³ãƒ†ãƒŠã‚’主眼ã«ãŠã„ãŸãƒ†ã‚¹ãƒˆã‚’記述ã™ã‚‹ + // (Antennaã‚’è¿”ã™ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã€Antennaエンティティを書ãæ›ãˆã‚‹ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã€Antennaã‹ã‚‰ãƒŽãƒ¼ãƒˆã‚’å–å¾—ã™ã‚‹ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‚’テストã™ã‚‹) + + // BUG misskey-jsã¨json-schemaãŒä¸€è‡´ã—ã¦ã„ãªã„。 + // - srcã®enumã«groupãŒæ®‹ã£ã¦ã„ã‚‹ + // - userGroupIdãŒæ®‹ã£ã¦ã„ã‚‹, isActiveãŒãªã„ + type Antenna = misskey.entities.Antenna | Packed<'Antenna'>; + type User = misskey.entities.MeDetailed & { token: string }; + type Note = misskey.entities.Note; + + // アンテナを作æˆã§ãる最å°ã®ãƒ‘ラメタ + const defaultParam = { + caseSensitive: false, + excludeKeywords: [['']], + keywords: [['keyword']], + name: 'test', + notify: false, + src: 'all' as const, + userListId: null, + users: [''], + withFile: false, + withReplies: false, + }; + + let app: INestApplicationContext; + + let root: User; + let alice: User; + let bob: User; + let carol: User; + + let alicePost: Note; + let aliceList: misskey.entities.UserList; + let bobFile: misskey.entities.DriveFile; + let bobList: misskey.entities.UserList; + + let userNotExplorable: User; + let userLocking: User; + let userSilenced: User; + let userSuspended: User; + let userDeletedBySelf: User; + let userDeletedByAdmin: User; + let userFollowingAlice: User; + let userFollowedByAlice: User; + let userBlockingAlice: User; + let userBlockedByAlice: User; + let userMutingAlice: User; + let userMutedByAlice: User; + + beforeAll(async () => { + app = await startServer(); + }, 1000 * 60 * 2); + + beforeAll(async () => { + root = await signup({ username: 'root' }); + alice = await signup({ username: 'alice' }); + alicePost = await post(alice, { text: 'test' }); + aliceList = await userList(alice, {}); + bob = await signup({ username: 'bob' }); + aliceList = await userList(alice, {}); + bobFile = (await uploadFile(bob)).body; + bobList = await userList(bob); + carol = await signup({ username: 'carol' }); + await api('users/lists/push', { listId: aliceList.id, userId: bob.id }, alice); + await api('users/lists/push', { listId: aliceList.id, userId: carol.id }, alice); + + userNotExplorable = await signup({ username: 'userNotExplorable' }); + await post(userNotExplorable, { text: 'test' }); + await api('i/update', { isExplorable: false }, userNotExplorable); + userLocking = await signup({ username: 'userLocking' }); + await post(userLocking, { text: 'test' }); + await api('i/update', { isLocked: true }, userLocking); + userSilenced = await signup({ username: 'userSilenced' }); + await post(userSilenced, { text: 'test' }); + const roleSilenced = await role(root, {}, { canPublicNote: { priority: 0, useDefault: false, value: false } }); + await api('admin/roles/assign', { userId: userSilenced.id, roleId: roleSilenced.id }, root); + userSuspended = await signup({ username: 'userSuspended' }); + await post(userSuspended, { text: 'test' }); + await successfulApiCall({ endpoint: 'i/update', parameters: { description: '#user_testuserSuspended' }, user: userSuspended }); + await api('admin/suspend-user', { userId: userSuspended.id }, root); + userDeletedBySelf = await signup({ username: 'userDeletedBySelf', password: 'userDeletedBySelf' }); + await post(userDeletedBySelf, { text: 'test' }); + await api('i/delete-account', { password: 'userDeletedBySelf' }, userDeletedBySelf); + userDeletedByAdmin = await signup({ username: 'userDeletedByAdmin' }); + await post(userDeletedByAdmin, { text: 'test' }); + await api('admin/delete-account', { userId: userDeletedByAdmin.id }, root); + userFollowedByAlice = await signup({ username: 'userFollowedByAlice' }); + await post(userFollowedByAlice, { text: 'test' }); + await api('following/create', { userId: userFollowedByAlice.id }, alice); + userFollowingAlice = await signup({ username: 'userFollowingAlice' }); + await post(userFollowingAlice, { text: 'test' }); + await api('following/create', { userId: alice.id }, userFollowingAlice); + userBlockingAlice = await signup({ username: 'userBlockingAlice' }); + await post(userBlockingAlice, { text: 'test' }); + await api('blocking/create', { userId: alice.id }, userBlockingAlice); + userBlockedByAlice = await signup({ username: 'userBlockedByAlice' }); + await post(userBlockedByAlice, { text: 'test' }); + await api('blocking/create', { userId: userBlockedByAlice.id }, alice); + userMutingAlice = await signup({ username: 'userMutingAlice' }); + await post(userMutingAlice, { text: 'test' }); + await api('mute/create', { userId: alice.id }, userMutingAlice); + userMutedByAlice = await signup({ username: 'userMutedByAlice' }); + await post(userMutedByAlice, { text: 'test' }); + await api('mute/create', { userId: userMutedByAlice.id }, alice); + }, 1000 * 60 * 10); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(async () => { + // テスト間ã§å½±éŸ¿ã—åˆã‚ãªã„よã†ã«æ¯Žå›žå…¨éƒ¨æ¶ˆã™ã€‚ + for (const user of [alice, bob]) { + const list = await api('/antennas/list', {}, user); + for (const antenna of list.body) { + await api('/antennas/delete', { antennaId: antenna.id }, user); + } + } + }); + + //#region 作æˆ(antennas/create) + + test('ãŒä½œæˆã§ãã‚‹ã“ã¨ã€ã‚ーãŒéŽä¸è¶³ãªãå…¥ã£ã¦ã„ã‚‹ã“ã¨ã€‚', async () => { + const response = await successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam }, + user: alice, + }); + assert.match(response.id, /[0-9a-z]{10}/); + const expected = { + id: response.id, + caseSensitive: false, + createdAt: new Date(response.createdAt).toISOString(), + excludeKeywords: [['']], + hasUnreadNote: false, + isActive: true, + keywords: [['keyword']], + name: 'test', + notify: false, + src: 'all', + userListId: null, + users: [''], + withFile: false, + withReplies: false, + } as Antenna; + assert.deepStrictEqual(response, expected); + }); + + test('ãŒä¸Šé™ã„ã£ã±ã„ã¾ã§ä½œæˆã§ãã‚‹ã“ã¨', async () => { + // antennaLimit + 1ã¾ã§ä½œã‚Œã‚‹ã®ãŒã‚モ + const response = await Promise.all([...Array(DEFAULT_POLICIES.antennaLimit + 1)].map(() => successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam }, + user: alice, + }))); + + const expected = await successfulApiCall({ endpoint: 'antennas/list', parameters: {}, user: alice }); + assert.deepStrictEqual( + response.sort(compareBy(s => s.id)), + expected.sort(compareBy(s => s.id))); + + failedApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam }, + user: alice, + }, { + status: 400, + code: 'TOO_MANY_ANTENNAS', + id: 'faf47050-e8b5-438c-913c-db2b1576fde4', + }); + }); + + test('を作æˆã™ã‚‹ã¨ã他人ã®ãƒªã‚¹ãƒˆã‚’指定ã—ãŸã‚‰ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹', async () => { + failedApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, src: 'list', userListId: bobList.id }, + user: alice, + }, { + status: 400, + code: 'NO_SUCH_USER_LIST', + id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f', + }); + }); + + const antennaParamPattern = [ + { parameters: (): object => ({ name: 'x'.repeat(100) }) }, + { parameters: (): object => ({ name: 'x' }) }, + { parameters: (): object => ({ src: 'home' }) }, + { parameters: (): object => ({ src: 'all' }) }, + { parameters: (): object => ({ src: 'users' }) }, + { parameters: (): object => ({ src: 'list' }) }, + { parameters: (): object => ({ userListId: null }) }, + { parameters: (): object => ({ src: 'list', userListId: aliceList.id }) }, + { parameters: (): object => ({ keywords: [['x']] }) }, + { parameters: (): object => ({ keywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, + { parameters: (): object => ({ excludeKeywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, + { parameters: (): object => ({ users: [alice.username] }) }, + { parameters: (): object => ({ users: [alice.username, bob.username, carol.username] }) }, + { parameters: (): object => ({ caseSensitive: false }) }, + { parameters: (): object => ({ caseSensitive: true }) }, + { parameters: (): object => ({ withReplies: false }) }, + { parameters: (): object => ({ withReplies: true }) }, + { parameters: (): object => ({ withFile: false }) }, + { parameters: (): object => ({ withFile: true }) }, + { parameters: (): object => ({ notify: false }) }, + { parameters: (): object => ({ notify: true }) }, + ]; + test.each(antennaParamPattern)('を作æˆã§ãã‚‹ã“ã¨($#)', async ({ parameters }) => { + const response = await successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, ...parameters() }, + user: alice, + }); + const expected = { ...response, ...parameters() }; + assert.deepStrictEqual(response, expected); + }); + + //#endregion + //#region æ›´æ–°(antennas/update) + + test.each(antennaParamPattern)('を変更ã§ãã‚‹ã“ã¨($#)', async ({ parameters }) => { + const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice }); + const response = await successfulApiCall({ + endpoint: 'antennas/update', + parameters: { antennaId: antenna.id, ...defaultParam, ...parameters() }, + user: alice, + }); + const expected = { ...response, ...parameters() }; + assert.deepStrictEqual(response, expected); + }); + test.todo('ã¯ä»–人ã®ã‚‚ã®ã¯å¤‰æ›´ã§ããªã„'); + + test('を変更ã™ã‚‹ã¨ã他人ã®ãƒªã‚¹ãƒˆã‚’指定ã—ãŸã‚‰ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹', async () => { + const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice }); + failedApiCall({ + endpoint: 'antennas/update', + parameters: { antennaId: antenna.id, ...defaultParam, src: 'list', userListId: bobList.id }, + user: alice, + }, { + status: 400, + code: 'NO_SUCH_USER_LIST', + id: '1c6b35c9-943e-48c2-81e4-2844989407f7', + }); + }); + + //#endregion + //#region 表示(antennas/show) + + test('ã‚’ID指定ã§è¡¨ç¤ºã§ãã‚‹ã“ã¨ã€‚', async () => { + const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice }); + const response = await successfulApiCall({ + endpoint: 'antennas/show', + parameters: { antennaId: antenna.id }, + user: alice, + }); + const expected = { ...antenna }; + assert.deepStrictEqual(response, expected); + }); + test.todo('ã¯ä»–人ã®ã‚‚ã®ã‚’ID指定ã§è¡¨ç¤ºã§ããªã„'); + + //#endregion + //#region 一覧(antennas/list) + + test('をリスト形å¼ã§å–å¾—ã§ãã‚‹ã“ã¨ã€‚', async () => { + const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice }); + await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: bob }); + const response = await successfulApiCall({ + endpoint: 'antennas/list', + parameters: {}, + user: alice, + }); + const expected = [{ ...antenna }]; + assert.deepStrictEqual(response, expected); + }); + + //#endregion + //#region 削除(antennas/delete) + + test('を削除ã§ãã‚‹ã“ã¨ã€‚', async () => { + const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice }); + const response = await successfulApiCall({ + endpoint: 'antennas/delete', + parameters: { antennaId: antenna.id }, + user: alice, + }); + assert.deepStrictEqual(response, null); + const list = await successfulApiCall({ endpoint: 'antennas/list', parameters: {}, user: alice }); + assert.deepStrictEqual(list, []); + }); + test.todo('ã¯ä»–人ã®ã‚‚ã®ã‚’削除ã§ããªã„'); + + //#endregion + + describe('ã®ãƒŽãƒ¼ãƒˆ', () => { + //#region アンテナã®ãƒŽãƒ¼ãƒˆå–å¾—(antennas/notes) + + test('ã‚’å–å¾—ã§ãã‚‹ã“ã¨ã€‚', async () => { + const keyword = 'ã‚ーワード'; + await post(bob, { text: `test ${keyword} beforehand` }); + const antenna = await successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, keywords: [[keyword]] }, + user: alice, + }); + const note = await post(bob, { text: `test ${keyword}` }); + const response = await successfulApiCall({ + endpoint: 'antennas/notes', + parameters: { antennaId: antenna.id }, + user: alice, + }); + const expected = [note]; + assert.deepStrictEqual(response, expected); + }); + + const keyword = 'ã‚ーワード'; + test.each([ + { + label: '全体ã‹ã‚‰', + parameters: (): object => ({ src: 'all' }), + posts: [ + { note: (): Promise<Note> => post(alice, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true }, + ], + }, + { + // BUG e4144a1 以é™home指定ã¯å£Šã‚Œã¦ã„ã‚‹(allã¨åŒã˜) + label: 'ホーム指定ã¯allã¨åŒã˜', + parameters: (): object => ({ src: 'home' }), + posts: [ + { note: (): Promise<Note> => post(alice, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true }, + ], + }, + { + // https://github.com/misskey-dev/misskey/issues/9025 + label: 'ãŸã ã—ã€ãƒ•ã‚©ãƒãƒ¯ãƒ¼é™å®šæŠ•ç¨¿ã¨DM投稿をå«ã¾ãªã„。フォãƒãƒ¯ãƒ¼ã§ã‚ã£ã¦ã‚‚。', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true }, + { note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true }, + { note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'followers' }) }, + { note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'specified', visibleUserIds: [alice.id] }) }, + ], + }, + { + label: 'ブãƒãƒƒã‚¯ã—ã¦ã„るユーザーã®ãƒŽãƒ¼ãƒˆã¯å«ã‚€', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userBlockedByAlice, { text: `${keyword}` }), included: true }, + ], + }, + { + label: 'ブãƒãƒƒã‚¯ã•ã‚Œã¦ã„るユーザーã®ãƒŽãƒ¼ãƒˆã¯å«ã¾ãªã„', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userBlockingAlice, { text: `${keyword}` }) }, + ], + }, + { + label: 'ミュートã—ã¦ã„るユーザーã®ãƒŽãƒ¼ãƒˆã¯å«ã¾ãªã„', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userMutedByAlice, { text: `${keyword}` }) }, + ], + }, + { + label: 'ミュートã•ã‚Œã¦ã„るユーザーã®ãƒŽãƒ¼ãƒˆã¯å«ã‚€', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userMutingAlice, { text: `${keyword}` }), included: true }, + ], + }, + { + label: '「見ã¤ã‘ã‚„ã™ãã™ã‚‹ã€ãŒOFFã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆã‚‚å«ã¾ã‚Œã‚‹', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userNotExplorable, { text: `${keyword}` }), included: true }, + ], + }, + { + label: 'éµä»˜ãユーザーã®ãƒŽãƒ¼ãƒˆã‚‚å«ã¾ã‚Œã‚‹', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userLocking, { text: `${keyword}` }), included: true }, + ], + }, + { + label: 'サイレンスã®ãƒŽãƒ¼ãƒˆã‚‚å«ã¾ã‚Œã‚‹', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userSilenced, { text: `${keyword}` }), included: true }, + ], + }, + { + label: '削除ユーザーã®ãƒŽãƒ¼ãƒˆã‚‚å«ã¾ã‚Œã‚‹', + parameters: (): object => ({}), + posts: [ + { note: (): Promise<Note> => post(userDeletedBySelf, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(userDeletedByAdmin, { text: `${keyword}` }), included: true }, + ], + }, + { + label: 'ユーザー指定ã§', + parameters: (): object => ({ src: 'users', users: [`@${bob.username}`, `@${carol.username}`] }), + posts: [ + { note: (): Promise<Note> => post(alice, { text: `test ${keyword}` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true }, + ], + }, + { + label: 'リスト指定ã§', + parameters: (): object => ({ src: 'list', userListId: aliceList.id }), + posts: [ + { note: (): Promise<Note> => post(alice, { text: `test ${keyword}` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true }, + ], + }, + { + label: 'CWã«ã‚‚マッãƒã™ã‚‹', + parameters: (): object => ({ keywords: [[keyword]] }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: 'test', cw: `cw ${keyword}` }), included: true }, + ], + }, + { + label: 'ã‚ーワード1ã¤', + parameters: (): object => ({ keywords: [[keyword]] }), + posts: [ + { note: (): Promise<Note> => post(alice, { text: 'test' }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(carol, { text: 'test' }) }, + ], + }, + { + label: 'ã‚ーワード3ã¤(AND)', + parameters: (): object => ({ keywords: [['A', 'B', 'C']] }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: 'test A' }) }, + { note: (): Promise<Note> => post(bob, { text: 'test A B' }) }, + { note: (): Promise<Note> => post(bob, { text: 'test B C' }) }, + { note: (): Promise<Note> => post(bob, { text: 'test A B C' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'test C B A A B C' }), included: true }, + ], + }, + { + label: 'ã‚ーワード3ã¤(OR)', + parameters: (): object => ({ keywords: [['A'], ['B'], ['C']] }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: 'test' }) }, + { note: (): Promise<Note> => post(bob, { text: 'test A' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'test A B' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'test B C' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'test B C A' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'test C B' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'test C' }), included: true }, + ], + }, + { + label: '除外ワード3ã¤(AND)', + parameters: (): object => ({ excludeKeywords: [['A', 'B', 'C']] }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} A` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} A B` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C A` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} C B` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} C` }), included: true }, + ], + }, + { + label: '除外ワード3ã¤(OR)', + parameters: (): object => ({ excludeKeywords: [['A'], ['B'], ['C']] }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} A` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} A B` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C A` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} C B` }) }, + { note: (): Promise<Note> => post(bob, { text: `test ${keyword} C` }) }, + ], + }, + { + label: 'ã‚ーワード1ã¤(大文å—å°æ–‡å—区別ã™ã‚‹)', + parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: true }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: 'keyword' }) }, + { note: (): Promise<Note> => post(bob, { text: 'kEyWoRd' }) }, + { note: (): Promise<Note> => post(bob, { text: 'KEYWORD' }), included: true }, + ], + }, + { + label: 'ã‚ーワード1ã¤(大文å—å°æ–‡å—区別ã—ãªã„)', + parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: false }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: 'keyword' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'kEyWoRd' }), included: true }, + { note: (): Promise<Note> => post(bob, { text: 'KEYWORD' }), included: true }, + ], + }, + { + label: '除外ワード1ã¤(大文å—å°æ–‡å—区別ã™ã‚‹)', + parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: true }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword} keyword` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword} kEyWoRd` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword} KEYWORD` }) }, + ], + }, + { + label: '除外ワード1ã¤(大文å—å°æ–‡å—区別ã—ãªã„)', + parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: false }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword} keyword` }) }, + { note: (): Promise<Note> => post(bob, { text: `${keyword} kEyWoRd` }) }, + { note: (): Promise<Note> => post(bob, { text: `${keyword} KEYWORD` }) }, + ], + }, + { + label: '添付ファイルをå•ã‚ãªã„', + parameters: (): object => ({ withFile: false }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true }, + ], + }, + { + label: '添付ファイル付ãã®ã¿', + parameters: (): object => ({ withFile: true }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword}` }) }, + ], + }, + { + label: 'リプライ以外', + parameters: (): object => ({ withReplies: false }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `${keyword}`, replyId: alicePost.id }) }, + { note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true }, + ], + }, + { + label: 'リプライもå«ã‚€', + parameters: (): object => ({ withReplies: true }), + posts: [ + { note: (): Promise<Note> => post(bob, { text: `${keyword}`, replyId: alicePost.id }), included: true }, + { note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true }, + ], + }, + ])('ãŒå–å¾—ã§ãã‚‹ã“ã¨ï¼ˆ$label)', async ({ parameters, posts }) => { + const antenna = await successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, keywords: [[keyword]], ...parameters() }, + user: alice, + }); + + const notes = await posts.reduce(async (prev, current) => { + // includedã«é–¢ã‚らãšnote()ã¯è©•ä¾¡ã—ã¦æŠ•ç¨¿ã™ã‚‹ã€‚ + const p = await prev; + const n = await current.note(); + if (current.included) return p.concat(n); + return p; + }, Promise.resolve([] as Note[])); + + // alice視点ã§Noteã‚’å–ã‚Šç›´ã™ + const expected = await Promise.all(notes.reverse().map(s => successfulApiCall({ + endpoint: 'notes/show', + parameters: { noteId: s.id }, + user: alice, + }))); + + const response = await successfulApiCall({ + endpoint: 'antennas/notes', + parameters: { antennaId: antenna.id }, + user: alice, + }); + assert.deepStrictEqual( + response.map(({ userId, id, text }) => ({ userId, id, text })), + expected.map(({ userId, id, text }) => ({ userId, id, text }))); + assert.deepStrictEqual(response, expected); + }); + + test.skip('ãŒå–å¾—ã§ãã€æ—¥ä»˜æŒ‡å®šã®Paginationã«ä¸€è²«æ€§ãŒã‚ã‚‹ã“ã¨', async () => { }); + test.each([ + { label: 'ID指定', offsetBy: 'id' }, + + // BUG sinceDate, untilDateã¯sinceIdã‚„ä»–ã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã¨ã¯ç•°ãªã‚Šã€ãã®æ™‚刻ã«ä¸€è‡´ã™ã‚‹ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’å«ã‚“ã§ã—ã¾ã†ã€‚ + // { label: '日付指定', offsetBy: 'createdAt' }, + ] as const)('ãŒå–å¾—ã§ãã€$labelã®Paginationã«ä¸€è²«æ€§ãŒã‚ã‚‹ã“ã¨', async ({ offsetBy }) => { + const antenna = await successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, keywords: [[keyword]] }, + user: alice, + }); + const notes = await [...Array(30)].reduce(async (prev, current, index) => { + const p = await prev; + const n = await post(alice, { text: `${keyword} (${index})` }); + return [n].concat(p); + }, Promise.resolve([] as Note[])); + + // antennas/notesã¯é™é †ã®ã¿ã§ã€æ˜‡é †ã‚’サãƒãƒ¼ãƒˆã—ãªã„。 + await testPaginationConsistency(notes, async (paginationParam) => { + return successfulApiCall({ + endpoint: 'antennas/notes', + parameters: { antennaId: antenna.id, ...paginationParam }, + user: alice, + }) as any as Note[]; + }, offsetBy, 'desc'); + }); + + // BUG 7æ—¥éŽãŽã‚‹ã¨ä½œã‚Šç›´ã™ã—ã‹ãªã„。 https://github.com/misskey-dev/misskey/issues/10476 + test.todo('ã‚’å–å¾—ã—ãŸã¨ãActiveã«æˆ»ã‚‹'); + + //#endregion + }); +}); diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index a7f8210c8e2dbe84a72c2ae4f7d34385d3979790..02684c93b888dd4e7bb0f4e93eb7e6617faddfbd 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -43,7 +43,6 @@ describe('ユーザー', () => { type MeDetailed = UserDetailedNotMe & misskey.entities.MeDetailed & { - showTimelineReplies: boolean, achievements: object[], loggedInDays: number, policies: object, @@ -160,7 +159,6 @@ describe('ユーザー', () => { mutedInstances: user.mutedInstances, mutingNotificationTypes: user.mutingNotificationTypes, emailNotificationTypes: user.emailNotificationTypes, - showTimelineReplies: user.showTimelineReplies, achievements: user.achievements, loggedInDays: user.loggedInDays, policies: user.policies, @@ -406,7 +404,6 @@ describe('ユーザー', () => { assert.deepStrictEqual(response.mutedInstances, []); assert.deepStrictEqual(response.mutingNotificationTypes, []); assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest']); - assert.strictEqual(response.showTimelineReplies, false); assert.deepStrictEqual(response.achievements, []); assert.deepStrictEqual(response.loggedInDays, 0); assert.deepStrictEqual(response.policies, DEFAULT_POLICIES); @@ -470,8 +467,6 @@ describe('ユーザー', () => { { parameters: (): object => ({ isBot: false }) }, { parameters: (): object => ({ isCat: true }) }, { parameters: (): object => ({ isCat: false }) }, - { parameters: (): object => ({ showTimelineReplies: true }) }, - { parameters: (): object => ({ showTimelineReplies: false }) }, { parameters: (): object => ({ injectFeaturedNote: true }) }, { parameters: (): object => ({ injectFeaturedNote: false }) }, { parameters: (): object => ({ receiveAnnouncementEmail: true }) }, diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 6b31e68616afb861204bcdade8ff2a9485014149..a7bcd859aef1a5c37a9ba3eecff40b5c1e8e6f90 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -52,11 +52,7 @@ export class MockResolver extends Resolver { const r = this._rs.get(value); if (!r) { - throw { - name: 'StatusError', - statusCode: 404, - message: 'Not registed for mock', - }; + throw new Error('Not registed for mock'); } const object = JSON.parse(r.content); diff --git a/packages/backend/test/unit/ReactionService.ts b/packages/backend/test/unit/ReactionService.ts index 38db081ac089ca359bc82199d2daba590895eaef..aa68f4117d93c2f32dfefe81ee4aa68f0624f181 100644 --- a/packages/backend/test/unit/ReactionService.ts +++ b/packages/backend/test/unit/ReactionService.ts @@ -15,78 +15,74 @@ describe('ReactionService', () => { reactionService = app.get<ReactionService>(ReactionService); }); - describe('toDbReaction', () => { + describe('normalize', () => { test('絵文å—リアクションã¯ãã®ã¾ã¾', async () => { - assert.strictEqual(await reactionService.toDbReaction('ðŸ‘'), 'ðŸ‘'); - assert.strictEqual(await reactionService.toDbReaction('ðŸ…'), 'ðŸ…'); + assert.strictEqual(await reactionService.normalize('ðŸ‘'), 'ðŸ‘'); + assert.strictEqual(await reactionService.normalize('ðŸ…'), 'ðŸ…'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ pudding', async () => { - assert.strictEqual(await reactionService.toDbReaction('pudding'), 'ðŸ®'); + assert.strictEqual(await reactionService.normalize('pudding'), 'ðŸ®'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ like', async () => { - assert.strictEqual(await reactionService.toDbReaction('like'), 'ðŸ‘'); + assert.strictEqual(await reactionService.normalize('like'), 'ðŸ‘'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ love', async () => { - assert.strictEqual(await reactionService.toDbReaction('love'), 'â¤'); + assert.strictEqual(await reactionService.normalize('love'), 'â¤'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ laugh', async () => { - assert.strictEqual(await reactionService.toDbReaction('laugh'), '😆'); + assert.strictEqual(await reactionService.normalize('laugh'), '😆'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ hmm', async () => { - assert.strictEqual(await reactionService.toDbReaction('hmm'), '🤔'); + assert.strictEqual(await reactionService.normalize('hmm'), '🤔'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ surprise', async () => { - assert.strictEqual(await reactionService.toDbReaction('surprise'), '😮'); + assert.strictEqual(await reactionService.normalize('surprise'), '😮'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ congrats', async () => { - assert.strictEqual(await reactionService.toDbReaction('congrats'), '🎉'); + assert.strictEqual(await reactionService.normalize('congrats'), '🎉'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ angry', async () => { - assert.strictEqual(await reactionService.toDbReaction('angry'), '💢'); + assert.strictEqual(await reactionService.normalize('angry'), '💢'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ confused', async () => { - assert.strictEqual(await reactionService.toDbReaction('confused'), '😥'); + assert.strictEqual(await reactionService.normalize('confused'), '😥'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ rip', async () => { - assert.strictEqual(await reactionService.toDbReaction('rip'), '😇'); + assert.strictEqual(await reactionService.normalize('rip'), '😇'); }); test('æ—¢å˜ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯çµµæ–‡å—化ã™ã‚‹ star', async () => { - assert.strictEqual(await reactionService.toDbReaction('star'), 'â'); + assert.strictEqual(await reactionService.normalize('star'), 'â'); }); test('異体å—セレクタ除去', async () => { - assert.strictEqual(await reactionService.toDbReaction('㊗ï¸'), '㊗'); + assert.strictEqual(await reactionService.normalize('㊗ï¸'), '㊗'); }); test('異体å—セレクタ除去 å¿…è¦ãªã—', async () => { - assert.strictEqual(await reactionService.toDbReaction('㊗'), '㊗'); - }); - - test('fallback - undefined', async () => { - assert.strictEqual(await reactionService.toDbReaction(undefined), 'â¤'); + assert.strictEqual(await reactionService.normalize('㊗'), '㊗'); }); test('fallback - null', async () => { - assert.strictEqual(await reactionService.toDbReaction(null), 'â¤'); + assert.strictEqual(await reactionService.normalize(null), 'â¤'); }); test('fallback - empty', async () => { - assert.strictEqual(await reactionService.toDbReaction(''), 'â¤'); + assert.strictEqual(await reactionService.normalize(''), 'â¤'); }); test('fallback - unknown', async () => { - assert.strictEqual(await reactionService.toDbReaction('unknown'), 'â¤'); + assert.strictEqual(await reactionService.normalize('unknown'), 'â¤'); }); }); }); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 809ed2c66cc15ca09d3a5e7b7d066b642b092118..22f7d81e4e08d8a4060cdbe6ad019484006080e1 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -124,6 +124,13 @@ export const react = async (user: any, note: any, reaction: string): Promise<any }, user); }; +export const userList = async (user: any, userList: any = {}): Promise<any> => { + const res = await api('users/lists/create', { + name: 'test', + }, user); + return res.body; +}; + export const page = async (user: any, page: any = {}): Promise<any> => { const res = await api('pages/create', { alignCenter: false, @@ -380,8 +387,98 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde }; }; +/** + * ã‚ã‚‹APIエンドãƒã‚¤ãƒ³ãƒˆã®PaginationãŒè¤‡æ•°ã®æ¡ä»¶ã§ä¸€è²«ã—ãŸæŒ™å‹•ã§ã‚ã‚‹ã“ã¨ã‚’テストã—ã¾ã™ã€‚ + * (sinceId, untilId, sinceDate, untilDate, offset, limit) + * @param expected 期待値ã¨ãªã‚‹Entityã®ä¸¦ã³ï¼ˆä¾‹ï¼šNote[]ï¼‰æ˜‡é †é™é †ãŒä¸€è‡´ã—ã¦ã„ã‚‹å¿…è¦ãŒã‚ã‚‹ + * @param fetchEntities Entity[]ã‚’è¿”å´ã™ã‚‹ãƒ†ã‚¹ãƒˆå¯¾è±¡ã®APIを呼ã³å‡ºã™é–¢æ•° + * @param offsetBy 何をã‚ーã¨ã—ã¦Paginationã™ã‚‹ã‹ã€‚ + * @param ordering æ˜‡é †ãƒ»é™é † + */ +export async function testPaginationConsistency<Entity extends { id: string, createdAt?: string }>( + expected: Entity[], + fetchEntities: (paginationParam: { + limit?: number, + offset?: number, + sinceId?: string, + untilId?: string, + sinceDate?: number, + untilDate?: number, + }) => Promise<Entity[]>, + offsetBy: 'offset' | 'id' | 'createdAt' = 'id', + ordering: 'desc' | 'asc' = 'desc'): Promise<void> { + const rangeToParam = (p: { limit?: number, until?: Entity, since?: Entity }): object => { + if (offsetBy === 'id') { + return { limit: p.limit, sinceId: p.since?.id, untilId: p.until?.id }; + } else { + const sinceDate = p.since?.createdAt !== undefined ? new Date(p.since.createdAt).getTime() : undefined; + const untilDate = p.until?.createdAt !== undefined ? new Date(p.until.createdAt).getTime() : undefined; + return { limit: p.limit, sinceDate, untilDate }; + } + }; + + for (const limit of [1, 5, 10, 100, undefined]) { + // 1. sinceId/Dateã¨untilId/Dateã§ä¸¡ç«¯ã‚’指定ã—ã¦å–å¾—ã—ãŸçµæžœãŒæœŸå¾…通りã«ãªã£ã¦ã„ã‚‹ã“㨠+ if (ordering === 'desc') { + const end = expected[expected.length - 1]; + let last = await fetchEntities(rangeToParam({ limit, since: end })); + const actual: Entity[] = []; + while (last.length !== 0) { + actual.push(...last); + last = await fetchEntities(rangeToParam({ limit, until: last[last.length - 1], since: end })); + } + actual.push(end); + assert.deepStrictEqual( + actual.map(({ id, createdAt }) => id + ':' + createdAt), + expected.map(({ id, createdAt }) => id + ':' + createdAt)); + } + + // 2. sinceId/Date指定+limitã§å–å¾—ã—ã¦ã¤ãªãŽåˆã‚ã›ãŸçµæžœãŒæœŸå¾…通りã«ãªã£ã¦ã„ã‚‹ã“㨠+ if (ordering === 'asc') { + // æ˜‡é †ã«ã—ãŸã¨ãã®å…ˆé (一番å¤ã„ã‚‚ã®)ã‚’ã‚‚ã£ã¦ãる(expected[1]を基準ã«é™é †ã«ã—ã¦0番目) + let last = await fetchEntities({ limit: 1, untilId: expected[1].id }); + const actual: Entity[] = []; + while (last.length !== 0) { + actual.push(...last); + last = await fetchEntities(rangeToParam({ limit, since: last[last.length - 1] })); + } + assert.deepStrictEqual( + actual.map(({ id, createdAt }) => id + ':' + createdAt), + expected.map(({ id, createdAt }) => id + ':' + createdAt)); + } + + // 3. untilId指定+limitã§å–å¾—ã—ã¦ã¤ãªãŽåˆã‚ã›ãŸçµæžœãŒæœŸå¾…通りã«ãªã£ã¦ã„ã‚‹ã“㨠+ if (ordering === 'desc') { + let last = await fetchEntities({ limit }); + const actual: Entity[] = []; + while (last.length !== 0) { + actual.push(...last); + last = await fetchEntities(rangeToParam({ limit, until: last[last.length - 1] })); + } + assert.deepStrictEqual( + actual.map(({ id, createdAt }) => id + ':' + createdAt), + expected.map(({ id, createdAt }) => id + ':' + createdAt)); + } + + // 4. offset指定+limitã§å–å¾—ã—ã¦ã¤ãªãŽåˆã‚ã›ãŸçµæžœãŒæœŸå¾…通りã«ãªã£ã¦ã„ã‚‹ã“㨠+ if (offsetBy === 'offset') { + let last = await fetchEntities({ limit, offset: 0 }); + let offset = limit ?? 10; + const actual: Entity[] = []; + while (last.length !== 0) { + actual.push(...last); + last = await fetchEntities({ limit, offset }); + offset += limit ?? 10; + } + assert.deepStrictEqual( + actual.map(({ id, createdAt }) => id + ':' + createdAt), + expected.map(({ id, createdAt }) => id + ':' + createdAt)); + } + } +} + export async function initTestDb(justBorrow = false, initEntities?: any[]) { - if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; + if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test'); const db = new DataSource({ type: 'postgres', diff --git a/packages/frontend/.eslintrc.js b/packages/frontend/.eslintrc.js index e8e0e57d2abb7978827e9ab78a8f8a1d11935206..24c3ad4b83746335e45dc07e91cb6ef75257fb4a 100644 --- a/packages/frontend/.eslintrc.js +++ b/packages/frontend/.eslintrc.js @@ -56,14 +56,15 @@ module.exports = { 'vue/require-v-for-key': 'warn', 'vue/no-unused-components': 'warn', 'vue/no-unused-vars': 'warn', + 'vue/no-dupe-keys': 'warn', 'vue/valid-v-for': 'warn', 'vue/return-in-computed-property': 'warn', 'vue/no-setup-props-destructure': 'warn', 'vue/max-attributes-per-line': 'off', 'vue/html-self-closing': 'off', 'vue/singleline-html-element-content-newline': 'off', - // (vue/vue3-recommended disabled the autofix for Vue 2 compatibility) - 'vue/v-on-event-hyphenation': ['warn', 'always', { autofix: true }], + 'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true }], + 'vue/attribute-hyphenation': ['error', 'never'], }, globals: { // Node.js diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 7c51d4c00ce432313fd90182624d1426321a46fb..f4424221090fd4e88d33c58f8c2fbbf092a22311 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -397,6 +397,7 @@ function toStories(component: string): string { Promise.all([ glob('src/components/global/*.vue'), glob('src/components/Mk{A,B}*.vue'), + glob('src/components/MkDigitalClock.vue'), glob('src/components/MkGalleryPostPreview.vue'), glob('src/components/MkSignupServerRules.vue'), glob('src/components/MkUserSetupDialog.vue'), diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html index ab694f64fb9876c7aeb014b7717bc4ef948fe11c..f6a9a4875de3ba8b476ba5909498dc73f8de76a1 100644 --- a/packages/frontend/.storybook/preview-head.html +++ b/packages/frontend/.storybook/preview-head.html @@ -1,6 +1,6 @@ <link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous"> <link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous"> -<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.12.0/tabler-icons.min.css"> +<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.21.0/tabler-icons.min.css"> <link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css"> <style> html { diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..3929bf06083a440c508ba5d0349d86a7434a8d64 --- /dev/null +++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts @@ -0,0 +1,597 @@ +import { parse } from 'acorn'; +import { generate } from 'astring'; +import { describe, expect, it } from 'vitest'; +import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name'; +import type * as estree from 'estree'; + +function parseExpression(code: string): estree.Expression { + const program = parse(code, { ecmaVersion: 'latest', sourceType: 'module' }) as unknown as estree.Program; + const statement = program.body[0] as estree.ExpressionStatement; + return statement.expression; +} + +describe(normalizeClass.name, () => { + it('should normalize string', () => { + expect(normalizeClass(parseExpression('"a b c"'))).toBe('a b c'); + }); + it('should trim redundant spaces', () => { + expect(normalizeClass(parseExpression('" a b c "'))).toBe('a b c'); + }); + it('should ignore undefined', () => { + expect(normalizeClass(parseExpression('undefined'))).toBe(''); + }); + it('should ignore non string literals', () => { + expect(normalizeClass(parseExpression('0'))).toBe(''); + expect(normalizeClass(parseExpression('true'))).toBe(''); + expect(normalizeClass(parseExpression('null'))).toBe(''); + expect(normalizeClass(parseExpression('/I.D/'))).toBe(''); + }); + it('should not normalize identifiers', () => { + expect(normalizeClass(parseExpression('EScape'))).toBeNull(); + }); + it('should normalize recursively array', () => { + expect(normalizeClass(parseExpression('["from", ...["Utopia"]]'))).toBe('from Utopia'); + expect(normalizeClass(parseExpression('["from", ...[Utopia]]'))).toBeNull(); + }); + it('should normalize recursively template literal', () => { + expect(normalizeClass(parseExpression('`name ${"shiho"} code ${33}`'))).toBe('name shiho code'); + expect(normalizeClass(parseExpression('`name ${shiho.name} code ${33}`'))).toBeNull(); + }); + it('should normalize recursively binary expression', () => { + expect(normalizeClass(parseExpression('"mirage" + "mirror"'))).toBe('miragemirror'); + expect(normalizeClass(parseExpression('"mirage" + mirror'))).toBeNull(); + }); + it('should normalize recursively object expression', () => { + expect(normalizeClass(parseExpression('({ a: true, b: "c" })'))).toBe('a b'); + expect(normalizeClass(parseExpression('({ a: false, b: "c" })'))).toBe('b'); + expect(normalizeClass(parseExpression('({ a: true, b: c })'))).toBeNull(); + expect(normalizeClass(parseExpression('({ a: true, b: "c", ...({ d: true }) })'))).toBe('a b d'); + expect(normalizeClass(parseExpression('({ a: true, [b]: "c" })'))).toBeNull(); + expect(normalizeClass(parseExpression('({ a: true, b: false, c: !false, d: !!0 })'))).toBe('a c'); + }); +}); + +it('Composition API (standard)', () => { + const ast = parse(` +import { c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js'; +import { M as MkContainer } from './MkContainer-!~{03M}~.js'; +import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js'; +import './photoswipe-!~{003}~.js'; + +const _hoisted_1 = /* @__PURE__ */ createBaseVNode("i", { class: "ti ti-photo" }, null, -1); +const _sfc_main = /* @__PURE__ */ defineComponent({ + __name: "index.photos", + props: { + user: {} + }, + setup(__props) { + const props = __props; + let fetching = ref(true); + let images = ref([]); + function thumbnail(image) { + return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl; + } + onMounted(() => { + const image = [ + "image/jpeg", + "image/webp", + "image/avif", + "image/png", + "image/gif", + "image/apng", + "image/vnd.mozilla.apng" + ]; + api("users/notes", { + userId: props.user.id, + fileType: image, + excludeNsfw: defaultStore.state.nsfw !== "ignore", + limit: 10 + }).then((notes) => { + for (const note of notes) { + for (const file of note.files) { + images.value.push({ + note, + file + }); + } + } + fetching.value = false; + }); + }); + return (_ctx, _cache) => { + const _component_MkLoading = resolveComponent("MkLoading"); + const _component_MkA = resolveComponent("MkA"); + return openBlock(), createBlock(MkContainer, { + "max-height": 300, + foldable: true + }, { + icon: withCtx(() => [ + _hoisted_1 + ]), + header: withCtx(() => [ + createTextVNode(toDisplayString(unref(i18n).ts.images), 1) + ]), + default: withCtx(() => [ + createBaseVNode("div", { + class: normalizeClass(_ctx.$style.root) + }, [ + unref(fetching) ? (openBlock(), createBlock(_component_MkLoading, { key: 0 })) : createCommentVNode("", true), + !unref(fetching) && unref(images).length > 0 ? (openBlock(), createElementBlock("div", { + key: 1, + class: normalizeClass(_ctx.$style.stream) + }, [ + (openBlock(true), createElementBlock(Fragment, null, renderList(unref(images), (image) => { + return openBlock(), createBlock(_component_MkA, { + key: image.note.id + image.file.id, + class: normalizeClass(_ctx.$style.img), + to: unref(notePage)(image.note) + }, { + default: withCtx(() => [ + createVNode(ImgWithBlurhash, { + hash: image.file.blurhash, + src: thumbnail(image.file), + title: image.file.name + }, null, 8, ["hash", "src", "title"]) + ]), + _: 2 + }, 1032, ["class", "to"]); + }), 128)) + ], 2)) : createCommentVNode("", true), + !unref(fetching) && unref(images).length == 0 ? (openBlock(), createElementBlock("p", { + key: 2, + class: normalizeClass(_ctx.$style.empty) + }, toDisplayString(unref(i18n).ts.nothing), 3)) : createCommentVNode("", true) + ], 2) + ]), + _: 1 + }); + }; + } +}); + +const root = "xenMW"; +const stream = "xaZzf"; +const img = "xtA8t"; +const empty = "xhYKj"; +const style0 = { + root: root, + stream: stream, + img: img, + empty: empty +}; + +const cssModules = { + "$style": style0 +}; +const index_photos = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]); + +export { index_photos as default }; +`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' }); + unwindCssModuleClassName(ast); + expect(generate(ast)).toBe(` +import {c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js'; +import {M as MkContainer} from './MkContainer-!~{03M}~.js'; +import {b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode} from './vue-!~{002}~.js'; +import './photoswipe-!~{003}~.js'; +const _hoisted_1 = createBaseVNode("i", { + class: "ti ti-photo" +}, null, -1); +const _sfc_main = defineComponent({ + __name: "index.photos", + props: { + user: {} + }, + setup(__props) { + const props = __props; + let fetching = ref(true); + let images = ref([]); + function thumbnail(image) { + return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl; + } + onMounted(() => { + const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"]; + api("users/notes", { + userId: props.user.id, + fileType: image, + excludeNsfw: defaultStore.state.nsfw !== "ignore", + limit: 10 + }).then(notes => { + for (const note of notes) { + for (const file of note.files) { + images.value.push({ + note, + file + }); + } + } + fetching.value = false; + }); + }); + return (_ctx, _cache) => { + const _component_MkLoading = resolveComponent("MkLoading"); + const _component_MkA = resolveComponent("MkA"); + return (openBlock(), createBlock(MkContainer, { + "max-height": 300, + foldable: true + }, { + icon: withCtx(() => [_hoisted_1]), + header: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).ts.images), 1)]), + default: withCtx(() => [createBaseVNode("div", { + class: "xenMW" + }, [unref(fetching) ? (openBlock(), createBlock(_component_MkLoading, { + key: 0 + })) : createCommentVNode("", true), !unref(fetching) && unref(images).length > 0 ? (openBlock(), createElementBlock("div", { + key: 1, + class: "xaZzf" + }, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(images), image => { + return (openBlock(), createBlock(_component_MkA, { + key: image.note.id + image.file.id, + class: "xtA8t", + to: unref(notePage)(image.note) + }, { + default: withCtx(() => [createVNode(ImgWithBlurhash, { + hash: image.file.blurhash, + src: thumbnail(image.file), + title: image.file.name + }, null, 8, ["hash", "src", "title"])]), + _: 2 + }, 1032, ["class", "to"])); + }), 128))], 2)) : createCommentVNode("", true), !unref(fetching) && unref(images).length == 0 ? (openBlock(), createElementBlock("p", { + key: 2, + class: "xhYKj" + }, toDisplayString(unref(i18n).ts.nothing), 3)) : createCommentVNode("", true)], 2)]), + _: 1 + })); + }; + } +}); +const root = "xenMW"; +const stream = "xaZzf"; +const img = "xtA8t"; +const empty = "xhYKj"; +const style0 = { + root: root, + stream: stream, + img: img, + empty: empty +}; +const cssModules = { + "$style": style0 +}; +const index_photos = _sfc_main; +export {index_photos as default}; +`.slice(1)); +}); + +it('Composition API (with `useCssModule()`)', () => { + const ast = parse(` +import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js'; +import { d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js'; + +function isDebuggerEnabled(id) { + try { + return localStorage.getItem(\`DEBUG_\${id}\`) !== null; + } catch { + return false; + } +} +function stackTraceInstances() { + let instance = getCurrentInstance(); + const stack = []; + while (instance) { + stack.push(instance); + instance = instance.parent; + } + return stack; +} + +const _sfc_main = defineComponent({ + props: { + items: { + type: Array, + required: true + }, + direction: { + type: String, + required: false, + default: "down" + }, + reversed: { + type: Boolean, + required: false, + default: false + }, + noGap: { + type: Boolean, + required: false, + default: false + }, + ad: { + type: Boolean, + required: false, + default: false + } + }, + setup(props, { slots, expose }) { + const $style = useCssModule(); + function getDateText(time) { + const date = new Date(time).getDate(); + const month = new Date(time).getMonth() + 1; + return i18n.t("monthAndDay", { + month: month.toString(), + day: date.toString() + }); + } + if (props.items.length === 0) + return; + const renderChildrenImpl = () => props.items.map((item, i) => { + if (!slots || !slots.default) + return; + const el = slots.default({ + item + })[0]; + if (el.key == null && item.id) + el.key = item.id; + if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) { + const separator = h("div", { + class: $style["separator"], + key: item.id + ":separator" + }, h("p", { + class: $style["date"] + }, [ + h("span", { + class: $style["date-1"] + }, [ + h("i", { + class: \`ti ti-chevron-up \${$style["date-1-icon"]}\` + }), + getDateText(item.createdAt) + ]), + h("span", { + class: $style["date-2"] + }, [ + getDateText(props.items[i + 1].createdAt), + h("i", { + class: \`ti ti-chevron-down \${$style["date-2-icon"]}\` + }) + ]) + ])); + return [el, separator]; + } else { + if (props.ad && item._shouldInsertAd_) { + return [h(MkAd, { + key: item.id + ":ad", + prefer: ["horizontal", "horizontal-big"] + }), el]; + } else { + return el; + } + } + }); + const renderChildren = () => { + const children = renderChildrenImpl(); + if (isDebuggerEnabled(6864)) { + const nodes = children.flatMap((node) => node ?? []); + const keys = new Set(nodes.map((node) => node.key)); + if (keys.size !== nodes.length) { + const id = crypto.randomUUID(); + const instances = stackTraceInstances(); + toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`)); + console.warn({ id, debugId: 6864, stack: instances }); + } + } + return children; + }; + function onBeforeLeave(el) { + el.style.top = \`\${el.offsetTop}px\`; + el.style.left = \`\${el.offsetLeft}px\`; + } + function onLeaveCanceled(el) { + el.style.top = ""; + el.style.left = ""; + } + return () => h( + defaultStore.state.animation ? TransitionGroup : "div", + { + class: { + [$style["date-separated-list"]]: true, + [$style["date-separated-list-nogap"]]: props.noGap, + [$style["reversed"]]: props.reversed, + [$style["direction-down"]]: props.direction === "down", + [$style["direction-up"]]: props.direction === "up" + }, + ...defaultStore.state.animation ? { + name: "list", + tag: "div", + onBeforeLeave, + onLeaveCanceled + } : {} + }, + { default: renderChildren } + ); + } +}); + +const reversed = "xxiZh"; +const separator = "xxeDx"; +const date = "xxawD"; +const style0 = { + "date-separated-list": "xfKPa", + "date-separated-list-nogap": "xf9zr", + "direction-up": "x7AeO", + "direction-down": "xBIqc", + reversed: reversed, + separator: separator, + date: date, + "date-1": "xwtmh", + "date-1-icon": "xsNPa", + "date-2": "x1xvw", + "date-2-icon": "x9ZiG" +}; + +const cssModules = { + "$style": style0 +}; +const MkDateSeparatedList = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]); + +export { MkDateSeparatedList as M }; +`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' }); + unwindCssModuleClassName(ast); + expect(generate(ast)).toBe(` +import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js'; +import {d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js'; +function isDebuggerEnabled(id) { + try { + return localStorage.getItem(\`DEBUG_\${id}\`) !== null; + } catch { + return false; + } +} +function stackTraceInstances() { + let instance = getCurrentInstance(); + const stack = []; + while (instance) { + stack.push(instance); + instance = instance.parent; + } + return stack; +} +const _sfc_main = defineComponent({ + props: { + items: { + type: Array, + required: true + }, + direction: { + type: String, + required: false, + default: "down" + }, + reversed: { + type: Boolean, + required: false, + default: false + }, + noGap: { + type: Boolean, + required: false, + default: false + }, + ad: { + type: Boolean, + required: false, + default: false + } + }, + setup(props, {slots, expose}) { + const $style = useCssModule(); + function getDateText(time) { + const date = new Date(time).getDate(); + const month = new Date(time).getMonth() + 1; + return i18n.t("monthAndDay", { + month: month.toString(), + day: date.toString() + }); + } + if (props.items.length === 0) return; + const renderChildrenImpl = () => props.items.map((item, i) => { + if (!slots || !slots.default) return; + const el = slots.default({ + item + })[0]; + if (el.key == null && item.id) el.key = item.id; + if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) { + const separator = h("div", { + class: $style["separator"], + key: item.id + ":separator" + }, h("p", { + class: $style["date"] + }, [h("span", { + class: $style["date-1"] + }, [h("i", { + class: \`ti ti-chevron-up \${$style["date-1-icon"]}\` + }), getDateText(item.createdAt)]), h("span", { + class: $style["date-2"] + }, [getDateText(props.items[i + 1].createdAt), h("i", { + class: \`ti ti-chevron-down \${$style["date-2-icon"]}\` + })])])); + return [el, separator]; + } else { + if (props.ad && item._shouldInsertAd_) { + return [h(MkAd, { + key: item.id + ":ad", + prefer: ["horizontal", "horizontal-big"] + }), el]; + } else { + return el; + } + } + }); + const renderChildren = () => { + const children = renderChildrenImpl(); + if (isDebuggerEnabled(6864)) { + const nodes = children.flatMap(node => node ?? []); + const keys = new Set(nodes.map(node => node.key)); + if (keys.size !== nodes.length) { + const id = crypto.randomUUID(); + const instances = stackTraceInstances(); + toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`)); + console.warn({ + id, + debugId: 6864, + stack: instances + }); + } + } + return children; + }; + function onBeforeLeave(el) { + el.style.top = \`\${el.offsetTop}px\`; + el.style.left = \`\${el.offsetLeft}px\`; + } + function onLeaveCanceled(el) { + el.style.top = ""; + el.style.left = ""; + } + return () => h(defaultStore.state.animation ? TransitionGroup : "div", { + class: { + [$style["date-separated-list"]]: true, + [$style["date-separated-list-nogap"]]: props.noGap, + [$style["reversed"]]: props.reversed, + [$style["direction-down"]]: props.direction === "down", + [$style["direction-up"]]: props.direction === "up" + }, + ...defaultStore.state.animation ? { + name: "list", + tag: "div", + onBeforeLeave, + onLeaveCanceled + } : {} + }, { + default: renderChildren + }); + } +}); +const reversed = "xxiZh"; +const separator = "xxeDx"; +const date = "xxawD"; +const style0 = { + "date-separated-list": "xfKPa", + "date-separated-list-nogap": "xf9zr", + "direction-up": "x7AeO", + "direction-down": "xBIqc", + reversed: reversed, + separator: separator, + date: date, + "date-1": "xwtmh", + "date-1-icon": "xsNPa", + "date-2": "x1xvw", + "date-2-icon": "x9ZiG" +}; +const cssModules = { + "$style": style0 +}; +const MkDateSeparatedList = _export_sfc(_sfc_main, [["__cssModules", cssModules]]); +export {MkDateSeparatedList as M}; +`.slice(1)); +}); diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts new file mode 100644 index 0000000000000000000000000000000000000000..a18f0d90494d432facd03362e53100d57ef9085d --- /dev/null +++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts @@ -0,0 +1,275 @@ +import { generate } from 'astring'; +import * as estree from 'estree'; +import { walk } from '../node_modules/estree-walker/src/index.js'; +import type * as estreeWalker from 'estree-walker'; +import type { Plugin } from 'vite'; + +function isFalsyIdentifier(identifier: estree.Identifier): boolean { + return identifier.name === 'undefined' || identifier.name === 'NaN'; +} + +function normalizeClassWalker(tree: estree.Node): string | null { + if (tree.type === 'Identifier') return isFalsyIdentifier(tree) ? '' : null; + if (tree.type === 'Literal') return typeof tree.value === 'string' ? tree.value : ''; + if (tree.type === 'BinaryExpression') { + if (tree.operator !== '+') return null; + const left = normalizeClassWalker(tree.left); + const right = normalizeClassWalker(tree.right); + if (left === null || right === null) return null; + return `${left}${right}`; + } + if (tree.type === 'TemplateLiteral') { + if (tree.expressions.some((x) => x.type !== 'Literal' && (x.type !== 'Identifier' || !isFalsyIdentifier(x)))) return null; + return tree.quasis.reduce((a, c, i) => { + const v = i === tree.quasis.length - 1 ? '' : (tree.expressions[i] as Partial<estree.Literal>).value; + return a + c.value.raw + (typeof v === 'string' ? v : ''); + }, ''); + } + if (tree.type === 'ArrayExpression') { + const values = tree.elements.map((treeNode) => { + if (treeNode === null) return ''; + if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument); + return normalizeClassWalker(treeNode); + }); + if (values.some((x) => x === null)) return null; + return values.join(' '); + } + if (tree.type === 'ObjectExpression') { + const values = tree.properties.map((treeNode) => { + if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument); + let x = treeNode.value; + let inveted = false; + while (x.type === 'UnaryExpression' && x.operator === '!') { + x = x.argument; + inveted = !inveted; + } + if (x.type === 'Literal') { + if (inveted === !x.value) { + return treeNode.key.type === 'Identifier' ? treeNode.computed ? null : treeNode.key.name : treeNode.key.type === 'Literal' ? treeNode.key.value : ''; + } else { + return ''; + } + } + if (x.type === 'Identifier') { + if (inveted !== isFalsyIdentifier(x)) { + return ''; + } else { + return null; + } + } + return null; + }); + if (values.some((x) => x === null)) return null; + return values.join(' '); + } + console.error(`Unexpected node type: ${tree.type}`); + return null; +} + +export function normalizeClass(tree: estree.Node): string | null { + const walked = normalizeClassWalker(tree); + return walked && walked.replace(/^\s+|\s+(?=\s)|\s+$/g, ''); +} + +export function unwindCssModuleClassName(ast: estree.Node): void { + (walk as typeof estreeWalker.walk)(ast, { + enter(node, parent): void { + if (parent?.type !== 'Program') return; + if (node.type !== 'VariableDeclaration') return; + if (node.declarations.length !== 1) return; + if (node.declarations[0].id.type !== 'Identifier') return; + const name = node.declarations[0].id.name; + if (node.declarations[0].init?.type !== 'CallExpression') return; + if (node.declarations[0].init.callee.type !== 'Identifier') return; + if (node.declarations[0].init.callee.name !== '_export_sfc') return; + if (node.declarations[0].init.arguments.length !== 2) return; + if (node.declarations[0].init.arguments[0].type !== 'Identifier') return; + const ident = node.declarations[0].init.arguments[0].name; + if (!ident.startsWith('_sfc_main')) return; + if (node.declarations[0].init.arguments[1].type !== 'ArrayExpression') return; + if (node.declarations[0].init.arguments[1].elements.length === 0) return; + const __cssModulesIndex = node.declarations[0].init.arguments[1].elements.findIndex((x) => { + if (x?.type !== 'ArrayExpression') return false; + if (x.elements.length !== 2) return false; + if (x.elements[0]?.type !== 'Literal') return false; + if (x.elements[0].value !== '__cssModules') return false; + if (x.elements[1]?.type !== 'Identifier') return false; + return true; + }); + if (!~__cssModulesIndex) return; + const cssModuleForestName = ((node.declarations[0].init.arguments[1].elements[__cssModulesIndex] as estree.ArrayExpression).elements[1] as estree.Identifier).name; + const cssModuleForestNode = parent.body.find((x) => { + if (x.type !== 'VariableDeclaration') return false; + if (x.declarations.length !== 1) return false; + if (x.declarations[0].id.type !== 'Identifier') return false; + if (x.declarations[0].id.name !== cssModuleForestName) return false; + if (x.declarations[0].init?.type !== 'ObjectExpression') return false; + return true; + }) as unknown as estree.VariableDeclaration; + const moduleForest = new Map((cssModuleForestNode.declarations[0].init as estree.ObjectExpression).properties.flatMap((property) => { + if (property.type !== 'Property') return []; + if (property.key.type !== 'Literal') return []; + if (property.value.type !== 'Identifier') return []; + return [[property.key.value as string, property.value.name as string]]; + })); + const sfcMain = parent.body.find((x) => { + if (x.type !== 'VariableDeclaration') return false; + if (x.declarations.length !== 1) return false; + if (x.declarations[0].id.type !== 'Identifier') return false; + if (x.declarations[0].id.name !== ident) return false; + return true; + }) as unknown as estree.VariableDeclaration; + if (sfcMain.declarations[0].init?.type !== 'CallExpression') return; + if (sfcMain.declarations[0].init.callee.type !== 'Identifier') return; + if (sfcMain.declarations[0].init.callee.name !== 'defineComponent') return; + if (sfcMain.declarations[0].init.arguments.length !== 1) return; + if (sfcMain.declarations[0].init.arguments[0].type !== 'ObjectExpression') return; + const setup = sfcMain.declarations[0].init.arguments[0].properties.find((x) => { + if (x.type !== 'Property') return false; + if (x.key.type !== 'Identifier') return false; + if (x.key.name !== 'setup') return false; + return true; + }) as unknown as estree.Property; + if (setup.value.type !== 'FunctionExpression') return; + const render = setup.value.body.body.find((x) => { + if (x.type !== 'ReturnStatement') return false; + return true; + }) as unknown as estree.ReturnStatement; + if (render.argument?.type !== 'ArrowFunctionExpression') return; + if (render.argument.params.length !== 2) return; + const ctx = render.argument.params[0]; + if (ctx.type !== 'Identifier') return; + if (ctx.name !== '_ctx') return; + if (render.argument.body.type !== 'BlockStatement') return; + for (const [key, value] of moduleForest) { + const cssModuleTreeNode = parent.body.find((x) => { + if (x.type !== 'VariableDeclaration') return false; + if (x.declarations.length !== 1) return false; + if (x.declarations[0].id.type !== 'Identifier') return false; + if (x.declarations[0].id.name !== value) return false; + return true; + }) as unknown as estree.VariableDeclaration; + if (cssModuleTreeNode.declarations[0].init?.type !== 'ObjectExpression') return; + const moduleTree = new Map(cssModuleTreeNode.declarations[0].init.properties.flatMap((property) => { + if (property.type !== 'Property') return []; + const actualKey = property.key.type === 'Identifier' ? property.key.name : property.key.type === 'Literal' ? property.key.value : null; + if (typeof actualKey !== 'string') return []; + if (property.value.type === 'Literal') return [[actualKey, property.value.value as string]]; + if (property.value.type !== 'Identifier') return []; + const labelledValue = property.value.name; + const actualValue = parent.body.find((x) => { + if (x.type !== 'VariableDeclaration') return false; + if (x.declarations.length !== 1) return false; + if (x.declarations[0].id.type !== 'Identifier') return false; + if (x.declarations[0].id.name !== labelledValue) return false; + return true; + }) as unknown as estree.VariableDeclaration; + if (actualValue.declarations[0].init?.type !== 'Literal') return []; + return [[actualKey, actualValue.declarations[0].init.value as string]]; + })); + (walk as typeof estreeWalker.walk)(render.argument.body, { + enter(childNode) { + if (childNode.type !== 'MemberExpression') return; + if (childNode.object.type !== 'MemberExpression') return; + if (childNode.object.object.type !== 'Identifier') return; + if (childNode.object.object.name !== ctx.name) return; + if (childNode.object.property.type !== 'Identifier') return; + if (childNode.object.property.name !== key) return; + if (childNode.property.type !== 'Identifier') return; + const actualValue = moduleTree.get(childNode.property.name); + if (actualValue === undefined) return; + this.replace({ + type: 'Literal', + value: actualValue, + }); + }, + }); + (walk as typeof estreeWalker.walk)(render.argument.body, { + enter(childNode) { + if (childNode.type !== 'MemberExpression') return; + if (childNode.object.type !== 'MemberExpression') return; + if (childNode.object.object.type !== 'Identifier') return; + if (childNode.object.object.name !== ctx.name) return; + if (childNode.object.property.type !== 'Identifier') return; + if (childNode.object.property.name !== key) return; + if (childNode.property.type !== 'Identifier') return; + console.error(`Undefined style detected: ${key}.${childNode.property.name} (in ${name})`); + this.replace({ + type: 'Identifier', + name: 'undefined', + }); + }, + }); + (walk as typeof estreeWalker.walk)(render.argument.body, { + enter(childNode) { + if (childNode.type !== 'CallExpression') return; + if (childNode.callee.type !== 'Identifier') return; + if (childNode.callee.name !== 'normalizeClass') return; + if (childNode.arguments.length !== 1) return; + const normalized = normalizeClass(childNode.arguments[0]); + if (normalized === null) return; + this.replace({ + type: 'Literal', + value: normalized, + }); + }, + }); + } + if (node.declarations[0].init.arguments[1].elements.length === 1) { + this.replace({ + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: node.declarations[0].id.name, + }, + init: { + type: 'Identifier', + name: ident, + }, + }], + kind: 'const', + }); + } else { + this.replace({ + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: node.declarations[0].id.name, + }, + init: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '_export_sfc', + }, + arguments: [{ + type: 'Identifier', + name: ident, + }, { + type: 'ArrayExpression', + elements: node.declarations[0].init.arguments[1].elements.slice(0, __cssModulesIndex).concat(node.declarations[0].init.arguments[1].elements.slice(__cssModulesIndex + 1)), + }], + }, + }], + kind: 'const', + }); + } + }, + }); +} + +// eslint-disable-next-line import/no-default-export +export default function pluginUnwindCssModuleClassName(): Plugin { + return { + name: 'UnwindCssModuleClassName', + renderChunk(code): { code: string } { + const ast = this.parse(code) as unknown as estree.Node; + unwindCssModuleClassName(ast); + return { code: generate(ast) }; + }, + }; +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 5b4004d8e3aab9893af96fafc3bf1ae39f459651..506d1879010134dbf79f99575ccc417e8ca75351 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -19,26 +19,28 @@ "@rollup/plugin-json": "6.0.0", "@rollup/plugin-replace": "5.0.2", "@rollup/pluginutils": "5.0.2", - "@syuilo/aiscript": "0.13.2", - "@tabler/icons-webfont": "2.17.0", - "@vitejs/plugin-vue": "4.2.2", - "@vue-macros/reactivity-transform": "0.3.6", - "@vue/compiler-sfc": "3.3.1", - "autosize": "5.0.2", - "blurhash": "2.0.5", - "broadcast-channel": "4.20.2", + "@syuilo/aiscript": "0.13.3", + "@tabler/icons-webfont": "2.21.0", + "@vitejs/plugin-vue": "4.2.3", + "@vue-macros/reactivity-transform": "0.3.9", + "@vue/compiler-sfc": "3.3.4", + "astring": "1.8.6", + "autosize": "6.0.1", + "broadcast-channel": "5.1.0", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", + "buraha": "github:misskey-dev/buraha", "canvas-confetti": "1.6.0", "chart.js": "4.3.0", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "6.17.4", + "chromatic": "6.18.0", "compare-versions": "5.0.3", "cropperjs": "2.0.0-beta.2", "date-fns": "2.30.0", "escape-regexp": "0.0.1", + "estree-walker": "^3.0.3", "eventemitter3": "5.0.1", "gsap": "3.11.5", "idb-keyval": "6.2.1", @@ -53,7 +55,7 @@ "punycode": "2.3.0", "querystring": "0.2.1", "rndstr": "1.0.0", - "rollup": "3.21.6", + "rollup": "3.23.0", "s-age": "1.1.2", "sanitize-html": "2.10.0", "sass": "1.62.1", @@ -61,71 +63,70 @@ "strict-event-emitter-types": "2.0.0", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.151.3", + "three": "0.153.0", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tsc-alias": "1.8.6", "tsconfig-paths": "4.2.0", "twemoji-parser": "14.0.0", - "typescript": "5.0.4", + "typescript": "5.1.3", "uuid": "9.0.0", "vanilla-tilt": "1.8.0", - "vite": "4.3.5", - "vue": "3.3.1", - "vue-plyr": "7.0.0", + "vite": "4.3.9", + "vue": "3.3.4", "vue-prism-editor": "2.0.0-alpha.2", "vuedraggable": "next" }, "devDependencies": { - "@storybook/addon-actions": "7.0.10", - "@storybook/addon-essentials": "7.0.10", - "@storybook/addon-interactions": "7.0.10", - "@storybook/addon-links": "7.0.10", - "@storybook/addon-storysource": "7.0.10", - "@storybook/addons": "7.0.10", - "@storybook/blocks": "7.0.10", - "@storybook/core-events": "7.0.10", + "@storybook/addon-actions": "7.0.18", + "@storybook/addon-essentials": "7.0.18", + "@storybook/addon-interactions": "7.0.18", + "@storybook/addon-links": "7.0.18", + "@storybook/addon-storysource": "7.0.18", + "@storybook/addons": "7.0.18", + "@storybook/blocks": "7.0.18", + "@storybook/core-events": "7.0.18", "@storybook/jest": "0.1.0", - "@storybook/manager-api": "7.0.10", - "@storybook/preview-api": "7.0.10", - "@storybook/react": "7.0.10", - "@storybook/react-vite": "7.0.10", + "@storybook/manager-api": "7.0.18", + "@storybook/preview-api": "7.0.18", + "@storybook/react": "7.0.18", + "@storybook/react-vite": "7.0.18", "@storybook/testing-library": "0.1.0", - "@storybook/theming": "7.0.10", - "@storybook/types": "7.0.10", - "@storybook/vue3": "7.0.10", - "@storybook/vue3-vite": "7.0.10", + "@storybook/theming": "7.0.18", + "@storybook/types": "7.0.18", + "@storybook/vue3": "7.0.18", + "@storybook/vue3-vite": "7.0.18", "@testing-library/jest-dom": "5.16.5", "@testing-library/vue": "7.0.0", "@types/escape-regexp": "0.0.1", "@types/estree": "1.0.1", "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.2", - "@types/matter-js": "0.18.3", + "@types/matter-js": "0.18.5", "@types/micromatch": "4.0.2", - "@types/node": "20.1.3", + "@types/node": "20.2.5", "@types/punycode": "2.1.0", "@types/sanitize-html": "2.9.0", "@types/seedrandom": "3.0.5", - "@types/testing-library__jest-dom": "^5.14.5", + "@types/testing-library__jest-dom": "^5.14.6", "@types/throttle-debounce": "5.0.0", "@types/tinycolor2": "1.4.3", "@types/uuid": "9.0.1", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.59.5", - "@typescript-eslint/parser": "5.59.5", - "@vitest/coverage-c8": "0.31.0", - "@vue/runtime-core": "3.3.1", - "astring": "1.8.4", + "@typescript-eslint/eslint-plugin": "5.59.8", + "@typescript-eslint/parser": "5.59.8", + "@vitest/coverage-c8": "0.31.4", + "@vue/runtime-core": "3.3.4", + "acorn": "^8.8.2", "chokidar-cli": "3.0.0", "cross-env": "7.0.3", - "cypress": "12.12.0", - "eslint": "8.40.0", + "cypress": "12.13.0", + "eslint": "8.41.0", "eslint-plugin-import": "2.27.5", - "eslint-plugin-vue": "9.12.0", + "eslint-plugin-vue": "9.14.1", "fast-glob": "3.2.12", - "happy-dom": "9.16.0", + "happy-dom": "9.20.3", "micromatch": "3.1.10", "msw": "1.2.1", "msw-storybook-addon": "1.8.0", @@ -133,13 +134,13 @@ "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.0", - "storybook": "7.0.10", + "storybook": "7.0.18", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.2", - "vitest": "0.31.0", + "vitest": "0.31.4", "vitest-fetch-mock": "0.2.2", - "vue-eslint-parser": "9.2.1", - "vue-tsc": "1.6.4" + "vue-eslint-parser": "9.3.0", + "vue-tsc": "1.6.5" } } diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts new file mode 100644 index 0000000000000000000000000000000000000000..921c161765fd1cfcb59fbffe6e5dd2f2a2f83ae4 --- /dev/null +++ b/packages/frontend/src/_boot_.ts @@ -0,0 +1,14 @@ +// https://vitejs.dev/config/build-options.html#build-modulepreload +import 'vite/modulepreload-polyfill'; + +import '@/style.scss'; +import { mainBoot } from './boot/main-boot'; +import { subBoot } from './boot/sub-boot'; + +const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete']; + +if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { + subBoot(); +} else { + mainBoot(); +} diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index 9b104391d7f940729dbc16782d98b3d20e676523..4770f616acb472e347ac90748672aef6505065e8 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -3,11 +3,11 @@ import * as misskey from 'misskey-js'; import { showSuspendedDialog } from './scripts/show-suspended-dialog'; import { i18n } from './i18n'; import { miLocalStorage } from './local-storage'; +import { MenuButton } from './types/menu'; import { del, get, set } from '@/scripts/idb-proxy'; import { apiUrl } from '@/config'; import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; -import { MenuButton } from './types/menu'; // TODO: ä»–ã®ã‚¿ãƒ–ã¨æ°¸ç¶šåŒ–ã•ã‚ŒãŸstateã‚’åŒæœŸ @@ -101,57 +101,57 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr 'Content-Type': 'application/json', }, }) - .then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => { - if (res.status >= 500 && res.status < 600) { + .then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => { + if (res.status >= 500 && res.status < 600) { // サーãƒãƒ¼ã‚¨ãƒ©ãƒ¼(5xx)ã®å ´åˆã‚’rejectã¨ã™ã‚‹ // (èªè¨¼ã‚¨ãƒ©ãƒ¼ãªã©4xxã¯resolve) - return fail2(res); - } - res.json().then(done2, fail2); - })) - .then(async res => { - if (res.error) { - if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { + return fail2(res); + } + res.json().then(done2, fail2); + })) + .then(async res => { + if (res.error) { + if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { // SUSPENDED - if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { - await showSuspendedDialog(); - } - } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') { + if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + await showSuspendedDialog(); + } + } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') { // USER_IS_DELETED // アカウントãŒå‰Šé™¤ã•ã‚Œã¦ã„ã‚‹ - if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { - await alert({ - type: 'error', - title: i18n.ts.accountDeleted, - text: i18n.ts.accountDeletedDescription, - }); - } - } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') { + if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + await alert({ + type: 'error', + title: i18n.ts.accountDeleted, + text: i18n.ts.accountDeletedDescription, + }); + } + } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') { // AUTHENTICATION_FAILED // トークンãŒç„¡åŠ¹åŒ–ã•ã‚Œã¦ã„ãŸã‚Šã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒå‰Šé™¤ã•ã‚ŒãŸã‚Šã—ã¦ã„ã‚‹ - if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + if (forceShowDialog || $i && (token === $i.token || id === $i.id)) { + await alert({ + type: 'error', + title: i18n.ts.tokenRevoked, + text: i18n.ts.tokenRevokedDescription, + }); + } + } else { await alert({ type: 'error', - title: i18n.ts.tokenRevoked, - text: i18n.ts.tokenRevokedDescription, + title: i18n.ts.failedToFetchAccountInformation, + text: JSON.stringify(res.error), }); } + + // rejectã‹ã¤ç†ç”±ãŒtrueã®å ´åˆã€å‰Šé™¤å¯¾è±¡ã§ã‚ã‚‹ã“ã¨ã‚’示㙠+ fail(true); } else { - await alert({ - type: 'error', - title: i18n.ts.failedToFetchAccountInformation, - text: JSON.stringify(res.error), - }); + (res as Account).token = token; + done(res as Account); } - - // rejectã‹ã¤ç†ç”±ãŒtrueã®å ´åˆã€å‰Šé™¤å¯¾è±¡ã§ã‚ã‚‹ã“ã¨ã‚’示㙠- fail(true); - } else { - (res as Account).token = token; - done(res as Account); - } - }) - .catch(fail); + }) + .catch(fail); }); } @@ -305,3 +305,7 @@ export async function openAccountMenu(opts: { }); } } + +if (_DEV_) { + (window as any).$i = $i; +} diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1b12fe7d6aa0f65ac6419d7f1bb4019526a9e92 --- /dev/null +++ b/packages/frontend/src/boot/common.ts @@ -0,0 +1,262 @@ +import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent, App } from 'vue'; +import { compareVersions } from 'compare-versions'; +import widgets from '@/widgets'; +import directives from '@/directives'; +import components from '@/components'; +import { version, ui, lang, updateLocale } from '@/config'; +import { applyTheme } from '@/scripts/theme'; +import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; +import { i18n, updateI18n } from '@/i18n'; +import { confirm, alert, post, popup, toast } from '@/os'; +import { $i, refreshAccount, login, updateAccount, signout } from '@/account'; +import { defaultStore, ColdDeviceStorage } from '@/store'; +import { fetchInstance, instance } from '@/instance'; +import { deviceKind } from '@/scripts/device-kind'; +import { reloadChannel } from '@/scripts/unison-reload'; +import { reactionPicker } from '@/scripts/reaction-picker'; +import { getUrlWithoutLoginId } from '@/scripts/login-id'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; +import { deckStore } from '@/ui/deck/deck-store'; +import { miLocalStorage } from '@/local-storage'; +import { fetchCustomEmojis } from '@/custom-emojis'; +import { mainRouter } from '@/router'; + +export async function common(createVue: () => App<Element>) { + console.info(`Misskey v${version}`); + + if (_DEV_) { + console.warn('Development mode!!!'); + + console.info(`vue ${vueVersion}`); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).$i = $i; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).$store = defaultStore; + + window.addEventListener('error', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled error', + text: event.message + }); + */ + }); + + window.addEventListener('unhandledrejection', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled promise rejection', + text: event.reason + }); + */ + }); + } + + const splash = document.getElementById('splash'); + // 念ã®ãŸã‚nullãƒã‚§ãƒƒã‚¯(HTMLãŒå¤ã„å ´åˆãŒã‚ã‚‹ãŸã‚(ãã®ã†ã¡æ¶ˆã™)) + if (splash) splash.addEventListener('transitionend', () => { + splash.remove(); + }); + + let isClientUpdated = false; + + //#region クライアントãŒæ›´æ–°ã•ã‚ŒãŸã‹ãƒã‚§ãƒƒã‚¯ + const lastVersion = miLocalStorage.getItem('lastVersion'); + if (lastVersion !== version) { + miLocalStorage.setItem('lastVersion', version); + + // テーマリビルドã™ã‚‹ãŸã‚ + miLocalStorage.removeItem('theme'); + + try { // 変ãªãƒãƒ¼ã‚¸ãƒ§ãƒ³æ–‡å—列æ¥ã‚‹ã¨compareVersionsã§ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ãŸã‚ + if (lastVersion != null && compareVersions(version, lastVersion) === 1) { + isClientUpdated = true; + } + } catch (err) { /* empty */ } + } + //#endregion + + //#region Detect language & fetch translations + const localeVersion = miLocalStorage.getItem('localeVersion'); + const localeOutdated = (localeVersion == null || localeVersion !== version); + if (localeOutdated) { + const res = await window.fetch(`/assets/locales/${lang}.${version}.json`); + if (res.status === 200) { + const newLocale = await res.text(); + const parsedNewLocale = JSON.parse(newLocale); + miLocalStorage.setItem('locale', newLocale); + miLocalStorage.setItem('localeVersion', version); + updateLocale(parsedNewLocale); + updateI18n(parsedNewLocale); + } + } + //#endregion + + // タッãƒãƒ‡ãƒã‚¤ã‚¹ã§CSSã®:hoverを機能ã•ã›ã‚‹ + document.addEventListener('touchend', () => {}, { passive: true }); + + // 一斉リãƒãƒ¼ãƒ‰ + reloadChannel.addEventListener('message', path => { + if (path !== null) location.href = path; + else location.reload(); + }); + + // If mobile, insert the viewport meta tag + if (['smartphone', 'tablet'].includes(deviceKind)) { + const viewport = document.getElementsByName('viewport').item(0); + viewport.setAttribute('content', + `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); + } + + //#region Set lang attr + const html = document.documentElement; + html.setAttribute('lang', lang); + //#endregion + + await defaultStore.ready; + await deckStore.ready; + + const fetchInstanceMetaPromise = fetchInstance(); + + fetchInstanceMetaPromise.then(() => { + miLocalStorage.setItem('v', instance.version); + }); + + //#region loginId + const params = new URLSearchParams(location.search); + const loginId = params.get('loginId'); + + if (loginId) { + const target = getUrlWithoutLoginId(location.href); + + if (!$i || $i.id !== loginId) { + const account = await getAccountFromId(loginId); + if (account) { + await login(account.token, target); + } + } + + history.replaceState({ misskey: 'loginId' }, '', target); + } + //#endregion + + // NOTE: ã“ã®å‡¦ç†ã¯å¿…ãšã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆæ›´æ–°ãƒã‚§ãƒƒã‚¯å‡¦ç†ã‚ˆã‚Šå¾Œã«æ¥ã‚‹ã“ã¨(テーマå†æ§‹ç¯‰ã®ãŸã‚) + watch(defaultStore.reactiveState.darkMode, (darkMode) => { + applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); + }, { immediate: miLocalStorage.getItem('theme') == null }); + + const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); + const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); + + watch(darkTheme, (theme) => { + if (defaultStore.state.darkMode) { + applyTheme(theme); + } + }); + + watch(lightTheme, (theme) => { + if (!defaultStore.state.darkMode) { + applyTheme(theme); + } + }); + + //#region Sync dark mode + if (ColdDeviceStorage.get('syncDeviceDarkMode')) { + defaultStore.set('darkMode', isDeviceDarkmode()); + } + + window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { + if (ColdDeviceStorage.get('syncDeviceDarkMode')) { + defaultStore.set('darkMode', mql.matches); + } + }); + //#endregion + + fetchInstanceMetaPromise.then(() => { + if (defaultStore.state.themeInitial) { + if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme)); + if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme)); + defaultStore.set('themeInitial', false); + } + }); + + watch(defaultStore.reactiveState.useBlurEffectForModal, v => { + document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); + }, { immediate: true }); + + watch(defaultStore.reactiveState.useBlurEffect, v => { + if (v) { + document.documentElement.style.removeProperty('--blur'); + } else { + document.documentElement.style.setProperty('--blur', 'none'); + } + }, { immediate: true }); + + //#region Fetch user + if ($i && $i.token) { + if (_DEV_) { + console.log('account cache found. refreshing...'); + } + + refreshAccount(); + } + //#endregion + + try { + await fetchCustomEmojis(); + } catch (err) { /* empty */ } + + const app = createVue(); + + if (_DEV_) { + app.config.performance = true; + } + + widgets(app); + directives(app); + components(app); + + // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 + // ãªãœã‹2回実行ã•ã‚Œã‚‹ã“ã¨ãŒã‚ã‚‹ãŸã‚ã€mountã™ã‚‹divã‚’1ã¤ã«åˆ¶é™ã™ã‚‹ + const rootEl = ((): HTMLElement => { + const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + + const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); + + if (currentRoot) { + console.warn('multiple import detected'); + return currentRoot; + } + + const root = document.createElement('div'); + root.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(root); + return root; + })(); + + app.mount(rootEl); + + // boot.jsã®ã‚„ã¤ã‚’解除 + window.onerror = null; + window.onunhandledrejection = null; + + removeSplash(); + + return { + isClientUpdated, + app, + }; +} + +function removeSplash() { + const splash = document.getElementById('splash'); + if (splash) { + splash.style.opacity = '0'; + splash.style.pointerEvents = 'none'; + } +} diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts new file mode 100644 index 0000000000000000000000000000000000000000..76e8c5072481e1ab1984fb80385efc63c8917932 --- /dev/null +++ b/packages/frontend/src/boot/main-boot.ts @@ -0,0 +1,254 @@ +import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; +import { common } from './common'; +import { version, ui, lang, updateLocale } from '@/config'; +import { i18n, updateI18n } from '@/i18n'; +import { confirm, alert, post, popup, toast } from '@/os'; +import { useStream } from '@/stream'; +import * as sound from '@/scripts/sound'; +import { $i, refreshAccount, login, updateAccount, signout } from '@/account'; +import { defaultStore, ColdDeviceStorage } from '@/store'; +import { makeHotkey } from '@/scripts/hotkey'; +import { reactionPicker } from '@/scripts/reaction-picker'; +import { miLocalStorage } from '@/local-storage'; +import { claimAchievement, claimedAchievements } from '@/scripts/achievements'; +import { mainRouter } from '@/router'; +import { initializeSw } from '@/scripts/initialize-sw'; + +export async function mainBoot() { + const { isClientUpdated } = await common(() => createApp( + new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) : + !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : + ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : + ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : + defineAsyncComponent(() => import('@/ui/universal.vue')), + )); + + reactionPicker.init(); + + if (isClientUpdated && $i) { + popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed'); + } + + const stream = useStream(); + + let reloadDialogShowing = false; + stream.on('_disconnected_', async () => { + if (defaultStore.state.serverDisconnectedBehavior === 'reload') { + location.reload(); + } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { + if (reloadDialogShowing) return; + reloadDialogShowing = true; + const { canceled } = await confirm({ + type: 'warning', + title: i18n.ts.disconnectedFromServer, + text: i18n.ts.reloadConfirm, + }); + reloadDialogShowing = false; + if (!canceled) { + location.reload(); + } + } + }); + + for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { + import('../plugin').then(async ({ install }) => { + // Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740 + await new Promise(r => setTimeout(r, 0)); + install(plugin); + }); + } + + const hotkeys = { + 'd': (): void => { + defaultStore.set('darkMode', !defaultStore.state.darkMode); + }, + 's': (): void => { + mainRouter.push('/search'); + }, + }; + + if ($i) { + // only add post shortcuts if logged in + hotkeys['p|n'] = post; + + defaultStore.loaded.then(() => { + if (defaultStore.state.accountSetupWizard !== -1) { + popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed'); + } + }); + + if ($i.isDeleted) { + alert({ + type: 'warning', + text: i18n.ts.accountDeletionInProgress, + }); + } + + const now = new Date(); + const m = now.getMonth() + 1; + const d = now.getDate(); + + if ($i.birthday) { + const bm = parseInt($i.birthday.split('-')[1]); + const bd = parseInt($i.birthday.split('-')[2]); + if (m === bm && d === bd) { + claimAchievement('loggedInOnBirthday'); + } + } + + if (m === 1 && d === 1) { + claimAchievement('loggedInOnNewYearsDay'); + } + + if ($i.loggedInDays >= 3) claimAchievement('login3'); + if ($i.loggedInDays >= 7) claimAchievement('login7'); + if ($i.loggedInDays >= 15) claimAchievement('login15'); + if ($i.loggedInDays >= 30) claimAchievement('login30'); + if ($i.loggedInDays >= 60) claimAchievement('login60'); + if ($i.loggedInDays >= 100) claimAchievement('login100'); + if ($i.loggedInDays >= 200) claimAchievement('login200'); + if ($i.loggedInDays >= 300) claimAchievement('login300'); + if ($i.loggedInDays >= 400) claimAchievement('login400'); + if ($i.loggedInDays >= 500) claimAchievement('login500'); + if ($i.loggedInDays >= 600) claimAchievement('login600'); + if ($i.loggedInDays >= 700) claimAchievement('login700'); + if ($i.loggedInDays >= 800) claimAchievement('login800'); + if ($i.loggedInDays >= 900) claimAchievement('login900'); + if ($i.loggedInDays >= 1000) claimAchievement('login1000'); + + if ($i.notesCount > 0) claimAchievement('notes1'); + if ($i.notesCount >= 10) claimAchievement('notes10'); + if ($i.notesCount >= 100) claimAchievement('notes100'); + if ($i.notesCount >= 500) claimAchievement('notes500'); + if ($i.notesCount >= 1000) claimAchievement('notes1000'); + if ($i.notesCount >= 5000) claimAchievement('notes5000'); + if ($i.notesCount >= 10000) claimAchievement('notes10000'); + if ($i.notesCount >= 20000) claimAchievement('notes20000'); + if ($i.notesCount >= 30000) claimAchievement('notes30000'); + if ($i.notesCount >= 40000) claimAchievement('notes40000'); + if ($i.notesCount >= 50000) claimAchievement('notes50000'); + if ($i.notesCount >= 60000) claimAchievement('notes60000'); + if ($i.notesCount >= 70000) claimAchievement('notes70000'); + if ($i.notesCount >= 80000) claimAchievement('notes80000'); + if ($i.notesCount >= 90000) claimAchievement('notes90000'); + if ($i.notesCount >= 100000) claimAchievement('notes100000'); + + if ($i.followersCount > 0) claimAchievement('followers1'); + if ($i.followersCount >= 10) claimAchievement('followers10'); + if ($i.followersCount >= 50) claimAchievement('followers50'); + if ($i.followersCount >= 100) claimAchievement('followers100'); + if ($i.followersCount >= 300) claimAchievement('followers300'); + if ($i.followersCount >= 500) claimAchievement('followers500'); + if ($i.followersCount >= 1000) claimAchievement('followers1000'); + + if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) { + claimAchievement('passedSinceAccountCreated1'); + } + if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) { + claimAchievement('passedSinceAccountCreated2'); + } + if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) { + claimAchievement('passedSinceAccountCreated3'); + } + + if (claimedAchievements.length >= 30) { + claimAchievement('collectAchievements30'); + } + + window.setInterval(() => { + if (Math.floor(Math.random() * 20000) === 0) { + claimAchievement('justPlainLucky'); + } + }, 1000 * 10); + + window.setTimeout(() => { + claimAchievement('client30min'); + }, 1000 * 60 * 30); + + window.setTimeout(() => { + claimAchievement('client60min'); + }, 1000 * 60 * 60); + + const lastUsed = miLocalStorage.getItem('lastUsed'); + if (lastUsed) { + const lastUsedDate = parseInt(lastUsed, 10); + // 二時間以上å‰ãªã‚‰ + if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { + toast(i18n.t('welcomeBackWithName', { + name: $i.name || $i.username, + })); + } + } + miLocalStorage.setItem('lastUsed', Date.now().toString()); + + const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); + const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); + if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { + if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { + popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed'); + } + } + + if ('Notification' in window) { + // 許å¯ã‚’å¾—ã¦ã„ãªã‹ã£ãŸã‚‰ãƒªã‚¯ã‚¨ã‚¹ãƒˆ + if (Notification.permission === 'default') { + Notification.requestPermission(); + } + } + + const main = markRaw(stream.useChannel('main', null, 'System')); + + // 自分ã®æƒ…å ±ãŒæ›´æ–°ã•ã‚ŒãŸã¨ã + main.on('meUpdated', i => { + updateAccount(i); + }); + + main.on('readAllNotifications', () => { + updateAccount({ hasUnreadNotification: false }); + }); + + main.on('unreadNotification', () => { + updateAccount({ hasUnreadNotification: true }); + }); + + main.on('unreadMention', () => { + updateAccount({ hasUnreadMentions: true }); + }); + + main.on('readAllUnreadMentions', () => { + updateAccount({ hasUnreadMentions: false }); + }); + + main.on('unreadSpecifiedNote', () => { + updateAccount({ hasUnreadSpecifiedNotes: true }); + }); + + main.on('readAllUnreadSpecifiedNotes', () => { + updateAccount({ hasUnreadSpecifiedNotes: false }); + }); + + main.on('readAllAntennas', () => { + updateAccount({ hasUnreadAntenna: false }); + }); + + main.on('unreadAntenna', () => { + updateAccount({ hasUnreadAntenna: true }); + sound.play('antenna'); + }); + + main.on('readAllAnnouncements', () => { + updateAccount({ hasUnreadAnnouncement: false }); + }); + + // トークンãŒå†ç”Ÿæˆã•ã‚ŒãŸã¨ã + // ã“ã®ã¾ã¾ã§ã¯MisskeyãŒåˆ©ç”¨ã§ããªã„ã®ã§å¼·åˆ¶çš„ã«ã‚µã‚¤ãƒ³ã‚¢ã‚¦ãƒˆã•ã›ã‚‹ + main.on('myTokenRegenerated', () => { + signout(); + }); + } + + // shortcut + document.addEventListener('keydown', makeHotkey(hotkeys)); + + initializeSw(); +} diff --git a/packages/frontend/src/boot/sub-boot.ts b/packages/frontend/src/boot/sub-boot.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2664f6c1d3e0e2ea9fa8e47b0c4392a5533a3a2 --- /dev/null +++ b/packages/frontend/src/boot/sub-boot.ts @@ -0,0 +1,8 @@ +import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; +import { common } from './common'; + +export async function subBoot() { + const { isClientUpdated } = await common(() => createApp( + defineAsyncComponent(() => import('@/ui/minimum.vue')), + )); +} diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue index 9f2bf993389f89c3e0b55fb5d1ca1805c7609135..48236782d98cd6450022e4bd429f8379023d3e36 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.vue +++ b/packages/frontend/src/components/MkAbuseReportWindow.vue @@ -1,5 +1,5 @@ <template> -<MkWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> +<MkWindow ref="uiWindow" :initialWidth="400" :initialHeight="500" :canResize="true" @closed="emit('closed')"> <template #header> <i class="ti ti-exclamation-circle" style="margin-right: 0.5em;"></i> <I18n :src="i18n.ts.reportAbuseOf" tag="span"> @@ -8,8 +8,8 @@ </template> </I18n> </template> - <MkSpacer :margin-min="20" :margin-max="28"> - <div class="dpvffvvy _gaps_m"> + <MkSpacer :marginMin="20" :marginMax="28"> + <div class="_gaps_m" :class="$style.root"> <div class=""> <MkTextarea v-model="comment"> <template #label>{{ i18n.ts.details }}</template> @@ -60,8 +60,8 @@ function send() { } </script> -<style lang="scss" scoped> -.dpvffvvy { +<style lang="scss" module> +.root { --root-margin: 16px; } </style> diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index b02bfdc2b82daa3e4a8973cfaeeab3c43d7b5f80..bc07b9ba5f356f70d8999c06e73fc875fa6847dd 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -7,11 +7,11 @@ </template> <script lang="ts" setup> +import { ref } from 'vue'; +import { UserLite } from 'misskey-js/built/entities'; import MkMention from './MkMention.vue'; import { i18n } from '@/i18n'; import { host as localHost } from '@/config'; -import { ref } from 'vue'; -import { UserLite } from 'misskey-js/built/entities'; import { api } from '@/os'; const user = ref<UserLite>(); diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index d30037dcf9657b91ce449a3bf5db6c9a1ab20633..3fdb261dac8275d149b5269eebd3e9feff454f47 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -3,7 +3,14 @@ <div v-if="achievements" :class="$style.root"> <div v-for="achievement in achievements" :key="achievement" :class="$style.achievement" class="_panel"> <div :class="$style.icon"> - <div :class="[$style.iconFrame, $style['iconFrame_' + ACHIEVEMENT_BADGES[achievement.name].frame]]"> + <div + :class="[$style.iconFrame, { + [$style.iconFrame_bronze]: ACHIEVEMENT_BADGES[achievement.name].frame === 'bronze', + [$style.iconFrame_silver]: ACHIEVEMENT_BADGES[achievement.name].frame === 'silver', + [$style.iconFrame_gold]: ACHIEVEMENT_BADGES[achievement.name].frame === 'gold', + [$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum', + }]" + > <div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg }"> <img :class="$style.iconImg" :src="ACHIEVEMENT_BADGES[achievement.name].img"> </div> diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts index e7fbb472846e28a1bb79355cf56f34b673b51010..0aebdccf4ff5060b5f261d2faecc9071518aab61 100644 --- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts +++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; +import isChromatic from 'chromatic/isChromatic'; import MkAnalogClock from './MkAnalogClock.vue'; -import isChromatic from 'chromatic'; export const Default = { render(args) { return { diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index f12020f81047e0a391ec342fd10310e01e2e44ff..05caffe7d005ee63c32aae254527ec07a3fbcfa1 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -39,6 +39,7 @@ --> <line + ref="sLine" :class="[$style.s, { [$style.animate]: !disableSAnimate && sAnimation !== 'none', [$style.elastic]: sAnimation === 'elastic', [$style.easeOut]: sAnimation === 'easeOut' }]" :x1="5 - (0 * (sHandLengthRatio * handsTailLength))" :y1="5 + (1 * (sHandLengthRatio * handsTailLength))" @@ -73,9 +74,10 @@ </template> <script lang="ts" setup> -import { computed, onMounted, onBeforeUnmount } from 'vue'; +import { computed, onMounted, onBeforeUnmount, ref } from 'vue'; import tinycolor from 'tinycolor2'; import { globalEvents } from '@/events.js'; +import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js'; // https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles const angleDiff = (a: number, b: number) => { @@ -145,6 +147,7 @@ let mAngle = $ref<number>(0); let sAngle = $ref<number>(0); let disableSAnimate = $ref(false); let sOneRound = false; +const sLine = ref<SVGPathElement>(); function tick() { const now = props.now(); @@ -160,17 +163,21 @@ function tick() { } hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6); mAngle = Math.PI * (m + s / 60) / 30; - if (sOneRound) { // 秒é‡ãŒä¸€å‘¨ã—ãŸéš›ã®ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã‚’よã—ãªã«å‡¦ç†ã™ã‚‹(ã“ã‚ŒãŒç„¡ã„ã¨ç§’ãŒ59->0ã«ãªã£ãŸã¨ãã«æœŸå¾…ã—ãŸã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã«ãªã‚‰ãªã„) + if (sOneRound && sLine.value) { // 秒é‡ãŒä¸€å‘¨ã—ãŸéš›ã®ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã‚’よã—ãªã«å‡¦ç†ã™ã‚‹(ã“ã‚ŒãŒç„¡ã„ã¨ç§’ãŒ59->0ã«ãªã£ãŸã¨ãã«æœŸå¾…ã—ãŸã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã«ãªã‚‰ãªã„) sAngle = Math.PI * 60 / 30; - window.setTimeout(() => { + defaultIdlingRenderScheduler.delete(tick); + sLine.value.addEventListener('transitionend', () => { disableSAnimate = true; - window.setTimeout(() => { + requestAnimationFrame(() => { sAngle = 0; - window.setTimeout(() => { + requestAnimationFrame(() => { disableSAnimate = false; - }, 100); - }, 100); - }, 700); + if (enabled) { + defaultIdlingRenderScheduler.add(tick); + } + }); + }); + }, { once: true }); } else { sAngle = Math.PI * s / 30; } @@ -194,20 +201,13 @@ function calcColors() { calcColors(); onMounted(() => { - const update = () => { - if (enabled) { - tick(); - window.setTimeout(update, 1000); - } - }; - update(); - + defaultIdlingRenderScheduler.add(tick); globalEvents.on('themeChanged', calcColors); }); onBeforeUnmount(() => { enabled = false; - + defaultIdlingRenderScheduler.delete(tick); globalEvents.off('themeChanged', calcColors); }); </script> diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue new file mode 100644 index 0000000000000000000000000000000000000000..575ea7c5e34968ed8e31145f6156c0b0800041cb --- /dev/null +++ b/packages/frontend/src/components/MkAnimBg.vue @@ -0,0 +1,243 @@ +<template> +<canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas> +</template> + +<script lang="ts" setup> +import { onMounted, onUnmounted, shallowRef } from 'vue'; +import isChromatic from 'chromatic/isChromatic'; + +const canvasEl = shallowRef<HTMLCanvasElement>(); + +const props = withDefaults(defineProps<{ + scale?: number; + focus?: number; +}>(), { + scale: 1.0, + focus: 1.0, +}); + +function loadShader(gl, type, source) { + const shader = gl.createShader(type); + + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + alert( + `falied to compile shader: ${gl.getShaderInfoLog(shader)}`, + ); + gl.deleteShader(shader); + return null; + } + + return shader; +} + +function initShaderProgram(gl, vsSource, fsSource) { + const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); + const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); + + const shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + alert( + `failed to init shader: ${gl.getProgramInfoLog( + shaderProgram, + )}`, + ); + return null; + } + + return shaderProgram; +} + +let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null; + +onMounted(() => { + const canvas = canvasEl.value!; + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + + const gl = canvas.getContext('webgl', { premultipliedAlpha: true }); + if (gl == null) return; + + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + const positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + + const shaderProgram = initShaderProgram(gl, ` + attribute vec2 vertex; + + uniform vec2 u_scale; + + varying vec2 v_pos; + + void main() { + gl_Position = vec4(vertex, 0.0, 1.0); + v_pos = vertex / u_scale; + } + `, ` + precision mediump float; + + vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; + } + + vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; + } + + vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); + } + + float snoise(vec2 v) { + const vec4 C = vec4(0.211324865405187, + 0.366025403784439, + -0.577350269189626, + 0.024390243902439); + + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + i = mod289(i); + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); + } + + uniform float u_time; + uniform vec2 u_resolution; + uniform float u_spread; + uniform float u_speed; + uniform float u_warp; + uniform float u_focus; + uniform float u_itensity; + + varying vec2 v_pos; + + float circle( in vec2 _pos, in vec2 _origin, in float _radius ) { + float SPREAD = 0.7 * u_spread; + float SPEED = 0.00055 * u_speed; + float WARP = 1.5 * u_warp; + float FOCUS = 1.15 * u_focus; + + vec2 dist = _pos - _origin; + + float distortion = snoise( vec2( + _pos.x * 1.587 * WARP + u_time * SPEED * 0.5, + _pos.y * 1.192 * WARP + u_time * SPEED * 0.3 + ) ) * 0.5 + 0.5; + + float feather = 0.01 + SPREAD * pow( distortion, FOCUS ); + + return 1.0 - smoothstep( + _radius - ( _radius * feather ), + _radius + ( _radius * feather ), + dot( dist, dist ) * 4.0 + ); + } + + void main() { + vec3 green = vec3( 1.0 ) - vec3( 153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0 ); + vec3 purple = vec3( 1.0 ) - vec3( 195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0 ); + vec3 orange = vec3( 1.0 ) - vec3( 255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0 ); + + float ratio = u_resolution.x / u_resolution.y; + + vec2 uv = vec2( v_pos.x, v_pos.y / ratio ) * 0.5 + 0.5; + + vec3 color = vec3( 0.0 ); + + float greenMix = snoise( v_pos * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5; + float purpleMix = snoise( v_pos * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5; + float orangeMix = snoise( v_pos * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5; + + float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 ); + float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 ); + float alphaThree = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 3917.0 ) * 0.00013, uv.x ) ) * 0.5 + 0.5, 1.2 ); + + color += vec3( circle( uv, vec2( 0.22 + sin( u_time * 0.000201 ) * 0.06, 0.80 + cos( u_time * 0.000151 ) * 0.06 ), 0.15 ) ) * alphaOne * ( purple * purpleMix + orange * orangeMix ); + color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix ); + color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix ); + + color *= u_itensity + 1.0 * pow( snoise( vec2( v_pos.y + u_time * 0.00013, v_pos.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 ); + + vec3 inverted = vec3( 1.0 ) - color; + gl_FragColor = vec4( color, max(max(color.x, color.y), color.z) ); + } + `); + + gl.useProgram(shaderProgram); + const u_resolution = gl.getUniformLocation(shaderProgram, 'u_resolution'); + const u_time = gl.getUniformLocation(shaderProgram, 'u_time'); + const u_spread = gl.getUniformLocation(shaderProgram, 'u_spread'); + const u_speed = gl.getUniformLocation(shaderProgram, 'u_speed'); + const u_warp = gl.getUniformLocation(shaderProgram, 'u_warp'); + const u_focus = gl.getUniformLocation(shaderProgram, 'u_focus'); + const u_itensity = gl.getUniformLocation(shaderProgram, 'u_itensity'); + const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale'); + gl.uniform2fv(u_resolution, [canvas.width, canvas.height]); + gl.uniform1f(u_spread, 1.0); + gl.uniform1f(u_speed, 1.0); + gl.uniform1f(u_warp, 1.0); + gl.uniform1f(u_focus, props.focus); + gl.uniform1f(u_itensity, 0.5); + gl.uniform2fv(u_scale, [props.scale, props.scale]); + + const vertex = gl.getAttribLocation(shaderProgram, 'vertex'); + gl.enableVertexAttribArray(vertex); + gl.vertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0); + + const vertices = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW); + + if (isChromatic()) { + gl!.uniform1f(u_time, 0); + gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4); + } else { + function render(timeStamp) { + gl!.uniform1f(u_time, timeStamp); + gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4); + + handle = window.requestAnimationFrame(render); + } + + handle = window.requestAnimationFrame(render); + } +}); + +onUnmounted(() => { + if (handle) { + window.cancelAnimationFrame(handle); + } +}); +</script> + +<style lang="scss" module> +</style> diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index 6ade5316c6812d4d8e5ca1495df23f41f0e7d9e2..8bfcfa6aa65bcaf6bdcd07a66fa0859247c77155 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -11,29 +11,29 @@ <div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }"> <MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton> </div> - <MkSwitch v-else-if="c.type === 'switch'" :model-value="valueForSwitch" @update:model-value="onSwitchUpdate"> + <MkSwitch v-else-if="c.type === 'switch'" :modelValue="valueForSwitch" @update:modelValue="onSwitchUpdate"> <template v-if="c.label" #label>{{ c.label }}</template> <template v-if="c.caption" #caption>{{ c.caption }}</template> </MkSwitch> - <MkTextarea v-else-if="c.type === 'textarea'" :model-value="c.default" @update:model-value="c.onInput"> + <MkTextarea v-else-if="c.type === 'textarea'" :modelValue="c.default" @update:modelValue="c.onInput"> <template v-if="c.label" #label>{{ c.label }}</template> <template v-if="c.caption" #caption>{{ c.caption }}</template> </MkTextarea> - <MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :model-value="c.default" @update:model-value="c.onInput"> + <MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :modelValue="c.default" @update:modelValue="c.onInput"> <template v-if="c.label" #label>{{ c.label }}</template> <template v-if="c.caption" #caption>{{ c.caption }}</template> </MkInput> - <MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :model-value="c.default" type="number" @update:model-value="c.onInput"> + <MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :modelValue="c.default" type="number" @update:modelValue="c.onInput"> <template v-if="c.label" #label>{{ c.label }}</template> <template v-if="c.caption" #caption>{{ c.caption }}</template> </MkInput> - <MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :model-value="c.default" @update:model-value="c.onChange"> + <MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :modelValue="c.default" @update:modelValue="c.onChange"> <template v-if="c.label" #label>{{ c.label }}</template> <template v-if="c.caption" #caption>{{ c.caption }}</template> <option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option> </MkSelect> <MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" inline @click="openPostForm">{{ c.text }}</MkButton> - <MkFolder v-else-if="c.type === 'folder'" :default-open="c.opened"> + <MkFolder v-else-if="c.type === 'folder'" :defaultOpen="c.opened"> <template #label>{{ c.title }}</template> <template v-for="child in c.children" :key="child"> <MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/> diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 663c57623d4ab421cd5107322a17f7dc766d78eb..fd892d8174e84215146de9c848935b93ba90eefb 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -10,7 +10,7 @@ </li> <li tabindex="-1" :class="$style.item" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li> </ol> - <ol v-else-if="hashtags.length > 0" ref="suggests" :class="[$style.list, $style.hashtags]"> + <ol v-else-if="hashtags.length > 0" ref="suggests" :class="$style.list"> <li v-for="hashtag in hashtags" tabindex="-1" :class="$style.item" @click="complete(type, hashtag)" @keydown="onKeydown"> <span class="name">{{ hashtag }}</span> </li> @@ -42,7 +42,7 @@ import { acct } from '@/filters/user'; import * as os from '@/os'; import { MFM_TAGS } from '@/scripts/mfm-tags'; import { defaultStore } from '@/store'; -import { emojilist } from '@/scripts/emojilist'; +import { emojilist, getEmojiName } from '@/scripts/emojilist'; import { i18n } from '@/i18n'; import { miLocalStorage } from '@/local-storage'; import { customEmojis } from '@/custom-emojis'; @@ -71,14 +71,14 @@ const emojiDb = computed(() => { url: char2path(x.char), })); - for (const x of lib) { - if (x.keywords) { - for (const k of x.keywords) { + for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { + for (const [emoji, keywords] of Object.entries(index)) { + for (const k of keywords) { unicodeEmojiDB.push({ - emoji: x.char, + emoji: emoji, name: k, - aliasOf: x.name, - url: char2path(x.char), + aliasOf: getEmojiName(emoji)!, + url: char2path(emoji), }); } } diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue index 995a72e511735c78f69710a00aa51cd922ef8e13..630620fc08730981d124fe20ca667adcdff21fa1 100644 --- a/packages/frontend/src/components/MkAvatars.vue +++ b/packages/frontend/src/components/MkAvatars.vue @@ -1,7 +1,7 @@ <template> <div> <div v-for="user in users" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;"> - <MkAvatar :user="user" style="width:32px;height:32px;" indicator link preview/> + <MkAvatar :user="user" style="width:32px; height:32px;" indicator link preview/> </div> </div> </template> diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 0ddee34f0a7748ad44ba17bf6c26e5f7568c74e9..16e44ec6186b5310b54fdd5f00570e78aed6a154 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -2,23 +2,23 @@ <button v-if="!link" ref="el" class="_button" - :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.asLike]: asLike }]" + :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]" :type="type" @click="emit('click', $event)" @mousedown="onMousedown" > - <div ref="ripples" :class="$style.ripples"></div> + <div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div> <div :class="$style.content"> <slot></slot> </div> </button> <MkA v-else class="_button" - :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.asLike]: asLike }]" + :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]" :to="to" @mousedown="onMousedown" > - <div ref="ripples" :class="$style.ripples"></div> + <div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div> <div :class="$style.content"> <slot></slot> </div> @@ -26,9 +26,7 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted, useCssModule } from 'vue'; - -const $style = useCssModule(); +import { nextTick, onMounted } from 'vue'; const props = defineProps<{ type?: 'button' | 'submit' | 'reset'; @@ -44,6 +42,7 @@ const props = defineProps<{ full?: boolean; small?: boolean; large?: boolean; + transparent?: boolean; asLike?: boolean; }>(); @@ -80,7 +79,7 @@ function onMousedown(evt: MouseEvent): void { const rect = target.getBoundingClientRect(); const ripple = document.createElement('div'); - ripple.classList.add($style.ripple); + ripple.classList.add(ripples!.dataset.childrenClass!); ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px'; ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px'; @@ -194,6 +193,10 @@ function onMousedown(evt: MouseEvent): void { } } + &.transparent { + background: transparent; + } + &.gradate { font-weight: bold; color: var(--fgOnAccent) !important; diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index 9e275d61723f09a3d59357ac12b1f0b3d5d9c894..7b7bef47873d41dd812223d172242414b0964b18 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -1,20 +1,20 @@ <template> <button - class="hdcaacmi _button" - :class="{ wait, active: isFollowing, full }" + class="_button" + :class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing, [$style.full]: full }]" :disabled="wait" @click="onClick" > <template v-if="!wait"> <template v-if="isFollowing"> - <span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i> </template> <template v-else> - <span v-if="full">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true"/> + <span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true"/> </template> </button> </template> @@ -57,8 +57,8 @@ async function onClick() { } </script> -<style lang="scss" scoped> -.hdcaacmi { +<style lang="scss" module> +.root { position: relative; display: inline-block; font-weight: bold; @@ -103,7 +103,7 @@ async function onClick() { } &.active { - color: #fff; + color: var(--fgOnAccent); background: var(--accent); &:hover { @@ -121,9 +121,9 @@ async function onClick() { cursor: wait !important; opacity: 0.7; } +} - > span { - margin-right: 6px; - } +.text { + margin-right: 6px; } </style> diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue index 408eab7399e6a56603a418cd1b4f06659c6f9dc6..4050520eb9bedd7c542c8be4768df20a7d61eb4f 100644 --- a/packages/frontend/src/components/MkChannelList.vue +++ b/packages/frontend/src/components/MkChannelList.vue @@ -26,6 +26,3 @@ const props = withDefaults(defineProps<{ extractor: (item) => item, }); </script> - -<style lang="scss" scoped> -</style> diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 06d5b9949ab06478a9ebf1fa03690dd4f6474174..00ff98774bce28d4e916bfae89a5d6019ee0c0ec 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -1,8 +1,8 @@ <template> -<div class="cbbedffa"> +<div :class="$style.root"> <canvas ref="chartEl"></canvas> <MkChartLegend ref="legendEl" style="margin-top: 8px;"/> - <div v-if="fetching" class="fetching"> + <div v-if="fetching" :class="$style.fetching"> <MkLoading/> </div> </div> @@ -817,22 +817,22 @@ onMounted(() => { /* eslint-enable id-denylist */ </script> -<style lang="scss" scoped> -.cbbedffa { +<style lang="scss" module> +.root { position: relative; +} - > .fetching { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - -webkit-backdrop-filter: var(--blur, blur(12px)); - backdrop-filter: var(--blur, blur(12px)); - display: flex; - justify-content: center; - align-items: center; - cursor: wait; - } +.fetching { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + -webkit-backdrop-filter: var(--blur, blur(12px)); + backdrop-filter: var(--blur, blur(12px)); + display: flex; + justify-content: center; + align-items: center; + cursor: wait; } </style> diff --git a/packages/frontend/src/components/MkChartTooltip.vue b/packages/frontend/src/components/MkChartTooltip.vue index 7cfe535edd1957453558c8694762d27cd1e4d272..fe5b78754daa9330b8e970866bba16f9993c3c0b 100644 --- a/packages/frontend/src/components/MkChartTooltip.vue +++ b/packages/frontend/src/components/MkChartTooltip.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" :direction="'top'" :inner-margin="16" @closed="emit('closed')"> +<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :maxWidth="340" :direction="'top'" :innerMargin="16" @closed="emit('closed')"> <div v-if="title || series"> <div v-if="title" :class="$style.title">{{ title }}</div> <template v-if="series"> diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue index da6439fd2cb64e91118e606404f86ae28f2527dd..a6ab5aded4272fd9d4f351549c97f5224abb43b0 100644 --- a/packages/frontend/src/components/MkClickerGame.vue +++ b/packages/frontend/src/components/MkClickerGame.vue @@ -3,7 +3,7 @@ <div v-if="game.ready" :class="$style.game"> <div :class="$style.cps" class="">{{ number(cps) }}cps</div> <div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div> - <button v-click-anime class="_button" :class="$style.button" @click="onClick"> + <button v-click-anime class="_button" @click="onClick"> <img src="/client-assets/cookie.png" :class="$style.img"> </button> </div> @@ -84,10 +84,6 @@ onUnmounted(() => { margin-bottom: 6px; } -.button { - -} - .img { max-width: 90px; } diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index d03331a6ebc14c9875dab31920b3ea431152436b..af1c57b349b7ffc19b9751f1defd5148c626d8f7 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -1,12 +1,12 @@ <template> -<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]"> +<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.scrollable]: scrollable }]"> <header v-if="showHeader" ref="headerEl" :class="$style.header"> <div :class="$style.title"> <span :class="$style.titleIcon"><slot name="icon"></slot></span> <slot name="header"></slot> </div> <div :class="$style.headerSub"> - <slot name="func" :button-style-class="$style.headerButton"></slot> + <slot name="func" :buttonStyleClass="$style.headerButton"></slot> <button v-if="foldable" :class="$style.headerButton" class="_button" @click="() => showBody = !showBody"> <template v-if="showBody"><i class="ti ti-chevron-up"></i></template> <template v-else><i class="ti ti-chevron-down"></i></template> @@ -14,14 +14,14 @@ </div> </header> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''" @enter="enter" - @after-enter="afterEnter" + @afterEnter="afterEnter" @leave="leave" - @after-leave="afterLeave" + @afterLeave="afterLeave" > <div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]"> <slot></slot> @@ -34,7 +34,7 @@ </template> <script lang="ts" setup> -import { onMounted, ref, shallowRef, watch } from 'vue'; +import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; @@ -83,13 +83,19 @@ function afterLeave(el) { const calcOmit = () => { if (omitted.value || ignoreOmit.value || props.maxHeight == null) return; + if (!contentEl.value) return; const height = contentEl.value.offsetHeight; omitted.value = height > props.maxHeight; }; +const omitObserver = new ResizeObserver((entries, observer) => { + calcOmit(); +}); + onMounted(() => { watch(showBody, v => { - const headerHeight = props.showHeader ? headerEl.value.offsetHeight : 0; + if (!rootEl.value) return; + const headerHeight = props.showHeader ? headerEl.value?.offsetHeight ?? 0 : 0; rootEl.value.style.minHeight = `${headerHeight}px`; if (v) { rootEl.value.style.flexBasis = 'auto'; @@ -100,13 +106,15 @@ onMounted(() => { immediate: true, }); - rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px'); + if (rootEl.value) rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px'); calcOmit(); - new ResizeObserver((entries, observer) => { - calcOmit(); - }).observe(contentEl.value); + if (contentEl.value) omitObserver.observe(contentEl.value); +}); + +onUnmounted(() => { + omitObserver.disconnect(); }); </script> diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue index b81c806b0c1deca849848cbac340633d8d8d67f3..fb11834f4d737e4c4548a4ff2d1e75e9ebb0d8e6 100644 --- a/packages/frontend/src/components/MkContextMenu.vue +++ b/packages/frontend/src/components/MkContextMenu.vue @@ -1,10 +1,10 @@ <template> <Transition appear - :enter-active-class="defaultStore.state.animation ? $style.transition_fade_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''" > <div ref="rootEl" :class="$style.root" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}"> <MkMenu :items="items" :align="'left'" @close="$emit('closed')"/> diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 043a614e461e0fbff34fad2eae13d94528607699..82363499b7a27bde308e84f0d24b9b473abe8c10 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -4,7 +4,7 @@ :width="800" :height="500" :scroll="false" - :with-ok-button="true" + :withOkButton="true" @close="cancel()" @ok="ok()" @closed="$emit('closed')" diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index d6303f967592d40ef74bbda54cdb62a69dbc24b6..6942a0e6c3f5ee7958052dcd65d7725fddc1a997 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -36,7 +36,7 @@ export default defineComponent({ }, setup(props, { slots, expose }) { - const $style = useCssModule(); + const $style = useCssModule(); // カスタムレンダラãªã®ã§ä½¿ã£ã¦ã‚‚大丈夫 function getDateText(time: string) { const date = new Date(time).getDate(); const month = new Date(time).getMonth() + 1; diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 9f5404ce15587921f34aed1c5ddaf1440b73941d..4d5df0bba4e9a3e2b85a72049ff05003dd3374f4 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -1,10 +1,18 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')"> +<MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="done(true)" @closed="emit('closed')"> <div :class="$style.root"> <div v-if="icon" :class="$style.icon"> <i :class="icon"></i> </div> - <div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]"> + <div + v-else-if="!input && !select" + :class="[$style.icon, { + [$style.type_success]: type === 'success', + [$style.type_error]: type === 'error', + [$style.type_warning]: type === 'warning', + [$style.type_info]: type === 'info', + }]" + > <i v-if="type === 'success'" :class="$style.iconInner" class="ti ti-check"></i> <i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i> <i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i> diff --git a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..344f6de47c3050cd241d5a5497956fd8b9e99a2d --- /dev/null +++ b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import isChromatic from 'chromatic/isChromatic'; +import MkDigitalClock from './MkDigitalClock.vue'; +export const Default = { + render(args) { + return { + components: { + MkDigitalClock, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkDigitalClock v-bind="props" />', + }; + }, + args: { + now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkDigitalClock>; diff --git a/packages/frontend/src/components/MkDigitalClock.vue b/packages/frontend/src/components/MkDigitalClock.vue index 278dc8a5e7ebbedb784bf52b430177d8071d159c..aea20f24890de1e7d70e15ca8dc8eced22d87ccf 100644 --- a/packages/frontend/src/components/MkDigitalClock.vue +++ b/packages/frontend/src/components/MkDigitalClock.vue @@ -11,19 +11,21 @@ </template> <script lang="ts" setup> -import { onUnmounted, ref, watch } from 'vue'; +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js'; const props = withDefaults(defineProps<{ showS?: boolean; showMs?: boolean; offset?: number; + now?: () => Date; }>(), { showS: true, showMs: false, offset: 0 - new Date().getTimezoneOffset(), + now: () => new Date(), }); -let intervalId; const hh = ref(''); const mm = ref(''); const ss = ref(''); @@ -39,9 +41,9 @@ watch(showColon, (v) => { } }); -const tick = () => { - const now = new Date(); - now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset)); +const tick = (): void => { + const now = props.now(); + now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset); hh.value = now.getHours().toString().padStart(2, '0'); mm.value = now.getMinutes().toString().padStart(2, '0'); ss.value = now.getSeconds().toString().padStart(2, '0'); @@ -52,13 +54,12 @@ const tick = () => { tick(); -watch(() => props.showMs, () => { - if (intervalId) window.clearInterval(intervalId); - intervalId = window.setInterval(tick, props.showMs ? 10 : 1000); -}, { immediate: true }); +onMounted(() => { + defaultIdlingRenderScheduler.add(tick); +}); onUnmounted(() => { - window.clearInterval(intervalId); + defaultIdlingRenderScheduler.delete(tick); }); </script> diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index ab408b500867d06fa9e175a9f138045ef84a9a85..f0641161bebd5d01167da0958fa2e492c9fd5cae 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -1,7 +1,6 @@ <template> <div - class="ncvczrfv" - :class="{ isSelected }" + :class="[$style.root, { [$style.isSelected]: isSelected }]" draggable="true" :title="title" @click="onClick" @@ -9,25 +8,27 @@ @dragstart="onDragstart" @dragend="onDragend" > - <div v-if="$i?.avatarId == file.id" class="label"> - <img src="/client-assets/label.svg"/> - <p>{{ i18n.ts.avatar }}</p> + <div style="pointer-events: none;"> + <div v-if="$i?.avatarId == file.id" :class="[$style.label]"> + <img :class="$style.labelImg" src="/client-assets/label.svg"/> + <p :class="$style.labelText">{{ i18n.ts.avatar }}</p> + </div> + <div v-if="$i?.bannerId == file.id" :class="[$style.label]"> + <img :class="$style.labelImg" src="/client-assets/label.svg"/> + <p :class="$style.labelText">{{ i18n.ts.banner }}</p> + </div> + <div v-if="file.isSensitive" :class="[$style.label, $style.red]"> + <img :class="$style.labelImg" src="/client-assets/label-red.svg"/> + <p :class="$style.labelText">{{ i18n.ts.nsfw }}</p> + </div> + + <MkDriveFileThumbnail :class="$style.thumbnail" :file="file" fit="contain"/> + + <p :class="$style.name"> + <span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> + <span v-if="file.name.lastIndexOf('.') != -1" style="opacity: 0.5;">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span> + </p> </div> - <div v-if="$i?.bannerId == file.id" class="label"> - <img src="/client-assets/label.svg"/> - <p>{{ i18n.ts.banner }}</p> - </div> - <div v-if="file.isSensitive" class="label red"> - <img src="/client-assets/label-red.svg"/> - <p>{{ i18n.ts.nsfw }}</p> - </div> - - <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> - - <p class="name"> - <span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> - <span v-if="file.name.lastIndexOf('.') != -1" class="ext">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span> - </p> </div> </template> @@ -88,20 +89,13 @@ function onDragend() { } </script> -<style lang="scss" scoped> -.ncvczrfv { +<style lang="scss" module> +.root { position: relative; padding: 8px 0 0 0; min-height: 180px; border-radius: 8px; - - &, * { - cursor: pointer; - } - - > * { - pointer-events: none; - } + cursor: pointer; &:hover { background: rgba(#000, 0.05); @@ -165,82 +159,78 @@ function onDragend() { color: #fff; } } +} + +.label { + position: absolute; + top: 0; + left: 0; + pointer-events: none; - > .label { + &:before, + &:after { + content: ""; + display: block; position: absolute; + z-index: 1; + background: #0c7ac9; + } + + &:before { top: 0; + left: 57px; + width: 28px; + height: 8px; + } + + &:after { + top: 57px; left: 0; - pointer-events: none; + width: 8px; + height: 28px; + } + &.red { &:before, &:after { - content: ""; - display: block; - position: absolute; - z-index: 1; - background: #0c7ac9; - } - - &:before { - top: 0; - left: 57px; - width: 28px; - height: 8px; - } - - &:after { - top: 57px; - left: 0; - width: 8px; - height: 28px; - } - - &.red { - &:before, - &:after { - background: #c12113; - } + background: #c12113; } + } +} - > img { - position: absolute; - z-index: 2; - top: 0; - left: 0; - } +.labelImg { + position: absolute; + z-index: 2; + top: 0; + left: 0; +} - > p { - position: absolute; - z-index: 3; - top: 19px; - left: -28px; - width: 120px; - margin: 0; - text-align: center; - line-height: 28px; - color: #fff; - transform: rotate(-45deg); - } - } +.labelText { + position: absolute; + z-index: 3; + top: 19px; + left: -28px; + width: 120px; + margin: 0; + text-align: center; + line-height: 28px; + color: #fff; + transform: rotate(-45deg); +} - > .thumbnail { - width: 110px; - height: 110px; - margin: auto; - } +.thumbnail { + width: 110px; + height: 110px; + margin: auto; +} - > .name { - display: block; - margin: 4px 0 0 0; - font-size: 0.8em; - text-align: center; - word-break: break-all; - color: var(--fg); - overflow: hidden; - - > .ext { - opacity: 0.5; - } - } +.name { + display: block; + margin: 4px 0 0 0; + font-size: 0.8em; + text-align: center; + word-break: break-all; + color: var(--fg); + overflow: hidden; } </style> diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 156013b9aa8401bba59151dc72f19789d96dcf56..1969342402627708dd3c677f4554493278fd8f94 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -1,7 +1,6 @@ <template> <div - class="rghtznwe" - :class="{ draghover }" + :class="[$style.root, { [$style.draghover]: draghover }]" draggable="true" :title="title" @click="onClick" @@ -15,15 +14,15 @@ @dragstart="onDragstart" @dragend="onDragend" > - <p class="name"> - <template v-if="hover"><i class="ti ti-folder ti-fw"></i></template> - <template v-if="!hover"><i class="ti ti-folder ti-fw"></i></template> + <p :class="$style.name"> + <template v-if="hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template> + <template v-if="!hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template> {{ folder.name }} </p> - <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> + <p v-if="defaultStore.state.uploadFolder == folder.id" :class="$style.upload"> {{ i18n.ts.uploadFolder }} </p> - <button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button> + <button v-if="selectMode" class="_button" :class="[$style.checkbox, { [$style.checked]: isSelected }]" @click.prevent.stop="checkboxClicked"></button> </div> </template> @@ -267,35 +266,14 @@ function onContextmenu(ev: MouseEvent) { } </script> -<style lang="scss" scoped> -.rghtznwe { +<style lang="scss" module> +.root { position: relative; padding: 8px; height: 64px; background: var(--driveFolderBg); border-radius: 4px; - - &, * { - cursor: pointer; - } - - *:not(.checkbox) { - pointer-events: none; - } - - > .checkbox { - position: absolute; - bottom: 8px; - right: 8px; - width: 16px; - height: 16px; - background: #fff; - border: solid 1px #000; - - &.checked { - background: var(--accent); - } - } + cursor: pointer; &.draghover { &:after { @@ -310,24 +288,38 @@ function onContextmenu(ev: MouseEvent) { border-radius: 4px; } } +} - > .name { - margin: 0; - font-size: 0.9em; - color: var(--desktopDriveFolderFg); - - > i { - margin-right: 4px; - margin-left: 2px; - text-align: left; - } +.checkbox { + position: absolute; + bottom: 8px; + right: 8px; + width: 16px; + height: 16px; + background: #fff; + border: solid 1px #000; + + &.checked { + background: var(--accent); } +} - > .upload { - margin: 4px 4px; - font-size: 0.8em; - text-align: right; - color: var(--desktopDriveFolderFg); - } +.name { + margin: 0; + font-size: 0.9em; + color: var(--desktopDriveFolderFg); +} + +.icon { + margin-right: 4px; + margin-left: 2px; + text-align: left; +} + +.upload { + margin: 4px 4px; + font-size: 0.8em; + text-align: right; + color: var(--desktopDriveFolderFg); } </style> diff --git a/packages/frontend/src/components/MkDrive.navFolder.vue b/packages/frontend/src/components/MkDrive.navFolder.vue index dbbfef5f0561b59c939034a7fd43f23a14a17a7b..3349603d3b98eafc32fe67cfa57eb4b0f16593cf 100644 --- a/packages/frontend/src/components/MkDrive.navFolder.vue +++ b/packages/frontend/src/components/MkDrive.navFolder.vue @@ -1,13 +1,13 @@ <template> -<div class="drylbebk" - :class="{ draghover }" +<div + :class="[$style.root, { [$style.draghover]: draghover }]" @click="onClick" @dragover.prevent.stop="onDragover" @dragenter="onDragenter" @dragleave="onDragleave" @drop.stop="onDrop" > - <i v-if="folder == null" class="ti ti-cloud"></i> + <i v-if="folder == null" class="ti ti-cloud" style="margin-right: 4px;"></i> <span>{{ folder == null ? i18n.ts.drive : folder.name }}</span> </div> </template> @@ -130,18 +130,10 @@ function onDrop(ev: DragEvent) { } </script> -<style lang="scss" scoped> -.drylbebk { - > * { - pointer-events: none; - } - +<style lang="scss" module> +.root { &.draghover { background: #eee; } - - > i { - margin-right: 4px; - } } </style> diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index bfec57d6a04ea4e146b1537f105cbef166bb5a30..52aef450d92b38d9bc9208cf11017ddd344bb7f1 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -1,89 +1,90 @@ <template> -<div class="yfudmmck"> - <nav> - <div class="path" @contextmenu.prevent.stop="() => {}"> +<div :class="$style.root"> + <nav :class="$style.nav"> + <div :class="$style.navPath" @contextmenu.prevent.stop="() => {}"> <XNavFolder - :class="{ current: folder == null }" - :parent-folder="folder" + :class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]" + :parentFolder="folder" @move="move" @upload="upload" - @remove-file="removeFile" - @remove-folder="removeFolder" + @removeFile="removeFile" + @removeFolder="removeFolder" /> <template v-for="f in hierarchyFolders"> - <span class="separator"><i class="ti ti-chevron-right"></i></span> + <span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span> <XNavFolder :folder="f" - :parent-folder="folder" + :parentFolder="folder" + :class="[$style.navPathItem]" @move="move" @upload="upload" - @remove-file="removeFile" - @remove-folder="removeFolder" + @removeFile="removeFile" + @removeFolder="removeFolder" /> </template> - <span v-if="folder != null" class="separator"><i class="ti ti-chevron-right"></i></span> - <span v-if="folder != null" class="folder current">{{ folder.name }}</span> + <span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span> + <span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span> </div> - <button class="menu _button" @click="showMenu"><i class="ti ti-dots"></i></button> + <button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button> </nav> <div - ref="main" class="main" - :class="{ uploading: uploadings.length > 0, fetching }" + ref="main" + :class="[$style.main, { [$style.uploading]: uploadings.length > 0, [$style.fetching]: fetching }]" @dragover.prevent.stop="onDragover" @dragenter="onDragenter" @dragleave="onDragleave" @drop.prevent.stop="onDrop" @contextmenu.stop="onContextmenu" > - <div ref="contents" class="contents"> - <div v-show="folders.length > 0" ref="foldersContainer" class="folders"> + <div ref="contents"> + <div v-show="folders.length > 0" ref="foldersContainer" :class="$style.folders"> <XFolder v-for="(f, i) in folders" :key="f.id" v-anim="i" - class="folder" + :class="$style.folder" :folder="f" - :select-mode="select === 'folder'" - :is-selected="selectedFolders.some(x => x.id === f.id)" + :selectMode="select === 'folder'" + :isSelected="selectedFolders.some(x => x.id === f.id)" @chosen="chooseFolder" @move="move" @upload="upload" - @remove-file="removeFile" - @remove-folder="removeFolder" + @removeFile="removeFile" + @removeFolder="removeFolder" @dragstart="isDragSource = true" @dragend="isDragSource = false" /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> - <div v-for="(n, i) in 16" :key="i" class="padding"></div> + <div v-for="(n, i) in 16" :key="i" :class="$style.padding"></div> <MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.ts.loadMore }}</MkButton> </div> - <div v-show="files.length > 0" ref="filesContainer" class="files"> + <div v-show="files.length > 0" ref="filesContainer" :class="$style.files"> <XFile v-for="(file, i) in files" :key="file.id" v-anim="i" - class="file" + :class="$style.file" :file="file" - :select-mode="select === 'file'" - :is-selected="selectedFiles.some(x => x.id === file.id)" + :selectMode="select === 'file'" + :isSelected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile" @dragstart="isDragSource = true" @dragend="isDragSource = false" /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> - <div v-for="(n, i) in 16" :key="i" class="padding"></div> + <div v-for="(n, i) in 16" :key="i" :class="$style.padding"></div> <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton> </div> - <div v-if="files.length == 0 && folders.length == 0 && !fetching" class="empty"> - <p v-if="draghover">{{ i18n.t('empty-draghover') }}</p> - <p v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p> - <p v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</p> + <div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty"> + <div v-if="draghover">{{ i18n.t('empty-draghover') }}</div> + <div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</div> + <div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div> </div> </div> <MkLoading v-if="fetching"/> </div> - <div v-if="draghover" class="dropzone"></div> - <input ref="fileInput" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/> + <div v-if="draghover" :class="$style.dropzone"></div> + <input ref="fileInput" style="display: none;" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/> </div> </template> @@ -95,7 +96,7 @@ import XNavFolder from '@/components/MkDrive.navFolder.vue'; import XFolder from '@/components/MkDrive.folder.vue'; import XFile from '@/components/MkDrive.file.vue'; import * as os from '@/os'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; import { uploadFile, uploads } from '@/scripts/upload'; @@ -131,7 +132,7 @@ const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]); const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); const uploadings = uploads; -const connection = stream.useChannel('drive'); +const connection = useStream().useChannel('drive'); const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡ã—ãŒå¤šã„ã®ã§$refã¯ä½¿ã‚ãªã„ã»ã†ãŒã‚ˆã„ // ドãƒãƒƒãƒ—ã•ã‚Œã‚ˆã†ã¨ã—ã¦ã„ã‚‹ã‹ @@ -658,147 +659,116 @@ onBeforeUnmount(() => { }); </script> -<style lang="scss" scoped> -.yfudmmck { +<style lang="scss" module> +.root { display: flex; flex-direction: column; height: 100%; +} - > nav { - display: flex; - z-index: 2; - width: 100%; - padding: 0 8px; - box-sizing: border-box; - overflow: auto; - font-size: 0.9em; - box-shadow: 0 1px 0 var(--divider); - - &, * { - user-select: none; - } +.nav { + display: flex; + z-index: 2; + width: 100%; + padding: 0 8px; + box-sizing: border-box; + overflow: auto; + font-size: 0.9em; + box-shadow: 0 1px 0 var(--divider); + user-select: none; +} + +.navPath { + display: inline-block; + vertical-align: bottom; + line-height: 42px; + white-space: nowrap; +} + +.navPathItem { + display: inline-block; + margin: 0; + padding: 0 8px; + line-height: 42px; + cursor: pointer; + + &:hover { + text-decoration: underline; + } - > .path { - display: inline-block; - vertical-align: bottom; - line-height: 42px; - white-space: nowrap; - - > * { - display: inline-block; - margin: 0; - padding: 0 8px; - line-height: 42px; - cursor: pointer; - - * { - pointer-events: none; - } - - &:hover { - text-decoration: underline; - } - - &.current { - font-weight: bold; - cursor: default; - - &:hover { - text-decoration: none; - } - } - - &.separator { - margin: 0; - padding: 0; - opacity: 0.5; - cursor: default; - - > i { - margin: 0; - } - } - } - } + &.navCurrent { + font-weight: bold; + cursor: default; - > .menu { - margin-left: auto; - padding: 0 12px; + &:hover { + text-decoration: none; } } - > .main { - flex: 1; - overflow: auto; - padding: var(--margin); - - &, * { - user-select: none; - } + &.navSeparator { + margin: 0; + padding: 0; + opacity: 0.5; + cursor: default; + } +} - &.fetching { - cursor: wait !important; +.navMenu { + margin-left: auto; + padding: 0 12px; +} - * { - pointer-events: none; - } +.main { + flex: 1; + overflow: auto; + padding: var(--margin); + user-select: none; - > .contents { - opacity: 0.5; - } - } + &.fetching { + cursor: wait !important; + opacity: 0.5; + pointer-events: none; + } - &.uploading { - height: calc(100% - 38px - 100px); - } + &.uploading { + height: calc(100% - 38px - 100px); + } +} - > .contents { - - > .folders, - > .files { - display: flex; - flex-wrap: wrap; - - > .folder, - > .file { - flex-grow: 1; - width: 128px; - margin: 4px; - box-sizing: border-box; - } - - > .padding { - flex-grow: 1; - pointer-events: none; - width: 128px + 8px; - } - } +.folders, +.files { + display: flex; + flex-wrap: wrap; +} - > .empty { - padding: 16px; - text-align: center; - pointer-events: none; - opacity: 0.5; +.folder, +.file { + flex-grow: 1; + width: 128px; + margin: 4px; + box-sizing: border-box; +} - > p { - margin: 0; - } - } - } - } +.padding { + flex-grow: 1; + pointer-events: none; + width: 128px + 8px; +} - > .dropzone { - position: absolute; - left: 0; - top: 38px; - width: 100%; - height: calc(100% - 38px); - border: dashed 2px var(--focus); - pointer-events: none; - } +.empty { + padding: 16px; + text-align: center; + pointer-events: none; + opacity: 0.5; +} - > input { - display: none; - } +.dropzone { + position: absolute; + left: 0; + top: 38px; + width: 100%; + height: calc(100% - 38px); + border: dashed 2px var(--focus); + pointer-events: none; } </style> diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue index 33379ed5ca70f413aef77fc76ae6d9280c6c7c51..490aed6e0462abfab1f0e38050ab494b61946773 100644 --- a/packages/frontend/src/components/MkDriveFileThumbnail.vue +++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue @@ -1,16 +1,16 @@ <template> -<div ref="thumbnail" class="zdjebgpv"> +<div ref="thumbnail" :class="$style.root"> <ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/> - <i v-else-if="is === 'image'" class="ti ti-photo icon"></i> - <i v-else-if="is === 'video'" class="ti ti-video icon"></i> - <i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music icon"></i> - <i v-else-if="is === 'csv'" class="ti ti-file-text icon"></i> - <i v-else-if="is === 'pdf'" class="ti ti-file-text icon"></i> - <i v-else-if="is === 'textfile'" class="ti ti-file-text icon"></i> - <i v-else-if="is === 'archive'" class="ti ti-file-zip icon"></i> - <i v-else class="ti ti-file icon"></i> + <i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i> + <i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i> + <i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music" :class="$style.icon"></i> + <i v-else-if="is === 'csv'" class="ti ti-file-text" :class="$style.icon"></i> + <i v-else-if="is === 'pdf'" class="ti ti-file-text" :class="$style.icon"></i> + <i v-else-if="is === 'textfile'" class="ti ti-file-text" :class="$style.icon"></i> + <i v-else-if="is === 'archive'" class="ti ti-file-zip" :class="$style.icon"></i> + <i v-else class="ti ti-file" :class="$style.icon"></i> - <i v-if="isThumbnailAvailable && is === 'video'" class="ti ti-video icon-sub"></i> + <i v-if="isThumbnailAvailable && is === 'video'" class="ti ti-video" :class="$style.iconSub"></i> </div> </template> @@ -53,28 +53,28 @@ const isThumbnailAvailable = computed(() => { }); </script> -<style lang="scss" scoped> -.zdjebgpv { +<style lang="scss" module> +.root { position: relative; display: flex; background: var(--panel); border-radius: 8px; overflow: clip; +} - > .icon-sub { - position: absolute; - width: 30%; - height: auto; - margin: 0; - right: 4%; - bottom: 4%; - } +.iconSub { + position: absolute; + width: 30%; + height: auto; + margin: 0; + right: 4%; + bottom: 4%; +} - > .icon { - pointer-events: none; - margin: auto; - font-size: 32px; - color: #777; - } +.icon { + pointer-events: none; + margin: auto; + font-size: 32px; + color: #777; } </style> diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue index 8d2b19c013aa0e5be86ccdafc0122cfdf3ca58ac..da873cb90bebf7fb9936bcc8c74b99e8a55fd2b7 100644 --- a/packages/frontend/src/components/MkDriveSelectDialog.vue +++ b/packages/frontend/src/components/MkDriveSelectDialog.vue @@ -3,8 +3,8 @@ ref="dialog" :width="800" :height="500" - :with-ok-button="true" - :ok-button-disabled="(type === 'file') && (selected.length === 0)" + :withOkButton="true" + :okButtonDisabled="(type === 'file') && (selected.length === 0)" @click="cancel()" @close="cancel()" @ok="ok()" @@ -14,7 +14,7 @@ {{ multiple ? ((type === 'file') ? i18n.ts.selectFiles : i18n.ts.selectFolders) : ((type === 'file') ? i18n.ts.selectFile : i18n.ts.selectFolder) }} <span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> </template> - <XDrive :multiple="multiple" :select="type" @change-selection="onChangeSelection" @selected="ok()"/> + <XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/> </MkModalWindow> </template> diff --git a/packages/frontend/src/components/MkDriveWindow.vue b/packages/frontend/src/components/MkDriveWindow.vue index 8b2abc15a3ea329edd25313f8933291fdeebc1e9..64ccbec9c3bad0d8aeaa24f03af6427537fbaf3f 100644 --- a/packages/frontend/src/components/MkDriveWindow.vue +++ b/packages/frontend/src/components/MkDriveWindow.vue @@ -1,15 +1,15 @@ <template> <MkWindow ref="window" - :initial-width="800" - :initial-height="500" - :can-resize="true" + :initialWidth="800" + :initialHeight="500" + :canResize="true" @closed="emit('closed')" > <template #header> {{ i18n.ts.drive }} </template> - <XDrive :initial-folder="initialFolder"/> + <XDrive :initialFolder="initialFolder"/> </MkWindow> </template> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 9eaf16374b7476c634b442c9092498c6d003a264..cf856fd31f0a7a63234ff0de493934fd7222b338 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -1,7 +1,8 @@ <template> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> <input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter"> - <div ref="emojisEl" class="emojis"> + <!-- Firefoxã®TabフォーカスãŒæƒ³å®šå¤–ã®æŒ™å‹•ã¨ãªã‚‹ãŸã‚tabindex="-1"ã‚’è¿½åŠ https://github.com/misskey-dev/misskey/issues/10744 --> + <div ref="emojisEl" class="emojis" tabindex="-1"> <section class="result"> <div v-if="searchResultCustom.length > 0" class="body"> <button @@ -69,8 +70,8 @@ <XSection v-for="category in customEmojiCategories" :key="`custom:${category}`" - :initial-shown="false" - :emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).map(e => `:${e.name}:`))" + :initialShown="false" + :emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).filter(filterAvailable).map(e => `:${e.name}:`))" @chosen="chosen" > {{ category || i18n.ts.other }} @@ -101,7 +102,8 @@ import { isTouchUsing } from '@/scripts/touch'; import { deviceKind } from '@/scripts/device-kind'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; -import { customEmojiCategories, customEmojis } from '@/custom-emojis'; +import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis'; +import { $i } from '@/account'; const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -222,7 +224,6 @@ watch(q, () => { if (newQ.includes(' ')) { // AND検索 const keywords = newQ.split(' '); - // åå‰ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ for (const emoji of emojis) { if (keywords.every(keyword => emoji.name.includes(keyword))) { matches.add(emoji); @@ -231,11 +232,12 @@ watch(q, () => { } if (matches.size >= max) return matches; - // åå‰ã¾ãŸã¯ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) { - matches.add(emoji); - if (matches.size >= max) break; + for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { + for (const emoji of emojis) { + if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) { + matches.add(emoji); + if (matches.size >= max) break; + } } } } else { @@ -247,13 +249,14 @@ watch(q, () => { } if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) { - matches.add(emoji); - if (matches.size >= max) break; + for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { + for (const emoji of emojis) { + if (index[emoji.char].some(k => k.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } } } - if (matches.size >= max) return matches; for (const emoji of emojis) { if (emoji.name.includes(newQ)) { @@ -263,10 +266,12 @@ watch(q, () => { } if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.includes(newQ))) { - matches.add(emoji); - if (matches.size >= max) break; + for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { + for (const emoji of emojis) { + if (index[emoji.char].some(k => k.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } } } } @@ -274,10 +279,14 @@ watch(q, () => { return matches; }; - searchResultCustom.value = Array.from(searchCustom()); + searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable); searchResultUnicode.value = Array.from(searchUnicode()); }); +function filterAvailable(emoji: Misskey.entities.CustomEmoji): boolean { + return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))); +} + function focus() { if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { searchEl.value?.focus({ @@ -347,7 +356,7 @@ function done(query?: string): boolean | void { if (query == null || typeof query !== 'string') return; const q2 = query.replace(/:/g, ''); - const exactMatchCustom = customEmojis.value.find(emoji => emoji.name === q2); + const exactMatchCustom = customEmojisMap.get(q2); if (exactMatchCustom) { chosen(exactMatchCustom); return true; diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index c568d4ed5cf06fb4313aac73a546aaf0e99c9d81..cfb65e3b63c613e6b5416e5dd65cbb9ec4584539 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -2,10 +2,10 @@ <MkModal ref="modal" v-slot="{ type, maxHeight }" - :z-priority="'middle'" - :prefer-type="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" - :transparent-bg="true" - :manual-showing="manualShowing" + :zPriority="'middle'" + :preferType="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" + :transparentBg="true" + :manualShowing="manualShowing" :src="src" @click="modal?.close()" @opening="opening" @@ -14,11 +14,11 @@ > <MkEmojiPicker ref="picker" - class="ryghynhb _popup _shadow" - :class="{ drawer: type === 'drawer' }" - :show-pinned="showPinned" - :as-reaction-picker="asReactionPicker" - :as-drawer="type === 'drawer'" + class="_popup _shadow" + :class="{ [$style.drawer]: type === 'drawer' }" + :showPinned="showPinned" + :asReactionPicker="asReactionPicker" + :asDrawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen" /> @@ -67,12 +67,10 @@ function opening() { } </script> -<style lang="scss" scoped> -.ryghynhb { - &.drawer { - border-radius: 24px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - } +<style lang="scss" module> +.drawer { + border-radius: 24px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } </style> diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue index 84970410e92b4e5538e48c2fa946a74b911c344f..9fecfd608223408bac5e8158b510c5e803a10f15 100644 --- a/packages/frontend/src/components/MkEmojiPickerWindow.vue +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -1,13 +1,14 @@ <template> -<MkWindow ref="window" - :initial-width="300" - :initial-height="290" - :can-resize="true" +<MkWindow + ref="window" + :initialWidth="300" + :initialHeight="290" + :canResize="true" :mini="true" :front="true" @closed="emit('closed')" > - <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" as-window :class="$style.picker" @chosen="chosen"/> + <MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/> </MkWindow> </template> diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue index 95eef45df054412073387f776a04587859a62c46..61b87bda78fb3fc5e0f819c138b6ab2294ed1b61 100644 --- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue +++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue @@ -3,14 +3,14 @@ ref="dialog" :width="400" :height="450" - :with-ok-button="true" - :ok-button-disabled="false" + :withOkButton="true" + :okButtonDisabled="false" @ok="ok()" @close="dialog.close()" @closed="emit('closed')" > <template #header>{{ i18n.ts.describeFile }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <MkDriveFileThumbnail :file="file" fit="contain" style="height: 100px; margin-bottom: 16px;"/> <MkTextarea v-model="caption" autofocus :placeholder="i18n.ts.inputNewDescription"> <template #label>{{ i18n.ts.caption }}</template> diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue index 475e01c8d4270663e283f5650efe1fb134e095db..5dd07fc7da3201ec5c307ea1bc1fda36518ffb18 100644 --- a/packages/frontend/src/components/MkFoldableSection.vue +++ b/packages/frontend/src/components/MkFoldableSection.vue @@ -1,9 +1,9 @@ <template> -<div class="ssazuxis"> - <header class="_button" :style="{ background: bg }" @click="showBody = !showBody"> - <div class="title"><div><slot name="header"></slot></div></div> - <div class="divider"></div> - <button class="_button"> +<div ref="el" :class="$style.root"> + <header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody"> + <div :class="$style.title"><div><slot name="header"></slot></div></div> + <div :class="$style.divider"></div> + <button class="_button" :class="$style.button"> <template v-if="showBody"><i class="ti ti-chevron-up"></i></template> <template v-else><i class="ti ti-chevron-down"></i></template> </button> @@ -11,9 +11,9 @@ <Transition :name="defaultStore.state.animation ? 'folder-toggle' : ''" @enter="enter" - @after-enter="afterEnter" + @afterEnter="afterEnter" @leave="leave" - @after-leave="afterLeave" + @afterLeave="afterLeave" > <div v-show="showBody"> <slot></slot> @@ -22,84 +22,71 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref, shallowRef, watch } from 'vue'; import tinycolor from 'tinycolor2'; import { miLocalStorage } from '@/local-storage'; import { defaultStore } from '@/store'; const miLocalStoragePrefix = 'ui:folder:' as const; -export default defineComponent({ - props: { - expanded: { - type: Boolean, - required: false, - default: true, - }, - persistKey: { - type: String, - required: false, - default: null, - }, - }, - data() { - return { - defaultStore, - bg: null, - showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded, - }; - }, - watch: { - showBody() { - if (this.persistKey) { - miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f'); - } - }, - }, - mounted() { - function getParentBg(el: Element | null): string { - if (el == null || el.tagName === 'BODY') return 'var(--bg)'; - const bg = el.style.background || el.style.backgroundColor; - if (bg) { - return bg; - } else { - return getParentBg(el.parentElement); - } +const props = withDefaults(defineProps<{ + expanded?: boolean; + persistKey?: string; +}>(), { + expanded: true, +}); + +const el = shallowRef<HTMLDivElement>(); +const bg = ref<string | null>(null); +const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded); + +watch(showBody, () => { + if (props.persistKey) { + miLocalStorage.setItem(`${miLocalStoragePrefix}${props.persistKey}`, showBody.value ? 't' : 'f'); + } +}); + +function enter(el: Element) { + const elementHeight = el.getBoundingClientRect().height; + el.style.height = 0; + el.offsetHeight; // reflow + el.style.height = elementHeight + 'px'; +} + +function afterEnter(el: Element) { + el.style.height = null; +} + +function leave(el: Element) { + const elementHeight = el.getBoundingClientRect().height; + el.style.height = elementHeight + 'px'; + el.offsetHeight; // reflow + el.style.height = 0; +} + +function afterLeave(el: Element) { + el.style.height = null; +} + +onMounted(() => { + function getParentBg(el: HTMLElement | null): string { + if (el == null || el.tagName === 'BODY') return 'var(--bg)'; + const bg = el.style.background || el.style.backgroundColor; + if (bg) { + return bg; + } else { + return getParentBg(el.parentElement); } - const rawBg = getParentBg(this.$el); - const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); - bg.setAlpha(0.85); - this.bg = bg.toRgbString(); - }, - methods: { - toggleContent(show: boolean) { - this.showBody = show; - }, - - enter(el) { - const elementHeight = el.getBoundingClientRect().height; - el.style.height = 0; - el.offsetHeight; // reflow - el.style.height = elementHeight + 'px'; - }, - afterEnter(el) { - el.style.height = null; - }, - leave(el) { - const elementHeight = el.getBoundingClientRect().height; - el.style.height = elementHeight + 'px'; - el.offsetHeight; // reflow - el.style.height = 0; - }, - afterLeave(el) { - el.style.height = null; - }, - }, + } + const rawBg = getParentBg(el.value); + const _bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); + _bg.setAlpha(0.85); + bg.value = _bg.toRgbString(); }); </script> -<style lang="scss" scoped> +<style lang="scss" module> .folder-toggle-enter-active, .folder-toggle-leave-active { overflow-y: clip; transition: opacity 0.5s, height 0.5s !important; @@ -111,45 +98,41 @@ export default defineComponent({ opacity: 0; } -.ssazuxis { +.root { position: relative; +} - > header { - display: flex; - position: relative; - z-index: 10; - position: sticky; - top: var(--stickyTop, 0px); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(20px)); - - > .title { - display: grid; - place-content: center; - margin: 0; - padding: 12px 16px 12px 0; - } +.header { + display: flex; + position: relative; + z-index: 10; + position: sticky; + top: var(--stickyTop, 0px); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(20px)); +} - > .divider { - flex: 1; - margin: auto; - height: 1px; - background: var(--divider); - } +.title { + display: grid; + place-content: center; + margin: 0; + padding: 12px 16px 12px 0; +} - > button { - padding: 12px 0 12px 16px; - } - } +.divider { + flex: 1; + margin: auto; + height: 1px; + background: var(--divider); +} + +.button { + padding: 12px 0 12px 16px; } @container (max-width: 500px) { - .ssazuxis { - > header { - > .title { - padding: 8px 10px 8px 0; - } - } + .title { + padding: 8px 10px 8px 0; } } </style> diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 10eee6aab106c5bd03d2ed26d8774041a0f83832..70f0cc5cda62146b60b0d8ee1365b058a609ec8f 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -5,8 +5,8 @@ <div :class="[$style.header, { [$style.opened]: opened }]" class="_button" role="button" data-cy-folder-header @click="toggle"> <div :class="$style.headerIcon"><slot name="icon"></slot></div> <div :class="$style.headerText"> - <div :class="$style.headerTextMain"> - <slot name="label"></slot> + <div> + <MkCondensedLine :minScale="2 / 3"><slot name="label"></slot></MkCondensedLine> </div> <div :class="$style.headerTextSub"> <slot name="caption"></slot> @@ -22,18 +22,18 @@ <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }" :aria-hidden="!opened"> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''" @enter="enter" - @after-enter="afterEnter" + @afterEnter="afterEnter" @leave="leave" - @after-leave="afterLeave" + @afterLeave="afterLeave" > <KeepAlive> <div v-show="opened"> - <MkSpacer :margin-min="14" :margin-max="22"> + <MkSpacer :marginMin="14" :marginMax="22"> <slot></slot> </MkSpacer> </div> @@ -185,10 +185,6 @@ onMounted(() => { padding-right: 12px; } -.headerTextMain { - -} - .headerTextSub { color: var(--fgTransparentWeak); font-size: .85em; diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index beee21c647c3f83f55450b6bc02e9a3d2b7ed9e1..b732fbb2b9baa2d7a8afcf689d60cbcd9b1be94d 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -1,30 +1,30 @@ <template> <button - class="kpoogebi _button" - :class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full, large }" + class="_button" + :class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]" :disabled="wait" @click="onClick" > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> - <span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i> </template> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- ã¤ã¾ã‚Šãƒªãƒ¢ãƒ¼ãƒˆãƒ•ã‚©ãƒãƒ¼ã®å ´åˆã€‚ --> - <span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/> + <span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/> </template> <template v-else-if="isFollowing"> - <span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> - <span v-if="full">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i> </template> <template v-else-if="!isFollowing && !user.isLocked"> - <span v-if="full">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/> + <span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/> </template> </button> </template> @@ -33,7 +33,7 @@ import { onBeforeUnmount, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; import { claimAchievement } from '@/scripts/achievements'; import { $i } from '@/account'; @@ -50,7 +50,7 @@ const props = withDefaults(defineProps<{ let isFollowing = $ref(props.user.isFollowing); let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); let wait = $ref(false); -const connection = stream.useChannel('main'); +const connection = useStream().useChannel('main'); if (props.user.isFollowing == null) { os.api('users/show', { @@ -126,13 +126,12 @@ onBeforeUnmount(() => { }); </script> -<style lang="scss" scoped> -.kpoogebi { +<style lang="scss" module> +.root { position: relative; display: inline-block; font-weight: bold; - color: var(--accent); - background: transparent; + color: var(--fgOnWhite); border: solid 1px var(--accent); padding: 0; height: 31px; @@ -196,9 +195,9 @@ onBeforeUnmount(() => { cursor: wait !important; opacity: 0.7; } +} - > span { - margin-right: 6px; - } +.text { + margin-right: 6px; } </style> diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue index 0befa7e3ae78ec20e1d43b85df2e2e9f7a6e385f..1264c42331deebca55924df5fcea9796209cc021 100644 --- a/packages/frontend/src/components/MkForgotPassword.vue +++ b/packages/frontend/src/components/MkForgotPassword.vue @@ -8,27 +8,28 @@ > <template #header>{{ i18n.ts.forgotPassword }}</template> - <form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> - <div class="main _gaps_m"> - <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required> - <template #label>{{ i18n.ts.username }}</template> - <template #prefix>@</template> - </MkInput> + <MkSpacer :marginMin="20" :marginMax="28"> + <form v-if="instance.enableEmail" @submit.prevent="onSubmit"> + <div class="_gaps_m"> + <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required> + <template #label>{{ i18n.ts.username }}</template> + <template #prefix>@</template> + </MkInput> - <MkInput v-model="email" type="email" :spellcheck="false" required> - <template #label>{{ i18n.ts.emailAddress }}</template> - <template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template> - </MkInput> + <MkInput v-model="email" type="email" :spellcheck="false" required> + <template #label>{{ i18n.ts.emailAddress }}</template> + <template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template> + </MkInput> - <MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton> - </div> - <div class="sub"> - <MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA> + <MkButton type="submit" rounded :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton> + + <MkInfo>{{ i18n.ts._forgotPassword.ifNoEmail }}</MkInfo> + </div> + </form> + <div v-else> + {{ i18n.ts._forgotPassword.contactAdmin }} </div> - </form> - <div v-else class="bafecedb"> - {{ i18n.ts._forgotPassword.contactAdmin }} - </div> + </MkSpacer> </MkModalWindow> </template> @@ -37,6 +38,7 @@ import { } from 'vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; +import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os'; import { instance } from '@/instance'; import { i18n } from '@/i18n'; @@ -62,20 +64,3 @@ async function onSubmit() { dialog.close(); } </script> - -<style lang="scss" scoped> -.bafeceda { - > .main { - padding: 24px; - } - - > .sub { - border-top: solid 0.5px var(--divider); - padding: 24px; - } -} - -.bafecedb { - padding: 24px; -} -</style> diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 979df2e7c1acb1292e56f484e653c9c657a4c3a7..6d2b391e6d0e1aba4c7815d622061eced1363981 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -2,9 +2,9 @@ <MkModalWindow ref="dialog" :width="450" - :can-close="false" - :with-ok-button="true" - :ok-button-disabled="false" + :canClose="false" + :withOkButton="true" + :okButtonDisabled="false" @click="cancel()" @ok="ok()" @close="cancel()" @@ -14,7 +14,7 @@ {{ title }} </template> - <MkSpacer :margin-min="20" :margin-max="32"> + <MkSpacer :marginMin="20" :marginMax="32"> <div class="_gaps_m"> <template v-for="item in Object.keys(form).filter(item => !form[item].hidden)"> <MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1"> @@ -41,7 +41,7 @@ <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> <option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option> </MkRadios> - <MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter"> + <MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :textConverter="form[item].textConverter"> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template> </MkRange> @@ -54,8 +54,8 @@ </MkModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { reactive, shallowRef } from 'vue'; import MkInput from './MkInput.vue'; import MkTextarea from './MkTextarea.vue'; import MkSwitch from './MkSwitch.vue'; @@ -66,58 +66,36 @@ import MkRadios from './MkRadios.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkModalWindow, - MkInput, - MkTextarea, - MkSwitch, - MkSelect, - MkRange, - MkButton, - MkRadios, - }, +const props = defineProps<{ + title: string; + form: any; +}>(); - props: { - title: { - type: String, - required: true, - }, - form: { - type: Object, - required: true, - }, - }, +const emit = defineEmits<{ + (ev: 'done', v: { + canceled?: boolean; + result?: any; + }): void; +}>(); - emits: ['done'], +const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); +const values = reactive({}); - data() { - return { - values: {}, - i18n, - }; - }, +for (const item in props.form) { + values[item] = props.form[item].default ?? null; +} - created() { - for (const item in this.form) { - this.values[item] = this.form[item].default ?? null; - } - }, +function ok() { + emit('done', { + result: values, + }); + dialog.value.close(); +} - methods: { - ok() { - this.$emit('done', { - result: this.values, - }); - this.$refs.dialog.close(); - }, - - cancel() { - this.$emit('done', { - canceled: true, - }); - this.$refs.dialog.close(); - }, - }, -}); +function cancel() { + emit('done', { + canceled: true, + }); + dialog.value.close(); +} </script> diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts index 57b3e755132f9274d8fbc4cf1a4ee1fdfa6c04e1..72ac0a58f94abb37460d2e00e434f10ef46e4f07 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts +++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts @@ -44,6 +44,10 @@ export const Default = { ], parameters: { layout: 'centered', + chromatic: { + // FIXME: flaky + disableSnapshot: true, + }, }, } satisfies StoryObj<typeof MkGalleryPostPreview>; export const Hover = { diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 4f8f7b945ac65ecd6e1fd64b04cc00dc9f0c324c..3a39ad963ba1b3f2ac15ef07dae457629629ea5c 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -5,16 +5,13 @@ <ImgWithBlurhash class="img layered" :transition="safe ? null : { - enterActiveClass: $style.transition_toggle_enterActive, + duration: 500, leaveActiveClass: $style.transition_toggle_leaveActive, - enterFromClass: $style.transition_toggle_enterFrom, leaveToClass: $style.transition_toggle_leaveTo, - enterToClass: $style.transition_toggle_enterTo, - leaveFromClass: $style.transition_toggle_leaveFrom, }" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash" - :force-blurhash="!show" + :forceBlurhash="!show" /> </Transition> </div> @@ -53,24 +50,16 @@ function leaveHover(): void { </script> <style lang="scss" module> -.transition_toggle_enterActive, .transition_toggle_leaveActive { - transition: opacity 0.5s; + transition: opacity .5s; position: absolute; top: 0; left: 0; } -.transition_toggle_enterFrom, .transition_toggle_leaveTo { opacity: 0; } - -.transition_toggle_enterTo, -.transition_toggle_leaveFrom { - transition: none; - opacity: 1; -} </style> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/MkImageViewer.vue b/packages/frontend/src/components/MkImageViewer.vue deleted file mode 100644 index a90e27e502063472451c06f70b49ff1b13f0cb43..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/MkImageViewer.vue +++ /dev/null @@ -1,78 +0,0 @@ -<template> -<MkModal ref="modal" :z-priority="'middle'" @click="modal.close()" @closed="emit('closed')"> - <div class="xubzgfga"> - <header>{{ image.name }}</header> - <img :src="image.url" :alt="image.comment" :title="image.comment" @click="modal.close()"/> - <footer> - <span>{{ image.type }}</span> - <span>{{ bytes(image.size) }}</span> - <span v-if="image.properties && image.properties.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span> - </footer> - </div> -</MkModal> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import * as misskey from 'misskey-js'; -import bytes from '@/filters/bytes'; -import number from '@/filters/number'; -import MkModal from '@/components/MkModal.vue'; - -const props = withDefaults(defineProps<{ - image: misskey.entities.DriveFile; -}>(), { -}); - -const emit = defineEmits<{ - (ev: 'closed'): void; -}>(); - -const modal = $shallowRef<InstanceType<typeof MkModal>>(); -</script> - -<style lang="scss" scoped> -.xubzgfga { - margin: auto; - display: flex; - flex-direction: column; - height: 100%; - - > header, - > footer { - align-self: center; - display: inline-block; - padding: 6px 9px; - font-size: 90%; - background: rgba(0, 0, 0, 0.5); - border-radius: 6px; - color: #fff; - } - - > header { - margin-bottom: 8px; - opacity: 0.9; - } - - > img { - display: block; - flex: 1; - min-height: 0; - object-fit: contain; - width: 100%; - cursor: zoom-out; - image-orientation: from-image; - } - - > footer { - margin-top: 8px; - opacity: 0.8; - - > span + span { - margin-left: 0.5em; - padding-left: 0.5em; - border-left: solid 1px rgba(255, 255, 255, 0.5); - } - } -} -</style> diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 6406a350601716d30d156bbb9a8fdbea0e457f8b..672a28f6d0cca1c82c2be238f5c1c350d1db5cea 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -1,30 +1,60 @@ <template> -<div :class="[$style.root, { [$style.cover]: cover }]" :title="title ?? ''"> - <img v-if="!loaded && src && !forceBlurhash" :class="$style.loader" :src="src" @load="onLoad"/> - <Transition - mode="in-out" - :enter-active-class="defaultStore.state.animation && (props.transition?.enterActiveClass ?? $style['transition_toggle_enterActive']) || undefined" - :leave-active-class="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style['transition_toggle_leaveActive']) || undefined" - :enter-from-class="defaultStore.state.animation && props.transition?.enterFromClass || undefined" - :leave-to-class="defaultStore.state.animation && props.transition?.leaveToClass || undefined" - :enter-to-class="defaultStore.state.animation && (props.transition?.enterToClass ?? $style['transition_toggle_enterTo']) || undefined" - :leave-from-class="defaultStore.state.animation && (props.transition?.leaveFromClass ?? $style['transition_toggle_leaveFrom']) || undefined" +<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''"> + <TransitionGroup + :duration="defaultStore.state.animation && props.transition?.duration || undefined" + :enterActiveClass="defaultStore.state.animation && props.transition?.enterActiveClass || undefined" + :leaveActiveClass="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined" + :enterFromClass="defaultStore.state.animation && props.transition?.enterFromClass || undefined" + :leaveToClass="defaultStore.state.animation && props.transition?.leaveToClass || undefined" + :enterToClass="defaultStore.state.animation && props.transition?.enterToClass || undefined" + :leaveFromClass="defaultStore.state.animation && props.transition?.leaveFromClass || undefined" > - <canvas v-if="!loaded || forceBlurhash" ref="canvas" :class="$style.canvas" :width="width" :height="height" :title="title ?? undefined"/> - <img v-else :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined"/> - </Transition> + <canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined"/> + <img v-show="!hide" key="img" ref="img" :height="imgHeight" :width="imgWidth" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async"/> + </TransitionGroup> </div> </template> +<script lang="ts"> +import { $ref } from 'vue/macros'; +import DrawBlurhash from '@/workers/draw-blurhash?worker'; +import TestWebGL2 from '@/workers/test-webgl2?worker'; +import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch'; +import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; + +const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => { + // テスト環境㧠Web Worker インスタンスã¯ä½œæˆã§ããªã„ + if (import.meta.env.MODE === 'test') { + resolve(null); + return; + } + const testWorker = new TestWebGL2(); + testWorker.addEventListener('message', event => { + if (event.data.result) { + const workers = new WorkerMultiDispatch( + () => new DrawBlurhash(), + Math.min(navigator.hardwareConcurrency - 1, 4), + ); + resolve(workers); + if (_DEV_) console.log('WebGL2 in worker is supported!'); + } else { + resolve(null); + if (_DEV_) console.log('WebGL2 in worker is not supported...'); + } + testWorker.terminate(); + }); +}); +</script> + <script lang="ts" setup> -import { onMounted, shallowRef, useCssModule, watch } from 'vue'; -import { decode } from 'blurhash'; +import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue'; +import { v4 as uuid } from 'uuid'; +import { render } from 'buraha'; import { defaultStore } from '@/store'; -const $style = useCssModule(); - const props = withDefaults(defineProps<{ transition?: { + duration?: number | { enter: number; leave: number; }; enterActiveClass?: string; leaveActiveClass?: string; enterFromClass?: string; @@ -51,67 +81,141 @@ const props = withDefaults(defineProps<{ forceBlurhash: false, }); +const viewId = uuid(); const canvas = shallowRef<HTMLCanvasElement>(); +const root = shallowRef<HTMLDivElement>(); +const img = shallowRef<HTMLImageElement>(); let loaded = $ref(false); -let width = $ref(props.width); -let height = $ref(props.height); - -function onLoad() { - loaded = true; +let canvasWidth = $ref(64); +let canvasHeight = $ref(64); +let imgWidth = $ref(props.width); +let imgHeight = $ref(props.height); +let bitmapTmp = $ref<CanvasImageSource | undefined>(); +const hide = computed(() => !loaded || props.forceBlurhash); + +function waitForDecode() { + if (props.src != null && props.src !== '') { + nextTick() + .then(() => img.value?.decode()) + .then(() => { + loaded = true; + }, error => { + console.error('Error occured during decoding image', img.value, error); + throw Error(error); + }); + } else { + loaded = false; + } } -watch([() => props.width, () => props.height], () => { +watch([() => props.width, () => props.height, root], () => { const ratio = props.width / props.height; if (ratio > 1) { - width = Math.round(64 * ratio); - height = 64; + canvasWidth = Math.round(64 * ratio); + canvasHeight = 64; } else { - width = 64; - height = Math.round(64 / ratio); + canvasWidth = 64; + canvasHeight = Math.round(64 / ratio); } + + const clientWidth = root.value?.clientWidth ?? 300; + imgWidth = clientWidth; + imgHeight = Math.round(clientWidth / ratio); }, { immediate: true, }); -function draw() { - if (props.hash == null || !canvas.value) return; - const pixels = decode(props.hash, width, height); +function drawImage(bitmap: CanvasImageSource) { + // canvasãŒãªã„(mountedã•ã‚Œã¦ã„ãªã„ï¼‰å ´åˆã¯Tmpã«ä¿å˜ã—ã¦ãŠã + if (!canvas.value) { + bitmapTmp = bitmap; + return; + } + + // canvasãŒã‚ã‚Œã°æç”»ã™ã‚‹ + bitmapTmp = undefined; + const ctx = canvas.value.getContext('2d'); + if (!ctx) return; + ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight); +} + +async function draw() { + if (!canvas.value || props.hash == null) return; + const ctx = canvas.value.getContext('2d'); - const imageData = ctx!.createImageData(width, height); - imageData.data.set(pixels); - ctx!.putImageData(imageData, 0, 0); + if (!ctx) return; + + // avgColorã§ãŠèŒ¶ã‚’ã«ã”ã™ + ctx.beginPath(); + ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888'; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + const workers = await workerPromise; + if (workers) { + workers.postMessage( + { + id: viewId, + hash: props.hash, + width: canvasWidth, + height: canvasHeight, + }, + undefined, + ); + } else { + try { + const work = document.createElement('canvas'); + work.width = canvasWidth; + work.height = canvasHeight; + render(props.hash, work); + ctx.drawImage(work, 0, 0, canvasWidth, canvasHeight); + } catch (error) { + console.error('Error occured during drawing blurhash', error); + } + } +} + +function workerOnMessage(event: MessageEvent) { + if (event.data.id !== viewId) return; + drawImage(event.data.bitmap as ImageBitmap); } -watch([() => props.hash, canvas], () => { +workerPromise.then(worker => { + if (worker) { + worker.addListener(workerOnMessage); + } + draw(); }); -onMounted(() => { +watch(() => props.src, () => { + waitForDecode(); +}); + +watch(() => props.hash, () => { draw(); }); -</script> -<style lang="scss" module> -.transition_toggle_enterActive, -.transition_toggle_leaveActive { - position: absolute; - top: 0; - left: 0; -} +onMounted(() => { + // drawImageãŒmountedより先ã«å‘¼ã°ã‚Œã¦ã„ã‚‹å ´åˆã¯ã“ã“ã§æç”»ã™ã‚‹ + if (bitmapTmp) { + drawImage(bitmapTmp); + } + waitForDecode(); +}); -.transition_toggle_enterTo, -.transition_toggle_leaveFrom { - opacity: 0; -} +onUnmounted(() => { + workerPromise.then(worker => { + worker?.removeListener(workerOnMessage); + }); +}); +</script> -.loader { +<style lang="scss" module> +.transition_leaveActive { position: absolute; top: 0; left: 0; - width: 0; - height: 0; } - .root { position: relative; width: 100%; diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue index ff69c7964124d04df1ff28b9bebc9068379a69f9..4b6a77563569cbdf24ac361177b1d0acf571bf89 100644 --- a/packages/frontend/src/components/MkKeyValue.vue +++ b/packages/frontend/src/components/MkKeyValue.vue @@ -1,9 +1,9 @@ <template> -<div class="alqyeyti" :class="{ oneline }"> - <div class="key"> +<div :class="[$style.root, { [$style.oneline]: oneline }]"> + <div :class="$style.key"> <slot name="key"></slot> </div> - <div class="value"> + <div :class="$style.value"> <slot name="value"></slot> <button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button> </div> @@ -30,24 +30,18 @@ const copy_ = () => { }; </script> -<style lang="scss" scoped> -.alqyeyti { - > .key { - font-size: 0.85em; - padding: 0 0 0.25em 0; - opacity: 0.75; - } - +<style lang="scss" module> +.root { &.oneline { display: flex; - > .key { + .key { width: 30%; font-size: 1em; padding: 0 8px 0 0; } - > .value { + .value { width: 70%; white-space: nowrap; overflow: hidden; @@ -55,4 +49,10 @@ const copy_ = () => { } } } + +.key { + font-size: 0.85em; + padding: 0 0 0.25em 0; + opacity: 0.75; +} </style> diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index 80e5cc82709ef9383ee7e0fcc019f114619a8d8c..9262778612529431f99e3760a71fe0d68f38cd34 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :prefer-type="preferedModalType" :anchor="anchor" :transparent-bg="true" :src="src" @click="modal.close()" @closed="emit('closed')"> +<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }"> <div class="main"> <template v-for="item in items"> diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 5ca4c5051849e7fcdb57b9e4d858e8e85a2480c1..5902d6fd25fda0fab716e79fd50e6b71c4f0bade 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -1,27 +1,27 @@ <template> -<div class="mk-media-banner"> - <div v-if="media.isSensitive && hide" class="sensitive" @click="hide = false"> - <span class="icon"><i class="ti ti-alert-triangle"></i></span> +<div :class="$style.root"> + <div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="hide = false"> + <span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span> <b>{{ i18n.ts.sensitive }}</b> <span>{{ i18n.ts.clickToShow }}</span> </div> - <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio"> - <VuePlyr :options="{ volume: 0.5 }"> - <audio controls preload="metadata"> - <source - :src="media.url" - :type="media.type" - /> - </audio> - </VuePlyr> + <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :class="$style.audio"> + <audio + ref="audioEl" + :src="media.url" + :title="media.name" + controls + preload="metadata" + @volumechange="volumechange" + /> </div> <a - v-else class="download" + v-else :class="$style.download" :href="media.url" :title="media.name" :download="media.name" > - <span class="icon"><i class="ti ti-download"></i></span> + <span style="font-size: 1.6em;"><i class="ti ti-download"></i></span> <b>{{ media.name }}</b> </a> </div> @@ -30,9 +30,7 @@ <script lang="ts" setup> import { onMounted } from 'vue'; import * as misskey from 'misskey-js'; -import VuePlyr from 'vue-plyr'; import { soundConfigStore } from '@/scripts/sound'; -import 'vue-plyr/dist/vue-plyr.css'; import { i18n } from '@/i18n'; const props = withDefaults(defineProps<{ @@ -52,55 +50,34 @@ onMounted(() => { }); </script> -<style lang="scss" scoped> -.mk-media-banner { +<style lang="scss" module> +.root { width: 100%; border-radius: 4px; margin-top: 4px; - // overflow: clip; - - --plyr-color-main: var(--accent); - --plyr-audio-controls-background: var(--bg); - --plyr-audio-controls-color: var(--accentLighten); - - > .download, - > .sensitive { - display: flex; - align-items: center; - font-size: 12px; - padding: 8px 12px; - white-space: nowrap; - - > * { - display: block; - } - - > b { - overflow: hidden; - text-overflow: ellipsis; - } - - > *:not(:last-child) { - margin-right: .2em; - } + overflow: clip; +} - > .icon { - font-size: 1.6em; - } - } +.download, +.sensitive { + display: flex; + align-items: center; + font-size: 12px; + padding: 8px 12px; + white-space: nowrap; +} - > .download { - background: var(--noteAttachedFile); - } +.download { + background: var(--noteAttachedFile); +} - > .sensitive { - background: #111; - color: #fff; - } +.sensitive { + background: #111; + color: #fff; +} - > .audio { - border-radius: 8px; - // overflow: clip; - } +.audio { + border-radius: 8px; + overflow: clip; } </style> diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 42dc9e79ff55c154d0eb9d6408d0d3712f4b082f..b29871c3635e1552bc8b1240bc26a8a5aeb0fdca 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -1,29 +1,39 @@ <template> -<div v-if="hide" :class="$style.hidden" @click="hide = false"> - <ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment" :width="image.properties.width" :height="image.properties.height" :force-blurhash="defaultStore.state.enableDataSaverMode"/> - <div :class="$style.hiddenText"> - <div :class="$style.hiddenTextWrapper"> - <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b> - <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b> - <span style="display: block;">{{ i18n.ts.clickToShow }}</span> - </div> - </div> -</div> -<div v-else :class="$style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'"> +<div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick"> <a :class="$style.imageContainer" :href="image.url" :title="image.name" > - <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :width="image.properties.width" :height="image.properties.height" :cover="false"/> + <ImgWithBlurhash + :hash="image.blurhash" + :src="(defaultStore.state.enableDataSaverMode && hide) ? null : url" + :forceBlurhash="hide" + :cover="hide" + :alt="image.comment || image.name" + :title="image.comment || image.name" + :width="image.properties.width" + :height="image.properties.height" + :style="hide ? 'filter: brightness(0.5);' : null" + /> </a> - <div :class="$style.indicators"> - <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> - <div v-if="image.comment" :class="$style.indicator">ALT</div> - <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);">NSFW</div> - </div> - <button v-tooltip="i18n.ts.hide" :class="$style.hide" class="_button" @click="hide = true"><i class="ti ti-eye-off"></i></button> - <button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots"></i></button> + <template v-if="hide"> + <div :class="$style.hiddenText"> + <div :class="$style.hiddenTextWrapper"> + <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b> + <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b> + <span style="display: block;">{{ i18n.ts.clickToShow }}</span> + </div> + </div> + </template> + <template v-else> + <div :class="$style.indicators"> + <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> + <div v-if="image.comment" :class="$style.indicator">ALT</div> + <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);">NSFW</div> + </div> + <button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button> + </template> </div> </template> @@ -53,6 +63,12 @@ const url = $computed(() => (props.raw || defaultStore.state.loadRawImages) : props.image.thumbnailUrl, ); +function onclick() { + if (hide) { + hide = false; + } +} + // Plugin:register_note_view_interruptor を使ã£ã¦æ›¸ãæ›ãˆã‚‰ã‚Œã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ãŸã‚watchã™ã‚‹ watch(() => props.image, () => { hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore'); @@ -62,9 +78,16 @@ watch(() => props.image, () => { }); function showMenu(ev: MouseEvent) { - os.popupMenu([...(iAmModerator ? [{ - text: i18n.ts.markAsSensitive, + os.popupMenu([{ + text: i18n.ts.hide, icon: 'ti ti-eye-off', + action: () => { + hide = true; + }, + }, ...(iAmModerator ? [{ + text: i18n.ts.markAsSensitive, + icon: 'ti ti-eye-exclamation', + danger: true, action: () => { os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); }, @@ -105,34 +128,20 @@ function showMenu(ev: MouseEvent) { background-size: 16px 16px; } -.hide { - display: block; - position: absolute; - border-radius: 6px; - background-color: var(--accentedBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - color: var(--accent); - font-size: 0.8em; - padding: 6px 8px; - text-align: center; - top: 12px; - right: 12px; -} - .menu { display: block; position: absolute; - border-radius: 6px; + border-radius: 999px; background-color: rgba(0, 0, 0, 0.3); -webkit-backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px)); color: #fff; font-size: 0.8em; - padding: 6px 8px; + width: 32px; + height: 32px; text-align: center; - bottom: 12px; - right: 12px; + bottom: 10px; + right: 10px; } .imageContainer { @@ -149,12 +158,10 @@ function showMenu(ev: MouseEvent) { .indicators { display: inline-flex; position: absolute; - top: 12px; - left: 12px; - text-align: center; + top: 10px; + left: 10px; pointer-events: none; opacity: .5; - font-size: 14px; gap: 6px; } @@ -165,7 +172,7 @@ function showMenu(ev: MouseEvent) { color: var(--accentLighten); display: inline-block; font-weight: bold; - font-size: 12px; - padding: 2px 6px; + font-size: 0.8em; + padding: 2px 5px; } </style> diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index e456ff3eec76c395ee4833bd0d3e01a4d2c16900..a0a2450054b1188a9ed52f1ac905e73ee736cf4b 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -6,7 +6,11 @@ ref="gallery" :class="[ $style.medias, - count <= 4 ? $style['n' + count] : $style.nMany, + count === 1 ? [$style.n1, { + [$style.n116_9]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '16_9', + [$style.n11_1]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '1_1', + [$style.n12_3]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '2_3', + }] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany, ]" > <template v-for="media in mediaList.filter(media => previewable(media))"> @@ -19,7 +23,7 @@ </template> <script lang="ts" setup> -import { onMounted, ref, useCssModule, watch } from 'vue'; +import { onMounted, watch, shallowRef } from 'vue'; import * as misskey from 'misskey-js'; import PhotoSwipeLightbox from 'photoswipe/lightbox'; import PhotoSwipe from 'photoswipe'; @@ -36,13 +40,42 @@ const props = defineProps<{ raw?: boolean; }>(); -const $style = useCssModule(); - -const gallery = ref<HTMLDivElement>(); +const gallery = shallowRef<HTMLDivElement>(); const pswpZIndex = os.claimZIndex('middle'); document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); const count = $computed(() => props.mediaList.filter(media => previewable(media)).length); +function calcAspectRatio() { + if (!gallery.value) return; + + let img = props.mediaList[0]; + + if (props.mediaList.length !== 1 || !(img.properties.width && img.properties.height)) { + gallery.value.style.aspectRatio = ''; + return; + } + + // アスペクト比上é™è¨å®šã§ã¯ã€æ¨ªé•·ã®å ´åˆã¯é«˜ã•ã‚’縮å°ã•ã›ã‚‹ + const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`; + + switch (defaultStore.state.mediaListWithOneImageAppearance) { + case '16_9': + gallery.value.style.aspectRatio = ratioMax(16 / 9); + break; + case '1_1': + gallery.value.style.aspectRatio = ratioMax(1); + break; + case '2_3': + gallery.value.style.aspectRatio = ratioMax(2 / 3); + break; + default: + gallery.value.style.aspectRatio = ''; + break; + } +} + +watch([defaultStore.reactiveState.mediaListWithOneImageAppearance, gallery], () => calcAspectRatio()); + onMounted(() => { const lightbox = new PhotoSwipeLightbox({ dataSource: props.mediaList @@ -64,7 +97,7 @@ onMounted(() => { return item; }), gallery: gallery.value, - mainClass: $style.pswp, + mainClass: 'pswp', children: '.image', thumbSelector: '.image', loop: false, @@ -162,12 +195,37 @@ const previewable = (file: misskey.entities.DriveFile): boolean => { display: grid; grid-gap: 8px; - // for webkit height: 100%; + width: 100%; &.n1 { - aspect-ratio: 16/9; grid-template-rows: 1fr; + + // default (expand) + min-height: 64px; + max-height: clamp( + 64px, + 50cqh, + min(360px, 50vh) + ); + + &.n116_9 { + min-height: none; + max-height: none; + aspect-ratio: 16 / 9; // fallback + } + + &.n11_1{ + min-height: none; + max-height: none; + aspect-ratio: 1 / 1; // fallback + } + + &.n12_3 { + min-height: none; + max-height: none; + aspect-ratio: 2 / 3; // fallback + } } &.n2 { @@ -211,7 +269,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => { border-radius: 8px; } -.pswp { +:global(.pswp) { --pswp-root-z-index: var(--mk-pswp-root-z-index, 2000700) !important; --pswp-bg: var(--modalBg) !important; } diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index a4b76300e6ad71274380aa63159dba087a148ae4..40bae90b5e49f2202f5f3562a5841cc137e8d45a 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -1,26 +1,28 @@ <template> -<div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false"> +<div v-if="hide" :class="$style.hidden" @click="hide = false"> <!-- ã€æ³¨æ„】dataSaverMode ãŒæœ‰åŠ¹ã«ãªã£ã¦ã„ã‚‹éš›ã«ã¯ã€hide ㌠false ã«ãªã‚‹ã¾ã§ã‚µãƒ ãƒã‚¤ãƒ«ã‚„動画をèªã¿è¾¼ã¾ãªã„よã†ã«ã™ã‚‹ã“㨠--> - <div> - <b v-if="video.isSensitive"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b> - <b v-else><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b> + <div :class="$style.sensitive"> + <b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b> + <b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b> <span>{{ i18n.ts.clickToShow }}</span> </div> </div> -<div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu"> - <VuePlyr :options="{ volume: 0.5 }"> - <video - controls - :data-poster="video.thumbnailUrl" +<div v-else :class="$style.visible"> + <video + :class="$style.video" + :poster="video.thumbnailUrl" + :title="video.comment" + :alt="video.comment" + preload="none" + controls + @contextmenu.stop + > + <source + :src="video.url" + :type="video.type" > - <source - size="720" - :src="video.url" - :type="video.type" - /> - </video> - </VuePlyr> - <i class="ti ti-eye-off" @click="hide = true"></i> + </video> + <i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i> </div> </template> @@ -28,9 +30,7 @@ import { ref } from 'vue'; import * as misskey from 'misskey-js'; import bytes from '@/filters/bytes'; -import VuePlyr from 'vue-plyr'; import { defaultStore } from '@/store'; -import 'vue-plyr/dist/vue-plyr.css'; import { i18n } from '@/i18n'; const props = defineProps<{ @@ -40,56 +40,49 @@ const props = defineProps<{ const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore')); </script> -<style lang="scss" scoped> -.kkjnbbplepmiyuadieoenjgutgcmtsvu { +<style lang="scss" module> +.visible { position: relative; +} - --plyr-color-main: var(--accent); - - > i { - display: block; - position: absolute; - border-radius: 6px; - background-color: var(--fg); - color: var(--accentLighten); - font-size: 14px; - opacity: .5; - padding: 3px 6px; - text-align: center; - cursor: pointer; - top: 12px; - right: 12px; - } - - > video { - display: flex; - justify-content: center; - align-items: center; +.hide { + display: block; + position: absolute; + border-radius: 6px; + background-color: var(--fg); + color: var(--accentLighten); + font-size: 14px; + opacity: .5; + padding: 3px 6px; + text-align: center; + cursor: pointer; + top: 12px; + right: 12px; +} - font-size: 3.5em; - overflow: hidden; - background-position: center; - background-size: cover; - width: 100%; - height: 100%; - } +.video { + display: flex; + justify-content: center; + align-items: center; + font-size: 3.5em; + overflow: hidden; + background-position: center; + background-size: cover; + width: 100%; + height: 100%; } -.icozogqfvdetwohsdglrbswgrejoxbdj { +.hidden { display: flex; justify-content: center; align-items: center; background: #111; color: #fff; +} - > div { - display: table-cell; - text-align: center; - font-size: 12px; - - > b { - display: block; - } - } +.sensitive { + display: table-cell; + text-align: center; + font-size: 12px; } </style> diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue index 481c3710ca714dcc3d5c8e6ddd0e1742b4200ea2..bb256c394b7fcd4e9c11743f63ebc85bdbcabecc 100644 --- a/packages/frontend/src/components/MkMention.vue +++ b/packages/frontend/src/components/MkMention.vue @@ -2,7 +2,7 @@ <MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }"> <img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt=""> <span> - <span :class="$style.username">@{{ username }}</span> + <span>@{{ username }}</span> <span v-if="(host != localHost) || defaultStore.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span> </span> </MkA> diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue index e0935efbe70277f38638f029c50d226cb1e04d99..4fedfe7014f911bf19dd91cc2013cc23a8be1183 100644 --- a/packages/frontend/src/components/MkMenu.child.vue +++ b/packages/frontend/src/components/MkMenu.child.vue @@ -1,6 +1,6 @@ <template> <div ref="el" :class="$style.root"> - <MkMenu :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/> + <MkMenu :items="items" :align="align" :width="width" :asDrawer="false" @close="onChildClosed"/> </div> </template> diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index e513a65a32ad5ddb0dd017607ca31fb35353e2a5..7dd6a8c88f0613bda654cbb7719b26b2e850ad69 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -49,8 +49,8 @@ <span>{{ i18n.ts.none }}</span> </span> </div> - <div v-if="childMenu" :class="$style.child"> - <XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/> + <div v-if="childMenu"> + <XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned"/> </div> </div> </template> diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 99df9e8150863f65a8f488ded028df5493e1814b..bb5c6c7aab7e08288ce060ea1ed27d854885b8d6 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -1,11 +1,31 @@ <template> <Transition :name="transitionName" - :enter-active-class="$style['transition_' + transitionName + '_enterActive']" - :leave-active-class="$style['transition_' + transitionName + '_leaveActive']" - :enter-from-class="$style['transition_' + transitionName + '_enterFrom']" - :leave-to-class="$style['transition_' + transitionName + '_leaveTo']" - :duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened" + :enterActiveClass="normalizeClass({ + [$style.transition_modalDrawer_enterActive]: transitionName === 'modal-drawer', + [$style.transition_modalPopup_enterActive]: transitionName === 'modal-popup', + [$style.transition_modal_enterActive]: transitionName === 'modal', + [$style.transition_send_enterActive]: transitionName === 'send', + })" + :leaveActiveClass="normalizeClass({ + [$style.transition_modalDrawer_leaveActive]: transitionName === 'modal-drawer', + [$style.transition_modalPopup_leaveActive]: transitionName === 'modal-popup', + [$style.transition_modal_leaveActive]: transitionName === 'modal', + [$style.transition_send_leaveActive]: transitionName === 'send', + })" + :enterFromClass="normalizeClass({ + [$style.transition_modalDrawer_enterFrom]: transitionName === 'modal-drawer', + [$style.transition_modalPopup_enterFrom]: transitionName === 'modal-popup', + [$style.transition_modal_enterFrom]: transitionName === 'modal', + [$style.transition_send_enterFrom]: transitionName === 'send', + })" + :leaveToClass="normalizeClass({ + [$style.transition_modalDrawer_leaveTo]: transitionName === 'modal-drawer', + [$style.transition_modalPopup_leaveTo]: transitionName === 'modal-popup', + [$style.transition_modal_leaveTo]: transitionName === 'modal', + [$style.transition_send_leaveTo]: transitionName === 'send', + })" + :duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened" > <div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <div data-cy-bg :data-cy-transparent="isEnableBgTransparent" class="_modalBg" :class="[$style.bg, { [$style.bgTransparent]: isEnableBgTransparent }]" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div> @@ -17,7 +37,7 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted, watch, provide } from 'vue'; +import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { defaultStore } from '@/store'; @@ -38,7 +58,7 @@ type ModalTypes = 'popup' | 'dialog' | 'drawer'; const props = withDefaults(defineProps<{ manualShowing?: boolean | null; anchor?: { x: string; y: string; }; - src?: HTMLElement; + src?: HTMLElement | null; preferType?: ModalTypes | 'auto'; zPriority?: 'low' | 'middle' | 'high'; noOverlap?: boolean; @@ -264,6 +284,10 @@ const onOpened = () => { }, { passive: true }); }; +const alignObserver = new ResizeObserver((entries, observer) => { + align(); +}); + onMounted(() => { watch(() => props.src, async () => { if (props.src) { @@ -278,12 +302,14 @@ onMounted(() => { }, { immediate: true }); nextTick(() => { - new ResizeObserver((entries, observer) => { - align(); - }).observe(content!); + alignObserver.observe(content!); }); }); +onUnmounted(() => { + alignObserver.disconnect(); +}); + defineExpose({ close, }); @@ -339,8 +365,8 @@ defineExpose({ } } -.transition_modal-popup_enterActive, -.transition_modal-popup_leaveActive { +.transition_modalPopup_enterActive, +.transition_modalPopup_leaveActive { > .bg { transition: opacity 0.1s !important; } @@ -350,8 +376,8 @@ defineExpose({ transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important; } } -.transition_modal-popup_enterFrom, -.transition_modal-popup_leaveTo { +.transition_modalPopup_enterFrom, +.transition_modalPopup_leaveTo { > .bg { opacity: 0; } @@ -364,7 +390,7 @@ defineExpose({ } } -.transition_modal-drawer_enterActive { +.transition_modalDrawer_enterActive { > .bg { transition: opacity 0.2s !important; } @@ -373,7 +399,7 @@ defineExpose({ transition: transform 0.2s cubic-bezier(0,.5,0,1) !important; } } -.transition_modal-drawer_leaveActive { +.transition_modalDrawer_leaveActive { > .bg { transition: opacity 0.2s !important; } @@ -382,8 +408,8 @@ defineExpose({ transition: transform 0.2s cubic-bezier(0,.5,0,1) !important; } } -.transition_modal-drawer_enterFrom, -.transition_modal-drawer_leaveTo { +.transition_modalDrawer_enterFrom, +.transition_modalDrawer_leaveTo { > .bg { opacity: 0; } diff --git a/packages/frontend/src/components/MkModalPageWindow.vue b/packages/frontend/src/components/MkModalPageWindow.vue deleted file mode 100644 index b38865f52510ca620bdaac9648b006844c31b157..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/MkModalPageWindow.vue +++ /dev/null @@ -1,182 +0,0 @@ -<template> -<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> - <div ref="rootEl" class="hrmcaedk" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> - <div class="header" @contextmenu="onContextmenu"> - <button v-if="history.length > 0" v-tooltip="i18n.ts.goBack" class="_button" @click="back()"><i class="ti ti-arrow-left"></i></button> - <span v-else style="display: inline-block; width: 20px"></span> - <span v-if="pageMetadata?.value" class="title"> - <i v-if="pageMetadata?.value.icon" class="icon" :class="pageMetadata?.value.icon"></i> - <span>{{ pageMetadata?.value.title }}</span> - </span> - <button class="_button" @click="$refs.modal.close()"><i class="ti ti-x"></i></button> - </div> - <div class="body" style="container-type: inline-size;"> - <MkStickyContainer> - <template #header><MkPageHeader v-if="pageMetadata?.value && !pageMetadata?.value.hideHeader" :info="pageMetadata?.value"/></template> - <RouterView :router="router"/> - </MkStickyContainer> - </div> - </div> -</MkModal> -</template> - -<script lang="ts" setup> -import { ComputedRef, provide } from 'vue'; -import MkModal from '@/components/MkModal.vue'; -import { popout as _popout } from '@/scripts/popout'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { url } from '@/config'; -import * as os from '@/os'; -import { mainRouter, routes } from '@/router'; -import { i18n } from '@/i18n'; -import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; -import { Router } from '@/nirax'; - -const props = defineProps<{ - initialPath: string; -}>(); - -defineEmits<{ - (ev: 'closed'): void; - (ev: 'click'): void; -}>(); - -const router = new Router(routes, props.initialPath); - -router.addListener('push', ctx => { - -}); - -let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); -let rootEl = $ref(); -let modal = $shallowRef<InstanceType<typeof MkModal>>(); -let path = $ref(props.initialPath); -let width = $ref(860); -let height = $ref(660); -const history = []; - -provide('router', router); -provideMetadataReceiver((info) => { - pageMetadata = info; -}); -provide('shouldOmitHeaderTitle', true); -provide('shouldHeaderThin', true); - -const pageUrl = $computed(() => url + path); -const contextmenu = $computed(() => { - return [{ - type: 'label', - text: path, - }, { - icon: 'ti ti-player-eject', - text: i18n.ts.showInPage, - action: expand, - }, { - icon: 'ti ti-window-maximize', - text: i18n.ts.popout, - action: popout, - }, null, { - icon: 'ti ti-external-link', - text: i18n.ts.openInNewTab, - action: () => { - window.open(pageUrl, '_blank'); - modal.close(); - }, - }, { - icon: 'ti ti-link', - text: i18n.ts.copyLink, - action: () => { - copyToClipboard(pageUrl); - }, - }]; -}); - -function navigate(path, record = true) { - if (record) history.push(router.getCurrentPath()); - router.push(path); -} - -function back() { - navigate(history.pop(), false); -} - -function expand() { - mainRouter.push(path); - modal.close(); -} - -function popout() { - _popout(path, rootEl); - modal.close(); -} - -function onContextmenu(ev: MouseEvent) { - os.contextMenu(contextmenu, ev); -} -</script> - -<style lang="scss" scoped> -.hrmcaedk { - margin: auto; - overflow: hidden; - display: flex; - flex-direction: column; - contain: content; - border-radius: var(--radius); - - --root-margin: 24px; - - @media (max-width: 500px) { - --root-margin: 16px; - } - - > .header { - $height: 52px; - $height-narrow: 42px; - display: flex; - flex-shrink: 0; - height: $height; - line-height: $height; - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - background: var(--windowHeader); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - - > button { - height: $height; - width: $height; - - &:hover { - color: var(--fgHighlighted); - } - } - - @media (max-width: 500px) { - height: $height-narrow; - line-height: $height-narrow; - padding-left: 16px; - - > button { - height: $height-narrow; - width: $height-narrow; - } - } - - > .title { - flex: 1; - - > .icon { - margin-right: 0.5em; - } - } - } - - > .body { - overflow: auto; - background: var(--bg); - } -} -</style> diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index 63c55b904af23e51ec400bfc7a9d4fdbcb23f7e0..08569b4d6ed5684f8f7fd77bc9018286ba510ce8 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> +<MkModal ref="modal" :preferType="'dialog'" @click="onBgClick" @closed="$emit('closed')"> <div ref="rootEl" :class="$style.root" :style="{ width: `${width}px`, height: `min(${height}px, 100%)` }" @keydown="onKeydown"> <div ref="headerEl" :class="$style.header"> <button v-if="withOkButton" :class="$style.headerButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index d95f8de311a9568adb1904c79f96d9f43312b296..7c9ddadbf8e3a3722945b34941ab02a59c27fd14 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -44,8 +44,8 @@ <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> <MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/> <div :class="$style.main"> - <MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/> - <MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/> + <MkNoteHeader :note="appearNote" :mini="true"/> + <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/> <div style="container-type: inline-size;"> <p v-if="appearNote.cw != null" :class="$style.cw"> <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/> @@ -55,17 +55,17 @@ <div :class="$style.text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> - <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> + <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/> <div v-if="translating || translation" :class="$style.translation"> <MkLoading v-if="translating" mini/> - <div v-else :class="$style.translated"> + <div v-else> <b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> + <Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/> </div> </div> </div> - <div v-if="appearNote.files.length > 0" :class="$style.files"> - <MkMediaList :media-list="appearNote.files"/> + <div v-if="appearNote.files.length > 0"> + <MkMediaList :mediaList="appearNote.files"/> </div> <MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/> <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> @@ -79,7 +79,7 @@ </div> <MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> </div> - <MkReactionsViewer :note="appearNote" :max-number="16"> + <MkReactionsViewer :note="appearNote" :maxNumber="16"> <template #more> <button class="_button" :class="$style.reactionDetailsButton" @click="showReactions"> {{ i18n.ts.more }} @@ -205,8 +205,11 @@ const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; const isLong = (appearNote.cw == null && appearNote.text != null && ( + (appearNote.text.includes('$[x2')) || (appearNote.text.includes('$[x3')) || (appearNote.text.includes('$[x4')) || + (appearNote.text.includes('$[scale')) || + (appearNote.text.includes('$[position')) || (appearNote.text.split('\n').length > 9) || (appearNote.text.length > 500) || (appearNote.files.length >= 5) || @@ -274,7 +277,7 @@ function renote(viaKeyboard = false) { const y = rect.top + (el.offsetHeight / 2); os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - + os.api('notes/create', { renoteId: appearNote.id, channelId: appearNote.channelId, @@ -305,7 +308,7 @@ function renote(viaKeyboard = false) { const y = rect.top + (el.offsetHeight / 2); os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - + os.api('notes/create', { renoteId: appearNote.id, }).then(() => { @@ -379,6 +382,8 @@ function undoReact(note): void { function onContextmenu(ev: MouseEvent): void { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; + // å†ç”Ÿé€Ÿåº¦ã®é¸æŠžãªã©ã®ãŸã‚ã«ã€Audioè¦ç´ ã®ã‚³ãƒ³ãƒ†ã‚ストメニューã¯ãƒ–ラウザデフォルトã¨ã™ã‚‹ã€‚ + if (el.tagName === 'AUDIO') return true; if (el.parentElement) { return isLink(el.parentElement); } diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 0d6d329d9813087523b6c4427e33a33d2ce72b56..a65039277b8ee62d9b02a8f6914a8b6fad229584 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -4,25 +4,25 @@ v-show="!isDeleted" ref="el" v-hotkey="keymap" - class="lxwezrsl" - :tabindex="!isDeleted ? '-1' : null" - :class="{ renote: isRenote }" + :class="$style.root" > - <MkNoteSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/> - <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="isRenote" class="renote"> - <MkAvatar class="avatar" :user="note.user" link preview/> - <i class="ti ti-repeat"></i> - <I18n :src="i18n.ts.renotedBy" tag="span"> - <template #user> - <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - </template> - </I18n> - <div class="info"> - <button ref="renoteTime" class="_button time" @click="showRenoteMenu()"> - <i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i> + <MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/> + <div v-if="isRenote" :class="$style.renote"> + <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> + <i class="ti ti-repeat" style="margin-right: 4px;"></i> + <span :class="$style.renoteText"> + <I18n :src="i18n.ts.renotedBy" tag="span"> + <template #user> + <MkA v-user-preview="note.userId" :class="$style.renoteName" :to="userPage(note.user)"> + <MkUserName :user="note.user"/> + </MkA> + </template> + </I18n> + </span> + <div :class="$style.renoteInfo"> + <button ref="renoteTime" class="_button" :class="$style.renoteTime" @click="showRenoteMenu()"> + <i v-if="isMyRenote" class="ti ti-dots" style="margin-right: 4px;"></i> <MkTime :time="note.createdAt"/> </button> <span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> @@ -33,16 +33,16 @@ <span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span> </div> </div> - <article class="article" @contextmenu.stop="onContextmenu"> - <header class="header"> - <MkAvatar class="avatar" :user="appearNote.user" indicator link preview/> - <div class="body"> - <div class="top"> - <MkA v-user-preview="appearNote.user.id" class="name" :to="userPage(appearNote.user)"> + <article :class="$style.note" @contextmenu.stop="onContextmenu"> + <header :class="$style.noteHeader"> + <MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/> + <div :class="$style.noteHeaderBody"> + <div> + <MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)"> <MkUserName :nowrap="false" :user="appearNote.user"/> </MkA> - <span v-if="appearNote.user.isBot" class="is-bot">bot</span> - <div class="info"> + <span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span> + <div :class="$style.noteHeaderInfo"> <span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]"> <i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i> <i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i> @@ -51,84 +51,81 @@ <span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span> </div> </div> - <div class="username"><MkAcct :user="appearNote.user"/></div> - <MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/> + <div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div> + <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/> </div> </header> - <div class="main"> - <div class="body"> - <p v-if="appearNote.cw != null" class="cw"> - <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/> - <MkCwButton v-model="showContent" :note="appearNote"/> - </p> - <div v-show="appearNote.cw == null || showContent" class="content"> - <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> - <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> - <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> - <a v-if="appearNote.renote != null" class="rp">RN:</a> - <div v-if="translating || translation" class="translation"> - <MkLoading v-if="translating" mini/> - <div v-else class="translated"> - <b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/> - </div> - </div> + <div :class="$style.noteContent"> + <p v-if="appearNote.cw != null" :class="$style.cw"> + <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/> + <MkCwButton v-model="showContent" :note="appearNote"/> + </p> + <div v-show="appearNote.cw == null || showContent"> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> + <MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> + <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/> + <a v-if="appearNote.renote != null" :class="$style.rn">RN:</a> + <div v-if="translating || translation" :class="$style.translation"> + <MkLoading v-if="translating" mini/> + <div v-else> + <b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> + <Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/> </div> - <div v-if="appearNote.files.length > 0" class="files"> - <MkMediaList :media-list="appearNote.files"/> - </div> - <MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" class="url-preview"/> - <div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div> </div> - <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> - </div> - <footer class="footer"> - <div class="info"> - <MkA class="created-at" :to="notePage(appearNote)"> - <MkTime :time="appearNote.createdAt" mode="detail"/> - </MkA> + <div v-if="appearNote.files.length > 0"> + <MkMediaList :mediaList="appearNote.files"/> </div> - <MkReactionsViewer ref="reactionsViewer" :note="appearNote"/> - <button class="button _button" @click="reply()"> - <i class="ti ti-arrow-back-up"></i> - <p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p> - </button> - <button - v-if="canRenote" - ref="renoteButton" - class="button _button" - @mousedown="renote()" - > - <i class="ti ti-repeat"></i> - <p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p> - </button> - <button v-else class="button _button" disabled> - <i class="ti ti-ban"></i> - </button> - <button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()"> - <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> - <i v-else class="ti ti-plus"></i> - </button> - <button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)"> - <i class="ti ti-minus"></i> - </button> - <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="button _button" @mousedown="clip()"> - <i class="ti ti-paperclip"></i> - </button> - <button ref="menuButton" class="button _button" @mousedown="menu()"> - <i class="ti ti-dots"></i> - </button> - </footer> + <MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/> + <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/> + <div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> + </div> + <MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> </div> + <footer> + <div :class="$style.noteFooterInfo"> + <MkA :to="notePage(appearNote)"> + <MkTime :time="appearNote.createdAt" mode="detail"/> + </MkA> + </div> + <MkReactionsViewer ref="reactionsViewer" :note="appearNote"/> + <button class="_button" :class="$style.noteFooterButton" @click="reply()"> + <i class="ti ti-arrow-back-up"></i> + <p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p> + </button> + <button + v-if="canRenote" + ref="renoteButton" + class="_button" + :class="$style.noteFooterButton" + @mousedown="renote()" + > + <i class="ti ti-repeat"></i> + <p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p> + </button> + <button v-else class="_button" :class="$style.noteFooterButton" disabled> + <i class="ti ti-ban"></i> + </button> + <button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()"> + <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> + <i v-else class="ti ti-plus"></i> + </button> + <button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)"> + <i class="ti ti-minus"></i> + </button> + <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()"> + <i class="ti ti-paperclip"></i> + </button> + <button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()"> + <i class="ti ti-dots"></i> + </button> + </footer> </article> - <MkNoteSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/> + <MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/> </div> -<div v-else class="_panel muted" @click="muted = false"> +<div v-else class="_panel" :class="$style.muted" @click="muted = false"> <I18n :src="i18n.ts.userSaysSomething" tag="small"> <template #name> - <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> + <MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)"> <MkUserName :user="appearNote.user"/> </MkA> </template> @@ -438,318 +435,249 @@ if (appearNote.replyId) { } </script> -<style lang="scss" scoped> -.lxwezrsl { +<style lang="scss" module> +.root { position: relative; transition: box-shadow 0.1s ease; overflow: clip; contain: content; +} - &:focus-visible { - outline: none; - - &:after { - content: ""; - pointer-events: none; - display: block; - position: absolute; - z-index: 10; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: calc(100% - 8px); - height: calc(100% - 8px); - border: dashed 1px var(--focus); - border-radius: var(--radius); - box-sizing: border-box; - } - } +.replyTo { + opacity: 0.7; + padding-bottom: 0; +} - &:hover > .article > .main > .footer > .button { - opacity: 1; - } +.replyToMore { + opacity: 0.7; +} - > .reply-to { - opacity: 0.7; - padding-bottom: 0; - } +.renote { + display: flex; + align-items: center; + padding: 16px 32px 8px 32px; + line-height: 28px; + white-space: pre; + color: var(--renote); +} - > .reply-to-more { - opacity: 0.7; - } +.renoteAvatar { + flex-shrink: 0; + display: inline-block; + width: 28px; + height: 28px; + margin: 0 8px 0 0; + border-radius: 6px; +} - > .renote { - display: flex; - align-items: center; - padding: 16px 32px 8px 32px; - line-height: 28px; - white-space: pre; - color: var(--renote); - - > .avatar { - flex-shrink: 0; - display: inline-block; - width: 28px; - height: 28px; - margin: 0 8px 0 0; - border-radius: 6px; - } +.renoteText { + overflow: hidden; + flex-shrink: 1; + text-overflow: ellipsis; + white-space: nowrap; +} - > i { - margin-right: 4px; - } +.renoteName { + font-weight: bold; +} - > span { - overflow: hidden; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; +.renoteInfo { + margin-left: auto; + font-size: 0.9em; +} - > .name { - font-weight: bold; - } - } +.renoteTime { + flex-shrink: 0; + color: inherit; +} - > .info { - margin-left: auto; - font-size: 0.9em; +.renote + .note { + padding-top: 8px; +} - > .time { - flex-shrink: 0; - color: inherit; +.note { + padding: 32px; + font-size: 1.2em; - > .dropdownIcon { - margin-right: 4px; - } - } - } + &:hover > .main > .footer > .button { + opacity: 1; } +} - > .renote + .article { - padding-top: 8px; - } +.noteHeader { + display: flex; + position: relative; + margin-bottom: 16px; + align-items: center; +} - > .article { - padding: 32px; - font-size: 1.2em; - - > .header { - display: flex; - position: relative; - margin-bottom: 16px; - align-items: center; - - > .avatar { - display: block; - flex-shrink: 0; - width: 58px; - height: 58px; - } +.noteHeaderAvatar { + display: block; + flex-shrink: 0; + width: 58px; + height: 58px; +} - > .body { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - padding-left: 16px; - font-size: 0.95em; - - > .top { - > .name { - font-weight: bold; - line-height: 1.3; - } - - > .is-bot { - display: inline-block; - margin: 0 0.5em; - padding: 4px 6px; - font-size: 80%; - line-height: 1; - border: solid 0.5px var(--divider); - border-radius: 4px; - } - - > .info { - float: right; - } - } +.noteHeaderBody { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + padding-left: 16px; + font-size: 0.95em; +} - > .username { - margin-bottom: 2px; - line-height: 1.3; - word-wrap: anywhere; - } - } - } +.noteHeaderName { + font-weight: bold; + line-height: 1.3; +} - > .main { - > .body { - container-type: inline-size; +.isBot { + display: inline-block; + margin: 0 0.5em; + padding: 4px 6px; + font-size: 80%; + line-height: 1; + border: solid 0.5px var(--divider); + border-radius: 4px; +} - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; +.noteHeaderInfo { + float: right; +} - > .text { - margin-right: 8px; - } - } +.noteHeaderUsername { + margin-bottom: 2px; + line-height: 1.3; + word-wrap: anywhere; +} - > .content { - > .text { - overflow-wrap: break-word; - - > .reply { - color: var(--accent); - margin-right: 0.5em; - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - - > .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); - padding: 12px; - margin-top: 8px; - } - } - - > .url-preview { - margin-top: 8px; - } - - > .poll { - font-size: 80%; - } - - > .renote { - padding: 8px 0; - - > .note { - padding: 16px; - border: dashed 1px var(--renote); - border-radius: 8px; - } - } - } +.noteContent { + container-type: inline-size; + overflow-wrap: break-word; +} - > .channel { - opacity: 0.7; - font-size: 80%; - } - } +.cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; +} - > .footer { - > .info { - margin: 16px 0; - opacity: 0.7; - font-size: 0.9em; - } +.noteReplyTarget { + color: var(--accent); + margin-right: 0.5em; +} + +.rn { + margin-left: 4px; + font-style: oblique; + color: var(--renote); +} - > .button { - margin: 0; - padding: 8px; - opacity: 0.7; +.translation { + border: solid 0.5px var(--divider); + border-radius: var(--radius); + padding: 12px; + margin-top: 8px; +} - &:not(:last-child) { - margin-right: 28px; - } +.poll { + font-size: 80%; +} - &:hover { - color: var(--fgHighlighted); - } +.quote { + padding: 8px 0; +} - > .count { - display: inline; - margin: 0 0 0 8px; - opacity: 0.7; - } +.quoteNote { + padding: 16px; + border: dashed 1px var(--renote); + border-radius: 8px; +} - &.reacted { - color: var(--accent); - } - } - } - } +.channel { + opacity: 0.7; + font-size: 80%; +} + +.noteFooterInfo { + margin: 16px 0; + opacity: 0.7; + font-size: 0.9em; +} + +.noteFooterButton { + margin: 0; + padding: 8px; + opacity: 0.7; + + &:not(:last-child) { + margin-right: 28px; } - > .reply { - border-top: solid 0.5px var(--divider); + &:hover { + color: var(--fgHighlighted); } } +.noteFooterButtonCount { + display: inline; + margin: 0 0 0 8px; + opacity: 0.7; + + &.reacted { + color: var(--accent); + } +} + +.reply { + border-top: solid 0.5px var(--divider); +} + @container (max-width: 500px) { - .lxwezrsl { + .root { font-size: 0.9em; } } @container (max-width: 450px) { - .lxwezrsl { - > .renote { - padding: 8px 16px 0 16px; - } + .renote { + padding: 8px 16px 0 16px; + } - > .article { - padding: 16px; + .note { + padding: 16px; + } - > .header { - > .avatar { - width: 50px; - height: 50px; - } - } - } + .noteHeaderAvatar { + width: 50px; + height: 50px; } } @container (max-width: 350px) { - .lxwezrsl { - > .article { - > .main { - > .footer { - > .button { - &:not(:last-child) { - margin-right: 18px; - } - } - } - } + .noteFooterButton { + &:not(:last-child) { + margin-right: 18px; } } } @container (max-width: 300px) { - .lxwezrsl { + .root { font-size: 0.825em; + } - > .article { - > .header { - > .avatar { - width: 50px; - height: 50px; - } - } + .noteHeaderAvatar { + width: 50px; + height: 50px; + } - > .main { - > .footer { - > .button { - &:not(:last-child) { - margin-right: 12px; - } - } - } - } + .noteFooterButton { + &:not(:last-child) { + margin-right: 12px; } } } diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 6b55c27869673a0a95196e1b3eedd4f546000fd1..6786f8b2569dd63bca4b17138943e43d92904823 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -6,7 +6,7 @@ <MkUserName :user="$i" :nowrap="true"/> </div> <div> - <div :class="$style.content"> + <div> <Mfm :text="text.trim()" :author="$i" :i="$i"/> </div> </div> diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index bd27a43b61e0a69ee8c639fe230d2a975aff1ad2..21be1454a7a0c51f1a462c4e9a85696a732b5b94 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -5,7 +5,7 @@ <MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <div> <p v-if="note.cw != null" :class="$style.cw"> - <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i" :emoji-urls="note.emojis"/> + <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i" :emojiUrls="note.emojis"/> <MkCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent"> diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index a4e949c898581a6b66f0b9bd91a5c36298608e07..9cc2b7a967a32b5a5cb68bd84b5a2758d5e15516 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -15,7 +15,7 @@ :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" - :no-gap="noGap" + :noGap="noGap" :ad="true" :class="$style.notes" > diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index efae687e667919c0641c891be75c84fa9ab7aa6a..d25332b10f0f44cc3e86bc6a06fad5594f7766eb 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -5,7 +5,19 @@ <MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/> <MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/> <img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/> - <div :class="[$style.subIcon, $style['t_' + notification.type]]"> + <div + :class="[$style.subIcon, { + [$style.t_follow]: notification.type === 'follow', + [$style.t_followRequestAccepted]: notification.type === 'followRequestAccepted', + [$style.t_receiveFollowRequest]: notification.type === 'receiveFollowRequest', + [$style.t_renote]: notification.type === 'renote', + [$style.t_reply]: notification.type === 'reply', + [$style.t_mention]: notification.type === 'mention', + [$style.t_quote]: notification.type === 'quote', + [$style.t_pollEnded]: notification.type === 'pollEnded', + [$style.t_achievementEarned]: notification.type === 'achievementEarned', + }]" + > <i v-if="notification.type === 'follow'" class="ti ti-plus"></i> <i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i> <i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i> @@ -20,8 +32,8 @@ v-else-if="notification.type === 'reaction'" ref="reactionRef" :reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction" - :custom-emojis="notification.note.emojis" - :no-style="true" + :customEmojis="notification.note.emojis" + :noStyle="true" style="width: 100%; height: 100%;" /> </div> @@ -34,7 +46,7 @@ <span v-else>{{ notification.header }}</span> <MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/> </header> - <div :class="$style.content"> + <div> <MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <i class="ti ti-quote" :class="$style.quote"></i> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/> @@ -243,9 +255,6 @@ useTooltip(reactionRef, (showing) => { font-size: 0.9em; } -.content { -} - .text { display: flex; width: 100%; diff --git a/packages/frontend/src/components/MkNotificationSettingWindow.vue b/packages/frontend/src/components/MkNotificationSettingWindow.vue index f6d0e5681ddf10aaeed7ab9493722dac0fd94c7b..598d3a05510e603bead1f46b4f043ed811f8c4c2 100644 --- a/packages/frontend/src/components/MkNotificationSettingWindow.vue +++ b/packages/frontend/src/components/MkNotificationSettingWindow.vue @@ -3,15 +3,15 @@ ref="dialog" :width="400" :height="450" - :with-ok-button="true" - :ok-button-disabled="false" + :withOkButton="true" + :okButtonDisabled="false" @ok="ok()" @close="dialog?.close()" @closed="emit('closed')" > <template #header>{{ i18n.ts.notificationSetting }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps_m"> <template v-if="showGlobalToggle"> <MkSwitch v-model="useGlobalSetting"> diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 1aea95fe0e0d7177ed70cc7409857a877635e988..70224bffa130d50c50e7be91fcbf9e965f38b300 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -8,9 +8,9 @@ </template> <template #default="{ items: notifications }"> - <MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :no-gap="true"> + <MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true"> <MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/> - <XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/> + <XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel notification"/> </MkDateSeparatedList> </template> </MkPagination> @@ -22,7 +22,7 @@ import MkPagination, { Paging } from '@/components/MkPagination.vue'; import XNotification from '@/components/MkNotification.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkNote from '@/components/MkNote.vue'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { notificationTypes } from '@/const'; @@ -45,7 +45,7 @@ const pagination: Paging = { const onNotification = (notification) => { const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type); if (isMuted || document.visibilityState === 'visible') { - stream.send('readNotification'); + useStream().send('readNotification'); } if (!isMuted) { @@ -56,7 +56,7 @@ const onNotification = (notification) => { let connection; onMounted(() => { - connection = stream.useChannel('main'); + connection = useStream().useChannel('main'); connection.on('notification', onNotification); }); diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue index e7fc73bce362eb4a965338b54531cf670b53a4b6..d48e7886eb045a4871db11c0d4afa7a68e95d8c4 100644 --- a/packages/frontend/src/components/MkObjectView.value.vue +++ b/packages/frontend/src/components/MkObjectView.value.vue @@ -28,54 +28,38 @@ </div> </template> -<script lang="ts"> -import { defineComponent, reactive } from 'vue'; +<script lang="ts" setup> +import { reactive } from 'vue'; import number from '@/filters/number'; +import XValue from '@/components/MkObjectView.value.vue'; -export default defineComponent({ - name: 'XValue', +const props = defineProps<{ + value: any; +}>(); - props: { - value: { - required: true, - }, - }, +const collapsed = reactive({}); - setup(props) { - const collapsed = reactive({}); - - if (isObject(props.value)) { - for (const key in props.value) { - collapsed[key] = collapsable(props.value[key]); - } - } - - function isObject(v): boolean { - return typeof v === 'object' && !Array.isArray(v) && v !== null; - } +if (isObject(props.value)) { + for (const key in props.value) { + collapsed[key] = collapsable(props.value[key]); + } +} - function isArray(v): boolean { - return Array.isArray(v); - } +function isObject(v): boolean { + return typeof v === 'object' && !Array.isArray(v) && v !== null; +} - function isEmpty(v): boolean { - return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0); - } +function isArray(v): boolean { + return Array.isArray(v); +} - function collapsable(v): boolean { - return (isObject(v) || isArray(v)) && !isEmpty(v); - } +function isEmpty(v): boolean { + return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0); +} - return { - number, - collapsed, - isObject, - isArray, - isEmpty, - collapsable, - }; - }, -}); +function collapsable(v): boolean { + return (isObject(v) || isArray(v)) && !isEmpty(v); +} </script> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/MkObjectView.vue b/packages/frontend/src/components/MkObjectView.vue index 55578a37f6b0c799bd7e3231b70f70a0a275df94..8b1ed74142b78a7e587a422583ba32fcb4640507 100644 --- a/packages/frontend/src/components/MkObjectView.vue +++ b/packages/frontend/src/components/MkObjectView.vue @@ -1,5 +1,5 @@ <template> -<div class="zhyxdalp"> +<div> <XValue :value="value" :collapsed="false"/> </div> </template> @@ -12,9 +12,3 @@ const props = defineProps<{ value: Record<string, unknown>; }>(); </script> - -<style lang="scss" scoped> -.zhyxdalp { - -} -</style> diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue index e2d68d12c348fa0f1f7717d957318c784b87be2e..668f9ff5af88d6da089964203781ef3c5032e765 100644 --- a/packages/frontend/src/components/MkOmit.vue +++ b/packages/frontend/src/components/MkOmit.vue @@ -8,7 +8,7 @@ </template> <script lang="ts" setup> -import { onMounted } from 'vue'; +import { onMounted, onUnmounted } from 'vue'; import { i18n } from '@/i18n'; const props = withDefaults(defineProps<{ @@ -21,16 +21,22 @@ let content = $shallowRef<HTMLElement>(); let omitted = $ref(false); let ignoreOmit = $ref(false); -onMounted(() => { - const calcOmit = () => { - if (omitted || ignoreOmit) return; - omitted = content.offsetHeight > props.maxHeight; - }; +const calcOmit = () => { + if (omitted || ignoreOmit) return; + omitted = content.offsetHeight > props.maxHeight; +}; +const omitObserver = new ResizeObserver((entries, observer) => { calcOmit(); - new ResizeObserver((entries, observer) => { - calcOmit(); - }).observe(content); +}); + +onMounted(() => { + calcOmit(); + omitObserver.observe(content); +}); + +onUnmounted(() => { + omitObserver.disconnect(); }); </script> diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 02ce58451db56bb42b8e90a2e391d1f7b45680f1..709b5a52dfe5f147c624bba639888e119ffdc672 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -1,23 +1,23 @@ <template> <MkWindow ref="windowEl" - :initial-width="500" - :initial-height="500" - :can-resize="true" - :close-button="true" - :buttons-left="buttonsLeft" - :buttons-right="buttonsRight" + :initialWidth="500" + :initialHeight="500" + :canResize="true" + :closeButton="true" + :buttonsLeft="buttonsLeft" + :buttonsRight="buttonsRight" :contextmenu="contextmenu" @closed="$emit('closed')" > <template #header> <template v-if="pageMetadata?.value"> - <i v-if="pageMetadata.value.icon" class="icon" :class="pageMetadata.value.icon" style="margin-right: 0.5em;"></i> + <i v-if="pageMetadata.value.icon" :class="pageMetadata.value.icon" style="margin-right: 0.5em;"></i> <span>{{ pageMetadata.value.title }}</span> </template> </template> - <div :class="$style.root" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;"> + <div :class="$style.root" style="container-type: inline-size;"> <RouterView :key="reloadCount" :router="router"/> </div> </MkWindow> diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index cd8af560e43cead1e4a1e6df14ab24efdd64d1e9..740094b1130e0944e646b11d9ed7b126e9fac961 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -1,9 +1,9 @@ <template> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_fade_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''" mode="out-in" > <MkLoading v-if="fetching"/> diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 0810061ff93642a28f647530d08ed6ea40ccf4a6..464e340116ba79b46b664c897efd873d43502a87 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -1,19 +1,19 @@ <template> -<div class="tivcixzd" :class="{ done: closed || isVoted }"> - <ul> - <li v-for="(choice, i) in note.poll.choices" :key="i" :class="{ voted: choice.voted }" @click="vote(i)"> - <div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div> - <span> - <template v-if="choice.isVoted"><i class="ti ti-check"></i></template> +<div :class="{ [$style.done]: closed || isVoted }"> + <ul :class="$style.choices"> + <li v-for="(choice, i) in note.poll.choices" :key="i" :class="$style.choice" @click="vote(i)"> + <div :class="$style.bg" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div> + <span :class="$style.fg"> + <template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template> <Mfm :text="choice.text" :plain="true"/> - <span v-if="showResult" class="votes">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span> + <span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span> </span> </li> </ul> - <p v-if="!readOnly"> + <p v-if="!readOnly" :class="$style.info"> <span>{{ i18n.t('_poll.totalVotes', { n: total }) }}</span> <span> · </span> - <a v-if="!closed && !isVoted" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a> + <a v-if="!closed && !isVoted" style="color: inherit;" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a> <span v-if="isVoted">{{ i18n.ts._poll.voted }}</span> <span v-else-if="closed">{{ i18n.ts._poll.closed }}</span> <span v-if="remaining > 0"> · {{ timer }}</span> @@ -86,67 +86,51 @@ const vote = async (id) => { }; </script> -<style lang="scss" scoped> -.tivcixzd { - > ul { - display: block; - margin: 0; - padding: 0; - list-style: none; - - > li { - display: block; - position: relative; - margin: 4px 0; - padding: 4px; - //border: solid 0.5px var(--divider); - background: var(--accentedBg); - border-radius: 4px; - overflow: clip; - cursor: pointer; - - > .backdrop { - position: absolute; - top: 0; - left: 0; - height: 100%; - background: var(--accent); - background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB)); - transition: width 1s ease; - } - - > span { - position: relative; - display: inline-block; - padding: 3px 5px; - background: var(--panel); - border-radius: 3px; - - > i { - margin-right: 4px; - color: var(--accent); - } - - > .votes { - margin-left: 4px; - opacity: 0.7; - } - } - } - } +<style lang="scss" module> +.choices { + display: block; + margin: 0; + padding: 0; + list-style: none; +} - > p { - color: var(--fg); +.choice { + display: block; + position: relative; + margin: 4px 0; + padding: 4px; + //border: solid 0.5px var(--divider); + background: var(--accentedBg); + border-radius: 4px; + overflow: clip; + cursor: pointer; +} - a { - color: inherit; - } - } +.bg { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: var(--accent); + background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB)); + transition: width 1s ease; +} - &.done { - > ul > li { - cursor: default; - } +.fg { + position: relative; + display: inline-block; + padding: 3px 5px; + background: var(--panel); + border-radius: 3px; +} + +.info { + color: var(--fg); +} + +.done { + .choice { + cursor: default; } } </style> diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue index 471ec391698ee22ceae66fbd65f1bfce701ba8b3..2da9339944caac0b4e87b574bfb3066cb5fb3c6f 100644 --- a/packages/frontend/src/components/MkPollEditor.vue +++ b/packages/frontend/src/components/MkPollEditor.vue @@ -5,7 +5,7 @@ </p> <ul> <li v-for="(choice, i) in choices" :key="i"> - <MkInput class="input" small :model-value="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:model-value="onInput(i, $event)"> + <MkInput class="input" small :modelValue="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)"> </MkInput> <button class="_button" @click="remove(i)"> <i class="ti ti-x"></i> diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue index 93b9eb401d7c248a8dd6b96837ad5890ad9ee6c7..30af36566947c8f2329bf808f80598fe9710f165 100644 --- a/packages/frontend/src/components/MkPopupMenu.vue +++ b/packages/frontend/src/components/MkPopupMenu.vue @@ -1,6 +1,6 @@ <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')"> - <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="modal.close()"/> +<MkModal ref="modal" v-slot="{ type, maxHeight }" :zPriority="'high'" :src="src" :transparentBg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')"> + <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="modal.close()"/> </MkModal> </template> diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index c65cb7d6e5d247c0ad61e1459bb8c96ae411b1a5..5c65569683041ea64d94bfb32d40cf97cc10f065 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -22,21 +22,21 @@ <span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span> <span :class="$style.headerRightButtonText">{{ i18n.ts._visibility[visibility] }}</span> </button> - <button v-else :class="['_button', $style.headerRightItem, $style.visibility]" disabled> + <button v-else class="_button" :class="[$style.headerRightItem, $style.visibility]" disabled> <span><i class="ti ti-device-tv"></i></span> <span :class="$style.headerRightButtonText">{{ channel.name }}</span> </button> </template> - <button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" :class="['_button', $style.headerRightItem, $style.localOnly, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly"> + <button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly"> <span v-if="!localOnly"><i class="ti ti-rocket"></i></span> <span v-else><i class="ti ti-rocket-off"></i></span> </button> - <button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" :class="['_button', $style.headerRightItem, $style.reactionAcceptance, { [$style.danger]: reactionAcceptance }]" @click="toggleReactionAcceptance"> + <button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance"> <span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span> <span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span> <span v-else><i class="ti ti-icons"></i></span> </button> - <button v-click-anime class="_button" :class="[$style.submit, { [$style.submitPosting]: posting }]" :disabled="!canPost" data-cy-open-post-form-submit @click="post"> + <button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post"> <div :class="$style.submitInner"> <template v-if="posted"></template> <template v-else-if="posting"><MkEllipsis/></template> @@ -66,7 +66,7 @@ <div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div> </div> <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags"> - <XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/> + <XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> <MkNotePreview v-if="showPreview" :class="$style.preview" :text="text"/> <div v-if="showingOptions" style="padding: 8px 16px;"> @@ -484,8 +484,10 @@ async function toggleReactionAcceptance() { title: i18n.ts.reactionAcceptance, items: [ { value: null, text: i18n.ts.all }, - { value: 'likeOnly' as const, text: i18n.ts.likeOnly }, { value: 'likeOnlyForRemote' as const, text: i18n.ts.likeOnlyForRemote }, + { value: 'nonSensitiveOnly' as const, text: i18n.ts.nonSensitiveOnly }, + { value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }, + { value: 'likeOnly' as const, text: i18n.ts.likeOnly }, ], default: reactionAcceptance, }); diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index 760c6e5d08966cbc7a9edc8aad7699afffcd2936..18fa142ebce45fff4257e3a942af6c4a41e8a04f 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -1,16 +1,16 @@ <template> -<div v-show="props.modelValue.length != 0" class="skeikyzd"> - <Sortable :model-value="props.modelValue" class="files" item-key="id" :animation="150" :delay="100" :delay-on-touch-only="true" @update:model-value="v => emit('update:modelValue', v)"> +<div v-show="props.modelValue.length != 0" :class="$style.root"> + <Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)"> <template #item="{element}"> - <div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> - <MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/> - <div v-if="element.isSensitive" class="sensitive"> - <i class="ti ti-alert-triangle icon"></i> + <div :class="$style.file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> + <MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover"/> + <div v-if="element.isSensitive" :class="$style.sensitive"> + <i class="ti ti-alert-triangle" style="margin: auto;"></i> </div> </div> </template> </Sortable> - <p class="remain">{{ 16 - props.modelValue.length }}/16</p> + <p :class="$style.remain">{{ 16 - props.modelValue.length }}/16</p> </div> </template> @@ -93,7 +93,7 @@ function showFileMenu(file, ev: MouseEvent) { action: () => { rename(file); }, }, { text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, - icon: file.isSensitive ? 'ti ti-eye-off' : 'ti ti-eye', + icon: file.isSensitive ? 'ti ti-eye-exclamation' : 'ti ti-eye', action: () => { toggleSensitive(file); }, }, { text: i18n.ts.describeFile, @@ -108,60 +108,53 @@ function showFileMenu(file, ev: MouseEvent) { } </script> -<style lang="scss" scoped> -.skeikyzd { +<style lang="scss" module> +.root { padding: 8px 16px; position: relative; +} - > .files { - display: flex; - flex-wrap: wrap; - - > .file { - position: relative; - width: 64px; - height: 64px; - margin-right: 4px; - border-radius: 4px; - overflow: hidden; - cursor: move; - - &:hover > .remove { - display: block; - } +.files { + display: flex; + flex-wrap: wrap; +} - > .thumbnail { - width: 100%; - height: 100%; - z-index: 1; - color: var(--fg); - } +.file { + position: relative; + width: 64px; + height: 64px; + margin-right: 4px; + border-radius: 4px; + overflow: hidden; + cursor: move; +} - > .sensitive { - display: flex; - position: absolute; - width: 64px; - height: 64px; - top: 0; - left: 0; - z-index: 2; - background: rgba(17, 17, 17, .7); - color: #fff; +.thumbnail { + width: 100%; + height: 100%; + z-index: 1; + color: var(--fg); +} - > .icon { - margin: auto; - } - } - } - } +.sensitive { + display: flex; + position: absolute; + width: 64px; + height: 64px; + top: 0; + left: 0; + z-index: 2; + background: rgba(17, 17, 17, .7); + color: #fff; +} - > .remain { - display: block; - position: absolute; - top: 8px; - right: 8px; - margin: 0; - padding: 0; - } +.remain { + display: block; + position: absolute; + top: 8px; + right: 8px; + margin: 0; + padding: 0; + font-size: 90%; } </style> diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue index 6326c498d7e197d366d674707176e3ff4a7bf9fa..98af92c6f844cc4739211ecd8937e80a23837b9c 100644 --- a/packages/frontend/src/components/MkPostFormDialog.vue +++ b/packages/frontend/src/components/MkPostFormDialog.vue @@ -1,6 +1,6 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" @click="modal.close()" @closed="onModalClosed()"> - <MkPostForm ref="form" style="margin: 0 auto auto auto;" v-bind="props" autofocus freeze-after-posted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/> +<MkModal ref="modal" :preferType="'dialog'" @click="modal.close()" @closed="onModalClosed()"> + <MkPostForm ref="form" style="margin: 0 auto auto auto;" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/> </MkModal> </template> diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index b98c814f24b2b270ddd9364534cc2c491849af5f..448084d9ba9527344b80dee6f962d1f23eeaeb02 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -72,28 +72,28 @@ function subscribe() { userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(instance.swPublickey), }) - .then(async subscription => { - pushSubscription = subscription; - - // Register - pushRegistrationInServer = await api('sw/register', { - endpoint: subscription.endpoint, - auth: encode(subscription.getKey('auth')), - publickey: encode(subscription.getKey('p256dh')), - }); - }, async err => { // When subscribe failed + .then(async subscription => { + pushSubscription = subscription; + + // Register + pushRegistrationInServer = await api('sw/register', { + endpoint: subscription.endpoint, + auth: encode(subscription.getKey('auth')), + publickey: encode(subscription.getKey('p256dh')), + }); + }, async err => { // When subscribe failed // 通知ãŒè¨±å¯ã•ã‚Œã¦ã„ãªã‹ã£ãŸã¨ã - if (err?.name === 'NotAllowedError') { - console.info('User denied the notification permission request.'); - return; - } + if (err?.name === 'NotAllowedError') { + console.info('User denied the notification permission request.'); + return; + } - // é•ã†applicationServerKey (ã¾ãŸã¯ gcm_sender_id)ã®ã‚µãƒ–スクリプション㌠- // æ—¢ã«å˜åœ¨ã—ã¦ã„ã‚‹ã“ã¨ãŒåŽŸå› ã§ã‚¨ãƒ©ãƒ¼ã«ãªã£ãŸå¯èƒ½æ€§ãŒã‚ã‚‹ã®ã§ã€ - // ãã®ã‚µãƒ–スクリプションを解除ã—ã¦ãŠã - // (ã“ã‚Œã¯å®Ÿè¡Œã•ã‚Œãªã•ãã†ã ã‘ã©ã€ãŠã¾ã˜ãªã„çš„ã«å¤ã„実装ã‹ã‚‰æ®‹ã—ã¦ã‚る) - await unsubscribe(); - }), null, null); + // é•ã†applicationServerKey (ã¾ãŸã¯ gcm_sender_id)ã®ã‚µãƒ–スクリプション㌠+ // æ—¢ã«å˜åœ¨ã—ã¦ã„ã‚‹ã“ã¨ãŒåŽŸå› ã§ã‚¨ãƒ©ãƒ¼ã«ãªã£ãŸå¯èƒ½æ€§ãŒã‚ã‚‹ã®ã§ã€ + // ãã®ã‚µãƒ–スクリプションを解除ã—ã¦ãŠã + // (ã“ã‚Œã¯å®Ÿè¡Œã•ã‚Œãªã•ãã†ã ã‘ã©ã€ãŠã¾ã˜ãªã„çš„ã«å¤ã„実装ã‹ã‚‰æ®‹ã—ã¦ã‚る) + await unsubscribe(); + }), null, null); } async function unsubscribe() { diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue index e2240fb4e13500bf02bf73b16801e03ba2ba3fe0..84be10078a610c58dfe200a195027c1172721176 100644 --- a/packages/frontend/src/components/MkRadios.vue +++ b/packages/frontend/src/components/MkRadios.vue @@ -1,37 +1,27 @@ <script lang="ts"> -import { VNode, defineComponent, h } from 'vue'; +import { VNode, defineComponent, h, ref, watch } from 'vue'; import MkRadio from './MkRadio.vue'; export default defineComponent({ - components: { - MkRadio, - }, props: { modelValue: { required: false, }, }, - data() { - return { - value: this.modelValue, - }; - }, - watch: { - value() { - this.$emit('update:modelValue', this.value); - }, - }, - render() { - console.log(this.$slots, this.$slots.label && this.$slots.label()); - if (!this.$slots.default) return null; - let options = this.$slots.default(); - const label = this.$slots.label && this.$slots.label(); - const caption = this.$slots.caption && this.$slots.caption(); + setup(props, context) { + const value = ref(props.modelValue); + watch(value, () => { + context.emit('update:modelValue', value.value); + }); + if (!context.slots.default) return null; + let options = context.slots.default(); + const label = context.slots.label && context.slots.label(); + const caption = context.slots.caption && context.slots.caption(); // ãªãœã‹Fragmentã«ãªã‚‹ã“ã¨ãŒã‚ã‚‹ãŸã‚ if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[]; - return h('div', { + return () => h('div', { class: 'novjtcto', }, [ ...(label ? [h('div', { @@ -42,8 +32,8 @@ export default defineComponent({ }, options.map(option => h(MkRadio, { key: option.key, value: option.props?.value, - modelValue: this.value, - 'onUpdate:modelValue': value => this.value = value, + modelValue: value.value, + 'onUpdate:modelValue': _v => value.value = _v, }, () => option.children)), ), ...(caption ? [h('div', { diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue index 0c0cc36692ead8642e3757216721e0812e432845..cd2a359d5c14afa35c03ba8be08d11309c328f06 100644 --- a/packages/frontend/src/components/MkReactedUsersDialog.vue +++ b/packages/frontend/src/components/MkReactedUsersDialog.vue @@ -8,7 +8,7 @@ > <template #header>{{ i18n.ts.reactionsList }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <div v-if="note" class="_gaps"> <div v-if="reactions.length === 0" class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> @@ -22,7 +22,7 @@ </button> </div> <MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()"> - <MkUserCardMini :user="user" :with-chart="false"/> + <MkUserCardMini :user="user" :withChart="false"/> </MkA> </template> </div> diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue index 29b3f9b85b451b0aedc195066f2e18a7843b9633..dfb06f63c44ea0791ff150e20d1d82809cee9955 100644 --- a/packages/frontend/src/components/MkReactionIcon.vue +++ b/packages/frontend/src/components/MkReactionIcon.vue @@ -1,6 +1,6 @@ <template> -<MkCustomEmoji v-if="reaction[0] === ':'" :name="reaction" :normal="true" :no-style="noStyle" :url="emojiUrl"/> -<MkEmoji v-else :emoji="reaction" :normal="true" :no-style="noStyle"/> +<MkCustomEmoji v-if="reaction[0] === ':'" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl"/> +<MkEmoji v-else :emoji="reaction" :normal="true" :noStyle="noStyle"/> </template> <script lang="ts" setup> diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue index 4d67dc3da24a018bbe445a8846ec19479da34725..34afa72232b7fe072360e726694b464fc8073686 100644 --- a/packages/frontend/src/components/MkReactionTooltip.vue +++ b/packages/frontend/src/components/MkReactionTooltip.vue @@ -1,7 +1,7 @@ <template> -<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')"> +<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')"> <div :class="$style.root"> - <MkReactionIcon :reaction="reaction" :class="$style.icon" :no-style="true"/> + <MkReactionIcon :reaction="reaction" :class="$style.icon" :noStyle="true"/> <div :class="$style.name">{{ reaction.replace('@.', '') }}</div> </div> </MkTooltip> diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index f5e611c62a08db711dde8c3863fa70ee8d61c734..99960f5d250aa294bf143c89841016f90bf93373 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -1,8 +1,8 @@ <template> -<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')"> +<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')"> <div :class="$style.root"> <div :class="$style.reaction"> - <MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :no-style="true"/> + <MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :noStyle="true"/> <div :class="$style.reactionName">{{ getReactionName(reaction) }}</div> </div> <div :class="$style.users"> diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 9480af51020cd1f421ebc6cf9e6f7e21602f5aaf..aabebb3abf759395ff2f89be908122435fac1f51 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -6,7 +6,7 @@ :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.large]: defaultStore.state.largeNoteReactions }]" @click="toggleReaction()" > - <MkReactionIcon :class="$style.icon" :reaction="reaction" :emoji-url="note.reactionEmojis[reaction.substr(1, reaction.length - 2)]"/> + <MkReactionIcon :class="$style.icon" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substr(1, reaction.length - 2)]"/> <span :class="$style.count">{{ count }}</span> </button> </template> @@ -22,6 +22,7 @@ import { $i } from '@/account'; import MkReactionEffect from '@/components/MkReactionEffect.vue'; import { claimAchievement } from '@/scripts/achievements'; import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; const props = defineProps<{ reaction: string; @@ -34,11 +35,19 @@ const buttonEl = shallowRef<HTMLElement>(); const canToggle = computed(() => !props.reaction.match(/@\w/) && $i); -const toggleReaction = () => { +async function toggleReaction() { if (!canToggle.value) return; + // TODO: ãã®çµµæ–‡å—を使ã†æ¨©é™ãŒã‚ã‚‹ã‹ã©ã†ã‹ç¢ºèª + const oldReaction = props.note.myReaction; if (oldReaction) { + const confirm = await os.confirm({ + type: 'warning', + text: oldReaction !== props.reaction ? i18n.ts.changeReactionConfirm : i18n.ts.cancelReactionConfirm, + }); + if (confirm.canceled) return; + os.api('notes/reactions/delete', { noteId: props.note.id, }).then(() => { @@ -58,9 +67,9 @@ const toggleReaction = () => { claimAchievement('reactWithoutRead'); } } -}; +} -const anime = () => { +function anime() { if (document.hidden) return; if (!defaultStore.state.animation) return; @@ -68,7 +77,7 @@ const anime = () => { const x = rect.left + 16; const y = rect.top + (buttonEl.value.offsetHeight / 2); os.popup(MkReactionEffect, { reaction: props.reaction, x, y }, {}, 'end'); -}; +} watch(() => props.count, (newCount, oldCount) => { if (oldCount < newCount) anime(); diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index 3219c8a92c15ae6c80c68e37925ae699b68e56c3..ce146463eceaa5bb0f3c65e06c306033bf1739e7 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -1,13 +1,13 @@ <template> <TransitionGroup - :enter-active-class="defaultStore.state.animation ? $style.transition_x_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_x_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_x_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_x_leaveTo : ''" - :move-class="defaultStore.state.animation ? $style.transition_x_move : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_x_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_x_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_x_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_x_leaveTo : ''" + :moveClass="defaultStore.state.animation ? $style.transition_x_move : ''" tag="div" :class="$style.root" > - <XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/> + <XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note"/> <slot v-if="hasMoreReactions" name="more"/> </TransitionGroup> </template> diff --git a/packages/frontend/src/components/MkRenotedUsersDialog.vue b/packages/frontend/src/components/MkRenotedUsersDialog.vue index 56025535f184ed989884e452f4c5b0452df923d2..814a68d4daf0e3b52711c35aeaa47c0ba9cea843 100644 --- a/packages/frontend/src/components/MkRenotedUsersDialog.vue +++ b/packages/frontend/src/components/MkRenotedUsersDialog.vue @@ -8,7 +8,7 @@ > <template #header>{{ i18n.ts.renotesList }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <div v-if="renotes" class="_gaps"> <div v-if="renotes.length === 0" class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> @@ -16,7 +16,7 @@ </div> <template v-else> <MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()"> - <MkUserCardMini :user="user" :with-chart="false"/> + <MkUserCardMini :user="user" :withChart="false"/> </MkA> </template> </div> diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index 8bd0279806208bf9424600196e14abe2061d6e19..9f56189f3efc2cd837f0293e598f288a22217b99 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -124,7 +124,3 @@ onMounted(async () => { }); }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/components/MkRippleEffect.vue b/packages/frontend/src/components/MkRippleEffect.vue index 9d93211d5f8aace5d3825ec1c0cdd9b4f1e72674..60c3a47385a592f2b95add8ba970a49912f12908 100644 --- a/packages/frontend/src/components/MkRippleEffect.vue +++ b/packages/frontend/src/components/MkRippleEffect.vue @@ -1,7 +1,7 @@ <template> -<div class="vswabwbm" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> +<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> <svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> - <circle fill="none" cx="64" cy="64"> + <circle fill="none" cx="64" cy="64" style="stroke: var(--accent);"> <animate attributeName="r" begin="0s" dur="0.5s" @@ -22,7 +22,7 @@ /> </circle> <g fill="none" fill-rule="evenodd"> - <circle v-for="(particle, i) in particles" :key="i" :fill="particle.color"> + <circle v-for="(particle, i) in particles" :key="i" :fill="particle.color" style="stroke: var(--accent);"> <animate attributeName="r" begin="0s" dur="0.8s" @@ -100,17 +100,11 @@ onMounted(() => { }); </script> -<style lang="scss" scoped> -.vswabwbm { +<style lang="scss" module> +.root { pointer-events: none; position: fixed; width: 128px; height: 128px; - - > svg { - > circle { - stroke: var(--accent); - } - } } </style> diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue index 2f5866f3401e8169fd0569a02500769cee7777be..9fbe1ec993f43e3afbecc2300a46e263fa1d9c79 100644 --- a/packages/frontend/src/components/MkRolePreview.vue +++ b/packages/frontend/src/components/MkRolePreview.vue @@ -12,8 +12,10 @@ </template> </span> <span :class="$style.name">{{ role.name }}</span> - <span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span> - <span v-else-if="role.target === 'conditional'" :class="$style.users">({{ i18n.ts._role.conditional }})</span> + <template v-if="detailed"> + <span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span> + <span v-else-if="role.target === 'conditional'" :class="$style.users">({{ i18n.ts._role.conditional }})</span> + </template> </div> <div :class="$style.description">{{ role.description }}</div> </MkA> @@ -23,10 +25,13 @@ import { } from 'vue'; import { i18n } from '@/i18n'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ role: any; forModeration: boolean; -}>(); + detailed: boolean; +}>(), { + detailed: true, +}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkSample.vue b/packages/frontend/src/components/MkSample.vue deleted file mode 100644 index 922b862b47020b7ed2e07884204e21759d450302..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/MkSample.vue +++ /dev/null @@ -1,118 +0,0 @@ -<template> -<div class=""> - <div class=""> - <MkInput v-model="text"> - <template #label>Text</template> - </MkInput> - <MkSwitch v-model="flag"> - <span>Switch is now {{ flag ? 'on' : 'off' }}</span> - </MkSwitch> - <div style="margin: 32px 0;"> - <MkRadio v-model="radio" value="misskey">Misskey</MkRadio> - <MkRadio v-model="radio" value="mastodon">Mastodon</MkRadio> - <MkRadio v-model="radio" value="pleroma">Pleroma</MkRadio> - </div> - <MkButton inline>This is</MkButton> - <MkButton inline primary>the button</MkButton> - </div> - <div class="" style="pointer-events: none;"> - <Mfm :text="mfm"/> - </div> - <div class=""> - <MkButton inline primary @click="openMenu">Open menu</MkButton> - <MkButton inline primary @click="openDialog">Open dialog</MkButton> - <MkButton inline primary @click="openForm">Open form</MkButton> - <MkButton inline primary @click="openDrive">Open drive</MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/MkButton.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkSwitch from '@/components/MkSwitch.vue'; -import MkTextarea from '@/components/MkTextarea.vue'; -import MkRadio from '@/components/MkRadio.vue'; -import * as os from '@/os'; -import * as config from '@/config'; -import { $i } from '@/account'; - -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSwitch, - MkTextarea, - MkRadio, - }, - - data() { - return { - text: '', - flag: true, - radio: 'misskey', - $i, - mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`, - }; - }, - - methods: { - async openDialog() { - os.alert({ - type: 'warning', - title: 'Oh my Aichan', - text: 'Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', - }); - }, - - async openForm() { - os.form('Example form', { - foo: { - type: 'boolean', - default: true, - label: 'This is a boolean property', - }, - bar: { - type: 'number', - default: 300, - label: 'This is a number property', - }, - baz: { - type: 'string', - default: 'Misskey makes you happy.', - label: 'This is a string property', - }, - }); - }, - - async openDrive() { - os.selectDriveFile(false); - }, - - async selectUser() { - os.selectUser(); - }, - - async openMenu(ev) { - os.popupMenu([{ - type: 'label', - text: 'Fruits', - }, { - text: 'Create some apples', - action: () => {}, - }, { - text: 'Read some oranges', - action: () => {}, - }, { - text: 'Update some melons', - action: () => {}, - }, null, { - text: 'Delete some bananas', - danger: true, - action: () => {}, - }], ev.currentTarget ?? ev.target); - }, - }, -}); -</script> diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index ffc5e82b56198de5a2c3f3acaa0cf2186d95b756..b1a509b9e678a29684a3747cca86a5f0183bd1f5 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -1,16 +1,16 @@ <template> -<form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> - <div class="auth _gaps_m"> - <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div> +<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> + <div class="_gaps_m"> + <div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div> <MkInfo v-if="message"> {{ message }} </MkInfo> <div v-if="!totpLogin" class="normal-signin _gaps_m"> - <MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username" autofocus required data-cy-signin-username @update:model-value="onUsernameChange"> + <MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password" :with-password-toggle="true" required data-cy-signin-password> + <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password" :withPasswordToggle="true" required data-cy-signin-password> <template #prefix><i class="ti ti-lock"></i></template> <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> </MkInput> @@ -28,7 +28,7 @@ </div> <div class="twofa-group totp-group"> <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p> - <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :with-password-toggle="true" required> + <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :withPasswordToggle="true" required> <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="ti ti-lock"></i></template> </MkInput> @@ -236,18 +236,14 @@ function resetPassword() { } </script> -<style lang="scss" scoped> -.eppvobhk { - > .auth { - > .avatar { - margin: 0 auto 0 auto; - width: 64px; - height: 64px; - background: #ddd; - background-position: center; - background-size: cover; - border-radius: 100%; - } - } +<style lang="scss" module> +.avatar { + margin: 0 auto 0 auto; + width: 64px; + height: 64px; + background: #ddd; + background-position: center; + background-size: cover; + border-radius: 100%; } </style> diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index 08e41d6ae56214dd52bdceaf0a25864443889cd1..eb5876e584367775a15ef61eb17b876756e05d98 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -8,8 +8,8 @@ > <template #header>{{ i18n.ts.login }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> - <MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/> + <MkSpacer :marginMin="20" :marginMax="28"> + <MkSignin :autoSet="autoSet" :message="message" @login="onLogin"/> </MkSpacer> </MkModalWindow> </template> diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index 0e8bdb321e243aa093c3df0109ee47de4a413939..472269abaf5a6fe371309e16cb1d0eeb34c7978c 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -3,13 +3,13 @@ <div :class="$style.banner"> <i class="ti ti-user-edit"></i> </div> - <MkSpacer :margin-min="20" :margin-max="32"> + <MkSpacer :marginMin="20" :marginMax="32"> <form class="_gaps_m" autocomplete="new-password" @submit.prevent="onSubmit"> <MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required> <template #label>{{ i18n.ts.invitationCode }}</template> <template #prefix><i class="ti ti-key"></i></template> </MkInput> - <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:model-value="onChangeUsername"> + <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" autocomplete="username" required data-cy-signup-username @update:modelValue="onChangeUsername"> <template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template> <template #prefix>@</template> <template #suffix>@{{ host }}</template> @@ -24,7 +24,7 @@ <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span> </template> </MkInput> - <MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:model-value="onChangeEmail"> + <MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> <template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template> <template #prefix><i class="ti ti-mail"></i></template> <template #caption> @@ -39,7 +39,7 @@ <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> </template> </MkInput> - <MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:model-value="onChangePassword"> + <MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="ti ti-lock"></i></template> <template #caption> @@ -48,7 +48,7 @@ <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span> </template> </MkInput> - <MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:model-value="onChangePasswordRetype"> + <MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> <template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template> <template #prefix><i class="ti ti-lock"></i></template> <template #caption> diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index 6da81c3bcb081f65b6af6fb0bf4a1776c443efa7..b6ffba6cc7080d9f1aaec25ae0b1d12d81bc39ad 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -3,7 +3,7 @@ <div :class="$style.banner"> <i class="ti ti-checklist"></i> </div> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps_m"> <div v-if="instance.disableRegistration"> <MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo> @@ -11,7 +11,7 @@ <div style="text-align: center;">{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}</div> - <MkFolder v-if="availableServerRules" :default-open="true"> + <MkFolder v-if="availableServerRules" :defaultOpen="true"> <template #label>{{ i18n.ts.serverRules }}</template> <template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--success)"></i></template> @@ -22,7 +22,7 @@ <MkSwitch v-model="agreeServerRules" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch> </MkFolder> - <MkFolder v-if="availableTos" :default-open="true"> + <MkFolder v-if="availableTos" :defaultOpen="true"> <template #label>{{ i18n.ts.termsOfService }}</template> <template #suffix><i v-if="agreeTos" class="ti ti-check" style="color: var(--success)"></i></template> @@ -31,7 +31,7 @@ <MkSwitch v-model="agreeTos" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch> </MkFolder> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template> <template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template> diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue index 17f8b864254a7e7782ee32953153fabf90376bba..d8d002fdb6d947ebbc7cc98be2cabed5378f2c27 100644 --- a/packages/frontend/src/components/MkSignupDialog.vue +++ b/packages/frontend/src/components/MkSignupDialog.vue @@ -11,16 +11,16 @@ <div style="overflow-x: clip;"> <Transition mode="out-in" - :enter-active-class="$style.transition_x_enterActive" - :leave-active-class="$style.transition_x_leaveActive" - :enter-from-class="$style.transition_x_enterFrom" - :leave-to-class="$style.transition_x_leaveTo" + :enterActiveClass="$style.transition_x_enterActive" + :leaveActiveClass="$style.transition_x_leaveActive" + :enterFromClass="$style.transition_x_enterFrom" + :leaveToClass="$style.transition_x_leaveTo" > <template v-if="!isAcceptedServerRule"> <XServerRules @done="isAcceptedServerRule = true" @cancel="dialog.close()"/> </template> <template v-else> - <XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/> + <XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/> </template> </Transition> </div> diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 1ac7107aa784f26189aceee13215ba0c61fb30b0..3a050889c8d685ec69faeaed8d8b9b659837d377 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -1,15 +1,15 @@ <template> <div :class="[$style.root, { [$style.collapsed]: collapsed }]"> - <div :class="$style.body"> + <div> <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span> <MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :emoji-urls="note.emojis"/> + <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :emojiUrls="note.emojis"/> <MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> </div> <details v-if="note.files.length > 0"> <summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary> - <MkMediaList :media-list="note.files"/> + <MkMediaList :mediaList="note.files"/> </details> <details v-if="note.poll"> <summary>{{ i18n.ts.poll }}</summary> @@ -76,10 +76,6 @@ const collapsed = $ref( } } -.body { - -} - .reply { margin-right: 6px; color: var(--accent); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 2a8e43c570b498f335d918f0d7b7f2176240f7b6..72b70416d9b9636f525055d06e494df7ff5972af 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -23,22 +23,13 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - def: { - type: Array, - required: true, - }, - grid: { - type: Boolean, - required: false, - default: false, - }, - }, -}); +<script lang="ts" setup> +import { } from 'vue'; + +defineProps<{ + def: any[]; + grid?: boolean; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue index 6f819bbbd741d116384b2edd04ae08eb991b4f62..7274f9b310d2e0821e1fb4be6c9b4dc612b845fa 100644 --- a/packages/frontend/src/components/MkTab.vue +++ b/packages/frontend/src/components/MkTab.vue @@ -7,17 +7,17 @@ export default defineComponent({ required: true, }, }, - render() { - const options = this.$slots.default(); + setup(props, { emit, slots }) { + const options = slots.default(); - return h('div', { + return () => h('div', { class: 'pxhvhrfw', }, options.map(option => withDirectives(h('button', { - class: ['_button', { active: this.modelValue === option.props.value }], + class: ['_button', { active: props.modelValue === option.props.value }], key: option.key, - disabled: this.modelValue === option.props.value, + disabled: props.modelValue === option.props.value, onClick: () => { - this.$emit('update:modelValue', option.props.value); + emit('update:modelValue', option.props.value); }, }, option.children), [ [resolveDirective('click-anime')], diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 4e8d5bab7fd0753799187a0e50f6a66dba65b8f9..6e4e054aad1c3e6d99af972b7135643bf41add16 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -1,7 +1,7 @@ <template> -<div ref="rootEl" class="meijqfqm"> - <canvas :id="idForCanvas" ref="canvasEl" class="canvas" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas> - <div :id="idForTags" ref="tagsEl" class="tags"> +<div ref="rootEl" :class="$style.root"> + <canvas :id="idForCanvas" ref="canvasEl" style="display: block;" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas> + <div :id="idForTags" ref="tagsEl" :class="$style.tags"> <ul> <slot></slot> </ul> @@ -70,21 +70,17 @@ defineExpose({ }); </script> -<style lang="scss" scoped> -.meijqfqm { +<style lang="scss" module> +.root { position: relative; overflow: clip; display: grid; place-items: center; +} - > .canvas { - display: block; - } - - > .tags { - position: absolute; - top: 999px; - left: 999px; - } +.tags { + position: absolute; + top: 999px; + left: 999px; } </style> diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index 82b631edda3ef4a5bcb1d7dcebf46870e91c7c2f..83b2ed24447a2818a30f3336ffa64a82006e027b 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -1,12 +1,12 @@ <template> -<div class="adhpbeos"> - <div class="label" @click="focus"><slot name="label"></slot></div> - <div class="input" :class="{ disabled, focused, tall, pre }"> +<div> + <div :class="$style.label" @click="focus"><slot name="label"></slot></div> + <div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;"> <textarea ref="inputEl" v-model="v" v-adaptive-border - :class="{ code, _monospace: code }" + :class="[$style.textarea, { _monospace: code }]" :disabled="disabled" :required="required" :readonly="readonly" @@ -20,243 +20,173 @@ @input="onInput" ></textarea> </div> - <div class="caption"><slot name="caption"></slot></div> + <div :class="$style.caption"><slot name="caption"></slot></div> - <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, nextTick, ref, watch, computed, toRefs } from 'vue'; +<script lang="ts" setup> +import { onMounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, - - props: { - modelValue: { - required: true, - }, - type: { - type: String, - required: false, - }, - required: { - type: Boolean, - required: false, - }, - readonly: { - type: Boolean, - required: false, - }, - disabled: { - type: Boolean, - required: false, - }, - pattern: { - type: String, - required: false, - }, - placeholder: { - type: String, - required: false, - }, - autofocus: { - type: Boolean, - required: false, - default: false, - }, - autocomplete: { - required: false, - }, - spellcheck: { - required: false, - }, - code: { - type: Boolean, - required: false, - }, - tall: { - type: Boolean, - required: false, - default: false, - }, - pre: { - type: Boolean, - required: false, - default: false, - }, - debounce: { - type: Boolean, - required: false, - default: false, - }, - manualSave: { - type: Boolean, - required: false, - default: false, - }, - }, - - emits: ['change', 'keydown', 'enter', 'update:modelValue'], - - setup(props, context) { - const { modelValue, autofocus } = toRefs(props); - const v = ref(modelValue.value); - const focused = ref(false); - const changed = ref(false); - const invalid = ref(false); - const filled = computed(() => v.value !== '' && v.value != null); - const inputEl = ref(null); - - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - const onKeydown = (ev: KeyboardEvent) => { - if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; - - context.emit('keydown', ev); - - if (ev.code === 'Enter') { - context.emit('enter'); - } - }; - - const updated = () => { - changed.value = false; - context.emit('update:modelValue', v.value); - }; +const props = defineProps<{ + modelValue: string | null; + required?: boolean; + readonly?: boolean; + disabled?: boolean; + pattern?: string; + placeholder?: string; + autofocus?: boolean; + autocomplete?: string; + spellcheck?: boolean; + debounce?: boolean; + manualSave?: boolean; + code?: boolean; + tall?: boolean; + pre?: boolean; +}>(); + +const emit = defineEmits<{ + (ev: 'change', _ev: KeyboardEvent): void; + (ev: 'keydown', _ev: KeyboardEvent): void; + (ev: 'enter'): void; + (ev: 'update:modelValue', value: string): void; +}>(); + +const { modelValue, autofocus } = toRefs(props); +const v = ref<string>(modelValue.value ?? ''); +const focused = ref(false); +const changed = ref(false); +const invalid = ref(false); +const filled = computed(() => v.value !== '' && v.value != null); +const inputEl = shallowRef<HTMLTextAreaElement>(); + +const focus = () => inputEl.value.focus(); +const onInput = (ev) => { + changed.value = true; + emit('change', ev); +}; +const onKeydown = (ev: KeyboardEvent) => { + if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; + + emit('keydown', ev); + + if (ev.code === 'Enter') { + emit('enter'); + } +}; - const debouncedUpdated = debounce(1000, updated); +const updated = () => { + changed.value = false; + emit('update:modelValue', v.value ?? ''); +}; - watch(modelValue, newValue => { - v.value = newValue; - }); +const debouncedUpdated = debounce(1000, updated); - watch(v, newValue => { - if (!props.manualSave) { - if (props.debounce) { - debouncedUpdated(); - } else { - updated(); - } - } +watch(modelValue, newValue => { + v.value = newValue; +}); - invalid.value = inputEl.value.validity.badInput; - }); +watch(v, newValue => { + if (!props.manualSave) { + if (props.debounce) { + debouncedUpdated(); + } else { + updated(); + } + } - onMounted(() => { - nextTick(() => { - if (autofocus.value) { - focus(); - } - }); - }); + invalid.value = inputEl.value.validity.badInput; +}); - return { - v, - focused, - invalid, - changed, - filled, - inputEl, - focus, - onInput, - onKeydown, - updated, - i18n, - }; - }, +onMounted(() => { + nextTick(() => { + if (autofocus.value) { + focus(); + } + }); }); </script> -<style lang="scss" scoped> -.adhpbeos { - > .label { - font-size: 0.85em; - padding: 0 0 8px 0; - user-select: none; +<style lang="scss" module> +.label { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; - &:empty { - display: none; - } + &:empty { + display: none; } +} - > .caption { - font-size: 0.85em; - padding: 8px 0 0 0; - color: var(--fgTransparentWeak); +.caption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); - &:empty { - display: none; - } + &:empty { + display: none; } +} - > .input { - position: relative; - - > textarea { - appearance: none; - -webkit-appearance: none; - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 130px; - margin: 0; - padding: 12px; - font: inherit; - font-weight: normal; - font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); - border-radius: 6px; - outline: none; - box-shadow: none; - box-sizing: border-box; - transition: border-color 0.1s ease-out; - - &:hover { - border-color: var(--inputBorderHover) !important; - } - } +.textarea { + appearance: none; + -webkit-appearance: none; + display: block; + width: 100%; + min-width: 100%; + max-width: 100%; + min-height: 130px; + margin: 0; + padding: 12px; + font: inherit; + font-weight: normal; + font-size: 1em; + color: var(--fg); + background: var(--panel); + border: solid 1px var(--panel); + border-radius: 6px; + outline: none; + box-shadow: none; + box-sizing: border-box; + transition: border-color 0.1s ease-out; + + &:hover { + border-color: var(--inputBorderHover) !important; + } +} - &.focused { - > textarea { - border-color: var(--accent) !important; - } - } +.focused { + > .textarea { + border-color: var(--accent) !important; + } +} - &.disabled { - opacity: 0.7; +.disabled { + opacity: 0.7; + cursor: not-allowed !important; - &, * { - cursor: not-allowed !important; - } - } - - &.tall { - > textarea { - min-height: 200px; - } - } + > .textarea { + cursor: not-allowed !important; + } +} - &.pre { - > textarea { - white-space: pre; - } - } +.tall { + > .textarea { + min-height: 200px; } +} - > .save { - margin: 8px 0 0 0; +.pre { + > .textarea { + white-space: pre; } } + +.save { + margin: 8px 0 0 0; +} </style> diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index fb0a3a4b67d823449f683ef54847f7db642e653a..2595ebc45d1b8cdcef1e37d869ab003e8f6f181f 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -1,11 +1,11 @@ <template> -<MkNotes ref="tlComponent" :no-gap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> +<MkNotes ref="tlComponent" :noGap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> </template> <script lang="ts" setup> import { computed, provide, onUnmounted } from 'vue'; import MkNotes from '@/components/MkNotes.vue'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; import { defaultStore } from '@/store'; @@ -46,17 +46,13 @@ const onUserRemoved = () => { tlComponent.pagingComponent?.reload(); }; -const onChangeFollowing = () => { - if (!tlComponent.pagingComponent?.backed) { - tlComponent.pagingComponent?.reload(); - } -}; - let endpoint; let query; let connection; let connection2; +const stream = useStream(); + if (props.src === 'antenna') { endpoint = 'antennas/notes'; query = { @@ -68,23 +64,41 @@ if (props.src === 'antenna') { connection.on('note', prepend); } else if (props.src === 'home') { endpoint = 'notes/timeline'; - connection = stream.useChannel('homeTimeline'); + query = { + withReplies: defaultStore.state.showTimelineReplies, + }; + connection = stream.useChannel('homeTimeline', { + withReplies: defaultStore.state.showTimelineReplies, + }); connection.on('note', prepend); connection2 = stream.useChannel('main'); - connection2.on('follow', onChangeFollowing); - connection2.on('unfollow', onChangeFollowing); } else if (props.src === 'local') { endpoint = 'notes/local-timeline'; - connection = stream.useChannel('localTimeline'); + query = { + withReplies: defaultStore.state.showTimelineReplies, + }; + connection = stream.useChannel('localTimeline', { + withReplies: defaultStore.state.showTimelineReplies, + }); connection.on('note', prepend); } else if (props.src === 'social') { endpoint = 'notes/hybrid-timeline'; - connection = stream.useChannel('hybridTimeline'); + query = { + withReplies: defaultStore.state.showTimelineReplies, + }; + connection = stream.useChannel('hybridTimeline', { + withReplies: defaultStore.state.showTimelineReplies, + }); connection.on('note', prepend); } else if (props.src === 'global') { endpoint = 'notes/global-timeline'; - connection = stream.useChannel('globalTimeline'); + query = { + withReplies: defaultStore.state.showTimelineReplies, + }; + connection = stream.useChannel('globalTimeline', { + withReplies: defaultStore.state.showTimelineReplies, + }); connection.on('note', prepend); } else if (props.src === 'mentions') { endpoint = 'notes/mentions'; diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue index ad53c7f28918fbb2120b1419e0d2b780b2b5c466..e135f564727cf388efd12b5fe9f60e42efcb3923 100644 --- a/packages/frontend/src/components/MkToast.vue +++ b/packages/frontend/src/components/MkToast.vue @@ -1,11 +1,11 @@ <template> <div> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_toast_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''" - appear @after-leave="emit('closed')" + :enterActiveClass="defaultStore.state.animation ? $style.transition_toast_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''" + appear @afterLeave="emit('closed')" > <div v-if="showing" class="_acrylic" :class="$style.root" :style="{ zIndex }"> <div style="padding: 16px 24px;"> diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index 56be0444052ece4ee3ab518d7643cae9984ab7c9..3ddd81aaee19720be3afd0ccc88bc0f2f477d8a8 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -3,16 +3,16 @@ ref="dialog" :width="400" :height="450" - :with-ok-button="true" - :ok-button-disabled="false" - :can-close="false" + :withOkButton="true" + :okButtonDisabled="false" + :canClose="false" @close="dialog.close()" @closed="$emit('closed')" @ok="ok()" > <template #header>{{ title || i18n.ts.generateAccessToken }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps_m"> <div v-if="information"> <MkInfo warn>{{ information }}</MkInfo> diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index 2d34b090ed6aaf10de3c6175dbf90df4caae72c5..91c9b70a5ae7c7b916f1a09467d7f017b3597dd0 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -1,10 +1,10 @@ <template> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''" - appear @after-leave="emit('closed')" + :enterActiveClass="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''" + appear @afterLeave="emit('closed')" > <div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }"> <slot> @@ -41,6 +41,9 @@ const emit = defineEmits<{ (ev: 'closed'): void; }>(); +// タイミングã«ã‚ˆã£ã¦ã¯æœ€åˆã‹ã‚‰ showing = false ãªå ´åˆãŒã‚ã‚Šã€ãã®å ´åˆã« closed 扱ã„ã«ã—ãªã„ã¨æ°¸ä¹…ã«DOMã«æ®‹ã‚‹ã“ã¨ã«ãªã‚‹ +if (!props.showing) emit('closed'); + const el = shallowRef<HTMLElement>(); const zIndex = os.claimZIndex('high'); @@ -66,10 +69,8 @@ onMounted(() => { setPosition(); const loop = () => { - loopHandler = window.requestAnimationFrame(() => { - setPosition(); - loop(); - }); + setPosition(); + loopHandler = window.requestAnimationFrame(loop); }; loop(); diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue index eed7fa71f6c33f1ba7eaf4e183c7e8e57f942388..3a0b2abb4e053e61b72108cf6b69b83af05fcaaa 100644 --- a/packages/frontend/src/components/MkUpdated.vue +++ b/packages/frontend/src/components/MkUpdated.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> +<MkModal ref="modal" :zPriority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> <div :class="$style.root"> <div :class="$style.title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div> <div :class="$style.version">✨{{ version }}🚀</div> diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 9c5622b1c586482c8c02314cc0adedc6c6d6f683..fcad5b8064f8c46ee98bc83af1fe795830a79e03 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -22,7 +22,7 @@ </div> </template> <template v-else-if="tweetId && tweetExpanded"> - <div ref="twitter" :class="$style.twitter"> + <div ref="twitter"> <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> </div> <div :class="$style.action"> @@ -31,7 +31,7 @@ </MkButton> </div> </template> -<div v-else :class="$style.urlPreview"> +<div v-else> <component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`"> </div> @@ -41,14 +41,14 @@ <h1 v-else-if="fetching" :class="$style.title"><MkEllipsis/></h1> <h1 v-else :class="$style.title" :title="title ?? undefined">{{ title }}</h1> </header> - <p v-if="unknownUrl" :class="$style.text">{{ i18n.ts.cannotLoad }}</p> + <p v-if="unknownUrl" :class="$style.text">{{ i18n.ts.failedToPreviewUrl }}</p> <p v-else-if="fetching" :class="$style.text"><MkEllipsis/></p> <p v-else-if="description" :class="$style.text" :title="description">{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p> <footer :class="$style.footer"> <img v-if="icon" :class="$style.siteIcon" :src="icon"/> - <p v-if="unknownUrl" :class="$style.siteName">?</p> + <p v-if="unknownUrl" :class="$style.siteName">{{ requestUrl.host }}</p> <p v-else-if="fetching" :class="$style.siteName"><MkEllipsis/></p> - <p v-else :class="$style.siteName" :title="sitename ?? undefined">{{ sitename }}</p> + <p v-else :class="$style.siteName" :title="sitename ?? requestUrl.host">{{ sitename ?? requestUrl.host }}</p> </footer> </article> </component> @@ -128,17 +128,33 @@ if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/ requestUrl.hash = ''; -window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`).then(res => { - res.json().then((info: SummalyResult) => { +window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`) + .then(res => { + if (!res.ok) { + fetching = false; + unknownUrl = true; + return; + } + + return res.json(); + }) + .then((info: SummalyResult) => { + if (info.url == null) { + fetching = false; + unknownUrl = true; + return; + } + + fetching = false; + unknownUrl = false; + title = info.title; description = info.description; thumbnail = info.thumbnail; icon = info.icon; sitename = info.sitename; - fetching = false; player = info.player; }); -}); function adjustTweetHeight(message: any) { if (message.origin !== 'https://platform.twitter.com') return; @@ -194,13 +210,6 @@ onUnmounted(() => { width: 100%; } -.twitter { - -} - -.urlPreview { -} - .link { position: relative; display: block; diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue index e244be3e963b3d3e5bd50690486f4c5e8a574847..36a9e2f73fbe7b6049d1f02c6d235a391be790e6 100644 --- a/packages/frontend/src/components/MkUrlPreviewPopup.vue +++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue @@ -1,6 +1,6 @@ <template> -<div class="fgmtyycl" :style="{ zIndex, top: top + 'px', left: left + 'px' }"> - <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @after-leave="emit('closed')"> +<div :class="$style.root" :style="{ zIndex, top: top + 'px', left: left + 'px' }"> + <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @afterLeave="emit('closed')"> <MkUrlPreview v-if="showing" class="_popup _shadow" :url="url"/> </Transition> </div> @@ -36,8 +36,8 @@ onMounted(() => { }); </script> -<style lang="scss" scoped> -.fgmtyycl { +<style lang="scss" module> +.root { position: absolute; width: 500px; max-width: calc(90vw - 12px); diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index f560ebcd8a3389cc305996005e72e6df01bbabdf..172b517511158a52ccc0bf37074ffad95015b281 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -8,7 +8,7 @@ </div> <span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span> <div :class="$style.description"> - <div v-if="user.description" class="mfm"> + <div v-if="user.description" :class="$style.mfm"> <Mfm :text="user.description" :author="user" :i="$i"/> </div> <span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span> @@ -105,7 +105,7 @@ defineProps<{ .mfm { display: -webkit-box; -webkit-line-clamp: 3; - -webkit-box-orient: vertical; + -webkit-box-orient: vertical; overflow: hidden; } diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue index 251ab5d79ae2533eee3ec4f09fc7d652d6bb9279..a2c2b53b08dc1117af5a126a962b4a176cb6a734 100644 --- a/packages/frontend/src/components/MkUserOnlineIndicator.vue +++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue @@ -1,5 +1,13 @@ <template> -<div v-tooltip="text" :class="[$style.root, $style['status_' + user.onlineStatus]]"></div> +<div + v-tooltip="text" + :class="[$style.root, { + [$style.status_online]: user.onlineStatus === 'online', + [$style.status_active]: user.onlineStatus === 'active', + [$style.status_offline]: user.onlineStatus === 'offline', + [$style.status_unknown]: user.onlineStatus === 'unknown', + }]" +></div> </template> <script lang="ts" setup> diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 8ca035544801cc9ad46f67c9b99bd611be89edde..c3b777a12ecd8571ef6d10c9c88cd5950242231d 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -1,10 +1,10 @@ <template> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_popup_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_popup_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_popup_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_popup_leaveTo : ''" - appear @after-leave="emit('closed')" + :enterActiveClass="defaultStore.state.animation ? $style.transition_popup_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_popup_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_popup_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_popup_leaveTo : ''" + appear @afterLeave="emit('closed')" > <div v-if="showing" :class="$style.root" class="_popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { emit('mouseover'); }" @mouseleave="() => { emit('mouseleave'); }"> <div v-if="user != null"> @@ -22,7 +22,7 @@ <div :class="$style.username"><MkAcct :user="user"/></div> </div> <div :class="$style.description"> - <Mfm v-if="user.description" :text="user.description" :author="user" :i="$i"/> + <Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user" :i="$i"/> <div v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</div> </div> <div :class="$style.status"> @@ -192,6 +192,13 @@ onMounted(() => { border-bottom: solid 1px var(--divider); } +.mfm { + display: -webkit-box; + -webkit-line-clamp: 5; + -webkit-box-orient: vertical; + overflow: hidden; +} + .status { padding: 16px 26px 16px 26px; } diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index dc78bbf42d3faa1c2db5e88fb76346bf6afc9565..792ff7afd7bca59f2b9337f649fa5bca2851ae4c 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -1,22 +1,22 @@ <template> <MkModalWindow ref="dialogEl" - :with-ok-button="true" - :ok-button-disabled="selected == null" + :withOkButton="true" + :okButtonDisabled="selected == null" @click="cancel()" @close="cancel()" @ok="ok()" @closed="$emit('closed')" > <template #header>{{ i18n.ts.selectUser }}</template> - <div :class="$style.root"> + <div> <div :class="$style.form"> - <FormSplit :min-width="170"> - <MkInput v-model="username" :autofocus="true" @update:model-value="search"> + <FormSplit :minWidth="170"> + <MkInput v-model="username" :autofocus="true" @update:modelValue="search"> <template #label>{{ i18n.ts.username }}</template> <template #prefix>@</template> </MkInput> - <MkInput v-model="host" :datalist="[hostname]" @update:model-value="search"> + <MkInput v-model="host" :datalist="[hostname]" @update:modelValue="search"> <template #label>{{ i18n.ts.host }}</template> <template #prefix>@</template> </MkInput> @@ -126,8 +126,6 @@ onMounted(() => { </script> <style lang="scss" module> -.root { -} .form { padding: 0 var(--root-margin); diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue index a2a195cb0994720685cb2ed3ad8476a4cb4256f9..789f88a8fe53fb6eb99853d41e28090a45927002 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -2,7 +2,7 @@ <div class="_gaps"> <div style="text-align: center;">{{ i18n.ts._initialAccountSetting.followUsers }}</div> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.recommended }}</template> <MkPagination :pagination="pinnedUsers"> @@ -14,7 +14,7 @@ </MkPagination> </MkFolder> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.popularUsers }}</template> <MkPagination :pagination="popularUsers"> diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue index e9f4f68df89a9f51f4c524f3340924b8b82e42c1..5cea67ccf51150f323a38412d63b35d92a53b81c 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue @@ -4,6 +4,7 @@ <MkFolder> <template #label>{{ i18n.ts.makeFollowManuallyApprove }}</template> + <template #icon><i class="ti ti-lock"></i></template> <template #suffix>{{ isLocked ? i18n.ts.on : i18n.ts.off }}</template> <MkSwitch v-model="isLocked">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch> @@ -11,6 +12,7 @@ <MkFolder> <template #label>{{ i18n.ts.hideOnlineStatus }}</template> + <template #icon><i class="ti ti-eye-off"></i></template> <template #suffix>{{ hideOnlineStatus ? i18n.ts.on : i18n.ts.off }}</template> <MkSwitch v-model="hideOnlineStatus">{{ i18n.ts.hideOnlineStatus }}<template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template></MkSwitch> @@ -18,6 +20,7 @@ <MkFolder> <template #label>{{ i18n.ts.noCrawle }}</template> + <template #icon><i class="ti ti-world-x"></i></template> <template #suffix>{{ noCrawle ? i18n.ts.on : i18n.ts.off }}</template> <MkSwitch v-model="noCrawle">{{ i18n.ts.noCrawle }}<template #caption>{{ i18n.ts.noCrawleDescription }}</template></MkSwitch> @@ -25,6 +28,7 @@ <MkFolder> <template #label>{{ i18n.ts.preventAiLearning }}</template> + <template #icon><i class="ti ti-photo-shield"></i></template> <template #suffix>{{ preventAiLearning ? i18n.ts.on : i18n.ts.off }}</template> <MkSwitch v-model="preventAiLearning">{{ i18n.ts.preventAiLearning }}<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template></MkSwitch> diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index f26ea112142523c7176f4c468613e60939527ead..3107209b97f0081544883d09cae0f2a37970b8a2 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -12,11 +12,11 @@ </div> </FormSlot> - <MkInput v-model="name" :max="30" manual-save data-cy-user-setup-user-name> + <MkInput v-model="name" :max="30" manualSave data-cy-user-setup-user-name> <template #label>{{ i18n.ts._profile.name }}</template> </MkInput> - <MkTextarea v-model="description" :max="500" tall manual-save data-cy-user-setup-user-description> + <MkTextarea v-model="description" :max="500" tall manualSave data-cy-user-setup-user-description> <template #label>{{ i18n.ts._profile.description }}</template> </MkTextarea> @@ -37,8 +37,8 @@ import { chooseFileFromPc } from '@/scripts/select-file'; import * as os from '@/os'; import { $i } from '@/account'; -const name = ref(''); -const description = ref(''); +const name = ref($i.name ?? ''); +const description = ref($i.description ?? ''); watch(name, () => { os.apiWithDialog('i/update', { diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index 4e80a5c0fbe6f7489502c83dc47d095436a12d2b..566441213e976067fa69a8629e74ed93ef50c89a 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -7,10 +7,10 @@ @close="close(true)" @closed="emit('closed')" > - <template v-if="page === 1" #header>{{ i18n.ts._initialAccountSetting.profileSetting }}</template> - <template v-else-if="page === 2" #header>{{ i18n.ts._initialAccountSetting.privacySetting }}</template> - <template v-else-if="page === 3" #header>{{ i18n.ts.follow }}</template> - <template v-else-if="page === 4" #header>{{ i18n.ts.pushNotification }}</template> + <template v-if="page === 1" #header><i class="ti ti-user-edit"></i> {{ i18n.ts._initialAccountSetting.profileSetting }}</template> + <template v-else-if="page === 2" #header><i class="ti ti-lock"></i> {{ i18n.ts._initialAccountSetting.privacySetting }}</template> + <template v-else-if="page === 3" #header><i class="ti ti-user-plus"></i> {{ i18n.ts.follow }}</template> + <template v-else-if="page === 4" #header><i class="ti ti-bell-plus"></i> {{ i18n.ts.pushNotification }}</template> <template v-else-if="page === 5" #header>{{ i18n.ts.done }}</template> <template v-else #header>{{ i18n.ts.initialAccountSetting }}</template> @@ -20,65 +20,80 @@ </div> <Transition mode="out-in" - :enter-active-class="$style.transition_x_enterActive" - :leave-active-class="$style.transition_x_leaveActive" - :enter-from-class="$style.transition_x_enterFrom" - :leave-to-class="$style.transition_x_leaveTo" + :enterActiveClass="$style.transition_x_enterActive" + :leaveActiveClass="$style.transition_x_leaveActive" + :enterFromClass="$style.transition_x_enterFrom" + :leaveToClass="$style.transition_x_leaveTo" > <template v-if="page === 0"> <div :class="$style.centerPage"> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/> + <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> <i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> <div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.accountCreated }}</div> <div>{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}</div> <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts._initialAccountSetting.profileSetting }} <i class="ti ti-arrow-right"></i></MkButton> + <MkButton style="margin: 0 auto;" transparent rounded @click="later(true)">{{ i18n.ts.later }}</MkButton> </div> </MkSpacer> </div> </template> <template v-else-if="page === 1"> <div style="height: 100cqh; overflow: auto;"> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <XProfile/> - <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + <div class="_buttonsCenter" style="margin-top: 16px;"> + <MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton> + <MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> </MkSpacer> </div> </template> <template v-else-if="page === 2"> <div style="height: 100cqh; overflow: auto;"> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <XPrivacy/> - <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + <div class="_buttonsCenter" style="margin-top: 16px;"> + <MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton> + <MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> </MkSpacer> </div> </template> <template v-else-if="page === 3"> <div style="height: 100cqh; overflow: auto;"> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <XFollow/> </MkSpacer> <div :class="$style.pageFooter"> - <MkButton primary rounded gradate style="margin: 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + <div class="_buttonsCenter"> + <MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton> + <MkButton primary rounded gradate style="" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> </div> </div> </template> <template v-else-if="page === 4"> <div :class="$style.centerPage"> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> <i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> <div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div> <div style="padding: 0 16px;">{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}</div> - <MkPushNotificationAllowButton primary show-only-to-register style="margin: 0 auto;"/> - <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + <MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/> + <div class="_buttonsCenter" style="margin-top: 16px;"> + <MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton> + <MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> </div> </MkSpacer> </div> </template> <template v-else-if="page === 5"> <div :class="$style.centerPage"> - <MkSpacer :margin-min="20" :margin-max="28"> + <MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/> + <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> <i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> <div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div> @@ -89,7 +104,10 @@ </template> </I18n> <div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div> - <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton> + <div class="_buttonsCenter" style="margin-top: 16px;"> + <MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton> + <MkButton primary rounded gradate data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton> + </div> </div> </MkSpacer> </div> @@ -106,6 +124,7 @@ import MkButton from '@/components/MkButton.vue'; import XProfile from '@/components/MkUserSetupDialog.Profile.vue'; import XFollow from '@/components/MkUserSetupDialog.Follow.vue'; import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue'; +import MkAnimBg from '@/components/MkAnimBg.vue'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; import { host } from '@/config'; @@ -137,6 +156,19 @@ async function close(skip: boolean) { dialog.value.close(); defaultStore.set('accountSetupWizard', -1); } + +async function later(later: boolean) { + if (later) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts._initialAccountSetting.laterAreYouSure, + }); + if (canceled) return; + } + + dialog.value.close(); + defaultStore.set('accountSetupWizard', 0); +} </script> <style lang="scss" module> @@ -183,7 +215,7 @@ async function close(skip: boolean) { left: 0; padding: 12px; border-top: solid 0.5px var(--divider); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: blur(15px); + backdrop-filter: blur(15px); } </style> diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue index d0f95fceda4bf05cc22389735ace02354655c3de..0b80c2edc78f27cef0c77abb56d148b103b5ca26 100644 --- a/packages/frontend/src/components/MkUsersTooltip.vue +++ b/packages/frontend/src/components/MkUsersTooltip.vue @@ -1,11 +1,11 @@ <template> -<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="250" @closed="emit('closed')"> +<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="250" @closed="emit('closed')"> <div :class="$style.root"> <div v-for="u in users" :key="u.id" :class="$style.user"> <MkAvatar :class="$style.avatar" :user="u"/> - <MkUserName :class="$style.name" :user="u" :nowrap="true"/> + <MkUserName :user="u" :nowrap="true"/> </div> - <div v-if="users.length < count" :class="$style.omitted">+{{ count - users.length }}</div> + <div v-if="users.length < count">+{{ count - users.length }}</div> </div> </MkTooltip> </template> @@ -43,14 +43,6 @@ const emit = defineEmits<{ } } -.name { - -} - -.omitted { - -} - .avatar { width: 24px; height: 24px; diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue index c181d84bc0f2ec5ac05c3b56ed9ea158f05b4698..c8dbe90944b2cb67da9e861671907b59756f44b5 100644 --- a/packages/frontend/src/components/MkVisibilityPicker.vue +++ b/packages/frontend/src/components/MkVisibilityPicker.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" v-slot="{ type }" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> +<MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="_popup" :class="{ [$style.root]: true, [$style.asDrawer]: type === 'drawer' }"> <div :class="[$style.label, $style.item]"> {{ i18n.ts.visibility }} diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index 62267681273b140c08ca00bb7a1a2c5afe8449b0..9566cc651f0a1efe99cca1502f2892136c53d1c3 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -39,7 +39,7 @@ <MkTimeline src="local"/> </div> </div> - <div :class="[$style.activeUsersChart, $style.panel]"> + <div :class="$style.panel"> <XActiveUsersChart/> </div> </div> @@ -220,8 +220,4 @@ function exploreOtherServers() { height: 350px; overflow: auto; } - -.activeUsersChart { - -} </style> diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue index da98da29d0a33e48f1fb81afa0aae1d126fba962..1b6ab1f13aac3a7bd342e6c316e89f3d4917576c 100644 --- a/packages/frontend/src/components/MkWaitingDialog.vue +++ b/packages/frontend/src/components/MkWaitingDialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> +<MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> <div :class="[$style.root, { [$style.iconOnly]: (text == null) || success }]"> <i v-if="success" :class="[$style.icon, $style.success]" class="ti ti-check"></i> <MkLoading v-else :class="[$style.icon, $style.waiting]" :em="true"/> diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index ad1c02a488d85191161bf2e0d5ed7ed397ea6bf3..30547c7444062546432d725d96829bf703821ba7 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -1,7 +1,7 @@ <template> <div :class="$style.root"> <template v-if="edit"> - <header :class="$style['edit-header']"> + <header :class="$style.editHeader"> <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select> <template #label>{{ i18n.ts.selectWidget }}</template> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option> @@ -10,26 +10,26 @@ <MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> </header> <Sortable - :model-value="props.widgets" - item-key="id" + :modelValue="props.widgets" + itemKey="id" handle=".handle" :animation="150" :group="{ name: 'SortableMkWidgets' }" - :class="$style['edit-editing']" - @update:model-value="v => emit('updateWidgets', v)" + :class="$style.editEditing" + @update:modelValue="v => emit('updateWidgets', v)" > <template #item="{element}"> - <div :class="[$style.widget, $style['customize-container']]" data-cy-customize-container> - <button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button> - <button :class="$style['customize-container-remove']" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button> + <div :class="[$style.widget, $style.customizeContainer]" data-cy-customize-container> + <button :class="$style.customizeContainerConfig" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button> + <button :class="$style.customizeContainerRemove" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button> <div class="handle"> - <component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/> + <component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style.customizeContainerHandleWidget" :widget="element" @updateProps="updateWidget(element.id, $event)"/> </div> </div> </template> </Sortable> </template> - <component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> + <component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> </div> </template> @@ -130,7 +130,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { } .edit { - &-header { + &Header { margin: 16px 0; > * { @@ -139,17 +139,17 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { } } - &-editing { + &Editing { min-height: 100px; } } -.customize-container { +.customizeContainer { position: relative; cursor: move; - &-config, - &-remove { + &Config, + &Remove { position: absolute; z-index: 10000; top: 8px; @@ -160,17 +160,17 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { border-radius: 4px; } - &-config { + &Config { right: 8px + 8px + 32px; } - &-remove { + &Remove { right: 8px; } - &-handle { + &Handle { - &-widget { + &Widget { pointer-events: none; } } diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index b662479b2a28ba032a2656f9890807fa6ce4d6de..dafabf2ba8582c52f9283ee655d727fb84c8d0fb 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -1,11 +1,11 @@ <template> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_window_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_window_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_window_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_window_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_window_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_window_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''" appear - @after-leave="$emit('closed')" + @afterLeave="$emit('closed')" > <div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]"> <div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown"> diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue index 4d765fe2f7cc09c4f9cd1bc2d40699a9f3a63df9..0edfd98efcf0aac09e84ecb1d5bf80020f6f0957 100644 --- a/packages/frontend/src/components/MkYouTubePlayer.vue +++ b/packages/frontend/src/components/MkYouTubePlayer.vue @@ -1,5 +1,5 @@ <template> -<MkWindow :initial-width="640" :initial-height="402" :can-resize="true" :close-button="true"> +<MkWindow :initialWidth="640" :initialHeight="402" :canResize="true" :closeButton="true"> <template #header> <i class="icon ti ti-brand-youtube" style="margin-right: 0.5em;"></i> <span>{{ title ?? 'YouTube' }}</span> diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue index a1775c0bdb125d64887a71df970adb92fffdce2f..22b5edc3c9fb2caed04a59ebf5921912eda306ee 100644 --- a/packages/frontend/src/components/form/link.vue +++ b/packages/frontend/src/components/form/link.vue @@ -1,19 +1,19 @@ <template> -<div class="ffcbddfc" :class="{ inline }"> - <a v-if="external" class="main _button" :href="to" target="_blank"> - <span class="icon"><slot name="icon"></slot></span> - <span class="text"><slot></slot></span> - <span class="right"> - <span class="text"><slot name="suffix"></slot></span> - <i class="ti ti-external-link icon"></i> +<div :class="[$style.root, { [$style.inline]: inline }]"> + <a v-if="external" :class="$style.main" class="_button" :href="to" target="_blank"> + <span :class="$style.icon"><slot name="icon"></slot></span> + <span :class="$style.text"><slot></slot></span> + <span :class="$style.suffix"> + <span :class="$style.suffixText"><slot name="suffix"></slot></span> + <i class="ti ti-external-link"></i> </span> </a> - <MkA v-else class="main _button" :class="{ active }" :to="to" :behavior="behavior"> - <span class="icon"><slot name="icon"></slot></span> - <span class="text"><slot></slot></span> - <span class="right"> - <span class="text"><slot name="suffix"></slot></span> - <i class="ti ti-chevron-right icon"></i> + <MkA v-else :class="[$style.main, { [$style.active]: active }]" class="_button" :to="to" :behavior="behavior"> + <span :class="$style.icon"><slot name="icon"></slot></span> + <span :class="$style.text"><slot></slot></span> + <span :class="$style.suffix"> + <span :class="$style.suffixText"><slot name="suffix"></slot></span> + <i class="ti ti-chevron-right"></i> </span> </MkA> </div> @@ -26,70 +26,70 @@ const props = defineProps<{ to: string; active?: boolean; external?: boolean; - behavior?: null | 'window' | 'browser' | 'modalWindow'; + behavior?: null | 'window' | 'browser'; inline?: boolean; }>(); </script> -<style lang="scss" scoped> -.ffcbddfc { +<style lang="scss" module> +.root { display: block; &.inline { display: inline-block; } +} - > .main { - display: flex; - align-items: center; - width: 100%; - box-sizing: border-box; - padding: 10px 14px; - background: var(--buttonBg); - border-radius: 6px; - font-size: 0.9em; +.main { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + padding: 10px 14px; + background: var(--buttonBg); + border-radius: 6px; + font-size: 0.9em; - &:hover { - text-decoration: none; - background: var(--buttonHoverBg); - } + &:hover { + text-decoration: none; + background: var(--buttonHoverBg); + } - &.active { - color: var(--accent); - background: var(--buttonHoverBg); - } + &.active { + color: var(--accent); + background: var(--buttonHoverBg); + } +} - > .icon { - margin-right: 0.75em; - flex-shrink: 0; - text-align: center; - color: var(--fgTransparentWeak); +.icon { + margin-right: 0.75em; + flex-shrink: 0; + text-align: center; + color: var(--fgTransparentWeak); - &:empty { - display: none; + &:empty { + display: none; - & + .text { - padding-left: 4px; - } - } + & + .text { + padding-left: 4px; } + } +} - > .text { - flex-shrink: 1; - white-space: normal; - padding-right: 12px; - text-align: center; - } +.text { + flex-shrink: 1; + white-space: normal; + padding-right: 12px; + text-align: center; +} - > .right { - margin-left: auto; - opacity: 0.7; - white-space: nowrap; +.suffix { + margin-left: auto; + opacity: 0.7; + white-space: nowrap; - > .text:not(:empty) { - margin-right: 0.75em; - } - } + > .suffixText:not(:empty) { + margin-right: 0.75em; } } </style> diff --git a/packages/frontend/src/components/form/slot.vue b/packages/frontend/src/components/form/slot.vue index 79ce8fe51fc0d6a444108f77a9a1973a91349acb..809d80620fbab60e7eb6c6f07e9a66ef1646c099 100644 --- a/packages/frontend/src/components/form/slot.vue +++ b/packages/frontend/src/components/form/slot.vue @@ -1,10 +1,10 @@ <template> -<div class="adhpbeou"> - <div class="label" @click="focus"><slot name="label"></slot></div> - <div class="content"> +<div> + <div :class="$style.label" @click="focus"><slot name="label"></slot></div> + <div> <slot></slot> </div> - <div class="caption"><slot name="caption"></slot></div> + <div :class="$style.caption"><slot name="caption"></slot></div> </div> </template> @@ -16,26 +16,24 @@ function focus() { } </script> -<style lang="scss" scoped> -.adhpbeou { - > .label { - font-size: 0.85em; - padding: 0 0 8px 0; - user-select: none; +<style lang="scss" module> +.label { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; - &:empty { - display: none; - } + &:empty { + display: none; } +} - > .caption { - font-size: 0.85em; - padding: 8px 0 0 0; - color: var(--fgTransparentWeak); +.caption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); - &:empty { - display: none; - } + &:empty { + display: none; } } </style> diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue index 3a44c3da3d17e7db36a67a802fdb14ae02787545..b3d8c22b27d30936e7ae47924489f2f4482bcace 100644 --- a/packages/frontend/src/components/form/suspense.vue +++ b/packages/frontend/src/components/form/suspense.vue @@ -1,102 +1,66 @@ <template> -<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> - <div v-if="pending"> - <MkLoading/> +<div v-if="pending"> + <MkLoading/> +</div> +<div v-else-if="resolved"> + <slot :result="result"></slot> +</div> +<div v-else> + <div :class="$style.error"> + <div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div> + <MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton> </div> - <div v-else-if="resolved"> - <slot :result="result"></slot> - </div> - <div v-else> - <div class="wszdbhzo"> - <div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div> - <MkButton inline class="retry" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton> - </div> - </div> -</Transition> +</div> </template> -<script lang="ts"> -import { defineComponent, PropType, ref, watch } from 'vue'; +<script lang="ts" setup> +import { ref, watch } from 'vue'; import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, - - props: { - p: { - type: Function as PropType<() => Promise<any>>, - required: true, - }, - }, +const props = defineProps<{ + p: () => Promise<any>; +}>(); - setup(props, context) { - const pending = ref(true); - const resolved = ref(false); - const rejected = ref(false); - const result = ref(null); +const pending = ref(true); +const resolved = ref(false); +const rejected = ref(false); +const result = ref(null); - const process = () => { - if (props.p == null) { - return; - } - const promise = props.p(); - pending.value = true; - resolved.value = false; - rejected.value = false; - promise.then((_result) => { - pending.value = false; - resolved.value = true; - result.value = _result; - }); - promise.catch(() => { - pending.value = false; - rejected.value = true; - }); - }; - - watch(() => props.p, () => { - process(); - }, { - immediate: true, - }); - - const retry = () => { - process(); - }; +const process = () => { + if (props.p == null) { + return; + } + const promise = props.p(); + pending.value = true; + resolved.value = false; + rejected.value = false; + promise.then((_result) => { + pending.value = false; + resolved.value = true; + result.value = _result; + }); + promise.catch(() => { + pending.value = false; + rejected.value = true; + }); +}; - return { - pending, - resolved, - rejected, - result, - retry, - defaultStore, - i18n, - }; - }, +watch(() => props.p, () => { + process(); +}, { + immediate: true, }); -</script> -<style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} +const retry = () => { + process(); +}; +</script> -.wszdbhzo { +<style lang="scss" module> +.error { padding: 16px; text-align: center; - - > .retry { - margin-top: 16px; - } } </style> diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 40d134dffb33a3cc65130f19cf4e6c8dd9048742..4e608c6efe995198038e5283a5685e54806ba1d2 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -15,7 +15,7 @@ import { useRouter } from '@/router'; const props = withDefaults(defineProps<{ to: string; activeClass?: null | string; - behavior?: null | 'window' | 'browser' | 'modalWindow'; + behavior?: null | 'window' | 'browser'; }>(), { activeClass: null, behavior: null, @@ -70,14 +70,6 @@ function openWindow() { os.pageWindow(props.to); } -function modalWindow() { - os.modalPageWindow(props.to); -} - -function popout() { - popout_(props.to); -} - function nav(ev: MouseEvent) { if (props.behavior === 'browser') { location.href = props.to; @@ -87,8 +79,6 @@ function nav(ev: MouseEvent) { if (props.behavior) { if (props.behavior === 'window') { return openWindow(); - } else if (props.behavior === 'modalWindow') { - return modalWindow(); } } diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index 59358aef70c0aa3dc9a70d9b5bc40c3f1ee77abe..f93659f5ed6f6c7b1ddcd4d81c802e6df00ac5c1 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -1,5 +1,5 @@ <template> -<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :min-scale="2 / 3"> +<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :minScale="2 / 3"> <span>@{{ user.username }}</span> <span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span> </MkCondensedLine> diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index aa975600f041170a651d819e0cace1d52e3c8b0f..8b25ab1b6a637f3fbdecd9f792f8a691e470a9b0 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -1,6 +1,14 @@ <template> <div v-if="chosen && !shouldHide" :class="$style.root"> - <div v-if="!showMenu" :class="[$style.main, $style['form_' + chosen.place]]"> + <div + v-if="!showMenu" + :class="[$style.main, { + [$style.form_square]: chosen.place === 'square', + [$style.form_horizontal]: chosen.place === 'horizontal', + [$style.form_horizontalBig]: chosen.place === 'horizontal-big', + [$style.form_vertical]: chosen.place === 'vertical', + }]" + > <a :href="chosen.url" target="_blank" :class="$style.link"> <img :src="chosen.imageUrl" :class="$style.img"> <button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button> @@ -122,7 +130,7 @@ function reduceFrequency(): void { } } - &.form_horizontal-big { + &.form_horizontalBig { padding: 8px; > .link, diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 42abdcbdccfc879c2a49410bcb60eb34e734ae15..422b35c9ddfe674cb78f780762a420d4982032fb 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -1,6 +1,6 @@ <template> <component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> - <img :class="$style.inner" :src="url" decoding="async"/> + <MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true"/> <MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/> <div v-if="user.isCat" :class="[$style.ears]"> <div :class="$style.earLeft"> @@ -24,6 +24,7 @@ <script lang="ts" setup> import { watch } from 'vue'; import * as misskey from 'misskey-js'; +import MkImgWithBlurhash from '../MkImgWithBlurhash.vue'; import MkA from './MkA.vue'; import { getStaticImageUrl } from '@/scripts/media-proxy'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue index 1d46ff1ec9f355e0a2110ade6ae2382fe3f5df4b..4b2e8e4750762c14712f7dc6065a3ffee2b89176 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.vue +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -13,13 +13,20 @@ interface Props { const contentSymbol = Symbol(); const observer = new ResizeObserver((entries) => { + const results: { + container: HTMLSpanElement; + transform: string; + }[] = []; for (const entry of entries) { const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement; const props: Required<Props> = content[contentSymbol]; const container = content.parentElement as HTMLSpanElement; const contentWidth = content.getBoundingClientRect().width; const containerWidth = container.getBoundingClientRect().width; - container.style.transform = `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})`; + results.push({ container, transform: `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})` }); + } + for (const result of results) { + result.container.style.transform = result.transform; } }); </script> diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index 0cb31ffcba5ce5d47e7a1290963c3da166d00a30..e8a7f17cc6af0ed79a93d2fa688750ff97fae8e9 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -7,7 +7,7 @@ import { computed } from 'vue'; import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy'; import { defaultStore } from '@/store'; -import { customEmojis } from '@/custom-emojis'; +import { customEmojisMap } from '@/custom-emojis'; const props = defineProps<{ name: string; @@ -26,7 +26,7 @@ const rawUrl = computed(() => { return props.url; } if (isLocal.value) { - return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null; + return customEmojisMap.get(customEmojiName.value)?.url ?? null; } return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; }); diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts index f6811b67475ece7404088ee8f08130b4811bcea9..685b3b8b8e6186ded648322d61dd94191880675a 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.vue'; import { within } from '@storybook/testing-library'; import { expect } from '@storybook/jest'; +import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.ts'; export const Default = { render(args) { return { diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a50a34390b243785fcc64247d17ea4a8843ba02 --- /dev/null +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -0,0 +1,367 @@ +import { VNode, h } from 'vue'; +import * as mfm from 'mfm-js'; +import * as Misskey from 'misskey-js'; +import MkUrl from '@/components/global/MkUrl.vue'; +import MkLink from '@/components/MkLink.vue'; +import MkMention from '@/components/MkMention.vue'; +import MkEmoji from '@/components/global/MkEmoji.vue'; +import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; +import MkCode from '@/components/MkCode.vue'; +import MkGoogle from '@/components/MkGoogle.vue'; +import MkSparkle from '@/components/MkSparkle.vue'; +import MkA from '@/components/global/MkA.vue'; +import { host } from '@/config'; +import { defaultStore } from '@/store'; + +const QUOTE_STYLE = ` +display: block; +margin: 8px; +padding: 6px 0 6px 12px; +color: var(--fg); +border-left: solid 3px var(--fg); +opacity: 0.7; +`.split('\n').join(' '); + +export default function(props: { + text: string; + plain?: boolean; + nowrap?: boolean; + author?: Misskey.entities.UserLite; + i?: Misskey.entities.UserLite; + isNote?: boolean; + emojiUrls?: string[]; + rootScale?: number; +}) { + const isNote = props.isNote !== undefined ? props.isNote : true; + + if (props.text == null || props.text === '') return; + + const ast = (props.plain ? mfm.parseSimple : mfm.parse)(props.text); + + const validTime = (t: string | null | undefined) => { + if (t == null) return null; + return t.match(/^[0-9.]+s$/) ? t : null; + }; + + const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm; + + /** + * Gen Vue Elements from MFM AST + * @param ast MFM AST + * @param scale How times large the text is + */ + const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => { + switch (token.type) { + case 'text': { + const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); + + if (!props.plain) { + const res: (VNode | string)[] = []; + for (const t of text.split('\n')) { + res.push(h('br')); + res.push(t); + } + res.shift(); + return res; + } else { + return [text.replace(/\n/g, ' ')]; + } + } + + case 'bold': { + return [h('b', genEl(token.children, scale))]; + } + + case 'strike': { + return [h('del', genEl(token.children, scale))]; + } + + case 'italic': { + return h('i', { + style: 'font-style: oblique;', + }, genEl(token.children, scale)); + } + + case 'fn': { + // TODO: CSSã‚’æ–‡å—列ã§çµ„ã¿ç«‹ã¦ã¦ã„ã㨠token.props.args.~~~ 経由ã§CSSインジェクションã§ãã‚‹ã®ã§ã‚ˆã—ãªã«ã‚„ã‚‹ + let style; + switch (token.props.name) { + case 'tada': { + const speed = validTime(token.props.args.speed) ?? '1s'; + style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both;` : ''); + break; + } + case 'jelly': { + const speed = validTime(token.props.args.speed) ?? '1s'; + style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both;` : ''); + break; + } + case 'twitch': { + const speed = validTime(token.props.args.speed) ?? '0.5s'; + style = useAnim ? `animation: mfm-twitch ${speed} ease infinite;` : ''; + break; + } + case 'shake': { + const speed = validTime(token.props.args.speed) ?? '0.5s'; + style = useAnim ? `animation: mfm-shake ${speed} ease infinite;` : ''; + break; + } + case 'spin': { + const direction = + token.props.args.left ? 'reverse' : + token.props.args.alternate ? 'alternate' : + 'normal'; + const anime = + token.props.args.x ? 'mfm-spinX' : + token.props.args.y ? 'mfm-spinY' : + 'mfm-spin'; + const speed = validTime(token.props.args.speed) ?? '1.5s'; + style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : ''; + break; + } + case 'jump': { + const speed = validTime(token.props.args.speed) ?? '0.75s'; + style = useAnim ? `animation: mfm-jump ${speed} linear infinite;` : ''; + break; + } + case 'bounce': { + const speed = validTime(token.props.args.speed) ?? '0.75s'; + style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : ''; + break; + } + case 'flip': { + const transform = + (token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' : + token.props.args.v ? 'scaleY(-1)' : + 'scaleX(-1)'; + style = `transform: ${transform};`; + break; + } + case 'x2': { + return h('span', { + class: defaultStore.state.advancedMfm ? 'mfm-x2' : '', + }, genEl(token.children, scale * 2)); + } + case 'x3': { + return h('span', { + class: defaultStore.state.advancedMfm ? 'mfm-x3' : '', + }, genEl(token.children, scale * 3)); + } + case 'x4': { + return h('span', { + class: defaultStore.state.advancedMfm ? 'mfm-x4' : '', + }, genEl(token.children, scale * 4)); + } + case 'font': { + const family = + token.props.args.serif ? 'serif' : + token.props.args.monospace ? 'monospace' : + token.props.args.cursive ? 'cursive' : + token.props.args.fantasy ? 'fantasy' : + token.props.args.emoji ? 'emoji' : + token.props.args.math ? 'math' : + null; + if (family) style = `font-family: ${family};`; + break; + } + case 'blur': { + return h('span', { + class: '_mfm_blur_', + }, genEl(token.children, scale)); + } + case 'rainbow': { + const speed = validTime(token.props.args.speed) ?? '1s'; + style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : ''; + break; + } + case 'sparkle': { + if (!useAnim) { + return genEl(token.children, scale); + } + return h(MkSparkle, {}, genEl(token.children, scale)); + } + case 'rotate': { + const degrees = parseFloat(token.props.args.deg ?? '90'); + style = `transform: rotate(${degrees}deg); transform-origin: center center;`; + break; + } + case 'position': { + if (!defaultStore.state.advancedMfm) break; + const x = parseFloat(token.props.args.x ?? '0'); + const y = parseFloat(token.props.args.y ?? '0'); + style = `transform: translateX(${x}em) translateY(${y}em);`; + break; + } + case 'scale': { + if (!defaultStore.state.advancedMfm) { + style = ''; + break; + } + const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5); + const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5); + style = `transform: scale(${x}, ${y});`; + scale = scale * Math.max(x, y); + break; + } + case 'fg': { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + style = `color: #${color};`; + break; + } + case 'bg': { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + style = `background-color: #${color};`; + break; + } + } + if (style == null) { + return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); + } else { + return h('span', { + style: 'display: inline-block; ' + style, + }, genEl(token.children, scale)); + } + } + + case 'small': { + return [h('small', { + style: 'opacity: 0.7;', + }, genEl(token.children, scale))]; + } + + case 'center': { + return [h('div', { + style: 'text-align:center;', + }, genEl(token.children, scale))]; + } + + case 'url': { + return [h(MkUrl, { + key: Math.random(), + url: token.props.url, + rel: 'nofollow noopener', + })]; + } + + case 'link': { + return [h(MkLink, { + key: Math.random(), + url: token.props.url, + rel: 'nofollow noopener', + }, genEl(token.children, scale))]; + } + + case 'mention': { + return [h(MkMention, { + key: Math.random(), + host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) || host, + username: token.props.username, + })]; + } + + case 'hashtag': { + return [h(MkA, { + key: Math.random(), + to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`, + style: 'color:var(--hashtag);', + }, `#${token.props.hashtag}`)]; + } + + case 'blockCode': { + return [h(MkCode, { + key: Math.random(), + code: token.props.code, + lang: token.props.lang, + })]; + } + + case 'inlineCode': { + return [h(MkCode, { + key: Math.random(), + code: token.props.code, + inline: true, + })]; + } + + case 'quote': { + if (!props.nowrap) { + return [h('div', { + style: QUOTE_STYLE, + }, genEl(token.children, scale))]; + } else { + return [h('span', { + style: QUOTE_STYLE, + }, genEl(token.children, scale))]; + } + } + + case 'emojiCode': { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (props.author?.host == null) { + return [h(MkCustomEmoji, { + key: Math.random(), + name: token.props.name, + normal: props.plain, + host: null, + useOriginalSize: scale >= 2.5, + })]; + } else { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (props.emojiUrls && (props.emojiUrls[token.props.name] == null)) { + return [h('span', `:${token.props.name}:`)]; + } else { + return [h(MkCustomEmoji, { + key: Math.random(), + name: token.props.name, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + url: props.emojiUrls ? props.emojiUrls[token.props.name] : null, + normal: props.plain, + host: props.author.host, + useOriginalSize: scale >= 2.5, + })]; + } + } + } + + case 'unicodeEmoji': { + return [h(MkEmoji, { + key: Math.random(), + emoji: token.props.emoji, + })]; + } + + case 'mathInline': { + return [h('code', token.props.formula)]; + } + + case 'mathBlock': { + return [h('code', token.props.formula)]; + } + + case 'search': { + return [h(MkGoogle, { + key: Math.random(), + q: token.props.query, + })]; + } + + case 'plain': { + return [h('span', genEl(token.children, scale))]; + } + + default: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + console.error('unrecognized ast type:', (token as any).type); + + return []; + } + } + }).flat(Infinity) as (VNode | string)[]; + + return h('span', { + // https://codeday.me/jp/qa/20190424/690106.html + style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;', + }, genEl(ast, props.rootScale ?? 1)); +} diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue deleted file mode 100644 index 28a0d1c9861c9c9b94b0320d3726114983dfa0e1..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue +++ /dev/null @@ -1,171 +0,0 @@ -<template> -<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :is-note="isNote" :class="[$style.root, { [$style.nowrap]: nowrap }]"/> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import MfmCore from '@/components/mfm'; - -const props = withDefaults(defineProps<{ - text: string; - plain?: boolean; - nowrap?: boolean; - author?: any; - isNote?: boolean; -}>(), { - plain: false, - nowrap: false, - author: null, - isNote: true, -}); -</script> - -<style lang="scss"> -._mfm_blur_ { - filter: blur(6px); - transition: filter 0.3s; - - &:hover { - filter: blur(0px); - } -} - -.mfm-x2 { - --mfm-zoom-size: 200%; -} - -.mfm-x3 { - --mfm-zoom-size: 400%; -} - -.mfm-x4 { - --mfm-zoom-size: 600%; -} - -.mfm-x2, .mfm-x3, .mfm-x4 { - font-size: var(--mfm-zoom-size); - - .mfm-x2, .mfm-x3, .mfm-x4 { - /* only half effective */ - font-size: calc(var(--mfm-zoom-size) / 2 + 50%); - - .mfm-x2, .mfm-x3, .mfm-x4 { - /* disabled */ - font-size: 100%; - } - } -} - -@keyframes mfm-spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -@keyframes mfm-spinX { - 0% { transform: perspective(128px) rotateX(0deg); } - 100% { transform: perspective(128px) rotateX(360deg); } -} - -@keyframes mfm-spinY { - 0% { transform: perspective(128px) rotateY(0deg); } - 100% { transform: perspective(128px) rotateY(360deg); } -} - -@keyframes mfm-jump { - 0% { transform: translateY(0); } - 25% { transform: translateY(-16px); } - 50% { transform: translateY(0); } - 75% { transform: translateY(-8px); } - 100% { transform: translateY(0); } -} - -@keyframes mfm-bounce { - 0% { transform: translateY(0) scale(1, 1); } - 25% { transform: translateY(-16px) scale(1, 1); } - 50% { transform: translateY(0) scale(1, 1); } - 75% { transform: translateY(0) scale(1.5, 0.75); } - 100% { transform: translateY(0) scale(1, 1); } -} - -// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`; -// let css = ''; -// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } -@keyframes mfm-twitch { - 0% { transform: translate(7px, -2px) } - 5% { transform: translate(-3px, 1px) } - 10% { transform: translate(-7px, -1px) } - 15% { transform: translate(0px, -1px) } - 20% { transform: translate(-8px, 6px) } - 25% { transform: translate(-4px, -3px) } - 30% { transform: translate(-4px, -6px) } - 35% { transform: translate(-8px, -8px) } - 40% { transform: translate(4px, 6px) } - 45% { transform: translate(-3px, 1px) } - 50% { transform: translate(2px, -10px) } - 55% { transform: translate(-7px, 0px) } - 60% { transform: translate(-2px, 4px) } - 65% { transform: translate(3px, -8px) } - 70% { transform: translate(6px, 7px) } - 75% { transform: translate(-7px, -2px) } - 80% { transform: translate(-7px, -8px) } - 85% { transform: translate(9px, 3px) } - 90% { transform: translate(-3px, -2px) } - 95% { transform: translate(-10px, 2px) } - 100% { transform: translate(-2px, -6px) } -} - -// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`; -// let css = ''; -// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } -@keyframes mfm-shake { - 0% { transform: translate(-3px, -1px) rotate(-8deg) } - 5% { transform: translate(0px, -1px) rotate(-10deg) } - 10% { transform: translate(1px, -3px) rotate(0deg) } - 15% { transform: translate(1px, 1px) rotate(11deg) } - 20% { transform: translate(-2px, 1px) rotate(1deg) } - 25% { transform: translate(-1px, -2px) rotate(-2deg) } - 30% { transform: translate(-1px, 2px) rotate(-3deg) } - 35% { transform: translate(2px, 1px) rotate(6deg) } - 40% { transform: translate(-2px, -3px) rotate(-9deg) } - 45% { transform: translate(0px, -1px) rotate(-12deg) } - 50% { transform: translate(1px, 2px) rotate(10deg) } - 55% { transform: translate(0px, -3px) rotate(8deg) } - 60% { transform: translate(1px, -1px) rotate(8deg) } - 65% { transform: translate(0px, -1px) rotate(-7deg) } - 70% { transform: translate(-1px, -3px) rotate(6deg) } - 75% { transform: translate(0px, -2px) rotate(4deg) } - 80% { transform: translate(-2px, -1px) rotate(3deg) } - 85% { transform: translate(1px, -3px) rotate(-10deg) } - 90% { transform: translate(1px, 0px) rotate(3deg) } - 95% { transform: translate(-2px, 0px) rotate(-3deg) } - 100% { transform: translate(2px, 1px) rotate(2deg) } -} - -@keyframes mfm-rubberBand { - from { transform: scale3d(1, 1, 1); } - 30% { transform: scale3d(1.25, 0.75, 1); } - 40% { transform: scale3d(0.75, 1.25, 1); } - 50% { transform: scale3d(1.15, 0.85, 1); } - 65% { transform: scale3d(0.95, 1.05, 1); } - 75% { transform: scale3d(1.05, 0.95, 1); } - to { transform: scale3d(1, 1, 1); } -} - -@keyframes mfm-rainbow { - 0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } - 100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } -} -</style> - -<style lang="scss" module> -.root { - white-space: pre-wrap; - - &.nowrap { - white-space: pre; - word-wrap: normal; // https://codeday.me/jp/qa/20190424/690106.html - overflow: hidden; - text-overflow: ellipsis; - } -} -</style> diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index 9e1da64e61bb7e0e13ce14224ff3e0e2f4739f07..d71343baf9f9a6ea499319954429c53ca39f6600 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -15,8 +15,8 @@ {{ t.title }} </div> <Transition - v-else mode="in-out" @enter="enter" @after-enter="afterEnter" @leave="leave" - @after-leave="afterLeave" + v-else mode="in-out" @enter="enter" @afterEnter="afterEnter" @leave="leave" + @afterLeave="afterLeave" > <div v-show="t.key === tab" :class="[$style.tabTitle, $style.animate]">{{ t.title }}</div> </Transition> diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index b91d378b17ee76d64c1b792d47fbc365bf018b05..0a21d39bcad9c95595fff6dce93f204218e6fa68 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -21,7 +21,7 @@ </div> </div> </div> - <XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :root-el="el" @update:tab="key => emit('update:tab', key)" @tab-click="onTabClick"/> + <XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :rootEl="el" @update:tab="key => emit('update:tab', key)" @tabClick="onTabClick"/> </template> <div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight"> <template v-for="action in actions"> @@ -30,7 +30,7 @@ </div> </div> <div v-if="(narrow && !hideTitle) && hasTabs" :class="[$style.lower, { [$style.slim]: narrow, [$style.thin]: thin_ }]"> - <XTabs :class="$style.tabs" :tab="tab" :tabs="tabs" :root-el="el" @update:tab="key => emit('update:tab', key)" @tab-click="onTabClick"/> + <XTabs :class="$style.tabs" :tab="tab" :tabs="tabs" :rootEl="el" @update:tab="key => emit('update:tab', key)" @tabClick="onTabClick"/> </div> </div> </template> diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue index 44c02088dab49351da3cd2bafa4d06c4e1b98212..e5dba54b4e747f04b5d0eaafcd94b4708049b4d8 100644 --- a/packages/frontend/src/components/global/MkStickyContainer.vue +++ b/packages/frontend/src/components/global/MkStickyContainer.vue @@ -14,6 +14,7 @@ <script lang="ts" setup> import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue'; +import { $$ } from 'vue/macros'; import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const'; const rootEl = $shallowRef<HTMLElement>(); @@ -83,8 +84,8 @@ onMounted(() => { onUnmounted(() => { observer.disconnect(); }); -</script> - -<style lang="scss" module> -</style> +defineExpose({ + rootEl: $$(rootEl), +}); +</script> diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 261cc0ee18ca2f93c7ac927d1aa2cb5f4af8d0ea..dfc3c89798b6b4837a35a06207b1c81ea7a9434f 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -58,7 +58,6 @@ function tick() { if (props.mode === 'relative' || props.mode === 'detail') { tick(); - onUnmounted(() => { window.clearTimeout(tickId); }); diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index 2a9278030652b9b1ad78ff750341e0ee2ab65a27..c1efd9a06b8582995eb377cbe7eaa5ffa6dcb701 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -6,7 +6,7 @@ <template v-if="!self"> <span :class="$style.schema">{{ schema }}//</span> <span :class="$style.hostname">{{ hostname }}</span> - <span v-if="port != ''" :class="$style.port">:{{ port }}</span> + <span v-if="port != ''">:{{ port }}</span> </template> <template v-if="pathname === '/' && self"> <span :class="$style.self">{{ hostname }}</span> diff --git a/packages/frontend/src/components/global/MkUserName.vue b/packages/frontend/src/components/global/MkUserName.vue index 4186a4a4fb4c0b14fd024abe78f4dabbf3d8ad0f..c9e85c54600350b97ec659e49773c808bfc76459 100644 --- a/packages/frontend/src/components/global/MkUserName.vue +++ b/packages/frontend/src/components/global/MkUserName.vue @@ -1,5 +1,5 @@ <template> -<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emoji-urls="user.emojis"/> +<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/> </template> <script lang="ts" setup> diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts index 1fd293ba10755e33ce42c92fd1272f69bebfec04..2708b759aa842277124fda3414db43695a2b5b41 100644 --- a/packages/frontend/src/components/global/i18n.ts +++ b/packages/frontend/src/components/global/i18n.ts @@ -1,42 +1,24 @@ -import { h, defineComponent } from 'vue'; +import { h } from 'vue'; -export default defineComponent({ - props: { - src: { - type: String, - required: true, - }, - tag: { - type: String, - required: false, - default: 'span', - }, - textTag: { - type: String, - required: false, - default: null, - }, - }, - render() { - let str = this.src; - const parsed = [] as (string | { arg: string; })[]; - while (true) { - const nextBracketOpen = str.indexOf('{'); - const nextBracketClose = str.indexOf('}'); +export default function(props: { src: string; tag?: string; textTag?: string; }, { slots }) { + let str = props.src; + const parsed = [] as (string | { arg: string; })[]; + while (true) { + const nextBracketOpen = str.indexOf('{'); + const nextBracketClose = str.indexOf('}'); - if (nextBracketOpen === -1) { - parsed.push(str); - break; - } else { - if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen)); - parsed.push({ - arg: str.substring(nextBracketOpen + 1, nextBracketClose), - }); - } - - str = str.substr(nextBracketClose + 1); + if (nextBracketOpen === -1) { + parsed.push(str); + break; + } else { + if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen)); + parsed.push({ + arg: str.substring(nextBracketOpen + 1, nextBracketClose), + }); } - return h(this.tag, parsed.map(x => typeof x === 'string' ? (this.textTag ? h(this.textTag, x) : x) : this.$slots[x.arg]())); - }, -}); + str = str.substr(nextBracketClose + 1); + } + + return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]())); +} diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 4ef8111da910dea910c82d6ce5e9ebf31d143848..ee2a2bc7bd14b3310192bdc9b106d08c3e338740 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -1,6 +1,6 @@ import { App } from 'vue'; -import Mfm from './global/MkMisskeyFlavoredMarkdown.vue'; +import Mfm from './global/MkMisskeyFlavoredMarkdown.ts'; import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; diff --git a/packages/frontend/src/components/mfm.ts b/packages/frontend/src/components/mfm.ts deleted file mode 100644 index c3c07b583416a3f1ea4d5cb7061cf2c04703bb40..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/mfm.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { VNode, defineComponent, h } from 'vue'; -import * as mfm from 'mfm-js'; -import MkUrl from '@/components/global/MkUrl.vue'; -import MkLink from '@/components/MkLink.vue'; -import MkMention from '@/components/MkMention.vue'; -import MkEmoji from '@/components/global/MkEmoji.vue'; -import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; -import MkCode from '@/components/MkCode.vue'; -import MkGoogle from '@/components/MkGoogle.vue'; -import MkSparkle from '@/components/MkSparkle.vue'; -import MkA from '@/components/global/MkA.vue'; -import { host } from '@/config'; -import { defaultStore } from '@/store'; - -const QUOTE_STYLE = ` -display: block; -margin: 8px; -padding: 6px 0 6px 12px; -color: var(--fg); -border-left: solid 3px var(--fg); -opacity: 0.7; -`.split('\n').join(' '); - -export default defineComponent({ - props: { - text: { - type: String, - required: true, - }, - plain: { - type: Boolean, - default: false, - }, - nowrap: { - type: Boolean, - default: false, - }, - author: { - type: Object, - default: null, - }, - i: { - type: Object, - default: null, - }, - isNote: { - type: Boolean, - default: true, - }, - emojiUrls: { - type: Object, - default: null, - }, - rootScale: { - type: Number, - default: 1, - } - }, - - render() { - if (this.text == null || this.text === '') return; - - const ast = (this.plain ? mfm.parseSimple : mfm.parse)(this.text); - - const validTime = (t: string | null | undefined) => { - if (t == null) return null; - return t.match(/^[0-9.]+s$/) ? t : null; - }; - - const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm; - - /** - * Gen Vue Elements from MFM AST - * @param ast MFM AST - * @param scale How times large the text is - */ - const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => { - switch (token.type) { - case 'text': { - const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); - - if (!this.plain) { - const res: (VNode | string)[] = []; - for (const t of text.split('\n')) { - res.push(h('br')); - res.push(t); - } - res.shift(); - return res; - } else { - return [text.replace(/\n/g, ' ')]; - } - } - - case 'bold': { - return [h('b', genEl(token.children, scale))]; - } - - case 'strike': { - return [h('del', genEl(token.children, scale))]; - } - - case 'italic': { - return h('i', { - style: 'font-style: oblique;', - }, genEl(token.children, scale)); - } - - case 'fn': { - // TODO: CSSã‚’æ–‡å—列ã§çµ„ã¿ç«‹ã¦ã¦ã„ã㨠token.props.args.~~~ 経由ã§CSSインジェクションã§ãã‚‹ã®ã§ã‚ˆã—ãªã«ã‚„ã‚‹ - let style; - switch (token.props.name) { - case 'tada': { - const speed = validTime(token.props.args.speed) ?? '1s'; - style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both;` : ''); - break; - } - case 'jelly': { - const speed = validTime(token.props.args.speed) ?? '1s'; - style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both;` : ''); - break; - } - case 'twitch': { - const speed = validTime(token.props.args.speed) ?? '0.5s'; - style = useAnim ? `animation: mfm-twitch ${speed} ease infinite;` : ''; - break; - } - case 'shake': { - const speed = validTime(token.props.args.speed) ?? '0.5s'; - style = useAnim ? `animation: mfm-shake ${speed} ease infinite;` : ''; - break; - } - case 'spin': { - const direction = - token.props.args.left ? 'reverse' : - token.props.args.alternate ? 'alternate' : - 'normal'; - const anime = - token.props.args.x ? 'mfm-spinX' : - token.props.args.y ? 'mfm-spinY' : - 'mfm-spin'; - const speed = validTime(token.props.args.speed) ?? '1.5s'; - style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : ''; - break; - } - case 'jump': { - const speed = validTime(token.props.args.speed) ?? '0.75s'; - style = useAnim ? `animation: mfm-jump ${speed} linear infinite;` : ''; - break; - } - case 'bounce': { - const speed = validTime(token.props.args.speed) ?? '0.75s'; - style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : ''; - break; - } - case 'flip': { - const transform = - (token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' : - token.props.args.v ? 'scaleY(-1)' : - 'scaleX(-1)'; - style = `transform: ${transform};`; - break; - } - case 'x2': { - return h('span', { - class: defaultStore.state.advancedMfm ? 'mfm-x2' : '', - }, genEl(token.children, scale * 2)); - } - case 'x3': { - return h('span', { - class: defaultStore.state.advancedMfm ? 'mfm-x3' : '', - }, genEl(token.children, scale * 3)); - } - case 'x4': { - return h('span', { - class: defaultStore.state.advancedMfm ? 'mfm-x4' : '', - }, genEl(token.children, scale * 4)); - } - case 'font': { - const family = - token.props.args.serif ? 'serif' : - token.props.args.monospace ? 'monospace' : - token.props.args.cursive ? 'cursive' : - token.props.args.fantasy ? 'fantasy' : - token.props.args.emoji ? 'emoji' : - token.props.args.math ? 'math' : - null; - if (family) style = `font-family: ${family};`; - break; - } - case 'blur': { - return h('span', { - class: '_mfm_blur_', - }, genEl(token.children, scale)); - } - case 'rainbow': { - const speed = validTime(token.props.args.speed) ?? '1s'; - style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : ''; - break; - } - case 'sparkle': { - if (!useAnim) { - return genEl(token.children, scale); - } - return h(MkSparkle, {}, genEl(token.children, scale)); - } - case 'rotate': { - const degrees = parseFloat(token.props.args.deg ?? '90'); - style = `transform: rotate(${degrees}deg); transform-origin: center center;`; - break; - } - case 'position': { - if (!defaultStore.state.advancedMfm) break; - const x = parseFloat(token.props.args.x ?? '0'); - const y = parseFloat(token.props.args.y ?? '0'); - style = `transform: translateX(${x}em) translateY(${y}em);`; - break; - } - case 'scale': { - if (!defaultStore.state.advancedMfm) { - style = ''; - break; - } - const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5); - const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5); - style = `transform: scale(${x}, ${y});`; - scale = scale * Math.max(x, y); - break; - } - case 'fg': { - let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; - style = `color: #${color};`; - break; - } - case 'bg': { - let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; - style = `background-color: #${color};`; - break; - } - } - if (style == null) { - return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); - } else { - return h('span', { - style: 'display: inline-block; ' + style, - }, genEl(token.children, scale)); - } - } - - case 'small': { - return [h('small', { - style: 'opacity: 0.7;', - }, genEl(token.children, scale))]; - } - - case 'center': { - return [h('div', { - style: 'text-align:center;', - }, genEl(token.children, scale))]; - } - - case 'url': { - return [h(MkUrl, { - key: Math.random(), - url: token.props.url, - rel: 'nofollow noopener', - })]; - } - - case 'link': { - return [h(MkLink, { - key: Math.random(), - url: token.props.url, - rel: 'nofollow noopener', - }, genEl(token.children, scale))]; - } - - case 'mention': { - return [h(MkMention, { - key: Math.random(), - host: (token.props.host == null && this.author && this.author.host != null ? this.author.host : token.props.host) || host, - username: token.props.username, - })]; - } - - case 'hashtag': { - return [h(MkA, { - key: Math.random(), - to: this.isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`, - style: 'color:var(--hashtag);', - }, `#${token.props.hashtag}`)]; - } - - case 'blockCode': { - return [h(MkCode, { - key: Math.random(), - code: token.props.code, - lang: token.props.lang, - })]; - } - - case 'inlineCode': { - return [h(MkCode, { - key: Math.random(), - code: token.props.code, - inline: true, - })]; - } - - case 'quote': { - if (!this.nowrap) { - return [h('div', { - style: QUOTE_STYLE, - }, genEl(token.children, scale))]; - } else { - return [h('span', { - style: QUOTE_STYLE, - }, genEl(token.children, scale))]; - } - } - - case 'emojiCode': { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.author?.host == null) { - return [h(MkCustomEmoji, { - key: Math.random(), - name: token.props.name, - normal: this.plain, - host: null, - useOriginalSize: scale >= 2.5, - })]; - } else { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.emojiUrls && (this.emojiUrls[token.props.name] == null)) { - return [h('span', `:${token.props.name}:`)]; - } else { - return [h(MkCustomEmoji, { - key: Math.random(), - name: token.props.name, - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - url: this.emojiUrls ? this.emojiUrls[token.props.name] : null, - normal: this.plain, - host: this.author.host, - useOriginalSize: scale >= 2.5, - })]; - } - } - } - - case 'unicodeEmoji': { - return [h(MkEmoji, { - key: Math.random(), - emoji: token.props.emoji, - })]; - } - - case 'mathInline': { - return [h('code', token.props.formula)]; - } - - case 'mathBlock': { - return [h('code', token.props.formula)]; - } - - case 'search': { - return [h(MkGoogle, { - key: Math.random(), - q: token.props.query, - })]; - } - - case 'plain': { - return [h('span', genEl(token.children, scale))]; - } - - default: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - console.error('unrecognized ast type:', (token as any).type); - - return []; - } - } - }).flat(Infinity) as (VNode | string)[]; - - // Parse ast to DOM - return h('span', genEl(ast, this.rootScale)); - }, -}); diff --git a/packages/frontend/src/components/page/block.type.ts b/packages/frontend/src/components/page/block.type.ts new file mode 100644 index 0000000000000000000000000000000000000000..71249a8aff5ba9e686e6f6c85dd086e150f7ac38 --- /dev/null +++ b/packages/frontend/src/components/page/block.type.ts @@ -0,0 +1,29 @@ +export type BlockBase = { + id: string; + type: string; +}; + +export type TextBlock = BlockBase & { + type: 'text'; + text: string; +}; + +export type SectionBlock = BlockBase & { + type: 'section'; + title: string; + children: Block[]; +}; + +export type ImageBlock = BlockBase & { + type: 'image'; + fileId: string | null; +}; + +export type NoteBlock = BlockBase & { + type: 'note'; + detailed: boolean; + note: string | null; +}; + +export type Block = + TextBlock | SectionBlock | ImageBlock | NoteBlock; diff --git a/packages/frontend/src/components/page/page.block.vue b/packages/frontend/src/components/page/page.block.vue index f3e7764604b99255320797853de3cae7a4cc8fc1..2bf3d12daafc3eb555d5b5f31192d112b63010e4 100644 --- a/packages/frontend/src/components/page/page.block.vue +++ b/packages/frontend/src/components/page/page.block.vue @@ -1,44 +1,29 @@ <template> -<component :is="'x-' + block.type" :key="block.id" :block="block" :hpml="hpml" :h="h"/> +<component :is="getComponent(block.type)" :key="block.id" :page="page" :block="block" :h="h"/> </template> -<script lang="ts"> -import { defineComponent, PropType } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; import XText from './page.text.vue'; import XSection from './page.section.vue'; import XImage from './page.image.vue'; -import XButton from './page.button.vue'; -import XNumberInput from './page.number-input.vue'; -import XTextInput from './page.text-input.vue'; -import XTextareaInput from './page.textarea-input.vue'; -import XSwitch from './page.switch.vue'; -import XIf from './page.if.vue'; -import XTextarea from './page.textarea.vue'; -import XPost from './page.post.vue'; -import XCounter from './page.counter.vue'; -import XRadioButton from './page.radio-button.vue'; -import XCanvas from './page.canvas.vue'; import XNote from './page.note.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { Block } from '@/scripts/hpml/block'; +import { Block } from './block.type'; -export default defineComponent({ - components: { - XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote, - }, - props: { - block: { - type: Object as PropType<Block>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - h: { - type: Number, - required: true, - }, - }, -}); +function getComponent(type: string) { + switch (type) { + case 'text': return XText; + case 'section': return XSection; + case 'image': return XImage; + case 'note': return XNote; + default: return null; + } +} + +defineProps<{ + block: Block, + h: number, + page: Misskey.entities.Page, +}>(); </script> diff --git a/packages/frontend/src/components/page/page.button.vue b/packages/frontend/src/components/page/page.button.vue deleted file mode 100644 index 83931021d856376ee7f62d64dfe1622aa68659bd..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.button.vue +++ /dev/null @@ -1,66 +0,0 @@ -<template> -<div> - <MkButton class="kudkigyw" :primary="block.primary" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton> -</div> -</template> - -<script lang="ts"> -import { defineComponent, PropType, unref } from 'vue'; -import MkButton from '../MkButton.vue'; -import * as os from '@/os'; -import { ButtonBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; - -export default defineComponent({ - components: { - MkButton, - }, - props: { - block: { - type: Object as PropType<ButtonBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - methods: { - click() { - if (this.block.action === 'dialog') { - this.hpml.eval(); - os.alert({ - text: this.hpml.interpolate(this.block.content), - }); - } else if (this.block.action === 'resetRandom') { - this.hpml.updateRandomSeed(Math.random()); - this.hpml.eval(); - } else if (this.block.action === 'pushEvent') { - os.api('page-push', { - pageId: this.hpml.page.id, - event: this.block.event, - ...(this.block.var ? { - var: unref(this.hpml.vars)[this.block.var], - } : {}), - }); - - os.alert({ - type: 'success', - text: this.hpml.interpolate(this.block.message), - }); - } else if (this.block.action === 'callAiScript') { - this.hpml.callAiScript(this.block.fn); - } - }, - }, -}); -</script> - -<style lang="scss" scoped> -.kudkigyw { - display: inline-block; - min-width: 200px; - max-width: 450px; - margin: 8px 0; -} -</style> diff --git a/packages/frontend/src/components/page/page.canvas.vue b/packages/frontend/src/components/page/page.canvas.vue deleted file mode 100644 index 82ff36ec36e0de9ababd950e6f219e57e88520f9..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.canvas.vue +++ /dev/null @@ -1,48 +0,0 @@ -<template> -<div class="ysrxegms"> - <canvas ref="canvas" :width="block.width" :height="block.height"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, PropType, Ref, ref } from 'vue'; -import { CanvasBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; - -export default defineComponent({ - props: { - block: { - type: Object as PropType<CanvasBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const canvas: Ref<any> = ref(null); - - onMounted(() => { - props.hpml.registerCanvas(props.block.name, canvas.value); - }); - - return { - canvas, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.ysrxegms { - display: inline-block; - vertical-align: bottom; - overflow: auto; - max-width: 100%; - - > canvas { - display: block; - } -} -</style> diff --git a/packages/frontend/src/components/page/page.counter.vue b/packages/frontend/src/components/page/page.counter.vue deleted file mode 100644 index 63fde6a1207f9bc5cf51a7f240a73924f95db9df..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.counter.vue +++ /dev/null @@ -1,51 +0,0 @@ -<template> -<div> - <MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, PropType } from 'vue'; -import MkButton from '../MkButton.vue'; -import { CounterVarBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; - -export default defineComponent({ - components: { - MkButton, - }, - props: { - block: { - type: Object as PropType<CounterVarBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const value = computed(() => { - return props.hpml.vars.value[props.block.name]; - }); - - function click() { - props.hpml.updatePageVar(props.block.name, value.value + (props.block.inc || 1)); - props.hpml.eval(); - } - - return { - click, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.llumlmnx { - display: inline-block; - min-width: 300px; - max-width: 450px; - margin: 8px 0; -} -</style> diff --git a/packages/frontend/src/components/page/page.if.vue b/packages/frontend/src/components/page/page.if.vue deleted file mode 100644 index 372a15f0c661346311db2da35d79700e98f4a99b..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.if.vue +++ /dev/null @@ -1,31 +0,0 @@ -<template> -<div v-show="hpml.vars.value[block.var]"> - <XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h"/> -</div> -</template> - -<script lang="ts"> -import { IfBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { defineComponent, defineAsyncComponent, PropType } from 'vue'; - -export default defineComponent({ - components: { - XBlock: defineAsyncComponent(() => import('./page.block.vue')), - }, - props: { - block: { - type: Object as PropType<IfBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - h: { - type: Number, - required: true, - }, - }, -}); -</script> diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue index 6ea81d257f086dbdfeebe38806f18d47977d669c..2edcfb8b1a30c72b3c7bd5ad8decd83dbe2786f4 100644 --- a/packages/frontend/src/components/page/page.image.vue +++ b/packages/frontend/src/components/page/page.image.vue @@ -5,15 +5,15 @@ </template> <script lang="ts" setup> -import { PropType } from 'vue'; +import { } from 'vue'; +import * as Misskey from 'misskey-js'; +import { ImageBlock } from './block.type'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; -import { ImageBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; const props = defineProps<{ - block: PropType<ImageBlock>, - hpml: PropType<Hpml>, + block: ImageBlock, + page: Misskey.entities.Page, }>(); -const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); +const image = props.page.attachedFiles.find(x => x.id === props.block.fileId); </script> diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index 8c65dabf08471966ecf0ec495cf26696ec627675..7133a7f5a15057b8baa11d08e35d151bd35457ea 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -1,47 +1,29 @@ <template> -<div class="voxdxuby"> +<div style="margin: 1em 0;"> <MkNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/> <MkNoteDetailed v-if="note && block.detailed" :key="note.id + ':detail'" v-model:note="note"/> </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, PropType, Ref, ref } from 'vue'; +<script lang="ts" setup> +import { onMounted, Ref, ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import { NoteBlock } from './block.type'; import MkNote from '@/components/MkNote.vue'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; import * as os from '@/os'; -import { NoteBlock } from '@/scripts/hpml/block'; -export default defineComponent({ - components: { - MkNote, - MkNoteDetailed, - }, - props: { - block: { - type: Object as PropType<NoteBlock>, - required: true, - }, - }, - setup(props, ctx) { - const note: Ref<Record<string, any> | null> = ref(null); +const props = defineProps<{ + block: NoteBlock, + page: Misskey.entities.Page, +}>(); - onMounted(() => { - os.api('notes/show', { noteId: props.block.note }) - .then(result => { - note.value = result; - }); - }); +const note: Ref<Misskey.entities.Note | null> = ref(null); - return { - note, - }; - }, +onMounted(() => { + os.api('notes/show', { noteId: props.block.note }) + .then(result => { + note.value = result; + }); }); </script> - -<style lang="scss" scoped> -.voxdxuby { - margin: 1em 0; -} -</style> diff --git a/packages/frontend/src/components/page/page.number-input.vue b/packages/frontend/src/components/page/page.number-input.vue deleted file mode 100644 index 72c1b6deb091fbcd68329fe3381f3cd384cbb81c..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.number-input.vue +++ /dev/null @@ -1,54 +0,0 @@ -<template> -<div> - <MkInput class="kudkigyw" :model-value="value" type="number" @update:model-value="updateValue($event)"> - <template #label>{{ hpml.interpolate(block.text) }}</template> - </MkInput> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, PropType } from 'vue'; -import MkInput from '../MkInput.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { NumberInputVarBlock } from '@/scripts/hpml/block'; - -export default defineComponent({ - components: { - MkInput, - }, - props: { - block: { - type: Object as PropType<NumberInputVarBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const value = computed(() => { - return props.hpml.vars.value[props.block.name]; - }); - - function updateValue(newValue) { - props.hpml.updatePageVar(props.block.name, newValue); - props.hpml.eval(); - } - - return { - value, - updateValue, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.kudkigyw { - display: inline-block; - min-width: 300px; - max-width: 450px; - margin: 8px 0; -} -</style> diff --git a/packages/frontend/src/components/page/page.post.vue b/packages/frontend/src/components/page/page.post.vue deleted file mode 100644 index 55da610cb693b3e8d383b4641416451810c39ca3..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.post.vue +++ /dev/null @@ -1,111 +0,0 @@ -<template> -<div class="ngbfujlo"> - <MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea> - <MkButton class="button" primary :disabled="posting || posted" @click="post()"> - <i v-if="posted" class="ti ti-check"></i> - <i v-else class="ti ti-send"></i> - </MkButton> -</div> -</template> - -<script lang="ts"> -import { defineComponent, PropType } from 'vue'; -import MkTextarea from '../MkTextarea.vue'; -import MkButton from '../MkButton.vue'; -import { apiUrl } from '@/config'; -import * as os from '@/os'; -import { PostBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { defaultStore } from '@/store'; -import { $i } from '@/account'; - -export default defineComponent({ - components: { - MkTextarea, - MkButton, - }, - props: { - block: { - type: Object as PropType<PostBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - data() { - return { - text: this.hpml.interpolate(this.block.text), - posted: false, - posting: false, - }; - }, - watch: { - 'hpml.vars': { - handler() { - this.text = this.hpml.interpolate(this.block.text); - }, - deep: true, - }, - }, - methods: { - upload() { - const promise = new Promise((ok) => { - const canvas = this.hpml.canvases[this.block.canvasId]; - canvas.toBlob(blob => { - const formData = new FormData(); - formData.append('file', blob); - formData.append('i', $i.token); - if (defaultStore.state.uploadFolder) { - formData.append('folderId', defaultStore.state.uploadFolder); - } - - window.fetch(apiUrl + '/drive/files/create', { - method: 'POST', - body: formData, - }) - .then(response => response.json()) - .then(f => { - ok(f); - }); - }); - }); - os.promiseDialog(promise); - return promise; - }, - async post() { - this.posting = true; - const file = this.block.attachCanvasImage ? await this.upload() : null; - os.apiWithDialog('notes/create', { - text: this.text === '' ? null : this.text, - fileIds: file ? [file.id] : undefined, - }).then(() => { - this.posted = true; - }); - }, - }, -}); -</script> - -<style lang="scss" scoped> -.ngbfujlo { - position: relative; - padding: 32px; - border-radius: 6px; - box-shadow: 0 2px 8px var(--shadow); - z-index: 1; - - > .button { - margin-top: 32px; - } - - @media (max-width: 600px) { - padding: 16px; - - > .button { - margin-top: 16px; - } - } -} -</style> diff --git a/packages/frontend/src/components/page/page.radio-button.vue b/packages/frontend/src/components/page/page.radio-button.vue deleted file mode 100644 index ce8f252e440f633eb2d46a95adb56ae31c4b3221..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.radio-button.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> -<div> - <div>{{ hpml.interpolate(block.title) }}</div> - <MkRadio v-for="item in block.values" :key="item" :modelValue="value" :value="item" @update:model-value="updateValue($event)">{{ item }}</MkRadio> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, PropType } from 'vue'; -import MkRadio from '../MkRadio.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { RadioButtonVarBlock } from '@/scripts/hpml/block'; - -export default defineComponent({ - components: { - MkRadio, - }, - props: { - block: { - type: Object as PropType<RadioButtonVarBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const value = computed(() => { - return props.hpml.vars.value[props.block.name]; - }); - - function updateValue(newValue: string) { - props.hpml.updatePageVar(props.block.name, newValue); - props.hpml.eval(); - } - - return { - value, - updateValue, - }; - }, -}); -</script> diff --git a/packages/frontend/src/components/page/page.section.vue b/packages/frontend/src/components/page/page.section.vue index 50181b390568f7fd3446ab553c3534f2c64e3123..83a16ae0a5b7bc6ca4b29add241c581a067e09dd 100644 --- a/packages/frontend/src/components/page/page.section.vue +++ b/packages/frontend/src/components/page/page.section.vue @@ -1,59 +1,49 @@ <template> -<section class="sdgxphyu"> - <component :is="'h' + h">{{ block.title }}</component> - - <div class="children"> - <XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h + 1"/> +<section> + <component + :is="'h' + h" + :class="{ + 'h2': h === 2, + 'h3': h === 3, + 'h4': h === 4, + }" + > + {{ block.title }} + </component> + + <div class="_gaps"> + <XBlock v-for="child in block.children" :key="child.id" :page="page" :block="child" :h="h + 1"/> </div> </section> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent, PropType } from 'vue'; -import { SectionBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; - -export default defineComponent({ - components: { - XBlock: defineAsyncComponent(() => import('./page.block.vue')), - }, - props: { - block: { - type: Object as PropType<SectionBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - h: { - required: true, - }, - }, -}); -</script> +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; +import * as Misskey from 'misskey-js'; +import { SectionBlock } from './block.type'; -<style lang="scss" scoped> -.sdgxphyu { - margin: 1.5em 0; +const XBlock = defineAsyncComponent(() => import('./page.block.vue')); - > h2 { - font-size: 1.35em; - margin: 0 0 0.5em 0; - } +defineProps<{ + block: SectionBlock, + h: number, + page: Misskey.entities.Page, +}>(); +</script> - > h3 { - font-size: 1em; - margin: 0 0 0.5em 0; - } +<style lang="scss" module> +.h2 { + font-size: 1.35em; + margin: 0 0 0.5em 0; +} - > h4 { - font-size: 1em; - margin: 0 0 0.5em 0; - } +.h3 { + font-size: 1em; + margin: 0 0 0.5em 0; +} - > .children { - //padding 16px - } +.h4 { + font-size: 1em; + margin: 0 0 0.5em 0; } </style> diff --git a/packages/frontend/src/components/page/page.switch.vue b/packages/frontend/src/components/page/page.switch.vue deleted file mode 100644 index b5f3464512babdeb315f4474b82251c44c5b56bf..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.switch.vue +++ /dev/null @@ -1,54 +0,0 @@ -<template> -<div class="hkcxmtwj"> - <MkSwitch :model-value="value" @update:model-value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, PropType } from 'vue'; -import MkSwitch from '../MkSwitch.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { SwitchVarBlock } from '@/scripts/hpml/block'; - -export default defineComponent({ - components: { - MkSwitch, - }, - props: { - block: { - type: Object as PropType<SwitchVarBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const value = computed(() => { - return props.hpml.vars.value[props.block.name]; - }); - - function updateValue(newValue: boolean) { - props.hpml.updatePageVar(props.block.name, newValue); - props.hpml.eval(); - } - - return { - value, - updateValue, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.hkcxmtwj { - display: inline-block; - margin: 16px auto; - - & + .hkcxmtwj { - margin-left: 16px; - } -} -</style> diff --git a/packages/frontend/src/components/page/page.text-input.vue b/packages/frontend/src/components/page/page.text-input.vue deleted file mode 100644 index d020a99de88318fc7dfa8c521885f12a9377d0b4..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.text-input.vue +++ /dev/null @@ -1,54 +0,0 @@ -<template> -<div> - <MkInput class="kudkigyw" :model-value="value" type="text" @update:model-value="updateValue($event)"> - <template #label>{{ hpml.interpolate(block.text) }}</template> - </MkInput> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, PropType } from 'vue'; -import MkInput from '../MkInput.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { TextInputVarBlock } from '@/scripts/hpml/block'; - -export default defineComponent({ - components: { - MkInput, - }, - props: { - block: { - type: Object as PropType<TextInputVarBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const value = computed(() => { - return props.hpml.vars.value[props.block.name]; - }); - - function updateValue(newValue) { - props.hpml.updatePageVar(props.block.name, newValue); - props.hpml.eval(); - } - - return { - value, - updateValue, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.kudkigyw { - display: inline-block; - min-width: 300px; - max-width: 450px; - margin: 8px 0; -} -</style> diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue index e0e4959efa26019ee04ab8c544cc451454324112..48ce4b0e1e4d70764a076be1856d3e7cfb0694d6 100644 --- a/packages/frontend/src/components/page/page.text.vue +++ b/packages/frontend/src/components/page/page.text.vue @@ -1,70 +1,24 @@ <template> -<div class="mrdgzndn"> - <Mfm :key="text" :text="text" :is-note="false" :i="$i"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" class="url"/> +<div class="_gaps"> + <Mfm :text="block.text" :isNote="false" :i="$i"/> + <MkUrlPreview v-for="url in urls" :key="url" :url="url"/> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, PropType } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; -import { TextBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; +import * as Misskey from 'misskey-js'; +import { TextBlock } from './block.type'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; import { $i } from '@/account'; -export default defineComponent({ - components: { - MkUrlPreview: defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')), - }, - props: { - block: { - type: Object as PropType<TextBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - data() { - return { - text: this.hpml.interpolate(this.block.text), - $i, - }; - }, - computed: { - urls(): string[] { - if (this.text) { - return extractUrlFromMfm(mfm.parse(this.text)); - } else { - return []; - } - }, - }, - watch: { - 'hpml.vars': { - handler() { - this.text = this.hpml.interpolate(this.block.text); - }, - deep: true, - }, - }, -}); -</script> - -<style lang="scss" scoped> -.mrdgzndn { - &:not(:first-child) { - margin-top: 0.5em; - } +const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); - &:not(:last-child) { - margin-bottom: 0.5em; - } +const props = defineProps<{ + block: TextBlock, + page: Misskey.entities.Page, +}>(); - > .url { - margin: 0.5em 0; - } -} -</style> +const urls = props.block.text ? extractUrlFromMfm(mfm.parse(props.block.text)) : []; +</script> diff --git a/packages/frontend/src/components/page/page.textarea-input.vue b/packages/frontend/src/components/page/page.textarea-input.vue deleted file mode 100644 index db3a96dd1b12387783ab514f09b69b8ada8b1c3f..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.textarea-input.vue +++ /dev/null @@ -1,45 +0,0 @@ -<template> -<div> - <MkTextarea :model-value="value" @update:model-value="updateValue($event)"> - <template #label>{{ hpml.interpolate(block.text) }}</template> - </MkTextarea> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, PropType } from 'vue'; -import MkTextarea from '../MkTextarea.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { TextInputVarBlock } from '@/scripts/hpml/block'; - -export default defineComponent({ - components: { - MkTextarea, - }, - props: { - block: { - type: Object as PropType<TextInputVarBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - setup(props, ctx) { - const value = computed(() => { - return props.hpml.vars.value[props.block.name]; - }); - - function updateValue(newValue) { - props.hpml.updatePageVar(props.block.name, newValue); - props.hpml.eval(); - } - - return { - value, - updateValue, - }; - }, -}); -</script> diff --git a/packages/frontend/src/components/page/page.textarea.vue b/packages/frontend/src/components/page/page.textarea.vue deleted file mode 100644 index 9b82412e8aef5f59241c2f3699d48451a35759d9..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/page/page.textarea.vue +++ /dev/null @@ -1,39 +0,0 @@ -<template> -<MkTextarea :model-value="text" readonly></MkTextarea> -</template> - -<script lang="ts"> -import { TextBlock } from '@/scripts/hpml/block'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { defineComponent, PropType } from 'vue'; -import MkTextarea from '../MkTextarea.vue'; - -export default defineComponent({ - components: { - MkTextarea, - }, - props: { - block: { - type: Object as PropType<TextBlock>, - required: true, - }, - hpml: { - type: Object as PropType<Hpml>, - required: true, - }, - }, - data() { - return { - text: this.hpml.interpolate(this.block.text), - }; - }, - watch: { - 'hpml.vars': { - handler() { - this.text = this.hpml.interpolate(this.block.text); - }, - deep: true, - }, - }, -}); -</script> diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue index 5f1f62581ea481c29636992327b2717a3b431924..c2c2693224758826e158d9085e395f7873b9f5b8 100644 --- a/packages/frontend/src/components/page/page.vue +++ b/packages/frontend/src/components/page/page.vue @@ -1,56 +1,25 @@ <template> -<div v-if="hpml" class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }"> - <XBlock v-for="child in page.content" :key="child.id" :block="child" :hpml="hpml" :h="2"/> +<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }"> + <XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/> </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, nextTick, PropType } from 'vue'; +<script lang="ts" setup> +import { onMounted, nextTick } from 'vue'; +import * as Misskey from 'misskey-js'; import XBlock from './page.block.vue'; -import { Hpml } from '@/scripts/hpml/evaluator'; -import { url } from '@/config'; -import { $i } from '@/account'; -export default defineComponent({ - components: { - XBlock, - }, - props: { - page: { - type: Object as PropType<Record<string, any>>, - required: true, - }, - }, - setup(props, ctx) { - const hpml = new Hpml(props.page, { - randomSeed: Math.random(), - visitor: $i, - url: url, - }); - - onMounted(() => { - nextTick(() => { - hpml.eval(); - }); - }); - - return { - hpml, - }; - }, -}); +defineProps<{ + page: Misskey.entities.Page, +}>(); </script> -<style lang="scss" scoped> -.iroscrza { - &.serif { - > div { - font-family: serif; - } - } +<style lang="scss" module> +.serif { + font-family: serif; +} - &.center { - text-align: center; - } +.center { + text-align: center; } </style> diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts index a89a420d77b8026e192d26aa16ab3f51b502d40e..9b738b2fd46357e2d6f294f91934562c563d33f9 100644 --- a/packages/frontend/src/custom-emojis.ts +++ b/packages/frontend/src/custom-emojis.ts @@ -1,8 +1,7 @@ -import { shallowRef, computed, markRaw } from 'vue'; +import { shallowRef, computed, markRaw, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { api, apiGet } from './os'; -import { miLocalStorage } from './local-storage'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { get, set } from '@/scripts/idb-proxy'; const storageCache = await get('emojis'); @@ -17,6 +16,17 @@ export const customEmojiCategories = computed<[ ...string[], null ]>(() => { return markRaw([...Array.from(categories), null]); }); +export const customEmojisMap = new Map<string, Misskey.entities.CustomEmoji>(); +watch(customEmojis, emojis => { + customEmojisMap.clear(); + for (const emoji of emojis) { + customEmojisMap.set(emoji.name, emoji); + } +}, { immediate: true }); + +// TODO: ã“ã“ら辺副作用ãªã®ã§ã„ã„æ„Ÿã˜ã«ã™ã‚‹ +const stream = useStream(); + stream.on('emojiAdded', emojiData => { customEmojis.value = [emojiData.emoji, ...customEmojis.value]; set('emojis', customEmojis.value); @@ -34,10 +44,9 @@ stream.on('emojiDeleted', emojiData => { export async function fetchCustomEmojis(force = false) { const now = Date.now(); - const needsMigration = miLocalStorage.getItem('emojis') != null; let res; - if (force || needsMigration) { + if (force) { res = await api('emojis', {}); } else { const lastFetchedAt = await get('lastEmojisFetchedAt'); @@ -48,10 +57,6 @@ export async function fetchCustomEmojis(force = false) { customEmojis.value = res.emojis; set('emojis', res.emojis); set('lastEmojisFetchedAt', now); - if (needsMigration) { - miLocalStorage.removeItem('emojis'); - miLocalStorage.removeItem('lastEmojisFetchedAt'); - } } let cachedTags; diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index 5d13497b5f569786f5ac1223656df3fe4d0d3743..373141fa359947b96ab2f127ade16a274fc0b531 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -5,7 +5,7 @@ import { defineAsyncComponent, Directive, ref } from 'vue'; import { isTouchUsing } from '@/scripts/touch'; import { popup, alert } from '@/os'; -const start = isTouchUsing ? 'touchstart' : 'mouseover'; +const start = isTouchUsing ? 'touchstart' : 'mouseenter'; const end = isTouchUsing ? 'touchend' : 'mouseleave'; export default { @@ -63,16 +63,24 @@ export default { ev.preventDefault(); }); - el.addEventListener(start, () => { + el.addEventListener(start, (ev) => { window.clearTimeout(self.showTimer); window.clearTimeout(self.hideTimer); - self.showTimer = window.setTimeout(self.show, delay); + if (delay === 0) { + self.show(); + } else { + self.showTimer = window.setTimeout(self.show, delay); + } }, { passive: true }); el.addEventListener(end, () => { window.clearTimeout(self.showTimer); window.clearTimeout(self.hideTimer); - self.hideTimer = window.setTimeout(self.close, delay); + if (delay === 0) { + self.close(); + } else { + self.hideTimer = window.setTimeout(self.close, delay); + } }, { passive: true }); el.addEventListener('click', () => { diff --git a/packages/frontend/src/emojilist.json b/packages/frontend/src/emojilist.json index 402e82e33b87f8b1cb25886873fa9022f3416861..fde06a4aa0a5e05e52a5db80a142316f7f643a10 100644 --- a/packages/frontend/src/emojilist.json +++ b/packages/frontend/src/emojilist.json @@ -1,1785 +1,1784 @@ [ - { "category": "face", "char": "😀", "name": "grinning", "keywords": ["face", "smile", "happy", "joy", ": D", "grin"] }, - { "category": "face", "char": "😬", "name": "grimacing", "keywords": ["face", "grimace", "teeth"] }, - { "category": "face", "char": "ðŸ˜", "name": "grin", "keywords": ["face", "happy", "smile", "joy", "kawaii"] }, - { "category": "face", "char": "😂", "name": "joy", "keywords": ["face", "cry", "tears", "weep", "happy", "happytears", "haha"] }, - { "category": "face", "char": "🤣", "name": "rofl", "keywords": ["face", "rolling", "floor", "laughing", "lol", "haha"] }, - { "category": "face", "char": "🥳", "name": "partying", "keywords": ["face", "celebration", "woohoo"] }, - { "category": "face", "char": "😃", "name": "smiley", "keywords": ["face", "happy", "joy", "haha", ": D", ": )", "smile", "funny"] }, - { "category": "face", "char": "😄", "name": "smile", "keywords": ["face", "happy", "joy", "funny", "haha", "laugh", "like", ": D", ": )"] }, - { "category": "face", "char": "😅", "name": "sweat_smile", "keywords": ["face", "hot", "happy", "laugh", "sweat", "smile", "relief"] }, - { "category": "face", "char": "🥲", "name": "smiling_face_with_tear", "keywords": ["face"] }, - { "category": "face", "char": "😆", "name": "laughing", "keywords": ["happy", "joy", "lol", "satisfied", "haha", "face", "glad", "XD", "laugh"] }, - { "category": "face", "char": "😇", "name": "innocent", "keywords": ["face", "angel", "heaven", "halo"] }, - { "category": "face", "char": "😉", "name": "wink", "keywords": ["face", "happy", "mischievous", "secret", ";)", "smile", "eye"] }, - { "category": "face", "char": "😊", "name": "blush", "keywords": ["face", "smile", "happy", "flushed", "crush", "embarrassed", "shy", "joy"] }, - { "category": "face", "char": "🙂", "name": "slightly_smiling_face", "keywords": ["face", "smile"] }, - { "category": "face", "char": "🙃", "name": "upside_down_face", "keywords": ["face", "flipped", "silly", "smile"] }, - { "category": "face", "char": "☺ï¸", "name": "relaxed", "keywords": ["face", "blush", "massage", "happiness"] }, - { "category": "face", "char": "😋", "name": "yum", "keywords": ["happy", "joy", "tongue", "smile", "face", "silly", "yummy", "nom", "delicious", "savouring"] }, - { "category": "face", "char": "😌", "name": "relieved", "keywords": ["face", "relaxed", "phew", "massage", "happiness"] }, - { "category": "face", "char": "ðŸ˜", "name": "heart_eyes", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "heart"] }, - { "category": "face", "char": "🥰", "name": "smiling_face_with_three_hearts", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "hearts", "adore"] }, - { "category": "face", "char": "😘", "name": "kissing_heart", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"] }, - { "category": "face", "char": "😗", "name": "kissing", "keywords": ["love", "like", "face", "3", "valentines", "infatuation", "kiss"] }, - { "category": "face", "char": "😙", "name": "kissing_smiling_eyes", "keywords": ["face", "affection", "valentines", "infatuation", "kiss"] }, - { "category": "face", "char": "😚", "name": "kissing_closed_eyes", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"] }, - { "category": "face", "char": "😜", "name": "stuck_out_tongue_winking_eye", "keywords": ["face", "prank", "childish", "playful", "mischievous", "smile", "wink", "tongue"] }, - { "category": "face", "char": "🤪", "name": "zany", "keywords": ["face", "goofy", "crazy"] }, - { "category": "face", "char": "🤨", "name": "raised_eyebrow", "keywords": ["face", "distrust", "scepticism", "disapproval", "disbelief", "surprise"] }, - { "category": "face", "char": "ðŸ§", "name": "monocle", "keywords": ["face", "stuffy", "wealthy"] }, - { "category": "face", "char": "ðŸ˜", "name": "stuck_out_tongue_closed_eyes", "keywords": ["face", "prank", "playful", "mischievous", "smile", "tongue"] }, - { "category": "face", "char": "😛", "name": "stuck_out_tongue", "keywords": ["face", "prank", "childish", "playful", "mischievous", "smile", "tongue"] }, - { "category": "face", "char": "🤑", "name": "money_mouth_face", "keywords": ["face", "rich", "dollar", "money"] }, - { "category": "face", "char": "🤓", "name": "nerd_face", "keywords": ["face", "nerdy", "geek", "dork"] }, - { "category": "face", "char": "🥸", "name": "disguised_face", "keywords": ["face", "nose", "glasses", "incognito"] }, - { "category": "face", "char": "😎", "name": "sunglasses", "keywords": ["face", "cool", "smile", "summer", "beach", "sunglass"] }, - { "category": "face", "char": "🤩", "name": "star_struck", "keywords": ["face", "smile", "starry", "eyes", "grinning"] }, - { "category": "face", "char": "🤡", "name": "clown_face", "keywords": ["face"] }, - { "category": "face", "char": "🤠", "name": "cowboy_hat_face", "keywords": ["face", "cowgirl", "hat"] }, - { "category": "face", "char": "🤗", "name": "hugs", "keywords": ["face", "smile", "hug"] }, - { "category": "face", "char": "ðŸ˜", "name": "smirk", "keywords": ["face", "smile", "mean", "prank", "smug", "sarcasm"] }, - { "category": "face", "char": "😶", "name": "no_mouth", "keywords": ["face", "hellokitty"] }, - { "category": "face", "char": "ðŸ˜", "name": "neutral_face", "keywords": ["indifference", "meh", ": |", "neutral"] }, - { "category": "face", "char": "😑", "name": "expressionless", "keywords": ["face", "indifferent", "-_-", "meh", "deadpan"] }, - { "category": "face", "char": "😒", "name": "unamused", "keywords": ["indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye"] }, - { "category": "face", "char": "🙄", "name": "roll_eyes", "keywords": ["face", "eyeroll", "frustrated"] }, - { "category": "face", "char": "🤔", "name": "thinking", "keywords": ["face", "hmmm", "think", "consider"] }, - { "category": "face", "char": "🤥", "name": "lying_face", "keywords": ["face", "lie", "pinocchio"] }, - { "category": "face", "char": "ðŸ¤", "name": "hand_over_mouth", "keywords": ["face", "whoops", "shock", "surprise"] }, - { "category": "face", "char": "🤫", "name": "shushing", "keywords": ["face", "quiet", "shhh"] }, - { "category": "face", "char": "🤬", "name": "symbols_over_mouth", "keywords": ["face", "swearing", "cursing", "cussing", "profanity", "expletive"] }, - { "category": "face", "char": "🤯", "name": "exploding_head", "keywords": ["face", "shocked", "mind", "blown"] }, - { "category": "face", "char": "😳", "name": "flushed", "keywords": ["face", "blush", "shy", "flattered"] }, - { "category": "face", "char": "😞", "name": "disappointed", "keywords": ["face", "sad", "upset", "depressed", ": ("] }, - { "category": "face", "char": "😟", "name": "worried", "keywords": ["face", "concern", "nervous", ": ("] }, - { "category": "face", "char": "😠", "name": "angry", "keywords": ["mad", "face", "annoyed", "frustrated"] }, - { "category": "face", "char": "😡", "name": "rage", "keywords": ["angry", "mad", "hate", "despise"] }, - { "category": "face", "char": "😔", "name": "pensive", "keywords": ["face", "sad", "depressed", "upset"] }, - { "category": "face", "char": "😕", "name": "confused", "keywords": ["face", "indifference", "huh", "weird", "hmmm", ": /"] }, - { "category": "face", "char": "ðŸ™", "name": "slightly_frowning_face", "keywords": ["face", "frowning", "disappointed", "sad", "upset"] }, - { "category": "face", "char": "☹", "name": "frowning_face", "keywords": ["face", "sad", "upset", "frown"] }, - { "category": "face", "char": "😣", "name": "persevere", "keywords": ["face", "sick", "no", "upset", "oops"] }, - { "category": "face", "char": "😖", "name": "confounded", "keywords": ["face", "confused", "sick", "unwell", "oops", ": S"] }, - { "category": "face", "char": "😫", "name": "tired_face", "keywords": ["sick", "whine", "upset", "frustrated"] }, - { "category": "face", "char": "😩", "name": "weary", "keywords": ["face", "tired", "sleepy", "sad", "frustrated", "upset"] }, - { "category": "face", "char": "🥺", "name": "pleading", "keywords": ["face", "begging", "mercy"] }, - { "category": "face", "char": "😤", "name": "triumph", "keywords": ["face", "gas", "phew", "proud", "pride"] }, - { "category": "face", "char": "😮", "name": "open_mouth", "keywords": ["face", "surprise", "impressed", "wow", "whoa", ": O"] }, - { "category": "face", "char": "😱", "name": "scream", "keywords": ["face", "munch", "scared", "omg"] }, - { "category": "face", "char": "😨", "name": "fearful", "keywords": ["face", "scared", "terrified", "nervous", "oops", "huh"] }, - { "category": "face", "char": "😰", "name": "cold_sweat", "keywords": ["face", "nervous", "sweat"] }, - { "category": "face", "char": "😯", "name": "hushed", "keywords": ["face", "woo", "shh"] }, - { "category": "face", "char": "😦", "name": "frowning", "keywords": ["face", "aw", "what"] }, - { "category": "face", "char": "😧", "name": "anguished", "keywords": ["face", "stunned", "nervous"] }, - { "category": "face", "char": "😢", "name": "cry", "keywords": ["face", "tears", "sad", "depressed", "upset", ": '("] }, - { "category": "face", "char": "😥", "name": "disappointed_relieved", "keywords": ["face", "phew", "sweat", "nervous"] }, - { "category": "face", "char": "🤤", "name": "drooling_face", "keywords": ["face"] }, - { "category": "face", "char": "😪", "name": "sleepy", "keywords": ["face", "tired", "rest", "nap"] }, - { "category": "face", "char": "😓", "name": "sweat", "keywords": ["face", "hot", "sad", "tired", "exercise"] }, - { "category": "face", "char": "🥵", "name": "hot", "keywords": ["face", "feverish", "heat", "red", "sweating"] }, - { "category": "face", "char": "🥶", "name": "cold", "keywords": ["face", "blue", "freezing", "frozen", "frostbite", "icicles"] }, - { "category": "face", "char": "ðŸ˜", "name": "sob", "keywords": ["face", "cry", "tears", "sad", "upset", "depressed"] }, - { "category": "face", "char": "😵", "name": "dizzy_face", "keywords": ["spent", "unconscious", "xox", "dizzy"] }, - { "category": "face", "char": "😲", "name": "astonished", "keywords": ["face", "xox", "surprised", "poisoned"] }, - { "category": "face", "char": "ðŸ¤", "name": "zipper_mouth_face", "keywords": ["face", "sealed", "zipper", "secret"] }, - { "category": "face", "char": "🤢", "name": "nauseated_face", "keywords": ["face", "vomit", "gross", "green", "sick", "throw up", "ill"] }, - { "category": "face", "char": "🤧", "name": "sneezing_face", "keywords": ["face", "gesundheit", "sneeze", "sick", "allergy"] }, - { "category": "face", "char": "🤮", "name": "vomiting", "keywords": ["face", "sick"] }, - { "category": "face", "char": "😷", "name": "mask", "keywords": ["face", "sick", "ill", "disease"] }, - { "category": "face", "char": "🤒", "name": "face_with_thermometer", "keywords": ["sick", "temperature", "thermometer", "cold", "fever"] }, - { "category": "face", "char": "🤕", "name": "face_with_head_bandage", "keywords": ["injured", "clumsy", "bandage", "hurt"] }, - { "category": "face", "char": "🥴", "name": "woozy", "keywords": ["face", "dizzy", "intoxicated", "tipsy", "wavy"] }, - { "category": "face", "char": "🥱", "name": "yawning", "keywords": ["face", "tired", "yawning"] }, - { "category": "face", "char": "😴", "name": "sleeping", "keywords": ["face", "tired", "sleepy", "night", "zzz"] }, - { "category": "face", "char": "💤", "name": "zzz", "keywords": ["sleepy", "tired", "dream"] }, - { "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] }, - { "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] }, - { "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] }, - { "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] }, - { "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] }, - { "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] }, - { "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] }, - { "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] }, - { "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] }, - { "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] }, - { "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] }, - { "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] }, - { "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] }, - { "category": "face", "char": "👹", "name": "japanese_ogre", "keywords": ["monster", "red", "mask", "halloween", "scary", "creepy", "devil", "demon", "japanese", "ogre"] }, - { "category": "face", "char": "👺", "name": "japanese_goblin", "keywords": ["red", "evil", "mask", "monster", "scary", "creepy", "japanese", "goblin"] }, - { "category": "face", "char": "💀", "name": "skull", "keywords": ["dead", "skeleton", "creepy", "death"] }, - { "category": "face", "char": "👻", "name": "ghost", "keywords": ["halloween", "spooky", "scary"] }, - { "category": "face", "char": "👽", "name": "alien", "keywords": ["UFO", "paul", "weird", "outer_space"] }, - { "category": "face", "char": "🤖", "name": "robot", "keywords": ["computer", "machine", "bot"] }, - { "category": "face", "char": "😺", "name": "smiley_cat", "keywords": ["animal", "cats", "happy", "smile"] }, - { "category": "face", "char": "😸", "name": "smile_cat", "keywords": ["animal", "cats", "smile"] }, - { "category": "face", "char": "😹", "name": "joy_cat", "keywords": ["animal", "cats", "haha", "happy", "tears"] }, - { "category": "face", "char": "😻", "name": "heart_eyes_cat", "keywords": ["animal", "love", "like", "affection", "cats", "valentines", "heart"] }, - { "category": "face", "char": "😼", "name": "smirk_cat", "keywords": ["animal", "cats", "smirk"] }, - { "category": "face", "char": "😽", "name": "kissing_cat", "keywords": ["animal", "cats", "kiss"] }, - { "category": "face", "char": "🙀", "name": "scream_cat", "keywords": ["animal", "cats", "munch", "scared", "scream"] }, - { "category": "face", "char": "😿", "name": "crying_cat_face", "keywords": ["animal", "tears", "weep", "sad", "cats", "upset", "cry"] }, - { "category": "face", "char": "😾", "name": "pouting_cat", "keywords": ["animal", "cats"] }, - { "category": "people", "char": "🤲", "name": "palms_up", "keywords": ["hands", "gesture", "cupped", "prayer"] }, - { "category": "people", "char": "🙌", "name": "raised_hands", "keywords": ["gesture", "hooray", "yea", "celebration", "hands"] }, - { "category": "people", "char": "ðŸ‘", "name": "clap", "keywords": ["hands", "praise", "applause", "congrats", "yay"] }, - { "category": "people", "char": "👋", "name": "wave", "keywords": ["hands", "gesture", "goodbye", "solong", "farewell", "hello", "hi", "palm"] }, - { "category": "people", "char": "🤙", "name": "call_me_hand", "keywords": ["hands", "gesture"] }, - { "category": "people", "char": "ðŸ‘", "name": "+1", "keywords": ["thumbsup", "yes", "awesome", "good", "agree", "accept", "cool", "hand", "like"] }, - { "category": "people", "char": "👎", "name": "-1", "keywords": ["thumbsdown", "no", "dislike", "hand"] }, - { "category": "people", "char": "👊", "name": "facepunch", "keywords": ["angry", "violence", "fist", "hit", "attack", "hand"] }, - { "category": "people", "char": "✊", "name": "fist", "keywords": ["fingers", "hand", "grasp"] }, - { "category": "people", "char": "🤛", "name": "fist_left", "keywords": ["hand", "fistbump"] }, - { "category": "people", "char": "🤜", "name": "fist_right", "keywords": ["hand", "fistbump"] }, - { "category": "people", "char": "✌", "name": "v", "keywords": ["fingers", "ohyeah", "hand", "peace", "victory", "two"] }, - { "category": "people", "char": "👌", "name": "ok_hand", "keywords": ["fingers", "limbs", "perfect", "ok", "okay"] }, - { "category": "people", "char": "✋", "name": "raised_hand", "keywords": ["fingers", "stop", "highfive", "palm", "ban"] }, - { "category": "people", "char": "🤚", "name": "raised_back_of_hand", "keywords": ["fingers", "raised", "backhand"] }, - { "category": "people", "char": "ðŸ‘", "name": "open_hands", "keywords": ["fingers", "butterfly", "hands", "open"] }, - { "category": "people", "char": "💪", "name": "muscle", "keywords": ["arm", "flex", "hand", "summer", "strong", "biceps"] }, - { "category": "people", "char": "🦾", "name": "mechanical_arm", "keywords": ["flex", "hand", "strong", "biceps"] }, - { "category": "people", "char": "ðŸ™", "name": "pray", "keywords": ["please", "hope", "wish", "namaste", "highfive"] }, - { "category": "people", "char": "🦶", "name": "foot", "keywords": ["kick", "stomp"] }, - { "category": "people", "char": "🦵", "name": "leg", "keywords": ["kick", "limb"] }, - { "category": "people", "char": "🦿", "name": "mechanical_leg", "keywords": ["kick", "limb"] }, - { "category": "people", "char": "ðŸ¤", "name": "handshake", "keywords": ["agreement", "shake"] }, - { "category": "people", "char": "â˜", "name": "point_up", "keywords": ["hand", "fingers", "direction", "up"] }, - { "category": "people", "char": "👆", "name": "point_up_2", "keywords": ["fingers", "hand", "direction", "up"] }, - { "category": "people", "char": "👇", "name": "point_down", "keywords": ["fingers", "hand", "direction", "down"] }, - { "category": "people", "char": "👈", "name": "point_left", "keywords": ["direction", "fingers", "hand", "left"] }, - { "category": "people", "char": "👉", "name": "point_right", "keywords": ["fingers", "hand", "direction", "right"] }, - { "category": "people", "char": "🖕", "name": "fu", "keywords": ["hand", "fingers", "rude", "middle", "flipping"] }, - { "category": "people", "char": "ðŸ–", "name": "raised_hand_with_fingers_splayed", "keywords": ["hand", "fingers", "palm"] }, - { "category": "people", "char": "🤟", "name": "love_you", "keywords": ["hand", "fingers", "gesture"] }, - { "category": "people", "char": "🤘", "name": "metal", "keywords": ["hand", "fingers", "evil_eye", "sign_of_horns", "rock_on"] }, - { "category": "people", "char": "🤞", "name": "crossed_fingers", "keywords": ["good", "lucky"] }, - { "category": "people", "char": "🖖", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] }, - { "category": "people", "char": "âœ", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] }, - { "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] }, - { "category": "people", "char": "ðŸ¤", "name": "pinching_hand", "keywords": ["hand", "fingers"] }, - { "category": "people", "char": "🤌", "name": "pinched_fingers", "keywords": ["hand", "fingers"] }, - { "category": "people", "char": "🤳", "name": "selfie", "keywords": ["camera", "phone"] }, - { "category": "people", "char": "💅", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] }, - { "category": "people", "char": "👄", "name": "lips", "keywords": ["mouth", "kiss"] }, - { "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] }, - { "category": "people", "char": "🦷", "name": "tooth", "keywords": ["teeth", "dentist"] }, - { "category": "people", "char": "👅", "name": "tongue", "keywords": ["mouth", "playful"] }, - { "category": "people", "char": "👂", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] }, - { "category": "people", "char": "🦻", "name": "ear_with_hearing_aid", "keywords": ["face", "hear", "sound", "listen"] }, - { "category": "people", "char": "👃", "name": "nose", "keywords": ["smell", "sniff"] }, - { "category": "people", "char": "ðŸ‘", "name": "eye", "keywords": ["face", "look", "see", "watch", "stare"] }, - { "category": "people", "char": "👀", "name": "eyes", "keywords": ["look", "watch", "stalk", "peek", "see"] }, - { "category": "people", "char": "🧠", "name": "brain", "keywords": ["smart", "intelligent"] }, - { "category": "people", "char": "🫀", "name": "anatomical_heart", "keywords": [] }, - { "category": "people", "char": "ðŸ«", "name": "lungs", "keywords": [] }, - { "category": "people", "char": "👤", "name": "bust_in_silhouette", "keywords": ["user", "person", "human"] }, - { "category": "people", "char": "👥", "name": "busts_in_silhouette", "keywords": ["user", "person", "human", "group", "team"] }, - { "category": "people", "char": "🗣", "name": "speaking_head", "keywords": ["user", "person", "human", "sing", "say", "talk"] }, - { "category": "people", "char": "👶", "name": "baby", "keywords": ["child", "boy", "girl", "toddler"] }, - { "category": "people", "char": "🧒", "name": "child", "keywords": ["gender-neutral", "young"] }, - { "category": "people", "char": "👦", "name": "boy", "keywords": ["man", "male", "guy", "teenager"] }, - { "category": "people", "char": "👧", "name": "girl", "keywords": ["female", "woman", "teenager"] }, - { "category": "people", "char": "🧑", "name": "adult", "keywords": ["gender-neutral", "person"] }, - { "category": "people", "char": "👨", "name": "man", "keywords": ["mustache", "father", "dad", "guy", "classy", "sir", "moustache"] }, - { "category": "people", "char": "👩", "name": "woman", "keywords": ["female", "girls", "lady"] }, - { "category": "people", "char": "🧑â€ðŸ¦±", "name": "curly_hair", "keywords": ["curly", "afro", "braids", "ringlets"] }, - { "category": "people", "char": "👩â€ðŸ¦±", "name": "curly_hair_woman", "keywords": ["woman", "female", "girl", "curly", "afro", "braids", "ringlets"] }, - { "category": "people", "char": "👨â€ðŸ¦±", "name": "curly_hair_man", "keywords": ["man", "male", "boy", "guy", "curly", "afro", "braids", "ringlets"] }, - { "category": "people", "char": "🧑â€ðŸ¦°", "name": "red_hair", "keywords": ["redhead"] }, - { "category": "people", "char": "👩â€ðŸ¦°", "name": "red_hair_woman", "keywords": ["woman", "female", "girl", "ginger", "redhead"] }, - { "category": "people", "char": "👨â€ðŸ¦°", "name": "red_hair_man", "keywords": ["man", "male", "boy", "guy", "ginger", "redhead"] }, - { "category": "people", "char": "👱â€â™€ï¸", "name": "blonde_woman", "keywords": ["woman", "female", "girl", "blonde", "person"] }, - { "category": "people", "char": "👱", "name": "blonde_man", "keywords": ["man", "male", "boy", "blonde", "guy", "person"] }, - { "category": "people", "char": "🧑â€ðŸ¦³", "name": "white_hair", "keywords": ["gray", "old", "white"] }, - { "category": "people", "char": "👩â€ðŸ¦³", "name": "white_hair_woman", "keywords": ["woman", "female", "girl", "gray", "old", "white"] }, - { "category": "people", "char": "👨â€ðŸ¦³", "name": "white_hair_man", "keywords": ["man", "male", "boy", "guy", "gray", "old", "white"] }, - { "category": "people", "char": "🧑â€ðŸ¦²", "name": "bald", "keywords": ["bald", "chemotherapy", "hairless", "shaven"] }, - { "category": "people", "char": "👩â€ðŸ¦²", "name": "bald_woman", "keywords": ["woman", "female", "girl", "bald", "chemotherapy", "hairless", "shaven"] }, - { "category": "people", "char": "👨â€ðŸ¦²", "name": "bald_man", "keywords": ["man", "male", "boy", "guy", "bald", "chemotherapy", "hairless", "shaven"] }, - { "category": "people", "char": "🧔", "name": "bearded_person", "keywords": ["person", "bewhiskered"] }, - { "category": "people", "char": "🧓", "name": "older_adult", "keywords": ["human", "elder", "senior", "gender-neutral"] }, - { "category": "people", "char": "👴", "name": "older_man", "keywords": ["human", "male", "men", "old", "elder", "senior"] }, - { "category": "people", "char": "👵", "name": "older_woman", "keywords": ["human", "female", "women", "lady", "old", "elder", "senior"] }, - { "category": "people", "char": "👲", "name": "man_with_gua_pi_mao", "keywords": ["male", "boy", "chinese"] }, - { "category": "people", "char": "🧕", "name": "woman_with_headscarf", "keywords": ["female", "hijab", "mantilla", "tichel"] }, - { "category": "people", "char": "👳â€â™€ï¸", "name": "woman_with_turban", "keywords": ["female", "indian", "hinduism", "arabs", "woman"] }, - { "category": "people", "char": "👳", "name": "man_with_turban", "keywords": ["male", "indian", "hinduism", "arabs"] }, - { "category": "people", "char": "👮â€â™€ï¸", "name": "policewoman", "keywords": ["woman", "police", "law", "legal", "enforcement", "arrest", "911", "female"] }, - { "category": "people", "char": "👮", "name": "policeman", "keywords": ["man", "police", "law", "legal", "enforcement", "arrest", "911"] }, - { "category": "people", "char": "👷â€â™€ï¸", "name": "construction_worker_woman", "keywords": ["female", "human", "wip", "build", "construction", "worker", "labor", "woman"] }, - { "category": "people", "char": "👷", "name": "construction_worker_man", "keywords": ["male", "human", "wip", "guy", "build", "construction", "worker", "labor"] }, - { "category": "people", "char": "💂â€â™€ï¸", "name": "guardswoman", "keywords": ["uk", "gb", "british", "female", "royal", "woman"] }, - { "category": "people", "char": "💂", "name": "guardsman", "keywords": ["uk", "gb", "british", "male", "guy", "royal"] }, - { "category": "people", "char": "🕵ï¸â€â™€ï¸", "name": "female_detective", "keywords": ["human", "spy", "detective", "female", "woman"] }, - { "category": "people", "char": "🕵", "name": "male_detective", "keywords": ["human", "spy", "detective"] }, - { "category": "people", "char": "🧑â€âš•ï¸", "name": "health_worker", "keywords": ["doctor", "nurse", "therapist", "healthcare", "human"] }, - { "category": "people", "char": "👩â€âš•ï¸", "name": "woman_health_worker", "keywords": ["doctor", "nurse", "therapist", "healthcare", "woman", "human"] }, - { "category": "people", "char": "👨â€âš•ï¸", "name": "man_health_worker", "keywords": ["doctor", "nurse", "therapist", "healthcare", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸŒ¾", "name": "farmer", "keywords": ["rancher", "gardener", "human"] }, - { "category": "people", "char": "👩â€ðŸŒ¾", "name": "woman_farmer", "keywords": ["rancher", "gardener", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸŒ¾", "name": "man_farmer", "keywords": ["rancher", "gardener", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸ³", "name": "cook", "keywords": ["chef", "human"] }, - { "category": "people", "char": "👩â€ðŸ³", "name": "woman_cook", "keywords": ["chef", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸ³", "name": "man_cook", "keywords": ["chef", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸŽ“", "name": "student", "keywords": ["graduate", "human"] }, - { "category": "people", "char": "👩â€ðŸŽ“", "name": "woman_student", "keywords": ["graduate", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸŽ“", "name": "man_student", "keywords": ["graduate", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸŽ¤", "name": "singer", "keywords": ["rockstar", "entertainer", "human"] }, - { "category": "people", "char": "👩â€ðŸŽ¤", "name": "woman_singer", "keywords": ["rockstar", "entertainer", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸŽ¤", "name": "man_singer", "keywords": ["rockstar", "entertainer", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸ«", "name": "teacher", "keywords": ["instructor", "professor", "human"] }, - { "category": "people", "char": "👩â€ðŸ«", "name": "woman_teacher", "keywords": ["instructor", "professor", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸ«", "name": "man_teacher", "keywords": ["instructor", "professor", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸ", "name": "factory_worker", "keywords": ["assembly", "industrial", "human"] }, - { "category": "people", "char": "👩â€ðŸ", "name": "woman_factory_worker", "keywords": ["assembly", "industrial", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸ", "name": "man_factory_worker", "keywords": ["assembly", "industrial", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸ’»", "name": "technologist", "keywords": ["coder", "developer", "engineer", "programmer", "software", "human", "laptop", "computer"] }, - { "category": "people", "char": "👩â€ðŸ’»", "name": "woman_technologist", "keywords": ["coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer"] }, - { "category": "people", "char": "👨â€ðŸ’»", "name": "man_technologist", "keywords": ["coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer"] }, - { "category": "people", "char": "🧑â€ðŸ’¼", "name": "office_worker", "keywords": ["business", "manager", "human"] }, - { "category": "people", "char": "👩â€ðŸ’¼", "name": "woman_office_worker", "keywords": ["business", "manager", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸ’¼", "name": "man_office_worker", "keywords": ["business", "manager", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸ”§", "name": "mechanic", "keywords": ["plumber", "human", "wrench"] }, - { "category": "people", "char": "👩â€ðŸ”§", "name": "woman_mechanic", "keywords": ["plumber", "woman", "human", "wrench"] }, - { "category": "people", "char": "👨â€ðŸ”§", "name": "man_mechanic", "keywords": ["plumber", "man", "human", "wrench"] }, - { "category": "people", "char": "🧑â€ðŸ”¬", "name": "scientist", "keywords": ["biologist", "chemist", "engineer", "physicist", "human"] }, - { "category": "people", "char": "👩â€ðŸ”¬", "name": "woman_scientist", "keywords": ["biologist", "chemist", "engineer", "physicist", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸ”¬", "name": "man_scientist", "keywords": ["biologist", "chemist", "engineer", "physicist", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸŽ¨", "name": "artist", "keywords": ["painter", "human"] }, - { "category": "people", "char": "👩â€ðŸŽ¨", "name": "woman_artist", "keywords": ["painter", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸŽ¨", "name": "man_artist", "keywords": ["painter", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸš’", "name": "firefighter", "keywords": ["fireman", "human"] }, - { "category": "people", "char": "👩â€ðŸš’", "name": "woman_firefighter", "keywords": ["fireman", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸš’", "name": "man_firefighter", "keywords": ["fireman", "man", "human"] }, - { "category": "people", "char": "🧑â€âœˆï¸", "name": "pilot", "keywords": ["aviator", "plane", "human"] }, - { "category": "people", "char": "👩â€âœˆï¸", "name": "woman_pilot", "keywords": ["aviator", "plane", "woman", "human"] }, - { "category": "people", "char": "👨â€âœˆï¸", "name": "man_pilot", "keywords": ["aviator", "plane", "man", "human"] }, - { "category": "people", "char": "🧑â€ðŸš€", "name": "astronaut", "keywords": ["space", "rocket", "human"] }, - { "category": "people", "char": "👩â€ðŸš€", "name": "woman_astronaut", "keywords": ["space", "rocket", "woman", "human"] }, - { "category": "people", "char": "👨â€ðŸš€", "name": "man_astronaut", "keywords": ["space", "rocket", "man", "human"] }, - { "category": "people", "char": "🧑â€âš–ï¸", "name": "judge", "keywords": ["justice", "court", "human"] }, - { "category": "people", "char": "👩â€âš–ï¸", "name": "woman_judge", "keywords": ["justice", "court", "woman", "human"] }, - { "category": "people", "char": "👨â€âš–ï¸", "name": "man_judge", "keywords": ["justice", "court", "man", "human"] }, - { "category": "people", "char": "🦸â€â™€ï¸", "name": "woman_superhero", "keywords": ["woman", "female", "good", "heroine", "superpowers"] }, - { "category": "people", "char": "🦸â€â™‚ï¸", "name": "man_superhero", "keywords": ["man", "male", "good", "hero", "superpowers"] }, - { "category": "people", "char": "🦹â€â™€ï¸", "name": "woman_supervillain", "keywords": ["woman", "female", "evil", "bad", "criminal", "heroine", "superpowers"] }, - { "category": "people", "char": "🦹â€â™‚ï¸", "name": "man_supervillain", "keywords": ["man", "male", "evil", "bad", "criminal", "hero", "superpowers"] }, - { "category": "people", "char": "🤶", "name": "mrs_claus", "keywords": ["woman", "female", "xmas", "mother christmas"] }, - { "category": "people", "char": "\uD83E\uDDD1\u200D\uD83C\uDF84", "name": "mx_claus", "keywords": ["xmas", "christmas"] }, - { "category": "people", "char": "🎅", "name": "santa", "keywords": ["festival", "man", "male", "xmas", "father christmas"] }, - { "category": "people", "char": "🥷", "name": "ninja", "keywords": [] }, - { "category": "people", "char": "🧙â€â™€ï¸", "name": "sorceress", "keywords": ["woman", "female", "mage", "witch"] }, - { "category": "people", "char": "🧙â€â™‚ï¸", "name": "wizard", "keywords": ["man", "male", "mage", "sorcerer"] }, - { "category": "people", "char": "ðŸ§â€â™€ï¸", "name": "woman_elf", "keywords": ["woman", "female"] }, - { "category": "people", "char": "ðŸ§â€â™‚ï¸", "name": "man_elf", "keywords": ["man", "male"] }, - { "category": "people", "char": "🧛â€â™€ï¸", "name": "woman_vampire", "keywords": ["woman", "female"] }, - { "category": "people", "char": "🧛â€â™‚ï¸", "name": "man_vampire", "keywords": ["man", "male", "dracula"] }, - { "category": "people", "char": "🧟â€â™€ï¸", "name": "woman_zombie", "keywords": ["woman", "female", "undead", "walking dead"] }, - { "category": "people", "char": "🧟â€â™‚ï¸", "name": "man_zombie", "keywords": ["man", "male", "dracula", "undead", "walking dead"] }, - { "category": "people", "char": "🧞â€â™€ï¸", "name": "woman_genie", "keywords": ["woman", "female"] }, - { "category": "people", "char": "🧞â€â™‚ï¸", "name": "man_genie", "keywords": ["man", "male"] }, - { "category": "people", "char": "🧜â€â™€ï¸", "name": "mermaid", "keywords": ["woman", "female", "merwoman", "ariel"] }, - { "category": "people", "char": "🧜â€â™‚ï¸", "name": "merman", "keywords": ["man", "male", "triton"] }, - { "category": "people", "char": "🧚â€â™€ï¸", "name": "woman_fairy", "keywords": ["woman", "female"] }, - { "category": "people", "char": "🧚â€â™‚ï¸", "name": "man_fairy", "keywords": ["man", "male"] }, - { "category": "people", "char": "👼", "name": "angel", "keywords": ["heaven", "wings", "halo"] }, - { "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] }, - { "category": "people", "char": "🤰", "name": "pregnant_woman", "keywords": ["baby"] }, - { "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] }, - { "category": "people", "char": "🤱", "name": "breastfeeding", "keywords": ["nursing", "baby"] }, - { "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] }, - { "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] }, - { "category": "people", "char": "\uD83E\uDDD1\u200D\uD83C\uDF7C", "name": "person_feeding_baby", "keywords": [] }, - { "category": "people", "char": "👸", "name": "princess", "keywords": ["girl", "woman", "female", "blond", "crown", "royal", "queen"] }, - { "category": "people", "char": "🤴", "name": "prince", "keywords": ["boy", "man", "male", "crown", "royal", "king"] }, - { "category": "people", "char": "👰", "name": "person_with_veil", "keywords": ["couple", "marriage", "wedding", "woman", "bride"] }, - { "category": "people", "char": "👰", "name": "bride_with_veil", "keywords": ["couple", "marriage", "wedding", "woman", "bride"] }, - { "category": "people", "char": "🤵", "name": "person_in_tuxedo", "keywords": ["couple", "marriage", "wedding", "groom"] }, - { "category": "people", "char": "🤵", "name": "man_in_tuxedo", "keywords": ["couple", "marriage", "wedding", "groom"] }, - { "category": "people", "char": "ðŸƒâ€â™€ï¸", "name": "running_woman", "keywords": ["woman", "walking", "exercise", "race", "running", "female"] }, - { "category": "people", "char": "ðŸƒ", "name": "running_man", "keywords": ["man", "walking", "exercise", "race", "running"] }, - { "category": "people", "char": "🚶â€â™€ï¸", "name": "walking_woman", "keywords": ["human", "feet", "steps", "woman", "female"] }, - { "category": "people", "char": "🚶", "name": "walking_man", "keywords": ["human", "feet", "steps"] }, - { "category": "people", "char": "💃", "name": "dancer", "keywords": ["female", "girl", "woman", "fun"] }, - { "category": "people", "char": "🕺", "name": "man_dancing", "keywords": ["male", "boy", "fun", "dancer"] }, - { "category": "people", "char": "👯", "name": "dancing_women", "keywords": ["female", "bunny", "women", "girls"] }, - { "category": "people", "char": "👯â€â™‚ï¸", "name": "dancing_men", "keywords": ["male", "bunny", "men", "boys"] }, - { "category": "people", "char": "👫", "name": "couple", "keywords": ["pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage"] }, - { "category": "people", "char": "\uD83E\uDDD1\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1", "name": "people_holding_hands", "keywords": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "human"] }, - { "category": "people", "char": "👬", "name": "two_men_holding_hands", "keywords": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "man", "human"] }, - { "category": "people", "char": "ðŸ‘", "name": "two_women_holding_hands", "keywords": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "female", "human"] }, - { "category": "people", "char": "🫂", "name": "people_hugging", "keywords": [] }, - { "category": "people", "char": "🙇â€â™€ï¸", "name": "bowing_woman", "keywords": ["woman", "female", "girl"] }, - { "category": "people", "char": "🙇", "name": "bowing_man", "keywords": ["man", "male", "boy"] }, - { "category": "people", "char": "🤦â€â™‚ï¸", "name": "man_facepalming", "keywords": ["man", "male", "boy", "disbelief"] }, - { "category": "people", "char": "🤦â€â™€ï¸", "name": "woman_facepalming", "keywords": ["woman", "female", "girl", "disbelief"] }, - { "category": "people", "char": "🤷", "name": "woman_shrugging", "keywords": ["woman", "female", "girl", "confused", "indifferent", "doubt"] }, - { "category": "people", "char": "🤷â€â™‚ï¸", "name": "man_shrugging", "keywords": ["man", "male", "boy", "confused", "indifferent", "doubt"] }, - { "category": "people", "char": "ðŸ’", "name": "tipping_hand_woman", "keywords": ["female", "girl", "woman", "human", "information"] }, - { "category": "people", "char": "ðŸ’â€â™‚ï¸", "name": "tipping_hand_man", "keywords": ["male", "boy", "man", "human", "information"] }, - { "category": "people", "char": "🙅", "name": "no_good_woman", "keywords": ["female", "girl", "woman", "nope"] }, - { "category": "people", "char": "🙅â€â™‚ï¸", "name": "no_good_man", "keywords": ["male", "boy", "man", "nope"] }, - { "category": "people", "char": "🙆", "name": "ok_woman", "keywords": ["women", "girl", "female", "pink", "human", "woman"] }, - { "category": "people", "char": "🙆â€â™‚ï¸", "name": "ok_man", "keywords": ["men", "boy", "male", "blue", "human", "man"] }, - { "category": "people", "char": "🙋", "name": "raising_hand_woman", "keywords": ["female", "girl", "woman"] }, - { "category": "people", "char": "🙋â€â™‚ï¸", "name": "raising_hand_man", "keywords": ["male", "boy", "man"] }, - { "category": "people", "char": "🙎", "name": "pouting_woman", "keywords": ["female", "girl", "woman"] }, - { "category": "people", "char": "🙎â€â™‚ï¸", "name": "pouting_man", "keywords": ["male", "boy", "man"] }, - { "category": "people", "char": "ðŸ™", "name": "frowning_woman", "keywords": ["female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy"] }, - { "category": "people", "char": "ðŸ™â€â™‚ï¸", "name": "frowning_man", "keywords": ["male", "boy", "man", "sad", "depressed", "discouraged", "unhappy"] }, - { "category": "people", "char": "💇", "name": "haircut_woman", "keywords": ["female", "girl", "woman"] }, - { "category": "people", "char": "💇â€â™‚ï¸", "name": "haircut_man", "keywords": ["male", "boy", "man"] }, - { "category": "people", "char": "💆", "name": "massage_woman", "keywords": ["female", "girl", "woman", "head"] }, - { "category": "people", "char": "💆â€â™‚ï¸", "name": "massage_man", "keywords": ["male", "boy", "man", "head"] }, - { "category": "people", "char": "🧖â€â™€ï¸", "name": "woman_in_steamy_room", "keywords": ["female", "woman", "spa", "steamroom", "sauna"] }, - { "category": "people", "char": "🧖â€â™‚ï¸", "name": "man_in_steamy_room", "keywords": ["male", "man", "spa", "steamroom", "sauna"] }, - { "category": "people", "char": "ðŸ§â€â™€ï¸", "name": "woman_deaf", "keywords": ["woman", "female"] }, - { "category": "people", "char": "ðŸ§â€â™‚ï¸", "name": "man_deaf", "keywords": ["man", "male"] }, - { "category": "people", "char": "ðŸ§â€â™€ï¸", "name": "woman_standing", "keywords": ["woman", "female"] }, - { "category": "people", "char": "ðŸ§â€â™‚ï¸", "name": "man_standing", "keywords": ["man", "male"] }, - { "category": "people", "char": "🧎â€â™€ï¸", "name": "woman_kneeling", "keywords": ["woman", "female"] }, - { "category": "people", "char": "🧎â€â™‚ï¸", "name": "man_kneeling", "keywords": ["man", "male"] }, - { "category": "people", "char": "🧑â€ðŸ¦¯", "name": "person_with_probing_cane", "keywords": ["accessibility", "blind"] }, - { "category": "people", "char": "👩â€ðŸ¦¯", "name": "woman_with_probing_cane", "keywords": ["woman", "female", "accessibility", "blind"] }, - { "category": "people", "char": "👨â€ðŸ¦¯", "name": "man_with_probing_cane", "keywords": ["man", "male", "accessibility", "blind"] }, - { "category": "people", "char": "🧑â€ðŸ¦¼", "name": "person_in_motorized_wheelchair", "keywords": ["accessibility"] }, - { "category": "people", "char": "👩â€ðŸ¦¼", "name": "woman_in_motorized_wheelchair", "keywords": ["woman", "female", "accessibility"] }, - { "category": "people", "char": "👨â€ðŸ¦¼", "name": "man_in_motorized_wheelchair", "keywords": ["man", "male", "accessibility"] }, - { "category": "people", "char": "🧑â€ðŸ¦½", "name": "person_in_manual_wheelchair", "keywords": ["accessibility"] }, - { "category": "people", "char": "👩â€ðŸ¦½", "name": "woman_in_manual_wheelchair", "keywords": ["woman", "female", "accessibility"] }, - { "category": "people", "char": "👨â€ðŸ¦½", "name": "man_in_manual_wheelchair", "keywords": ["man", "male", "accessibility"] }, - { "category": "people", "char": "💑", "name": "couple_with_heart_woman_man", "keywords": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"] }, - { "category": "people", "char": "👩â€â¤ï¸â€ðŸ‘©", "name": "couple_with_heart_woman_woman", "keywords": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"] }, - { "category": "people", "char": "👨â€â¤ï¸â€ðŸ‘¨", "name": "couple_with_heart_man_man", "keywords": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"] }, - { "category": "people", "char": "ðŸ’", "name": "couplekiss_man_woman", "keywords": ["pair", "valentines", "love", "like", "dating", "marriage"] }, - { "category": "people", "char": "👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©", "name": "couplekiss_woman_woman", "keywords": ["pair", "valentines", "love", "like", "dating", "marriage"] }, - { "category": "people", "char": "👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨", "name": "couplekiss_man_man", "keywords": ["pair", "valentines", "love", "like", "dating", "marriage"] }, - { "category": "people", "char": "👪", "name": "family_man_woman_boy", "keywords": ["home", "parents", "child", "mom", "dad", "father", "mother", "people", "human"] }, - { "category": "people", "char": "👨â€ðŸ‘©â€ðŸ‘§", "name": "family_man_woman_girl", "keywords": ["home", "parents", "people", "human", "child"] }, - { "category": "people", "char": "👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦", "name": "family_man_woman_girl_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦", "name": "family_man_woman_boy_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§", "name": "family_man_woman_girl_girl", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘©â€ðŸ‘¦", "name": "family_woman_woman_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘©â€ðŸ‘§", "name": "family_woman_woman_girl", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦", "name": "family_woman_woman_girl_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦", "name": "family_woman_woman_boy_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§", "name": "family_woman_woman_girl_girl", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¨â€ðŸ‘¦", "name": "family_man_man_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¨â€ðŸ‘§", "name": "family_man_man_girl", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦", "name": "family_man_man_girl_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦", "name": "family_man_man_boy_boy", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§", "name": "family_man_man_girl_girl", "keywords": ["home", "parents", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘¦", "name": "family_woman_boy", "keywords": ["home", "parent", "people", "human", "child"] }, - { "category": "people", "char": "👩â€ðŸ‘§", "name": "family_woman_girl", "keywords": ["home", "parent", "people", "human", "child"] }, - { "category": "people", "char": "👩â€ðŸ‘§â€ðŸ‘¦", "name": "family_woman_girl_boy", "keywords": ["home", "parent", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘¦â€ðŸ‘¦", "name": "family_woman_boy_boy", "keywords": ["home", "parent", "people", "human", "children"] }, - { "category": "people", "char": "👩â€ðŸ‘§â€ðŸ‘§", "name": "family_woman_girl_girl", "keywords": ["home", "parent", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¦", "name": "family_man_boy", "keywords": ["home", "parent", "people", "human", "child"] }, - { "category": "people", "char": "👨â€ðŸ‘§", "name": "family_man_girl", "keywords": ["home", "parent", "people", "human", "child"] }, - { "category": "people", "char": "👨â€ðŸ‘§â€ðŸ‘¦", "name": "family_man_girl_boy", "keywords": ["home", "parent", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘¦â€ðŸ‘¦", "name": "family_man_boy_boy", "keywords": ["home", "parent", "people", "human", "children"] }, - { "category": "people", "char": "👨â€ðŸ‘§â€ðŸ‘§", "name": "family_man_girl_girl", "keywords": ["home", "parent", "people", "human", "children"] }, - { "category": "people", "char": "🧶", "name": "yarn", "keywords": ["ball", "crochet", "knit"] }, - { "category": "people", "char": "🧵", "name": "thread", "keywords": ["needle", "sewing", "spool", "string"] }, - { "category": "people", "char": "🧥", "name": "coat", "keywords": ["jacket"] }, - { "category": "people", "char": "🥼", "name": "labcoat", "keywords": ["doctor", "experiment", "scientist", "chemist"] }, - { "category": "people", "char": "👚", "name": "womans_clothes", "keywords": ["fashion", "shopping_bags", "female"] }, - { "category": "people", "char": "👕", "name": "tshirt", "keywords": ["fashion", "cloth", "casual", "shirt", "tee"] }, - { "category": "people", "char": "👖", "name": "jeans", "keywords": ["fashion", "shopping"] }, - { "category": "people", "char": "👔", "name": "necktie", "keywords": ["shirt", "suitup", "formal", "fashion", "cloth", "business"] }, - { "category": "people", "char": "👗", "name": "dress", "keywords": ["clothes", "fashion", "shopping"] }, - { "category": "people", "char": "👙", "name": "bikini", "keywords": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"] }, - { "category": "people", "char": "🩱", "name": "one_piece_swimsuit", "keywords": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"] }, - { "category": "people", "char": "👘", "name": "kimono", "keywords": ["dress", "fashion", "women", "female", "japanese"] }, - { "category": "people", "char": "🥻", "name": "sari", "keywords": ["dress", "fashion", "women", "female"] }, - { "category": "people", "char": "🩲", "name": "briefs", "keywords": ["dress", "fashion"] }, - { "category": "people", "char": "🩳", "name": "shorts", "keywords": ["dress", "fashion"] }, - { "category": "people", "char": "💄", "name": "lipstick", "keywords": ["female", "girl", "fashion", "woman"] }, - { "category": "people", "char": "💋", "name": "kiss", "keywords": ["face", "lips", "love", "like", "affection", "valentines"] }, - { "category": "people", "char": "👣", "name": "footprints", "keywords": ["feet", "tracking", "walking", "beach"] }, - { "category": "people", "char": "🥿", "name": "flat_shoe", "keywords": ["ballet", "slip-on", "slipper"] }, - { "category": "people", "char": "👠", "name": "high_heel", "keywords": ["fashion", "shoes", "female", "pumps", "stiletto"] }, - { "category": "people", "char": "👡", "name": "sandal", "keywords": ["shoes", "fashion", "flip flops"] }, - { "category": "people", "char": "👢", "name": "boot", "keywords": ["shoes", "fashion"] }, - { "category": "people", "char": "👞", "name": "mans_shoe", "keywords": ["fashion", "male"] }, - { "category": "people", "char": "👟", "name": "athletic_shoe", "keywords": ["shoes", "sports", "sneakers"] }, - { "category": "people", "char": "🩴", "name": "thong_sandal", "keywords": [] }, - { "category": "people", "char": "🩰", "name": "ballet_shoes", "keywords": ["shoes", "sports"] }, - { "category": "people", "char": "🧦", "name": "socks", "keywords": ["stockings", "clothes"] }, - { "category": "people", "char": "🧤", "name": "gloves", "keywords": ["hands", "winter", "clothes"] }, - { "category": "people", "char": "🧣", "name": "scarf", "keywords": ["neck", "winter", "clothes"] }, - { "category": "people", "char": "👒", "name": "womans_hat", "keywords": ["fashion", "accessories", "female", "lady", "spring"] }, - { "category": "people", "char": "🎩", "name": "tophat", "keywords": ["magic", "gentleman", "classy", "circus"] }, - { "category": "people", "char": "🧢", "name": "billed_hat", "keywords": ["cap", "baseball"] }, - { "category": "people", "char": "⛑", "name": "rescue_worker_helmet", "keywords": ["construction", "build"] }, - { "category": "people", "char": "🪖", "name": "military_helmet", "keywords": [] }, - { "category": "people", "char": "🎓", "name": "mortar_board", "keywords": ["school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education"] }, - { "category": "people", "char": "👑", "name": "crown", "keywords": ["king", "kod", "leader", "royalty", "lord"] }, - { "category": "people", "char": "🎒", "name": "school_satchel", "keywords": ["student", "education", "bag", "backpack"] }, - { "category": "people", "char": "🧳", "name": "luggage", "keywords": ["packing", "travel"] }, - { "category": "people", "char": "ðŸ‘", "name": "pouch", "keywords": ["bag", "accessories", "shopping"] }, - { "category": "people", "char": "👛", "name": "purse", "keywords": ["fashion", "accessories", "money", "sales", "shopping"] }, - { "category": "people", "char": "👜", "name": "handbag", "keywords": ["fashion", "accessory", "accessories", "shopping"] }, - { "category": "people", "char": "💼", "name": "briefcase", "keywords": ["business", "documents", "work", "law", "legal", "job", "career"] }, - { "category": "people", "char": "👓", "name": "eyeglasses", "keywords": ["fashion", "accessories", "eyesight", "nerdy", "dork", "geek"] }, - { "category": "people", "char": "🕶", "name": "dark_sunglasses", "keywords": ["face", "cool", "accessories"] }, - { "category": "people", "char": "🥽", "name": "goggles", "keywords": ["eyes", "protection", "safety"] }, - { "category": "people", "char": "ðŸ’", "name": "ring", "keywords": ["wedding", "propose", "marriage", "valentines", "diamond", "fashion", "jewelry", "gem", "engagement"] }, - { "category": "people", "char": "🌂", "name": "closed_umbrella", "keywords": ["weather", "rain", "drizzle"] }, - { "category": "animals_and_nature", "char": "ðŸ¶", "name": "dog", "keywords": ["animal", "friend", "nature", "woof", "puppy", "pet", "faithful"] }, - { "category": "animals_and_nature", "char": "ðŸ±", "name": "cat", "keywords": ["animal", "meow", "nature", "pet", "kitten"] }, - { "category": "animals_and_nature", "char": "ðŸˆâ€â¬›", "name": "black_cat", "keywords": ["animal", "meow", "nature", "pet", "kitten"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "mouse", "keywords": ["animal", "nature", "cheese_wedge", "rodent"] }, - { "category": "animals_and_nature", "char": "ðŸ¹", "name": "hamster", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ°", "name": "rabbit", "keywords": ["animal", "nature", "pet", "spring", "magic", "bunny"] }, - { "category": "animals_and_nature", "char": "🦊", "name": "fox_face", "keywords": ["animal", "nature", "face"] }, - { "category": "animals_and_nature", "char": "ðŸ»", "name": "bear", "keywords": ["animal", "nature", "wild"] }, - { "category": "animals_and_nature", "char": "ðŸ¼", "name": "panda_face", "keywords": ["animal", "nature", "panda"] }, - { "category": "animals_and_nature", "char": "ðŸ¨", "name": "koala", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ¯", "name": "tiger", "keywords": ["animal", "cat", "danger", "wild", "nature", "roar"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "lion", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ®", "name": "cow", "keywords": ["beef", "ox", "animal", "nature", "moo", "milk"] }, - { "category": "animals_and_nature", "char": "ðŸ·", "name": "pig", "keywords": ["animal", "oink", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ½", "name": "pig_nose", "keywords": ["animal", "oink"] }, - { "category": "animals_and_nature", "char": "ðŸ¸", "name": "frog", "keywords": ["animal", "nature", "croak", "toad"] }, - { "category": "animals_and_nature", "char": "🦑", "name": "squid", "keywords": ["animal", "nature", "ocean", "sea"] }, - { "category": "animals_and_nature", "char": "ðŸ™", "name": "octopus", "keywords": ["animal", "creature", "ocean", "sea", "nature", "beach"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "shrimp", "keywords": ["animal", "ocean", "nature", "seafood"] }, - { "category": "animals_and_nature", "char": "ðŸµ", "name": "monkey_face", "keywords": ["animal", "nature", "circus"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "gorilla", "keywords": ["animal", "nature", "circus"] }, - { "category": "animals_and_nature", "char": "🙈", "name": "see_no_evil", "keywords": ["monkey", "animal", "nature", "haha"] }, - { "category": "animals_and_nature", "char": "🙉", "name": "hear_no_evil", "keywords": ["animal", "monkey", "nature"] }, - { "category": "animals_and_nature", "char": "🙊", "name": "speak_no_evil", "keywords": ["monkey", "animal", "nature", "omg"] }, - { "category": "animals_and_nature", "char": "ðŸ’", "name": "monkey", "keywords": ["animal", "nature", "banana", "circus"] }, - { "category": "animals_and_nature", "char": "ðŸ”", "name": "chicken", "keywords": ["animal", "cluck", "nature", "bird"] }, - { "category": "animals_and_nature", "char": "ðŸ§", "name": "penguin", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "bird", "keywords": ["animal", "nature", "fly", "tweet", "spring"] }, - { "category": "animals_and_nature", "char": "ðŸ¤", "name": "baby_chick", "keywords": ["animal", "chicken", "bird"] }, - { "category": "animals_and_nature", "char": "ðŸ£", "name": "hatching_chick", "keywords": ["animal", "chicken", "egg", "born", "baby", "bird"] }, - { "category": "animals_and_nature", "char": "ðŸ¥", "name": "hatched_chick", "keywords": ["animal", "chicken", "baby", "bird"] }, - { "category": "animals_and_nature", "char": "🦆", "name": "duck", "keywords": ["animal", "nature", "bird", "mallard"] }, - { "category": "animals_and_nature", "char": "🦅", "name": "eagle", "keywords": ["animal", "nature", "bird"] }, - { "category": "animals_and_nature", "char": "🦉", "name": "owl", "keywords": ["animal", "nature", "bird", "hoot"] }, - { "category": "animals_and_nature", "char": "🦇", "name": "bat", "keywords": ["animal", "nature", "blind", "vampire"] }, - { "category": "animals_and_nature", "char": "ðŸº", "name": "wolf", "keywords": ["animal", "nature", "wild"] }, - { "category": "animals_and_nature", "char": "ðŸ—", "name": "boar", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ´", "name": "horse", "keywords": ["animal", "brown", "nature"] }, - { "category": "animals_and_nature", "char": "🦄", "name": "unicorn", "keywords": ["animal", "nature", "mystical"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "honeybee", "keywords": ["animal", "insect", "nature", "bug", "spring", "honey"] }, - { "category": "animals_and_nature", "char": "ðŸ›", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] }, - { "category": "animals_and_nature", "char": "🦋", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] }, - { "category": "animals_and_nature", "char": "ðŸŒ", "name": "snail", "keywords": ["slow", "animal", "shell"] }, - { "category": "animals_and_nature", "char": "ðŸž", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, - { "category": "animals_and_nature", "char": "ðŸœ", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] }, - { "category": "animals_and_nature", "char": "🦗", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] }, - { "category": "animals_and_nature", "char": "🕷", "name": "spider", "keywords": ["animal", "arachnid"] }, - { "category": "animals_and_nature", "char": "🪲", "name": "beetle", "keywords": ["animal"] }, - { "category": "animals_and_nature", "char": "🪳", "name": "cockroach", "keywords": ["animal"] }, - { "category": "animals_and_nature", "char": "🪰", "name": "fly", "keywords": ["animal"] }, - { "category": "animals_and_nature", "char": "🪱", "name": "worm", "keywords": ["animal"] }, - { "category": "animals_and_nature", "char": "🦂", "name": "scorpion", "keywords": ["animal", "arachnid"] }, - { "category": "animals_and_nature", "char": "🦀", "name": "crab", "keywords": ["animal", "crustacean"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "snake", "keywords": ["animal", "evil", "nature", "hiss", "python"] }, - { "category": "animals_and_nature", "char": "🦎", "name": "lizard", "keywords": ["animal", "nature", "reptile"] }, - { "category": "animals_and_nature", "char": "🦖", "name": "t-rex", "keywords": ["animal", "nature", "dinosaur", "tyrannosaurus", "extinct"] }, - { "category": "animals_and_nature", "char": "🦕", "name": "sauropod", "keywords": ["animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct"] }, - { "category": "animals_and_nature", "char": "ðŸ¢", "name": "turtle", "keywords": ["animal", "slow", "nature", "tortoise"] }, - { "category": "animals_and_nature", "char": "ðŸ ", "name": "tropical_fish", "keywords": ["animal", "swim", "ocean", "beach", "nemo"] }, - { "category": "animals_and_nature", "char": "ðŸŸ", "name": "fish", "keywords": ["animal", "food", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ¡", "name": "blowfish", "keywords": ["animal", "nature", "food", "sea", "ocean"] }, - { "category": "animals_and_nature", "char": "ðŸ¬", "name": "dolphin", "keywords": ["animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach"] }, - { "category": "animals_and_nature", "char": "🦈", "name": "shark", "keywords": ["animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach"] }, - { "category": "animals_and_nature", "char": "ðŸ³", "name": "whale", "keywords": ["animal", "nature", "sea", "ocean"] }, - { "category": "animals_and_nature", "char": "ðŸ‹", "name": "whale2", "keywords": ["animal", "nature", "sea", "ocean"] }, - { "category": "animals_and_nature", "char": "ðŸŠ", "name": "crocodile", "keywords": ["animal", "nature", "reptile", "lizard", "alligator"] }, - { "category": "animals_and_nature", "char": "ðŸ†", "name": "leopard", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦓", "name": "zebra", "keywords": ["animal", "nature", "stripes", "safari"] }, - { "category": "animals_and_nature", "char": "ðŸ…", "name": "tiger2", "keywords": ["animal", "nature", "roar"] }, - { "category": "animals_and_nature", "char": "ðŸƒ", "name": "water_buffalo", "keywords": ["animal", "nature", "ox", "cow"] }, - { "category": "animals_and_nature", "char": "ðŸ‚", "name": "ox", "keywords": ["animal", "cow", "beef"] }, - { "category": "animals_and_nature", "char": "ðŸ„", "name": "cow2", "keywords": ["beef", "ox", "animal", "nature", "moo", "milk"] }, - { "category": "animals_and_nature", "char": "🦌", "name": "deer", "keywords": ["animal", "nature", "horns", "venison"] }, - { "category": "animals_and_nature", "char": "ðŸª", "name": "dromedary_camel", "keywords": ["animal", "hot", "desert", "hump"] }, - { "category": "animals_and_nature", "char": "ðŸ«", "name": "camel", "keywords": ["animal", "nature", "hot", "desert", "hump"] }, - { "category": "animals_and_nature", "char": "🦒", "name": "giraffe", "keywords": ["animal", "nature", "spots", "safari"] }, - { "category": "animals_and_nature", "char": "ðŸ˜", "name": "elephant", "keywords": ["animal", "nature", "nose", "th", "circus"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "rhinoceros", "keywords": ["animal", "nature", "horn"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "goat", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "ram", "keywords": ["animal", "sheep", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ‘", "name": "sheep", "keywords": ["animal", "nature", "wool", "shipit"] }, - { "category": "animals_and_nature", "char": "ðŸŽ", "name": "racehorse", "keywords": ["animal", "gamble", "luck"] }, - { "category": "animals_and_nature", "char": "ðŸ–", "name": "pig2", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ€", "name": "rat", "keywords": ["animal", "mouse", "rodent"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "mouse2", "keywords": ["animal", "nature", "rodent"] }, - { "category": "animals_and_nature", "char": "ðŸ“", "name": "rooster", "keywords": ["animal", "nature", "chicken"] }, - { "category": "animals_and_nature", "char": "🦃", "name": "turkey", "keywords": ["animal", "bird"] }, - { "category": "animals_and_nature", "char": "🕊", "name": "dove", "keywords": ["animal", "bird"] }, - { "category": "animals_and_nature", "char": "ðŸ•", "name": "dog2", "keywords": ["animal", "nature", "friend", "doge", "pet", "faithful"] }, - { "category": "animals_and_nature", "char": "ðŸ©", "name": "poodle", "keywords": ["dog", "animal", "101", "nature", "pet"] }, - { "category": "animals_and_nature", "char": "ðŸˆ", "name": "cat2", "keywords": ["animal", "meow", "pet", "cats"] }, - { "category": "animals_and_nature", "char": "ðŸ‡", "name": "rabbit2", "keywords": ["animal", "nature", "pet", "magic", "spring"] }, - { "category": "animals_and_nature", "char": "ðŸ¿", "name": "chipmunk", "keywords": ["animal", "nature", "rodent", "squirrel"] }, - { "category": "animals_and_nature", "char": "🦔", "name": "hedgehog", "keywords": ["animal", "nature", "spiny"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "raccoon", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦙", "name": "llama", "keywords": ["animal", "nature", "alpaca"] }, - { "category": "animals_and_nature", "char": "🦛", "name": "hippopotamus", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦘", "name": "kangaroo", "keywords": ["animal", "nature", "australia", "joey", "hop", "marsupial"] }, - { "category": "animals_and_nature", "char": "🦡", "name": "badger", "keywords": ["animal", "nature", "honey"] }, - { "category": "animals_and_nature", "char": "🦢", "name": "swan", "keywords": ["animal", "nature", "bird"] }, - { "category": "animals_and_nature", "char": "🦚", "name": "peacock", "keywords": ["animal", "nature", "peahen", "bird"] }, - { "category": "animals_and_nature", "char": "🦜", "name": "parrot", "keywords": ["animal", "nature", "bird", "pirate", "talk"] }, - { "category": "animals_and_nature", "char": "🦞", "name": "lobster", "keywords": ["animal", "nature", "bisque", "claws", "seafood"] }, - { "category": "animals_and_nature", "char": "🦠", "name": "microbe", "keywords": ["amoeba", "bacteria", "germs"] }, - { "category": "animals_and_nature", "char": "🦟", "name": "mosquito", "keywords": ["animal", "nature", "insect", "malaria"] }, - { "category": "animals_and_nature", "char": "🦬", "name": "bison", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦣", "name": "mammoth", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦫", "name": "beaver", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ»â€â„ï¸", "name": "polar_bear", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦤", "name": "dodo", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🪶", "name": "feather", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ¦", "name": "seal", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ¾", "name": "paw_prints", "keywords": ["animal", "tracking", "footprints", "dog", "cat", "pet", "feet"] }, - { "category": "animals_and_nature", "char": "ðŸ‰", "name": "dragon", "keywords": ["animal", "myth", "nature", "chinese", "green"] }, - { "category": "animals_and_nature", "char": "ðŸ²", "name": "dragon_face", "keywords": ["animal", "myth", "nature", "chinese", "green"] }, - { "category": "animals_and_nature", "char": "🦧", "name": "orangutan", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦮", "name": "guide_dog", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "ðŸ•â€ðŸ¦º", "name": "service_dog", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦥", "name": "sloth", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦦", "name": "otter", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦨", "name": "skunk", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🦩", "name": "flamingo", "keywords": ["animal", "nature"] }, - { "category": "animals_and_nature", "char": "🌵", "name": "cactus", "keywords": ["vegetable", "plant", "nature"] }, - { "category": "animals_and_nature", "char": "🎄", "name": "christmas_tree", "keywords": ["festival", "vacation", "december", "xmas", "celebration"] }, - { "category": "animals_and_nature", "char": "🌲", "name": "evergreen_tree", "keywords": ["plant", "nature"] }, - { "category": "animals_and_nature", "char": "🌳", "name": "deciduous_tree", "keywords": ["plant", "nature"] }, - { "category": "animals_and_nature", "char": "🌴", "name": "palm_tree", "keywords": ["plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical"] }, - { "category": "animals_and_nature", "char": "🌱", "name": "seedling", "keywords": ["plant", "nature", "grass", "lawn", "spring"] }, - { "category": "animals_and_nature", "char": "🌿", "name": "herb", "keywords": ["vegetable", "plant", "medicine", "weed", "grass", "lawn"] }, - { "category": "animals_and_nature", "char": "☘", "name": "shamrock", "keywords": ["vegetable", "plant", "nature", "irish", "clover"] }, - { "category": "animals_and_nature", "char": "ðŸ€", "name": "four_leaf_clover", "keywords": ["vegetable", "plant", "nature", "lucky", "irish"] }, - { "category": "animals_and_nature", "char": "ðŸŽ", "name": "bamboo", "keywords": ["plant", "nature", "vegetable", "panda", "pine_decoration"] }, - { "category": "animals_and_nature", "char": "🎋", "name": "tanabata_tree", "keywords": ["plant", "nature", "branch", "summer"] }, - { "category": "animals_and_nature", "char": "ðŸƒ", "name": "leaves", "keywords": ["nature", "plant", "tree", "vegetable", "grass", "lawn", "spring"] }, - { "category": "animals_and_nature", "char": "ðŸ‚", "name": "fallen_leaf", "keywords": ["nature", "plant", "vegetable", "leaves"] }, - { "category": "animals_and_nature", "char": "ðŸ", "name": "maple_leaf", "keywords": ["nature", "plant", "vegetable", "ca", "fall"] }, - { "category": "animals_and_nature", "char": "🌾", "name": "ear_of_rice", "keywords": ["nature", "plant"] }, - { "category": "animals_and_nature", "char": "🌺", "name": "hibiscus", "keywords": ["plant", "vegetable", "flowers", "beach"] }, - { "category": "animals_and_nature", "char": "🌻", "name": "sunflower", "keywords": ["nature", "plant", "fall"] }, - { "category": "animals_and_nature", "char": "🌹", "name": "rose", "keywords": ["flowers", "valentines", "love", "spring"] }, - { "category": "animals_and_nature", "char": "🥀", "name": "wilted_flower", "keywords": ["plant", "nature", "flower"] }, - { "category": "animals_and_nature", "char": "🌷", "name": "tulip", "keywords": ["flowers", "plant", "nature", "summer", "spring"] }, - { "category": "animals_and_nature", "char": "🌼", "name": "blossom", "keywords": ["nature", "flowers", "yellow"] }, - { "category": "animals_and_nature", "char": "🌸", "name": "cherry_blossom", "keywords": ["nature", "plant", "spring", "flower"] }, - { "category": "animals_and_nature", "char": "ðŸ’", "name": "bouquet", "keywords": ["flowers", "nature", "spring"] }, - { "category": "animals_and_nature", "char": "ðŸ„", "name": "mushroom", "keywords": ["plant", "vegetable"] }, - { "category": "animals_and_nature", "char": "🪴", "name": "potted_plant", "keywords": ["plant"] }, - { "category": "animals_and_nature", "char": "🌰", "name": "chestnut", "keywords": ["food", "squirrel"] }, - { "category": "animals_and_nature", "char": "🎃", "name": "jack_o_lantern", "keywords": ["halloween", "light", "pumpkin", "creepy", "fall"] }, - { "category": "animals_and_nature", "char": "ðŸš", "name": "shell", "keywords": ["nature", "sea", "beach"] }, - { "category": "animals_and_nature", "char": "🕸", "name": "spider_web", "keywords": ["animal", "insect", "arachnid", "silk"] }, - { "category": "animals_and_nature", "char": "🌎", "name": "earth_americas", "keywords": ["globe", "world", "USA", "international"] }, - { "category": "animals_and_nature", "char": "ðŸŒ", "name": "earth_africa", "keywords": ["globe", "world", "international"] }, - { "category": "animals_and_nature", "char": "ðŸŒ", "name": "earth_asia", "keywords": ["globe", "world", "east", "international"] }, - { "category": "animals_and_nature", "char": "ðŸª", "name": "ringed_planet", "keywords": ["saturn"] }, - { "category": "animals_and_nature", "char": "🌕", "name": "full_moon", "keywords": ["nature", "yellow", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌖", "name": "waning_gibbous_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep", "waxing_gibbous_moon"] }, - { "category": "animals_and_nature", "char": "🌗", "name": "last_quarter_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌘", "name": "waning_crescent_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌑", "name": "new_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌒", "name": "waxing_crescent_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌓", "name": "first_quarter_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌔", "name": "waxing_gibbous_moon", "keywords": ["nature", "night", "sky", "gray", "twilight", "planet", "space", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌚", "name": "new_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "ðŸŒ", "name": "full_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌛", "name": "first_quarter_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌜", "name": "last_quarter_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] }, - { "category": "animals_and_nature", "char": "🌞", "name": "sun_with_face", "keywords": ["nature", "morning", "sky"] }, - { "category": "animals_and_nature", "char": "🌙", "name": "crescent_moon", "keywords": ["night", "sleep", "sky", "evening", "magic"] }, - { "category": "animals_and_nature", "char": "â", "name": "star", "keywords": ["night", "yellow"] }, - { "category": "animals_and_nature", "char": "🌟", "name": "star2", "keywords": ["night", "sparkle", "awesome", "good", "magic"] }, - { "category": "animals_and_nature", "char": "💫", "name": "dizzy", "keywords": ["star", "sparkle", "shoot", "magic"] }, - { "category": "animals_and_nature", "char": "✨", "name": "sparkles", "keywords": ["stars", "shine", "shiny", "cool", "awesome", "good", "magic"] }, - { "category": "animals_and_nature", "char": "☄", "name": "comet", "keywords": ["space"] }, - { "category": "animals_and_nature", "char": "☀ï¸", "name": "sunny", "keywords": ["weather", "nature", "brightness", "summer", "beach", "spring"] }, - { "category": "animals_and_nature", "char": "🌤", "name": "sun_behind_small_cloud", "keywords": ["weather"] }, - { "category": "animals_and_nature", "char": "â›…", "name": "partly_sunny", "keywords": ["weather", "nature", "cloudy", "morning", "fall", "spring"] }, - { "category": "animals_and_nature", "char": "🌥", "name": "sun_behind_large_cloud", "keywords": ["weather"] }, - { "category": "animals_and_nature", "char": "🌦", "name": "sun_behind_rain_cloud", "keywords": ["weather"] }, - { "category": "animals_and_nature", "char": "â˜ï¸", "name": "cloud", "keywords": ["weather", "sky"] }, - { "category": "animals_and_nature", "char": "🌧", "name": "cloud_with_rain", "keywords": ["weather"] }, - { "category": "animals_and_nature", "char": "⛈", "name": "cloud_with_lightning_and_rain", "keywords": ["weather", "lightning"] }, - { "category": "animals_and_nature", "char": "🌩", "name": "cloud_with_lightning", "keywords": ["weather", "thunder"] }, - { "category": "animals_and_nature", "char": "âš¡", "name": "zap", "keywords": ["thunder", "weather", "lightning bolt", "fast"] }, - { "category": "animals_and_nature", "char": "🔥", "name": "fire", "keywords": ["hot", "cook", "flame"] }, - { "category": "animals_and_nature", "char": "💥", "name": "boom", "keywords": ["bomb", "explode", "explosion", "collision", "blown"] }, - { "category": "animals_and_nature", "char": "â„ï¸", "name": "snowflake", "keywords": ["winter", "season", "cold", "weather", "christmas", "xmas"] }, - { "category": "animals_and_nature", "char": "🌨", "name": "cloud_with_snow", "keywords": ["weather"] }, - { "category": "animals_and_nature", "char": "⛄", "name": "snowman", "keywords": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen", "without_snow"] }, - { "category": "animals_and_nature", "char": "☃", "name": "snowman_with_snow", "keywords": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen"] }, - { "category": "animals_and_nature", "char": "🌬", "name": "wind_face", "keywords": ["gust", "air"] }, - { "category": "animals_and_nature", "char": "💨", "name": "dash", "keywords": ["wind", "air", "fast", "shoo", "fart", "smoke", "puff"] }, - { "category": "animals_and_nature", "char": "🌪", "name": "tornado", "keywords": ["weather", "cyclone", "twister"] }, - { "category": "animals_and_nature", "char": "🌫", "name": "fog", "keywords": ["weather"] }, - { "category": "animals_and_nature", "char": "☂", "name": "open_umbrella", "keywords": ["weather", "spring"] }, - { "category": "animals_and_nature", "char": "☔", "name": "umbrella", "keywords": ["rainy", "weather", "spring"] }, - { "category": "animals_and_nature", "char": "💧", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] }, - { "category": "animals_and_nature", "char": "💦", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] }, - { "category": "animals_and_nature", "char": "🌊", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] }, - { "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] }, - { "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] }, - { "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] }, - { "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] }, - { "category": "food_and_drink", "char": "ðŸ", "name": "green_apple", "keywords": ["fruit", "nature"] }, - { "category": "food_and_drink", "char": "ðŸŽ", "name": "apple", "keywords": ["fruit", "mac", "school"] }, - { "category": "food_and_drink", "char": "ðŸ", "name": "pear", "keywords": ["fruit", "nature", "food"] }, - { "category": "food_and_drink", "char": "ðŸŠ", "name": "tangerine", "keywords": ["food", "fruit", "nature", "orange"] }, - { "category": "food_and_drink", "char": "ðŸ‹", "name": "lemon", "keywords": ["fruit", "nature"] }, - { "category": "food_and_drink", "char": "ðŸŒ", "name": "banana", "keywords": ["fruit", "food", "monkey"] }, - { "category": "food_and_drink", "char": "ðŸ‰", "name": "watermelon", "keywords": ["fruit", "food", "picnic", "summer"] }, - { "category": "food_and_drink", "char": "ðŸ‡", "name": "grapes", "keywords": ["fruit", "food", "wine"] }, - { "category": "food_and_drink", "char": "ðŸ“", "name": "strawberry", "keywords": ["fruit", "food", "nature"] }, - { "category": "food_and_drink", "char": "ðŸˆ", "name": "melon", "keywords": ["fruit", "nature", "food"] }, - { "category": "food_and_drink", "char": "ðŸ’", "name": "cherries", "keywords": ["food", "fruit"] }, - { "category": "food_and_drink", "char": "ðŸ‘", "name": "peach", "keywords": ["fruit", "nature", "food"] }, - { "category": "food_and_drink", "char": "ðŸ", "name": "pineapple", "keywords": ["fruit", "nature", "food"] }, - { "category": "food_and_drink", "char": "🥥", "name": "coconut", "keywords": ["fruit", "nature", "food", "palm"] }, - { "category": "food_and_drink", "char": "ðŸ¥", "name": "kiwi_fruit", "keywords": ["fruit", "food"] }, - { "category": "food_and_drink", "char": "ðŸ¥", "name": "mango", "keywords": ["fruit", "food", "tropical"] }, - { "category": "food_and_drink", "char": "🥑", "name": "avocado", "keywords": ["fruit", "food"] }, - { "category": "food_and_drink", "char": "🥦", "name": "broccoli", "keywords": ["fruit", "food", "vegetable"] }, - { "category": "food_and_drink", "char": "ðŸ…", "name": "tomato", "keywords": ["fruit", "vegetable", "nature", "food"] }, - { "category": "food_and_drink", "char": "ðŸ†", "name": "eggplant", "keywords": ["vegetable", "nature", "food", "aubergine"] }, - { "category": "food_and_drink", "char": "🥒", "name": "cucumber", "keywords": ["fruit", "food", "pickle"] }, - { "category": "food_and_drink", "char": "ðŸ«", "name": "blueberries", "keywords": ["fruit", "food"] }, - { "category": "food_and_drink", "char": "🫒", "name": "olive", "keywords": ["fruit", "food"] }, - { "category": "food_and_drink", "char": "🫑", "name": "bell_pepper", "keywords": ["fruit", "food"] }, - { "category": "food_and_drink", "char": "🥕", "name": "carrot", "keywords": ["vegetable", "food", "orange"] }, - { "category": "food_and_drink", "char": "🌶", "name": "hot_pepper", "keywords": ["food", "spicy", "chilli", "chili"] }, - { "category": "food_and_drink", "char": "🥔", "name": "potato", "keywords": ["food", "tuber", "vegatable", "starch"] }, - { "category": "food_and_drink", "char": "🌽", "name": "corn", "keywords": ["food", "vegetable", "plant"] }, - { "category": "food_and_drink", "char": "🥬", "name": "leafy_greens", "keywords": ["food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce"] }, - { "category": "food_and_drink", "char": "ðŸ ", "name": "sweet_potato", "keywords": ["food", "nature"] }, - { "category": "food_and_drink", "char": "🥜", "name": "peanuts", "keywords": ["food", "nut"] }, - { "category": "food_and_drink", "char": "🧄", "name": "garlic", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🧅", "name": "onion", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "ðŸ¯", "name": "honey_pot", "keywords": ["bees", "sweet", "kitchen"] }, - { "category": "food_and_drink", "char": "ðŸ¥", "name": "croissant", "keywords": ["food", "bread", "french"] }, - { "category": "food_and_drink", "char": "ðŸž", "name": "bread", "keywords": ["food", "wheat", "breakfast", "toast"] }, - { "category": "food_and_drink", "char": "🥖", "name": "baguette_bread", "keywords": ["food", "bread", "french"] }, - { "category": "food_and_drink", "char": "🥯", "name": "bagel", "keywords": ["food", "bread", "bakery", "schmear"] }, - { "category": "food_and_drink", "char": "🥨", "name": "pretzel", "keywords": ["food", "bread", "twisted"] }, - { "category": "food_and_drink", "char": "🧀", "name": "cheese", "keywords": ["food", "chadder"] }, - { "category": "food_and_drink", "char": "🥚", "name": "egg", "keywords": ["food", "chicken", "breakfast"] }, - { "category": "food_and_drink", "char": "🥓", "name": "bacon", "keywords": ["food", "breakfast", "pork", "pig", "meat"] }, - { "category": "food_and_drink", "char": "🥩", "name": "steak", "keywords": ["food", "cow", "meat", "cut", "chop", "lambchop", "porkchop"] }, - { "category": "food_and_drink", "char": "🥞", "name": "pancakes", "keywords": ["food", "breakfast", "flapjacks", "hotcakes"] }, - { "category": "food_and_drink", "char": "ðŸ—", "name": "poultry_leg", "keywords": ["food", "meat", "drumstick", "bird", "chicken", "turkey"] }, - { "category": "food_and_drink", "char": "ðŸ–", "name": "meat_on_bone", "keywords": ["good", "food", "drumstick"] }, - { "category": "food_and_drink", "char": "🦴", "name": "bone", "keywords": ["skeleton"] }, - { "category": "food_and_drink", "char": "ðŸ¤", "name": "fried_shrimp", "keywords": ["food", "animal", "appetizer", "summer"] }, - { "category": "food_and_drink", "char": "ðŸ³", "name": "fried_egg", "keywords": ["food", "breakfast", "kitchen", "egg"] }, - { "category": "food_and_drink", "char": "ðŸ”", "name": "hamburger", "keywords": ["meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king"] }, - { "category": "food_and_drink", "char": "ðŸŸ", "name": "fries", "keywords": ["chips", "snack", "fast food"] }, - { "category": "food_and_drink", "char": "🥙", "name": "stuffed_flatbread", "keywords": ["food", "flatbread", "stuffed", "gyro"] }, - { "category": "food_and_drink", "char": "ðŸŒ", "name": "hotdog", "keywords": ["food", "frankfurter"] }, - { "category": "food_and_drink", "char": "ðŸ•", "name": "pizza", "keywords": ["food", "party"] }, - { "category": "food_and_drink", "char": "🥪", "name": "sandwich", "keywords": ["food", "lunch", "bread"] }, - { "category": "food_and_drink", "char": "🥫", "name": "canned_food", "keywords": ["food", "soup"] }, - { "category": "food_and_drink", "char": "ðŸ", "name": "spaghetti", "keywords": ["food", "italian", "noodle"] }, - { "category": "food_and_drink", "char": "🌮", "name": "taco", "keywords": ["food", "mexican"] }, - { "category": "food_and_drink", "char": "🌯", "name": "burrito", "keywords": ["food", "mexican"] }, - { "category": "food_and_drink", "char": "🥗", "name": "green_salad", "keywords": ["food", "healthy", "lettuce"] }, - { "category": "food_and_drink", "char": "🥘", "name": "shallow_pan_of_food", "keywords": ["food", "cooking", "casserole", "paella"] }, - { "category": "food_and_drink", "char": "ðŸœ", "name": "ramen", "keywords": ["food", "japanese", "noodle", "chopsticks"] }, - { "category": "food_and_drink", "char": "ðŸ²", "name": "stew", "keywords": ["food", "meat", "soup"] }, - { "category": "food_and_drink", "char": "ðŸ¥", "name": "fish_cake", "keywords": ["food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen"] }, - { "category": "food_and_drink", "char": "🥠", "name": "fortune_cookie", "keywords": ["food", "prophecy"] }, - { "category": "food_and_drink", "char": "ðŸ£", "name": "sushi", "keywords": ["food", "fish", "japanese", "rice"] }, - { "category": "food_and_drink", "char": "ðŸ±", "name": "bento", "keywords": ["food", "japanese", "box"] }, - { "category": "food_and_drink", "char": "ðŸ›", "name": "curry", "keywords": ["food", "spicy", "hot", "indian"] }, - { "category": "food_and_drink", "char": "ðŸ™", "name": "rice_ball", "keywords": ["food", "japanese"] }, - { "category": "food_and_drink", "char": "ðŸš", "name": "rice", "keywords": ["food", "china", "asian"] }, - { "category": "food_and_drink", "char": "ðŸ˜", "name": "rice_cracker", "keywords": ["food", "japanese"] }, - { "category": "food_and_drink", "char": "ðŸ¢", "name": "oden", "keywords": ["food", "japanese"] }, - { "category": "food_and_drink", "char": "ðŸ¡", "name": "dango", "keywords": ["food", "dessert", "sweet", "japanese", "barbecue", "meat"] }, - { "category": "food_and_drink", "char": "ðŸ§", "name": "shaved_ice", "keywords": ["hot", "dessert", "summer"] }, - { "category": "food_and_drink", "char": "ðŸ¨", "name": "ice_cream", "keywords": ["food", "hot", "dessert"] }, - { "category": "food_and_drink", "char": "ðŸ¦", "name": "icecream", "keywords": ["food", "hot", "dessert", "summer"] }, - { "category": "food_and_drink", "char": "🥧", "name": "pie", "keywords": ["food", "dessert", "pastry"] }, - { "category": "food_and_drink", "char": "ðŸ°", "name": "cake", "keywords": ["food", "dessert"] }, - { "category": "food_and_drink", "char": "ðŸ§", "name": "cupcake", "keywords": ["food", "dessert", "bakery", "sweet"] }, - { "category": "food_and_drink", "char": "🥮", "name": "moon_cake", "keywords": ["food", "autumn"] }, - { "category": "food_and_drink", "char": "🎂", "name": "birthday", "keywords": ["food", "dessert", "cake"] }, - { "category": "food_and_drink", "char": "ðŸ®", "name": "custard", "keywords": ["dessert", "food"] }, - { "category": "food_and_drink", "char": "ðŸ¬", "name": "candy", "keywords": ["snack", "dessert", "sweet", "lolly"] }, - { "category": "food_and_drink", "char": "ðŸ", "name": "lollipop", "keywords": ["food", "snack", "candy", "sweet"] }, - { "category": "food_and_drink", "char": "ðŸ«", "name": "chocolate_bar", "keywords": ["food", "snack", "dessert", "sweet"] }, - { "category": "food_and_drink", "char": "ðŸ¿", "name": "popcorn", "keywords": ["food", "movie theater", "films", "snack"] }, - { "category": "food_and_drink", "char": "🥟", "name": "dumpling", "keywords": ["food", "empanada", "pierogi", "potsticker"] }, - { "category": "food_and_drink", "char": "ðŸ©", "name": "doughnut", "keywords": ["food", "dessert", "snack", "sweet", "donut"] }, - { "category": "food_and_drink", "char": "ðŸª", "name": "cookie", "keywords": ["food", "snack", "oreo", "chocolate", "sweet", "dessert"] }, - { "category": "food_and_drink", "char": "🧇", "name": "waffle", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🧆", "name": "falafel", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🧈", "name": "butter", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🦪", "name": "oyster", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🫓", "name": "flatbread", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🫔", "name": "tamale", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🫕", "name": "fondue", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🥛", "name": "milk_glass", "keywords": ["beverage", "drink", "cow"] }, - { "category": "food_and_drink", "char": "ðŸº", "name": "beer", "keywords": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"] }, - { "category": "food_and_drink", "char": "ðŸ»", "name": "beers", "keywords": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"] }, - { "category": "food_and_drink", "char": "🥂", "name": "clinking_glasses", "keywords": ["beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast"] }, - { "category": "food_and_drink", "char": "ðŸ·", "name": "wine_glass", "keywords": ["drink", "beverage", "drunk", "alcohol", "booze"] }, - { "category": "food_and_drink", "char": "🥃", "name": "tumbler_glass", "keywords": ["drink", "beverage", "drunk", "alcohol", "liquor", "booze", "bourbon", "scotch", "whisky", "glass", "shot"] }, - { "category": "food_and_drink", "char": "ðŸ¸", "name": "cocktail", "keywords": ["drink", "drunk", "alcohol", "beverage", "booze", "mojito"] }, - { "category": "food_and_drink", "char": "ðŸ¹", "name": "tropical_drink", "keywords": ["beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito"] }, - { "category": "food_and_drink", "char": "ðŸ¾", "name": "champagne", "keywords": ["drink", "wine", "bottle", "celebration"] }, - { "category": "food_and_drink", "char": "ðŸ¶", "name": "sake", "keywords": ["wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze"] }, - { "category": "food_and_drink", "char": "ðŸµ", "name": "tea", "keywords": ["drink", "bowl", "breakfast", "green", "british"] }, - { "category": "food_and_drink", "char": "🥤", "name": "cup_with_straw", "keywords": ["drink", "soda"] }, - { "category": "food_and_drink", "char": "☕", "name": "coffee", "keywords": ["beverage", "caffeine", "latte", "espresso"] }, - { "category": "food_and_drink", "char": "🫖", "name": "teapot", "keywords": [] }, - { "category": "food_and_drink", "char": "🧋", "name": "bubble_tea", "keywords": ["tapioca"] }, - { "category": "food_and_drink", "char": "ðŸ¼", "name": "baby_bottle", "keywords": ["food", "container", "milk"] }, - { "category": "food_and_drink", "char": "🧃", "name": "beverage_box", "keywords": ["food", "drink"] }, - { "category": "food_and_drink", "char": "🧉", "name": "mate", "keywords": ["food", "drink"] }, - { "category": "food_and_drink", "char": "🧊", "name": "ice_cube", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "🧂", "name": "salt", "keywords": ["condiment", "shaker"] }, - { "category": "food_and_drink", "char": "🥄", "name": "spoon", "keywords": ["cutlery", "kitchen", "tableware"] }, - { "category": "food_and_drink", "char": "ðŸ´", "name": "fork_and_knife", "keywords": ["cutlery", "kitchen"] }, - { "category": "food_and_drink", "char": "ðŸ½", "name": "plate_with_cutlery", "keywords": ["food", "eat", "meal", "lunch", "dinner", "restaurant"] }, - { "category": "food_and_drink", "char": "🥣", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] }, - { "category": "food_and_drink", "char": "🥡", "name": "takeout_box", "keywords": ["food", "leftovers"] }, - { "category": "food_and_drink", "char": "🥢", "name": "chopsticks", "keywords": ["food"] }, - { "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] }, - { "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] }, - { "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] }, - { "category": "activity", "char": "âš½", "name": "soccer", "keywords": ["sports", "football"] }, - { "category": "activity", "char": "ðŸ€", "name": "basketball", "keywords": ["sports", "balls", "NBA"] }, - { "category": "activity", "char": "ðŸˆ", "name": "football", "keywords": ["sports", "balls", "NFL"] }, - { "category": "activity", "char": "âš¾", "name": "baseball", "keywords": ["sports", "balls"] }, - { "category": "activity", "char": "🥎", "name": "softball", "keywords": ["sports", "balls"] }, - { "category": "activity", "char": "🎾", "name": "tennis", "keywords": ["sports", "balls", "green"] }, - { "category": "activity", "char": "ðŸ", "name": "volleyball", "keywords": ["sports", "balls"] }, - { "category": "activity", "char": "ðŸ‰", "name": "rugby_football", "keywords": ["sports", "team"] }, - { "category": "activity", "char": "ðŸ¥", "name": "flying_disc", "keywords": ["sports", "frisbee", "ultimate"] }, - { "category": "activity", "char": "🎱", "name": "8ball", "keywords": ["pool", "hobby", "game", "luck", "magic"] }, - { "category": "activity", "char": "⛳", "name": "golf", "keywords": ["sports", "business", "flag", "hole", "summer"] }, - { "category": "activity", "char": "ðŸŒï¸â€â™€ï¸", "name": "golfing_woman", "keywords": ["sports", "business", "woman", "female"] }, - { "category": "activity", "char": "ðŸŒ", "name": "golfing_man", "keywords": ["sports", "business"] }, - { "category": "activity", "char": "ðŸ“", "name": "ping_pong", "keywords": ["sports", "pingpong"] }, - { "category": "activity", "char": "ðŸ¸", "name": "badminton", "keywords": ["sports"] }, - { "category": "activity", "char": "🥅", "name": "goal_net", "keywords": ["sports"] }, - { "category": "activity", "char": "ðŸ’", "name": "ice_hockey", "keywords": ["sports"] }, - { "category": "activity", "char": "ðŸ‘", "name": "field_hockey", "keywords": ["sports"] }, - { "category": "activity", "char": "ðŸ¥", "name": "lacrosse", "keywords": ["sports", "ball", "stick"] }, - { "category": "activity", "char": "ðŸ", "name": "cricket", "keywords": ["sports"] }, - { "category": "activity", "char": "🎿", "name": "ski", "keywords": ["sports", "winter", "cold", "snow"] }, - { "category": "activity", "char": "â›·", "name": "skier", "keywords": ["sports", "winter", "snow"] }, - { "category": "activity", "char": "ðŸ‚", "name": "snowboarder", "keywords": ["sports", "winter"] }, - { "category": "activity", "char": "🤺", "name": "person_fencing", "keywords": ["sports", "fencing", "sword"] }, - { "category": "activity", "char": "🤼â€â™€ï¸", "name": "women_wrestling", "keywords": ["sports", "wrestlers"] }, - { "category": "activity", "char": "🤼â€â™‚ï¸", "name": "men_wrestling", "keywords": ["sports", "wrestlers"] }, - { "category": "activity", "char": "🤸â€â™€ï¸", "name": "woman_cartwheeling", "keywords": ["gymnastics"] }, - { "category": "activity", "char": "🤸â€â™‚ï¸", "name": "man_cartwheeling", "keywords": ["gymnastics"] }, - { "category": "activity", "char": "🤾â€â™€ï¸", "name": "woman_playing_handball", "keywords": ["sports"] }, - { "category": "activity", "char": "🤾â€â™‚ï¸", "name": "man_playing_handball", "keywords": ["sports"] }, - { "category": "activity", "char": "⛸", "name": "ice_skate", "keywords": ["sports"] }, - { "category": "activity", "char": "🥌", "name": "curling_stone", "keywords": ["sports"] }, - { "category": "activity", "char": "🛹", "name": "skateboard", "keywords": ["board"] }, - { "category": "activity", "char": "🛷", "name": "sled", "keywords": ["sleigh", "luge", "toboggan"] }, - { "category": "activity", "char": "ðŸ¹", "name": "bow_and_arrow", "keywords": ["sports"] }, - { "category": "activity", "char": "🎣", "name": "fishing_pole_and_fish", "keywords": ["food", "hobby", "summer"] }, - { "category": "activity", "char": "🥊", "name": "boxing_glove", "keywords": ["sports", "fighting"] }, - { "category": "activity", "char": "🥋", "name": "martial_arts_uniform", "keywords": ["judo", "karate", "taekwondo"] }, - { "category": "activity", "char": "🚣â€â™€ï¸", "name": "rowing_woman", "keywords": ["sports", "hobby", "water", "ship", "woman", "female"] }, - { "category": "activity", "char": "🚣", "name": "rowing_man", "keywords": ["sports", "hobby", "water", "ship"] }, - { "category": "activity", "char": "🧗â€â™€ï¸", "name": "climbing_woman", "keywords": ["sports", "hobby", "woman", "female", "rock"] }, - { "category": "activity", "char": "🧗â€â™‚ï¸", "name": "climbing_man", "keywords": ["sports", "hobby", "man", "male", "rock"] }, - { "category": "activity", "char": "ðŸŠâ€â™€ï¸", "name": "swimming_woman", "keywords": ["sports", "exercise", "human", "athlete", "water", "summer", "woman", "female"] }, - { "category": "activity", "char": "ðŸŠ", "name": "swimming_man", "keywords": ["sports", "exercise", "human", "athlete", "water", "summer"] }, - { "category": "activity", "char": "🤽â€â™€ï¸", "name": "woman_playing_water_polo", "keywords": ["sports", "pool"] }, - { "category": "activity", "char": "🤽â€â™‚ï¸", "name": "man_playing_water_polo", "keywords": ["sports", "pool"] }, - { "category": "activity", "char": "🧘â€â™€ï¸", "name": "woman_in_lotus_position", "keywords": ["woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness"] }, - { "category": "activity", "char": "🧘â€â™‚ï¸", "name": "man_in_lotus_position", "keywords": ["man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness"] }, - { "category": "activity", "char": "ðŸ„â€â™€ï¸", "name": "surfing_woman", "keywords": ["sports", "ocean", "sea", "summer", "beach", "woman", "female"] }, - { "category": "activity", "char": "ðŸ„", "name": "surfing_man", "keywords": ["sports", "ocean", "sea", "summer", "beach"] }, - { "category": "activity", "char": "🛀", "name": "bath", "keywords": ["clean", "shower", "bathroom"] }, - { "category": "activity", "char": "⛹ï¸â€â™€ï¸", "name": "basketball_woman", "keywords": ["sports", "human", "woman", "female"] }, - { "category": "activity", "char": "⛹", "name": "basketball_man", "keywords": ["sports", "human"] }, - { "category": "activity", "char": "ðŸ‹ï¸â€â™€ï¸", "name": "weight_lifting_woman", "keywords": ["sports", "training", "exercise", "woman", "female"] }, - { "category": "activity", "char": "ðŸ‹", "name": "weight_lifting_man", "keywords": ["sports", "training", "exercise"] }, - { "category": "activity", "char": "🚴â€â™€ï¸", "name": "biking_woman", "keywords": ["sports", "bike", "exercise", "hipster", "woman", "female"] }, - { "category": "activity", "char": "🚴", "name": "biking_man", "keywords": ["sports", "bike", "exercise", "hipster"] }, - { "category": "activity", "char": "🚵â€â™€ï¸", "name": "mountain_biking_woman", "keywords": ["transportation", "sports", "human", "race", "bike", "woman", "female"] }, - { "category": "activity", "char": "🚵", "name": "mountain_biking_man", "keywords": ["transportation", "sports", "human", "race", "bike"] }, - { "category": "activity", "char": "ðŸ‡", "name": "horse_racing", "keywords": ["animal", "betting", "competition", "gambling", "luck"] }, - { "category": "activity", "char": "🤿", "name": "diving_mask", "keywords": ["sports"] }, - { "category": "activity", "char": "🪀", "name": "yo_yo", "keywords": ["sports"] }, - { "category": "activity", "char": "ðŸª", "name": "kite", "keywords": ["sports"] }, - { "category": "activity", "char": "🦺", "name": "safety_vest", "keywords": ["sports"] }, - { "category": "activity", "char": "🪡", "name": "sewing_needle", "keywords": [] }, - { "category": "activity", "char": "🪢", "name": "knot", "keywords": [] }, - { "category": "activity", "char": "🕴", "name": "business_suit_levitating", "keywords": ["suit", "business", "levitate", "hover", "jump"] }, - { "category": "activity", "char": "ðŸ†", "name": "trophy", "keywords": ["win", "award", "contest", "place", "ftw", "ceremony"] }, - { "category": "activity", "char": "🎽", "name": "running_shirt_with_sash", "keywords": ["play", "pageant"] }, - { "category": "activity", "char": "ðŸ…", "name": "medal_sports", "keywords": ["award", "winning"] }, - { "category": "activity", "char": "🎖", "name": "medal_military", "keywords": ["award", "winning", "army"] }, - { "category": "activity", "char": "🥇", "name": "1st_place_medal", "keywords": ["award", "winning", "first"] }, - { "category": "activity", "char": "🥈", "name": "2nd_place_medal", "keywords": ["award", "second"] }, - { "category": "activity", "char": "🥉", "name": "3rd_place_medal", "keywords": ["award", "third"] }, - { "category": "activity", "char": "🎗", "name": "reminder_ribbon", "keywords": ["sports", "cause", "support", "awareness"] }, - { "category": "activity", "char": "ðŸµ", "name": "rosette", "keywords": ["flower", "decoration", "military"] }, - { "category": "activity", "char": "🎫", "name": "ticket", "keywords": ["event", "concert", "pass"] }, - { "category": "activity", "char": "🎟", "name": "tickets", "keywords": ["sports", "concert", "entrance"] }, - { "category": "activity", "char": "ðŸŽ", "name": "performing_arts", "keywords": ["acting", "theater", "drama"] }, - { "category": "activity", "char": "🎨", "name": "art", "keywords": ["design", "paint", "draw", "colors"] }, - { "category": "activity", "char": "🎪", "name": "circus_tent", "keywords": ["festival", "carnival", "party"] }, - { "category": "activity", "char": "🤹â€â™€ï¸", "name": "woman_juggling", "keywords": ["juggle", "balance", "skill", "multitask"] }, - { "category": "activity", "char": "🤹â€â™‚ï¸", "name": "man_juggling", "keywords": ["juggle", "balance", "skill", "multitask"] }, - { "category": "activity", "char": "🎤", "name": "microphone", "keywords": ["sound", "music", "PA", "sing", "talkshow"] }, - { "category": "activity", "char": "🎧", "name": "headphones", "keywords": ["music", "score", "gadgets"] }, - { "category": "activity", "char": "🎼", "name": "musical_score", "keywords": ["treble", "clef", "compose"] }, - { "category": "activity", "char": "🎹", "name": "musical_keyboard", "keywords": ["piano", "instrument", "compose"] }, - { "category": "activity", "char": "ðŸ¥", "name": "drum", "keywords": ["music", "instrument", "drumsticks", "snare"] }, - { "category": "activity", "char": "🎷", "name": "saxophone", "keywords": ["music", "instrument", "jazz", "blues"] }, - { "category": "activity", "char": "🎺", "name": "trumpet", "keywords": ["music", "brass"] }, - { "category": "activity", "char": "🎸", "name": "guitar", "keywords": ["music", "instrument"] }, - { "category": "activity", "char": "🎻", "name": "violin", "keywords": ["music", "instrument", "orchestra", "symphony"] }, - { "category": "activity", "char": "🪕", "name": "banjo", "keywords": ["music", "instrument"] }, - { "category": "activity", "char": "🪗", "name": "accordion", "keywords": ["music", "instrument"] }, - { "category": "activity", "char": "🪘", "name": "long_drum", "keywords": ["music", "instrument"] }, - { "category": "activity", "char": "🎬", "name": "clapper", "keywords": ["movie", "film", "record"] }, - { "category": "activity", "char": "🎮", "name": "video_game", "keywords": ["play", "console", "PS4", "controller"] }, - { "category": "activity", "char": "👾", "name": "space_invader", "keywords": ["game", "arcade", "play"] }, - { "category": "activity", "char": "🎯", "name": "dart", "keywords": ["game", "play", "bar", "target", "bullseye"] }, - { "category": "activity", "char": "🎲", "name": "game_die", "keywords": ["dice", "random", "tabletop", "play", "luck"] }, - { "category": "activity", "char": "♟ï¸", "name": "chess_pawn", "keywords": ["expendable"] }, - { "category": "activity", "char": "🎰", "name": "slot_machine", "keywords": ["bet", "gamble", "vegas", "fruit machine", "luck", "casino"] }, - { "category": "activity", "char": "🧩", "name": "jigsaw", "keywords": ["interlocking", "puzzle", "piece"] }, - { "category": "activity", "char": "🎳", "name": "bowling", "keywords": ["sports", "fun", "play"] }, - { "category": "activity", "char": "🪄", "name": "magic_wand", "keywords": [] }, - { "category": "activity", "char": "🪅", "name": "pinata", "keywords": [] }, - { "category": "activity", "char": "🪆", "name": "nesting_dolls", "keywords": [] }, - { "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] }, - { "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] }, - { "category": "travel_and_places", "char": "🚗", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚕", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] }, - { "category": "travel_and_places", "char": "🚙", "name": "blue_car", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚌", "name": "bus", "keywords": ["car", "vehicle", "transportation"] }, - { "category": "travel_and_places", "char": "🚎", "name": "trolleybus", "keywords": ["bart", "transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "ðŸŽ", "name": "racing_car", "keywords": ["sports", "race", "fast", "formula", "f1"] }, - { "category": "travel_and_places", "char": "🚓", "name": "police_car", "keywords": ["vehicle", "cars", "transportation", "law", "legal", "enforcement"] }, - { "category": "travel_and_places", "char": "🚑", "name": "ambulance", "keywords": ["health", "911", "hospital"] }, - { "category": "travel_and_places", "char": "🚒", "name": "fire_engine", "keywords": ["transportation", "cars", "vehicle"] }, - { "category": "travel_and_places", "char": "ðŸš", "name": "minibus", "keywords": ["vehicle", "car", "transportation"] }, - { "category": "travel_and_places", "char": "🚚", "name": "truck", "keywords": ["cars", "transportation"] }, - { "category": "travel_and_places", "char": "🚛", "name": "articulated_lorry", "keywords": ["vehicle", "cars", "transportation", "express"] }, - { "category": "travel_and_places", "char": "🚜", "name": "tractor", "keywords": ["vehicle", "car", "farming", "agriculture"] }, - { "category": "travel_and_places", "char": "🛴", "name": "kick_scooter", "keywords": ["vehicle", "kick", "razor"] }, - { "category": "travel_and_places", "char": "ðŸ", "name": "motorcycle", "keywords": ["race", "sports", "fast"] }, - { "category": "travel_and_places", "char": "🚲", "name": "bike", "keywords": ["sports", "bicycle", "exercise", "hipster"] }, - { "category": "travel_and_places", "char": "🛵", "name": "motor_scooter", "keywords": ["vehicle", "vespa", "sasha"] }, - { "category": "travel_and_places", "char": "🦽", "name": "manual_wheelchair", "keywords": ["vehicle"] }, - { "category": "travel_and_places", "char": "🦼", "name": "motorized_wheelchair", "keywords": ["vehicle"] }, - { "category": "travel_and_places", "char": "🛺", "name": "auto_rickshaw", "keywords": ["vehicle"] }, - { "category": "travel_and_places", "char": "🪂", "name": "parachute", "keywords": ["vehicle"] }, - { "category": "travel_and_places", "char": "🚨", "name": "rotating_light", "keywords": ["police", "ambulance", "911", "emergency", "alert", "error", "pinged", "law", "legal"] }, - { "category": "travel_and_places", "char": "🚔", "name": "oncoming_police_car", "keywords": ["vehicle", "law", "legal", "enforcement", "911"] }, - { "category": "travel_and_places", "char": "ðŸš", "name": "oncoming_bus", "keywords": ["vehicle", "transportation"] }, - { "category": "travel_and_places", "char": "🚘", "name": "oncoming_automobile", "keywords": ["car", "vehicle", "transportation"] }, - { "category": "travel_and_places", "char": "🚖", "name": "oncoming_taxi", "keywords": ["vehicle", "cars", "uber"] }, - { "category": "travel_and_places", "char": "🚡", "name": "aerial_tramway", "keywords": ["transportation", "vehicle", "ski"] }, - { "category": "travel_and_places", "char": "🚠", "name": "mountain_cableway", "keywords": ["transportation", "vehicle", "ski"] }, - { "category": "travel_and_places", "char": "🚟", "name": "suspension_railway", "keywords": ["vehicle", "transportation"] }, - { "category": "travel_and_places", "char": "🚃", "name": "railway_car", "keywords": ["transportation", "vehicle", "train"] }, - { "category": "travel_and_places", "char": "🚋", "name": "train", "keywords": ["transportation", "vehicle", "carriage", "public", "travel"] }, - { "category": "travel_and_places", "char": "ðŸš", "name": "monorail", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚄", "name": "bullettrain_side", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚅", "name": "bullettrain_front", "keywords": ["transportation", "vehicle", "speed", "fast", "public", "travel"] }, - { "category": "travel_and_places", "char": "🚈", "name": "light_rail", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚞", "name": "mountain_railway", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚂", "name": "steam_locomotive", "keywords": ["transportation", "vehicle", "train"] }, - { "category": "travel_and_places", "char": "🚆", "name": "train2", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚇", "name": "metro", "keywords": ["transportation", "blue-square", "mrt", "underground", "tube"] }, - { "category": "travel_and_places", "char": "🚊", "name": "tram", "keywords": ["transportation", "vehicle"] }, - { "category": "travel_and_places", "char": "🚉", "name": "station", "keywords": ["transportation", "vehicle", "public"] }, - { "category": "travel_and_places", "char": "🛸", "name": "flying_saucer", "keywords": ["transportation", "vehicle", "ufo"] }, - { "category": "travel_and_places", "char": "ðŸš", "name": "helicopter", "keywords": ["transportation", "vehicle", "fly"] }, - { "category": "travel_and_places", "char": "🛩", "name": "small_airplane", "keywords": ["flight", "transportation", "fly", "vehicle"] }, - { "category": "travel_and_places", "char": "✈ï¸", "name": "airplane", "keywords": ["vehicle", "transportation", "flight", "fly"] }, - { "category": "travel_and_places", "char": "🛫", "name": "flight_departure", "keywords": ["airport", "flight", "landing"] }, - { "category": "travel_and_places", "char": "🛬", "name": "flight_arrival", "keywords": ["airport", "flight", "boarding"] }, - { "category": "travel_and_places", "char": "⛵", "name": "sailboat", "keywords": ["ship", "summer", "transportation", "water", "sailing"] }, - { "category": "travel_and_places", "char": "🛥", "name": "motor_boat", "keywords": ["ship"] }, - { "category": "travel_and_places", "char": "🚤", "name": "speedboat", "keywords": ["ship", "transportation", "vehicle", "summer"] }, - { "category": "travel_and_places", "char": "â›´", "name": "ferry", "keywords": ["boat", "ship", "yacht"] }, - { "category": "travel_and_places", "char": "🛳", "name": "passenger_ship", "keywords": ["yacht", "cruise", "ferry"] }, - { "category": "travel_and_places", "char": "🚀", "name": "rocket", "keywords": ["launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly"] }, - { "category": "travel_and_places", "char": "🛰", "name": "artificial_satellite", "keywords": ["communication", "gps", "orbit", "spaceflight", "NASA", "ISS"] }, - { "category": "travel_and_places", "char": "🛻", "name": "pickup_truck", "keywords": ["car"] }, - { "category": "travel_and_places", "char": "🛼", "name": "roller_skate", "keywords": [] }, - { "category": "travel_and_places", "char": "💺", "name": "seat", "keywords": ["sit", "airplane", "transport", "bus", "flight", "fly"] }, - { "category": "travel_and_places", "char": "🛶", "name": "canoe", "keywords": ["boat", "paddle", "water", "ship"] }, - { "category": "travel_and_places", "char": "âš“", "name": "anchor", "keywords": ["ship", "ferry", "sea", "boat"] }, - { "category": "travel_and_places", "char": "🚧", "name": "construction", "keywords": ["wip", "progress", "caution", "warning"] }, - { "category": "travel_and_places", "char": "⛽", "name": "fuelpump", "keywords": ["gas station", "petroleum"] }, - { "category": "travel_and_places", "char": "ðŸš", "name": "busstop", "keywords": ["transportation", "wait"] }, - { "category": "travel_and_places", "char": "🚦", "name": "vertical_traffic_light", "keywords": ["transportation", "driving"] }, - { "category": "travel_and_places", "char": "🚥", "name": "traffic_light", "keywords": ["transportation", "signal"] }, - { "category": "travel_and_places", "char": "ðŸ", "name": "checkered_flag", "keywords": ["contest", "finishline", "race", "gokart"] }, - { "category": "travel_and_places", "char": "🚢", "name": "ship", "keywords": ["transportation", "titanic", "deploy"] }, - { "category": "travel_and_places", "char": "🎡", "name": "ferris_wheel", "keywords": ["photo", "carnival", "londoneye"] }, - { "category": "travel_and_places", "char": "🎢", "name": "roller_coaster", "keywords": ["carnival", "playground", "photo", "fun"] }, - { "category": "travel_and_places", "char": "🎠", "name": "carousel_horse", "keywords": ["photo", "carnival"] }, - { "category": "travel_and_places", "char": "ðŸ—", "name": "building_construction", "keywords": ["wip", "working", "progress"] }, - { "category": "travel_and_places", "char": "ðŸŒ", "name": "foggy", "keywords": ["photo", "mountain"] }, - { "category": "travel_and_places", "char": "ðŸ", "name": "factory", "keywords": ["building", "industry", "pollution", "smoke"] }, - { "category": "travel_and_places", "char": "⛲", "name": "fountain", "keywords": ["photo", "summer", "water", "fresh"] }, - { "category": "travel_and_places", "char": "🎑", "name": "rice_scene", "keywords": ["photo", "japan", "asia", "tsukimi"] }, - { "category": "travel_and_places", "char": "â›°", "name": "mountain", "keywords": ["photo", "nature", "environment"] }, - { "category": "travel_and_places", "char": "ðŸ”", "name": "mountain_snow", "keywords": ["photo", "nature", "environment", "winter", "cold"] }, - { "category": "travel_and_places", "char": "🗻", "name": "mount_fuji", "keywords": ["photo", "mountain", "nature", "japanese"] }, - { "category": "travel_and_places", "char": "🌋", "name": "volcano", "keywords": ["photo", "nature", "disaster"] }, - { "category": "travel_and_places", "char": "🗾", "name": "japan", "keywords": ["nation", "country", "japanese", "asia"] }, - { "category": "travel_and_places", "char": "ðŸ•", "name": "camping", "keywords": ["photo", "outdoors", "tent"] }, - { "category": "travel_and_places", "char": "⛺", "name": "tent", "keywords": ["photo", "camping", "outdoors"] }, - { "category": "travel_and_places", "char": "ðŸž", "name": "national_park", "keywords": ["photo", "environment", "nature"] }, - { "category": "travel_and_places", "char": "🛣", "name": "motorway", "keywords": ["road", "cupertino", "interstate", "highway"] }, - { "category": "travel_and_places", "char": "🛤", "name": "railway_track", "keywords": ["train", "transportation"] }, - { "category": "travel_and_places", "char": "🌅", "name": "sunrise", "keywords": ["morning", "view", "vacation", "photo"] }, - { "category": "travel_and_places", "char": "🌄", "name": "sunrise_over_mountains", "keywords": ["view", "vacation", "photo"] }, - { "category": "travel_and_places", "char": "ðŸœ", "name": "desert", "keywords": ["photo", "warm", "saharah"] }, - { "category": "travel_and_places", "char": "ðŸ–", "name": "beach_umbrella", "keywords": ["weather", "summer", "sunny", "sand", "mojito"] }, - { "category": "travel_and_places", "char": "ðŸ", "name": "desert_island", "keywords": ["photo", "tropical", "mojito"] }, - { "category": "travel_and_places", "char": "🌇", "name": "city_sunrise", "keywords": ["photo", "good morning", "dawn"] }, - { "category": "travel_and_places", "char": "🌆", "name": "city_sunset", "keywords": ["photo", "evening", "sky", "buildings"] }, - { "category": "travel_and_places", "char": "ðŸ™", "name": "cityscape", "keywords": ["photo", "night life", "urban"] }, - { "category": "travel_and_places", "char": "🌃", "name": "night_with_stars", "keywords": ["evening", "city", "downtown"] }, - { "category": "travel_and_places", "char": "🌉", "name": "bridge_at_night", "keywords": ["photo", "sanfrancisco"] }, - { "category": "travel_and_places", "char": "🌌", "name": "milky_way", "keywords": ["photo", "space", "stars"] }, - { "category": "travel_and_places", "char": "🌠", "name": "stars", "keywords": ["night", "photo"] }, - { "category": "travel_and_places", "char": "🎇", "name": "sparkler", "keywords": ["stars", "night", "shine"] }, - { "category": "travel_and_places", "char": "🎆", "name": "fireworks", "keywords": ["photo", "festival", "carnival", "congratulations"] }, - { "category": "travel_and_places", "char": "🌈", "name": "rainbow", "keywords": ["nature", "happy", "unicorn_face", "photo", "sky", "spring"] }, - { "category": "travel_and_places", "char": "ðŸ˜", "name": "houses", "keywords": ["buildings", "photo"] }, - { "category": "travel_and_places", "char": "ðŸ°", "name": "european_castle", "keywords": ["building", "royalty", "history"] }, - { "category": "travel_and_places", "char": "ðŸ¯", "name": "japanese_castle", "keywords": ["photo", "building"] }, - { "category": "travel_and_places", "char": "🗼", "name": "tokyo_tower", "keywords": ["photo", "japanese"] }, - { "category": "travel_and_places", "char": "", "name": "shibuya_109", "keywords": ["photo", "japanese"] }, - { "category": "travel_and_places", "char": "ðŸŸ", "name": "stadium", "keywords": ["photo", "place", "sports", "concert", "venue"] }, - { "category": "travel_and_places", "char": "🗽", "name": "statue_of_liberty", "keywords": ["american", "newyork"] }, - { "category": "travel_and_places", "char": "ðŸ ", "name": "house", "keywords": ["building", "home"] }, - { "category": "travel_and_places", "char": "ðŸ¡", "name": "house_with_garden", "keywords": ["home", "plant", "nature"] }, - { "category": "travel_and_places", "char": "ðŸš", "name": "derelict_house", "keywords": ["abandon", "evict", "broken", "building"] }, - { "category": "travel_and_places", "char": "ðŸ¢", "name": "office", "keywords": ["building", "bureau", "work"] }, - { "category": "travel_and_places", "char": "ðŸ¬", "name": "department_store", "keywords": ["building", "shopping", "mall"] }, - { "category": "travel_and_places", "char": "ðŸ£", "name": "post_office", "keywords": ["building", "envelope", "communication"] }, - { "category": "travel_and_places", "char": "ðŸ¤", "name": "european_post_office", "keywords": ["building", "email"] }, - { "category": "travel_and_places", "char": "ðŸ¥", "name": "hospital", "keywords": ["building", "health", "surgery", "doctor"] }, - { "category": "travel_and_places", "char": "ðŸ¦", "name": "bank", "keywords": ["building", "money", "sales", "cash", "business", "enterprise"] }, - { "category": "travel_and_places", "char": "ðŸ¨", "name": "hotel", "keywords": ["building", "accomodation", "checkin"] }, - { "category": "travel_and_places", "char": "ðŸª", "name": "convenience_store", "keywords": ["building", "shopping", "groceries"] }, - { "category": "travel_and_places", "char": "ðŸ«", "name": "school", "keywords": ["building", "student", "education", "learn", "teach"] }, - { "category": "travel_and_places", "char": "ðŸ©", "name": "love_hotel", "keywords": ["like", "affection", "dating"] }, - { "category": "travel_and_places", "char": "💒", "name": "wedding", "keywords": ["love", "like", "affection", "couple", "marriage", "bride", "groom"] }, - { "category": "travel_and_places", "char": "ðŸ›", "name": "classical_building", "keywords": ["art", "culture", "history"] }, - { "category": "travel_and_places", "char": "⛪", "name": "church", "keywords": ["building", "religion", "christ"] }, - { "category": "travel_and_places", "char": "🕌", "name": "mosque", "keywords": ["islam", "worship", "minaret"] }, - { "category": "travel_and_places", "char": "ðŸ•", "name": "synagogue", "keywords": ["judaism", "worship", "temple", "jewish"] }, - { "category": "travel_and_places", "char": "🕋", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] }, - { "category": "travel_and_places", "char": "⛩", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] }, - { "category": "travel_and_places", "char": "🛕", "name": "hindu_temple", "keywords": ["temple"] }, - { "category": "travel_and_places", "char": "🪨", "name": "rock", "keywords": [] }, - { "category": "travel_and_places", "char": "🪵", "name": "wood", "keywords": [] }, - { "category": "travel_and_places", "char": "🛖", "name": "hut", "keywords": [] }, - { "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] }, - { "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] }, - { "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] }, - { "category": "objects", "char": "⌚", "name": "watch", "keywords": ["time", "accessories"] }, - { "category": "objects", "char": "📱", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] }, - { "category": "objects", "char": "📲", "name": "calling", "keywords": ["iphone", "incoming"] }, - { "category": "objects", "char": "💻", "name": "computer", "keywords": ["technology", "laptop", "screen", "display", "monitor"] }, - { "category": "objects", "char": "⌨", "name": "keyboard", "keywords": ["technology", "computer", "type", "input", "text"] }, - { "category": "objects", "char": "🖥", "name": "desktop_computer", "keywords": ["technology", "computing", "screen"] }, - { "category": "objects", "char": "🖨", "name": "printer", "keywords": ["paper", "ink"] }, - { "category": "objects", "char": "🖱", "name": "computer_mouse", "keywords": ["click"] }, - { "category": "objects", "char": "🖲", "name": "trackball", "keywords": ["technology", "trackpad"] }, - { "category": "objects", "char": "🕹", "name": "joystick", "keywords": ["game", "play"] }, - { "category": "objects", "char": "🗜", "name": "clamp", "keywords": ["tool"] }, - { "category": "objects", "char": "💽", "name": "minidisc", "keywords": ["technology", "record", "data", "disk", "90s"] }, - { "category": "objects", "char": "💾", "name": "floppy_disk", "keywords": ["oldschool", "technology", "save", "90s", "80s"] }, - { "category": "objects", "char": "💿", "name": "cd", "keywords": ["technology", "dvd", "disk", "disc", "90s"] }, - { "category": "objects", "char": "📀", "name": "dvd", "keywords": ["cd", "disk", "disc"] }, - { "category": "objects", "char": "📼", "name": "vhs", "keywords": ["record", "video", "oldschool", "90s", "80s"] }, - { "category": "objects", "char": "📷", "name": "camera", "keywords": ["gadgets", "photography"] }, - { "category": "objects", "char": "📸", "name": "camera_flash", "keywords": ["photography", "gadgets"] }, - { "category": "objects", "char": "📹", "name": "video_camera", "keywords": ["film", "record"] }, - { "category": "objects", "char": "🎥", "name": "movie_camera", "keywords": ["film", "record"] }, - { "category": "objects", "char": "📽", "name": "film_projector", "keywords": ["video", "tape", "record", "movie"] }, - { "category": "objects", "char": "🎞", "name": "film_strip", "keywords": ["movie"] }, - { "category": "objects", "char": "📞", "name": "telephone_receiver", "keywords": ["technology", "communication", "dial"] }, - { "category": "objects", "char": "☎ï¸", "name": "phone", "keywords": ["technology", "communication", "dial", "telephone"] }, - { "category": "objects", "char": "📟", "name": "pager", "keywords": ["bbcall", "oldschool", "90s"] }, - { "category": "objects", "char": "📠", "name": "fax", "keywords": ["communication", "technology"] }, - { "category": "objects", "char": "📺", "name": "tv", "keywords": ["technology", "program", "oldschool", "show", "television"] }, - { "category": "objects", "char": "📻", "name": "radio", "keywords": ["communication", "music", "podcast", "program"] }, - { "category": "objects", "char": "🎙", "name": "studio_microphone", "keywords": ["sing", "recording", "artist", "talkshow"] }, - { "category": "objects", "char": "🎚", "name": "level_slider", "keywords": ["scale"] }, - { "category": "objects", "char": "🎛", "name": "control_knobs", "keywords": ["dial"] }, - { "category": "objects", "char": "ðŸ§", "name": "compass", "keywords": ["magnetic", "navigation", "orienteering"] }, - { "category": "objects", "char": "â±", "name": "stopwatch", "keywords": ["time", "deadline"] }, - { "category": "objects", "char": "â²", "name": "timer_clock", "keywords": ["alarm"] }, - { "category": "objects", "char": "â°", "name": "alarm_clock", "keywords": ["time", "wake"] }, - { "category": "objects", "char": "🕰", "name": "mantelpiece_clock", "keywords": ["time"] }, - { "category": "objects", "char": "â³", "name": "hourglass_flowing_sand", "keywords": ["oldschool", "time", "countdown"] }, - { "category": "objects", "char": "⌛", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] }, - { "category": "objects", "char": "📡", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] }, - { "category": "objects", "char": "🔋", "name": "battery", "keywords": ["power", "energy", "sustain"] }, - { "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] }, - { "category": "objects", "char": "🔌", "name": "electric_plug", "keywords": ["charger", "power"] }, - { "category": "objects", "char": "💡", "name": "bulb", "keywords": ["light", "electricity", "idea"] }, - { "category": "objects", "char": "🔦", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] }, - { "category": "objects", "char": "🕯", "name": "candle", "keywords": ["fire", "wax"] }, - { "category": "objects", "char": "🧯", "name": "fire_extinguisher", "keywords": ["quench"] }, - { "category": "objects", "char": "🗑", "name": "wastebasket", "keywords": ["bin", "trash", "rubbish", "garbage", "toss"] }, - { "category": "objects", "char": "🛢", "name": "oil_drum", "keywords": ["barrell"] }, - { "category": "objects", "char": "💸", "name": "money_with_wings", "keywords": ["dollar", "bills", "payment", "sale"] }, - { "category": "objects", "char": "💵", "name": "dollar", "keywords": ["money", "sales", "bill", "currency"] }, - { "category": "objects", "char": "💴", "name": "yen", "keywords": ["money", "sales", "japanese", "dollar", "currency"] }, - { "category": "objects", "char": "💶", "name": "euro", "keywords": ["money", "sales", "dollar", "currency"] }, - { "category": "objects", "char": "💷", "name": "pound", "keywords": ["british", "sterling", "money", "sales", "bills", "uk", "england", "currency"] }, - { "category": "objects", "char": "💰", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] }, - { "category": "objects", "char": "🪙", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] }, - { "category": "objects", "char": "💳", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] }, - { "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] }, - { "category": "objects", "char": "💎", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] }, - { "category": "objects", "char": "âš–", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] }, - { "category": "objects", "char": "🧰", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] }, - { "category": "objects", "char": "🔧", "name": "wrench", "keywords": ["tools", "diy", "ikea", "fix", "maintainer"] }, - { "category": "objects", "char": "🔨", "name": "hammer", "keywords": ["tools", "build", "create"] }, - { "category": "objects", "char": "âš’", "name": "hammer_and_pick", "keywords": ["tools", "build", "create"] }, - { "category": "objects", "char": "🛠", "name": "hammer_and_wrench", "keywords": ["tools", "build", "create"] }, - { "category": "objects", "char": "â›", "name": "pick", "keywords": ["tools", "dig"] }, - { "category": "objects", "char": "🪓", "name": "axe", "keywords": ["tools"] }, - { "category": "objects", "char": "🦯", "name": "probing_cane", "keywords": ["tools"] }, - { "category": "objects", "char": "🔩", "name": "nut_and_bolt", "keywords": ["handy", "tools", "fix"] }, - { "category": "objects", "char": "âš™", "name": "gear", "keywords": ["cog"] }, - { "category": "objects", "char": "🪃", "name": "boomerang", "keywords": ["tool"] }, - { "category": "objects", "char": "🪚", "name": "carpentry_saw", "keywords": ["tool"] }, - { "category": "objects", "char": "🪛", "name": "screwdriver", "keywords": ["tool"] }, - { "category": "objects", "char": "ðŸª", "name": "hook", "keywords": ["tool"] }, - { "category": "objects", "char": "🪜", "name": "ladder", "keywords": ["tool"] }, - { "category": "objects", "char": "🧱", "name": "brick", "keywords": ["bricks"] }, - { "category": "objects", "char": "⛓", "name": "chains", "keywords": ["lock", "arrest"] }, - { "category": "objects", "char": "🧲", "name": "magnet", "keywords": ["attraction", "magnetic"] }, - { "category": "objects", "char": "🔫", "name": "gun", "keywords": ["violence", "weapon", "pistol", "revolver"] }, - { "category": "objects", "char": "💣", "name": "bomb", "keywords": ["boom", "explode", "explosion", "terrorism"] }, - { "category": "objects", "char": "🧨", "name": "firecracker", "keywords": ["dynamite", "boom", "explode", "explosion", "explosive"] }, - { "category": "objects", "char": "🔪", "name": "hocho", "keywords": ["knife", "blade", "cutlery", "kitchen", "weapon"] }, - { "category": "objects", "char": "🗡", "name": "dagger", "keywords": ["weapon"] }, - { "category": "objects", "char": "âš”", "name": "crossed_swords", "keywords": ["weapon"] }, - { "category": "objects", "char": "🛡", "name": "shield", "keywords": ["protection", "security"] }, - { "category": "objects", "char": "🚬", "name": "smoking", "keywords": ["kills", "tobacco", "cigarette", "joint", "smoke"] }, - { "category": "objects", "char": "☠", "name": "skull_and_crossbones", "keywords": ["poison", "danger", "deadly", "scary", "death", "pirate", "evil"] }, - { "category": "objects", "char": "âš°", "name": "coffin", "keywords": ["vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box"] }, - { "category": "objects", "char": "âš±", "name": "funeral_urn", "keywords": ["dead", "die", "death", "rip", "ashes"] }, - { "category": "objects", "char": "ðŸº", "name": "amphora", "keywords": ["vase", "jar"] }, - { "category": "objects", "char": "🔮", "name": "crystal_ball", "keywords": ["disco", "party", "magic", "circus", "fortune_teller"] }, - { "category": "objects", "char": "📿", "name": "prayer_beads", "keywords": ["dhikr", "religious"] }, - { "category": "objects", "char": "🧿", "name": "nazar_amulet", "keywords": ["bead", "charm"] }, - { "category": "objects", "char": "💈", "name": "barber", "keywords": ["hair", "salon", "style"] }, - { "category": "objects", "char": "âš—", "name": "alembic", "keywords": ["distilling", "science", "experiment", "chemistry"] }, - { "category": "objects", "char": "ðŸ”", "name": "telescope", "keywords": ["stars", "space", "zoom", "science", "astronomy"] }, - { "category": "objects", "char": "🔬", "name": "microscope", "keywords": ["laboratory", "experiment", "zoomin", "science", "study"] }, - { "category": "objects", "char": "🕳", "name": "hole", "keywords": ["embarrassing"] }, - { "category": "objects", "char": "💊", "name": "pill", "keywords": ["health", "medicine", "doctor", "pharmacy", "drug"] }, - { "category": "objects", "char": "💉", "name": "syringe", "keywords": ["health", "hospital", "drugs", "blood", "medicine", "needle", "doctor", "nurse"] }, - { "category": "objects", "char": "🩸", "name": "drop_of_blood", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, - { "category": "objects", "char": "🩹", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, - { "category": "objects", "char": "🩺", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, - { "category": "objects", "char": "🪒", "name": "razor", "keywords": ["health"] }, - { "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] }, - { "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] }, - { "category": "objects", "char": "🧬", "name": "dna", "keywords": ["biologist", "genetics", "life"] }, - { "category": "objects", "char": "🧫", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] }, - { "category": "objects", "char": "🧪", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] }, - { "category": "objects", "char": "🌡", "name": "thermometer", "keywords": ["weather", "temperature", "hot", "cold"] }, - { "category": "objects", "char": "🧹", "name": "broom", "keywords": ["cleaning", "sweeping", "witch"] }, - { "category": "objects", "char": "🧺", "name": "basket", "keywords": ["laundry"] }, - { "category": "objects", "char": "🧻", "name": "toilet_paper", "keywords": ["roll"] }, - { "category": "objects", "char": "ðŸ·", "name": "label", "keywords": ["sale", "tag"] }, - { "category": "objects", "char": "🔖", "name": "bookmark", "keywords": ["favorite", "label", "save"] }, - { "category": "objects", "char": "🚽", "name": "toilet", "keywords": ["restroom", "wc", "washroom", "bathroom", "potty"] }, - { "category": "objects", "char": "🚿", "name": "shower", "keywords": ["clean", "water", "bathroom"] }, - { "category": "objects", "char": "ðŸ›", "name": "bathtub", "keywords": ["clean", "shower", "bathroom"] }, - { "category": "objects", "char": "🧼", "name": "soap", "keywords": ["bar", "bathing", "cleaning", "lather"] }, - { "category": "objects", "char": "🧽", "name": "sponge", "keywords": ["absorbing", "cleaning", "porous"] }, - { "category": "objects", "char": "🧴", "name": "lotion_bottle", "keywords": ["moisturizer", "sunscreen"] }, - { "category": "objects", "char": "🔑", "name": "key", "keywords": ["lock", "door", "password"] }, - { "category": "objects", "char": "ðŸ—", "name": "old_key", "keywords": ["lock", "door", "password"] }, - { "category": "objects", "char": "🛋", "name": "couch_and_lamp", "keywords": ["read", "chill"] }, - { "category": "objects", "char": "🪔", "name": "diya_Lamp", "keywords": ["light", "oil"] }, - { "category": "objects", "char": "🛌", "name": "sleeping_bed", "keywords": ["bed", "rest"] }, - { "category": "objects", "char": "ðŸ›", "name": "bed", "keywords": ["sleep", "rest"] }, - { "category": "objects", "char": "🚪", "name": "door", "keywords": ["house", "entry", "exit"] }, - { "category": "objects", "char": "🪑", "name": "chair", "keywords": ["house", "desk"] }, - { "category": "objects", "char": "🛎", "name": "bellhop_bell", "keywords": ["service"] }, - { "category": "objects", "char": "🧸", "name": "teddy_bear", "keywords": ["plush", "stuffed"] }, - { "category": "objects", "char": "🖼", "name": "framed_picture", "keywords": ["photography"] }, - { "category": "objects", "char": "🗺", "name": "world_map", "keywords": ["location", "direction"] }, - { "category": "objects", "char": "🛗", "name": "elevator", "keywords": ["household"] }, - { "category": "objects", "char": "🪞", "name": "mirror", "keywords": ["household"] }, - { "category": "objects", "char": "🪟", "name": "window", "keywords": ["household"] }, - { "category": "objects", "char": "🪠", "name": "plunger", "keywords": ["household"] }, - { "category": "objects", "char": "🪤", "name": "mouse_trap", "keywords": ["household"] }, - { "category": "objects", "char": "🪣", "name": "bucket", "keywords": ["household"] }, - { "category": "objects", "char": "🪥", "name": "toothbrush", "keywords": ["household"] }, - { "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] }, - { "category": "objects", "char": "â›±", "name": "parasol_on_ground", "keywords": ["weather", "summer"] }, - { "category": "objects", "char": "🗿", "name": "moyai", "keywords": ["rock", "easter island", "moai"] }, - { "category": "objects", "char": "ðŸ›", "name": "shopping", "keywords": ["mall", "buy", "purchase"] }, - { "category": "objects", "char": "🛒", "name": "shopping_cart", "keywords": ["trolley"] }, - { "category": "objects", "char": "🎈", "name": "balloon", "keywords": ["party", "celebration", "birthday", "circus"] }, - { "category": "objects", "char": "ðŸŽ", "name": "flags", "keywords": ["fish", "japanese", "koinobori", "carp", "banner"] }, - { "category": "objects", "char": "🎀", "name": "ribbon", "keywords": ["decoration", "pink", "girl", "bowtie"] }, - { "category": "objects", "char": "ðŸŽ", "name": "gift", "keywords": ["present", "birthday", "christmas", "xmas"] }, - { "category": "objects", "char": "🎊", "name": "confetti_ball", "keywords": ["festival", "party", "birthday", "circus"] }, - { "category": "objects", "char": "🎉", "name": "tada", "keywords": ["party", "congratulations", "birthday", "magic", "circus", "celebration"] }, - { "category": "objects", "char": "🎎", "name": "dolls", "keywords": ["japanese", "toy", "kimono"] }, - { "category": "objects", "char": "ðŸŽ", "name": "wind_chime", "keywords": ["nature", "ding", "spring", "bell"] }, - { "category": "objects", "char": "🎌", "name": "crossed_flags", "keywords": ["japanese", "nation", "country", "border"] }, - { "category": "objects", "char": "ðŸ®", "name": "izakaya_lantern", "keywords": ["light", "paper", "halloween", "spooky"] }, - { "category": "objects", "char": "🧧", "name": "red_envelope", "keywords": ["gift"] }, - { "category": "objects", "char": "✉ï¸", "name": "email", "keywords": ["letter", "postal", "inbox", "communication"] }, - { "category": "objects", "char": "📩", "name": "envelope_with_arrow", "keywords": ["email", "communication"] }, - { "category": "objects", "char": "📨", "name": "incoming_envelope", "keywords": ["email", "inbox"] }, - { "category": "objects", "char": "📧", "name": "e-mail", "keywords": ["communication", "inbox"] }, - { "category": "objects", "char": "💌", "name": "love_letter", "keywords": ["email", "like", "affection", "envelope", "valentines"] }, - { "category": "objects", "char": "📮", "name": "postbox", "keywords": ["email", "letter", "envelope"] }, - { "category": "objects", "char": "📪", "name": "mailbox_closed", "keywords": ["email", "communication", "inbox"] }, - { "category": "objects", "char": "📫", "name": "mailbox", "keywords": ["email", "inbox", "communication"] }, - { "category": "objects", "char": "📬", "name": "mailbox_with_mail", "keywords": ["email", "inbox", "communication"] }, - { "category": "objects", "char": "ðŸ“", "name": "mailbox_with_no_mail", "keywords": ["email", "inbox"] }, - { "category": "objects", "char": "📦", "name": "package", "keywords": ["mail", "gift", "cardboard", "box", "moving"] }, - { "category": "objects", "char": "📯", "name": "postal_horn", "keywords": ["instrument", "music"] }, - { "category": "objects", "char": "📥", "name": "inbox_tray", "keywords": ["email", "documents"] }, - { "category": "objects", "char": "📤", "name": "outbox_tray", "keywords": ["inbox", "email"] }, - { "category": "objects", "char": "📜", "name": "scroll", "keywords": ["documents", "ancient", "history", "paper"] }, - { "category": "objects", "char": "📃", "name": "page_with_curl", "keywords": ["documents", "office", "paper"] }, - { "category": "objects", "char": "📑", "name": "bookmark_tabs", "keywords": ["favorite", "save", "order", "tidy"] }, - { "category": "objects", "char": "🧾", "name": "receipt", "keywords": ["accounting", "expenses"] }, - { "category": "objects", "char": "📊", "name": "bar_chart", "keywords": ["graph", "presentation", "stats"] }, - { "category": "objects", "char": "📈", "name": "chart_with_upwards_trend", "keywords": ["graph", "presentation", "stats", "recovery", "business", "economics", "money", "sales", "good", "success"] }, - { "category": "objects", "char": "📉", "name": "chart_with_downwards_trend", "keywords": ["graph", "presentation", "stats", "recession", "business", "economics", "money", "sales", "bad", "failure"] }, - { "category": "objects", "char": "📄", "name": "page_facing_up", "keywords": ["documents", "office", "paper", "information"] }, - { "category": "objects", "char": "📅", "name": "date", "keywords": ["calendar", "schedule"] }, - { "category": "objects", "char": "📆", "name": "calendar", "keywords": ["schedule", "date", "planning"] }, - { "category": "objects", "char": "🗓", "name": "spiral_calendar", "keywords": ["date", "schedule", "planning"] }, - { "category": "objects", "char": "📇", "name": "card_index", "keywords": ["business", "stationery"] }, - { "category": "objects", "char": "🗃", "name": "card_file_box", "keywords": ["business", "stationery"] }, - { "category": "objects", "char": "🗳", "name": "ballot_box", "keywords": ["election", "vote"] }, - { "category": "objects", "char": "🗄", "name": "file_cabinet", "keywords": ["filing", "organizing"] }, - { "category": "objects", "char": "📋", "name": "clipboard", "keywords": ["stationery", "documents"] }, - { "category": "objects", "char": "🗒", "name": "spiral_notepad", "keywords": ["memo", "stationery"] }, - { "category": "objects", "char": "ðŸ“", "name": "file_folder", "keywords": ["documents", "business", "office"] }, - { "category": "objects", "char": "📂", "name": "open_file_folder", "keywords": ["documents", "load"] }, - { "category": "objects", "char": "🗂", "name": "card_index_dividers", "keywords": ["organizing", "business", "stationery"] }, - { "category": "objects", "char": "🗞", "name": "newspaper_roll", "keywords": ["press", "headline"] }, - { "category": "objects", "char": "📰", "name": "newspaper", "keywords": ["press", "headline"] }, - { "category": "objects", "char": "📓", "name": "notebook", "keywords": ["stationery", "record", "notes", "paper", "study"] }, - { "category": "objects", "char": "📕", "name": "closed_book", "keywords": ["read", "library", "knowledge", "textbook", "learn"] }, - { "category": "objects", "char": "📗", "name": "green_book", "keywords": ["read", "library", "knowledge", "study"] }, - { "category": "objects", "char": "📘", "name": "blue_book", "keywords": ["read", "library", "knowledge", "learn", "study"] }, - { "category": "objects", "char": "📙", "name": "orange_book", "keywords": ["read", "library", "knowledge", "textbook", "study"] }, - { "category": "objects", "char": "📔", "name": "notebook_with_decorative_cover", "keywords": ["classroom", "notes", "record", "paper", "study"] }, - { "category": "objects", "char": "📒", "name": "ledger", "keywords": ["notes", "paper"] }, - { "category": "objects", "char": "📚", "name": "books", "keywords": ["literature", "library", "study"] }, - { "category": "objects", "char": "📖", "name": "open_book", "keywords": ["book", "read", "library", "knowledge", "literature", "learn", "study"] }, - { "category": "objects", "char": "🧷", "name": "safety_pin", "keywords": ["diaper"] }, - { "category": "objects", "char": "🔗", "name": "link", "keywords": ["rings", "url"] }, - { "category": "objects", "char": "📎", "name": "paperclip", "keywords": ["documents", "stationery"] }, - { "category": "objects", "char": "🖇", "name": "paperclips", "keywords": ["documents", "stationery"] }, - { "category": "objects", "char": "✂ï¸", "name": "scissors", "keywords": ["stationery", "cut"] }, - { "category": "objects", "char": "ðŸ“", "name": "triangular_ruler", "keywords": ["stationery", "math", "architect", "sketch"] }, - { "category": "objects", "char": "ðŸ“", "name": "straight_ruler", "keywords": ["stationery", "calculate", "length", "math", "school", "drawing", "architect", "sketch"] }, - { "category": "objects", "char": "🧮", "name": "abacus", "keywords": ["calculation"] }, - { "category": "objects", "char": "📌", "name": "pushpin", "keywords": ["stationery", "mark", "here"] }, - { "category": "objects", "char": "ðŸ“", "name": "round_pushpin", "keywords": ["stationery", "location", "map", "here"] }, - { "category": "objects", "char": "🚩", "name": "triangular_flag_on_post", "keywords": ["mark", "milestone", "place"] }, - { "category": "objects", "char": "ðŸ³", "name": "white_flag", "keywords": ["losing", "loser", "lost", "surrender", "give up", "fail"] }, - { "category": "objects", "char": "ðŸ´", "name": "black_flag", "keywords": ["pirate"] }, - { "category": "objects", "char": "ðŸ³ï¸â€ðŸŒˆ", "name": "rainbow_flag", "keywords": ["flag", "rainbow", "pride", "gay", "lgbt", "glbt", "queer", "homosexual", "lesbian", "bisexual", "transgender"] }, - { "category": "objects", "char": "ðŸ³ï¸â€âš§ï¸", "name": "transgender_flag", "keywords": ["flag", "transgender"] }, - { "category": "objects", "char": "ðŸ”", "name": "closed_lock_with_key", "keywords": ["security", "privacy"] }, - { "category": "objects", "char": "🔒", "name": "lock", "keywords": ["security", "password", "padlock"] }, - { "category": "objects", "char": "🔓", "name": "unlock", "keywords": ["privacy", "security"] }, - { "category": "objects", "char": "ðŸ”", "name": "lock_with_ink_pen", "keywords": ["security", "secret"] }, - { "category": "objects", "char": "🖊", "name": "pen", "keywords": ["stationery", "writing", "write"] }, - { "category": "objects", "char": "🖋", "name": "fountain_pen", "keywords": ["stationery", "writing", "write"] }, - { "category": "objects", "char": "✒ï¸", "name": "black_nib", "keywords": ["pen", "stationery", "writing", "write"] }, - { "category": "objects", "char": "ðŸ“", "name": "memo", "keywords": ["write", "documents", "stationery", "pencil", "paper", "writing", "legal", "exam", "quiz", "test", "study", "compose"] }, - { "category": "objects", "char": "âœï¸", "name": "pencil2", "keywords": ["stationery", "write", "paper", "writing", "school", "study"] }, - { "category": "objects", "char": "ðŸ–", "name": "crayon", "keywords": ["drawing", "creativity"] }, - { "category": "objects", "char": "🖌", "name": "paintbrush", "keywords": ["drawing", "creativity", "art"] }, - { "category": "objects", "char": "ðŸ”", "name": "mag", "keywords": ["search", "zoom", "find", "detective"] }, - { "category": "objects", "char": "🔎", "name": "mag_right", "keywords": ["search", "zoom", "find", "detective"] }, - { "category": "objects", "char": "🪦", "name": "headstone", "keywords": [] }, - { "category": "objects", "char": "🪧", "name": "placard", "keywords": [] }, - { "category": "symbols", "char": "💯", "name": "100", "keywords": ["score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred"] }, - { "category": "symbols", "char": "🔢", "name": "1234", "keywords": ["numbers", "blue-square"] }, - { "category": "symbols", "char": "â¤ï¸", "name": "heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "🧡", "name": "orange_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💛", "name": "yellow_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💚", "name": "green_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💙", "name": "blue_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💜", "name": "purple_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "🤎", "name": "brown_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "🖤", "name": "black_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "ðŸ¤", "name": "white_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💔", "name": "broken_heart", "keywords": ["sad", "sorry", "break", "heart", "heartbreak"] }, - { "category": "symbols", "char": "â£", "name": "heavy_heart_exclamation", "keywords": ["decoration", "love"] }, - { "category": "symbols", "char": "💕", "name": "two_hearts", "keywords": ["love", "like", "affection", "valentines", "heart"] }, - { "category": "symbols", "char": "💞", "name": "revolving_hearts", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💓", "name": "heartbeat", "keywords": ["love", "like", "affection", "valentines", "pink", "heart"] }, - { "category": "symbols", "char": "💗", "name": "heartpulse", "keywords": ["like", "love", "affection", "valentines", "pink"] }, - { "category": "symbols", "char": "💖", "name": "sparkling_heart", "keywords": ["love", "like", "affection", "valentines"] }, - { "category": "symbols", "char": "💘", "name": "cupid", "keywords": ["love", "like", "heart", "affection", "valentines"] }, - { "category": "symbols", "char": "ðŸ’", "name": "gift_heart", "keywords": ["love", "valentines"] }, - { "category": "symbols", "char": "💟", "name": "heart_decoration", "keywords": ["purple-square", "love", "like"] }, - { "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83D\uDD25", "name": "heart_on_fire", "keywords": [] }, - { "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83E\uDE79", "name": "mending_heart", "keywords": [] }, - { "category": "symbols", "char": "☮", "name": "peace_symbol", "keywords": ["hippie"] }, - { "category": "symbols", "char": "âœ", "name": "latin_cross", "keywords": ["christianity"] }, - { "category": "symbols", "char": "☪", "name": "star_and_crescent", "keywords": ["islam"] }, - { "category": "symbols", "char": "🕉", "name": "om", "keywords": ["hinduism", "buddhism", "sikhism", "jainism"] }, - { "category": "symbols", "char": "☸", "name": "wheel_of_dharma", "keywords": ["hinduism", "buddhism", "sikhism", "jainism"] }, - { "category": "symbols", "char": "✡", "name": "star_of_david", "keywords": ["judaism"] }, - { "category": "symbols", "char": "🔯", "name": "six_pointed_star", "keywords": ["purple-square", "religion", "jewish", "hexagram"] }, - { "category": "symbols", "char": "🕎", "name": "menorah", "keywords": ["hanukkah", "candles", "jewish"] }, - { "category": "symbols", "char": "☯", "name": "yin_yang", "keywords": ["balance"] }, - { "category": "symbols", "char": "☦", "name": "orthodox_cross", "keywords": ["suppedaneum", "religion"] }, - { "category": "symbols", "char": "ðŸ›", "name": "place_of_worship", "keywords": ["religion", "church", "temple", "prayer"] }, - { "category": "symbols", "char": "⛎", "name": "ophiuchus", "keywords": ["sign", "purple-square", "constellation", "astrology"] }, - { "category": "symbols", "char": "♈", "name": "aries", "keywords": ["sign", "purple-square", "zodiac", "astrology"] }, - { "category": "symbols", "char": "♉", "name": "taurus", "keywords": ["purple-square", "sign", "zodiac", "astrology"] }, - { "category": "symbols", "char": "♊", "name": "gemini", "keywords": ["sign", "zodiac", "purple-square", "astrology"] }, - { "category": "symbols", "char": "♋", "name": "cancer", "keywords": ["sign", "zodiac", "purple-square", "astrology"] }, - { "category": "symbols", "char": "♌", "name": "leo", "keywords": ["sign", "purple-square", "zodiac", "astrology"] }, - { "category": "symbols", "char": "â™", "name": "virgo", "keywords": ["sign", "zodiac", "purple-square", "astrology"] }, - { "category": "symbols", "char": "♎", "name": "libra", "keywords": ["sign", "purple-square", "zodiac", "astrology"] }, - { "category": "symbols", "char": "â™", "name": "scorpius", "keywords": ["sign", "zodiac", "purple-square", "astrology", "scorpio"] }, - { "category": "symbols", "char": "â™", "name": "sagittarius", "keywords": ["sign", "zodiac", "purple-square", "astrology"] }, - { "category": "symbols", "char": "♑", "name": "capricorn", "keywords": ["sign", "zodiac", "purple-square", "astrology"] }, - { "category": "symbols", "char": "â™’", "name": "aquarius", "keywords": ["sign", "purple-square", "zodiac", "astrology"] }, - { "category": "symbols", "char": "♓", "name": "pisces", "keywords": ["purple-square", "sign", "zodiac", "astrology"] }, - { "category": "symbols", "char": "🆔", "name": "id", "keywords": ["purple-square", "words"] }, - { "category": "symbols", "char": "âš›", "name": "atom_symbol", "keywords": ["science", "physics", "chemistry"] }, - { "category": "symbols", "char": "⚧ï¸", "name": "transgender_symbol", "keywords": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"] }, - { "category": "symbols", "char": "🈳", "name": "u7a7a", "keywords": ["kanji", "japanese", "chinese", "empty", "sky", "blue-square", "aki"] }, - { "category": "symbols", "char": "🈹", "name": "u5272", "keywords": ["cut", "divide", "chinese", "kanji", "pink-square", "waribiki"] }, - { "category": "symbols", "char": "☢", "name": "radioactive", "keywords": ["nuclear", "danger"] }, - { "category": "symbols", "char": "☣", "name": "biohazard", "keywords": ["danger"] }, - { "category": "symbols", "char": "📴", "name": "mobile_phone_off", "keywords": ["mute", "orange-square", "silence", "quiet"] }, - { "category": "symbols", "char": "📳", "name": "vibration_mode", "keywords": ["orange-square", "phone"] }, - { "category": "symbols", "char": "🈶", "name": "u6709", "keywords": ["orange-square", "chinese", "have", "kanji", "ari"] }, - { "category": "symbols", "char": "🈚", "name": "u7121", "keywords": ["nothing", "chinese", "kanji", "japanese", "orange-square", "nashi"] }, - { "category": "symbols", "char": "🈸", "name": "u7533", "keywords": ["chinese", "japanese", "kanji", "orange-square", "moushikomi"] }, - { "category": "symbols", "char": "🈺", "name": "u55b6", "keywords": ["japanese", "opening hours", "orange-square", "eigyo"] }, - { "category": "symbols", "char": "🈷ï¸", "name": "u6708", "keywords": ["chinese", "month", "moon", "japanese", "orange-square", "kanji", "tsuki", "tsukigime", "getsugaku"] }, - { "category": "symbols", "char": "✴ï¸", "name": "eight_pointed_black_star", "keywords": ["orange-square", "shape", "polygon"] }, - { "category": "symbols", "char": "🆚", "name": "vs", "keywords": ["words", "orange-square"] }, - { "category": "symbols", "char": "🉑", "name": "accept", "keywords": ["ok", "good", "chinese", "kanji", "agree", "yes", "orange-circle"] }, - { "category": "symbols", "char": "💮", "name": "white_flower", "keywords": ["japanese", "spring"] }, - { "category": "symbols", "char": "ðŸ‰", "name": "ideograph_advantage", "keywords": ["chinese", "kanji", "obtain", "get", "circle"] }, - { "category": "symbols", "char": "㊙ï¸", "name": "secret", "keywords": ["privacy", "chinese", "sshh", "kanji", "red-circle"] }, - { "category": "symbols", "char": "㊗ï¸", "name": "congratulations", "keywords": ["chinese", "kanji", "japanese", "red-circle"] }, - { "category": "symbols", "char": "🈴", "name": "u5408", "keywords": ["japanese", "chinese", "join", "kanji", "red-square", "goukaku", "pass"] }, - { "category": "symbols", "char": "🈵", "name": "u6e80", "keywords": ["full", "chinese", "japanese", "red-square", "kanji", "man"] }, - { "category": "symbols", "char": "🈲", "name": "u7981", "keywords": ["kanji", "japanese", "chinese", "forbidden", "limit", "restricted", "red-square", "kinshi"] }, - { "category": "symbols", "char": "🅰ï¸", "name": "a", "keywords": ["red-square", "alphabet", "letter"] }, - { "category": "symbols", "char": "🅱ï¸", "name": "b", "keywords": ["red-square", "alphabet", "letter"] }, - { "category": "symbols", "char": "🆎", "name": "ab", "keywords": ["red-square", "alphabet"] }, - { "category": "symbols", "char": "🆑", "name": "cl", "keywords": ["alphabet", "words", "red-square"] }, - { "category": "symbols", "char": "🅾ï¸", "name": "o2", "keywords": ["alphabet", "red-square", "letter"] }, - { "category": "symbols", "char": "🆘", "name": "sos", "keywords": ["help", "red-square", "words", "emergency", "911"] }, - { "category": "symbols", "char": "â›”", "name": "no_entry", "keywords": ["limit", "security", "privacy", "bad", "denied", "stop", "circle"] }, - { "category": "symbols", "char": "📛", "name": "name_badge", "keywords": ["fire", "forbid"] }, - { "category": "symbols", "char": "🚫", "name": "no_entry_sign", "keywords": ["forbid", "stop", "limit", "denied", "disallow", "circle"] }, - { "category": "symbols", "char": "âŒ", "name": "x", "keywords": ["no", "delete", "remove", "cancel", "red"] }, - { "category": "symbols", "char": "â•", "name": "o", "keywords": ["circle", "round"] }, - { "category": "symbols", "char": "🛑", "name": "stop_sign", "keywords": ["stop"] }, - { "category": "symbols", "char": "💢", "name": "anger", "keywords": ["angry", "mad"] }, - { "category": "symbols", "char": "♨ï¸", "name": "hotsprings", "keywords": ["bath", "warm", "relax"] }, - { "category": "symbols", "char": "🚷", "name": "no_pedestrians", "keywords": ["rules", "crossing", "walking", "circle"] }, - { "category": "symbols", "char": "🚯", "name": "do_not_litter", "keywords": ["trash", "bin", "garbage", "circle"] }, - { "category": "symbols", "char": "🚳", "name": "no_bicycles", "keywords": ["cyclist", "prohibited", "circle"] }, - { "category": "symbols", "char": "🚱", "name": "non-potable_water", "keywords": ["drink", "faucet", "tap", "circle"] }, - { "category": "symbols", "char": "🔞", "name": "underage", "keywords": ["18", "drink", "pub", "night", "minor", "circle"] }, - { "category": "symbols", "char": "📵", "name": "no_mobile_phones", "keywords": ["iphone", "mute", "circle"] }, - { "category": "symbols", "char": "â—", "name": "exclamation", "keywords": ["heavy_exclamation_mark", "danger", "surprise", "punctuation", "wow", "warning"] }, - { "category": "symbols", "char": "â•", "name": "grey_exclamation", "keywords": ["surprise", "punctuation", "gray", "wow", "warning"] }, - { "category": "symbols", "char": "â“", "name": "question", "keywords": ["doubt", "confused"] }, - { "category": "symbols", "char": "â”", "name": "grey_question", "keywords": ["doubts", "gray", "huh", "confused"] }, - { "category": "symbols", "char": "‼ï¸", "name": "bangbang", "keywords": ["exclamation", "surprise"] }, - { "category": "symbols", "char": "â‰ï¸", "name": "interrobang", "keywords": ["wat", "punctuation", "surprise"] }, - { "category": "symbols", "char": "🔅", "name": "low_brightness", "keywords": ["sun", "afternoon", "warm", "summer"] }, - { "category": "symbols", "char": "🔆", "name": "high_brightness", "keywords": ["sun", "light"] }, - { "category": "symbols", "char": "🔱", "name": "trident", "keywords": ["weapon", "spear"] }, - { "category": "symbols", "char": "âšœ", "name": "fleur_de_lis", "keywords": ["decorative", "scout"] }, - { "category": "symbols", "char": "〽ï¸", "name": "part_alternation_mark", "keywords": ["graph", "presentation", "stats", "business", "economics", "bad"] }, - { "category": "symbols", "char": "âš ï¸", "name": "warning", "keywords": ["exclamation", "wip", "alert", "error", "problem", "issue"] }, - { "category": "symbols", "char": "🚸", "name": "children_crossing", "keywords": ["school", "warning", "danger", "sign", "driving", "yellow-diamond"] }, - { "category": "symbols", "char": "🔰", "name": "beginner", "keywords": ["badge", "shield"] }, - { "category": "symbols", "char": "â™»ï¸", "name": "recycle", "keywords": ["arrow", "environment", "garbage", "trash"] }, - { "category": "symbols", "char": "🈯", "name": "u6307", "keywords": ["chinese", "point", "green-square", "kanji", "reserved", "shiteiseki"] }, - { "category": "symbols", "char": "💹", "name": "chart", "keywords": ["green-square", "graph", "presentation", "stats"] }, - { "category": "symbols", "char": "â‡ï¸", "name": "sparkle", "keywords": ["stars", "green-square", "awesome", "good", "fireworks"] }, - { "category": "symbols", "char": "✳ï¸", "name": "eight_spoked_asterisk", "keywords": ["star", "sparkle", "green-square"] }, - { "category": "symbols", "char": "âŽ", "name": "negative_squared_cross_mark", "keywords": ["x", "green-square", "no", "deny"] }, - { "category": "symbols", "char": "✅", "name": "white_check_mark", "keywords": ["green-square", "ok", "agree", "vote", "election", "answer", "tick"] }, - { "category": "symbols", "char": "💠", "name": "diamond_shape_with_a_dot_inside", "keywords": ["jewel", "blue", "gem", "crystal", "fancy"] }, - { "category": "symbols", "char": "🌀", "name": "cyclone", "keywords": ["weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon"] }, - { "category": "symbols", "char": "âž¿", "name": "loop", "keywords": ["tape", "cassette"] }, - { "category": "symbols", "char": "ðŸŒ", "name": "globe_with_meridians", "keywords": ["earth", "international", "world", "internet", "interweb", "i18n"] }, - { "category": "symbols", "char": "â“‚ï¸", "name": "m", "keywords": ["alphabet", "blue-circle", "letter"] }, - { "category": "symbols", "char": "ðŸ§", "name": "atm", "keywords": ["money", "sales", "cash", "blue-square", "payment", "bank"] }, - { "category": "symbols", "char": "🈂ï¸", "name": "sa", "keywords": ["japanese", "blue-square", "katakana"] }, - { "category": "symbols", "char": "🛂", "name": "passport_control", "keywords": ["custom", "blue-square"] }, - { "category": "symbols", "char": "🛃", "name": "customs", "keywords": ["passport", "border", "blue-square"] }, - { "category": "symbols", "char": "🛄", "name": "baggage_claim", "keywords": ["blue-square", "airport", "transport"] }, - { "category": "symbols", "char": "🛅", "name": "left_luggage", "keywords": ["blue-square", "travel"] }, - { "category": "symbols", "char": "♿", "name": "wheelchair", "keywords": ["blue-square", "disabled", "a11y", "accessibility"] }, - { "category": "symbols", "char": "ðŸš", "name": "no_smoking", "keywords": ["cigarette", "blue-square", "smell", "smoke"] }, - { "category": "symbols", "char": "🚾", "name": "wc", "keywords": ["toilet", "restroom", "blue-square"] }, - { "category": "symbols", "char": "🅿ï¸", "name": "parking", "keywords": ["cars", "blue-square", "alphabet", "letter"] }, - { "category": "symbols", "char": "🚰", "name": "potable_water", "keywords": ["blue-square", "liquid", "restroom", "cleaning", "faucet"] }, - { "category": "symbols", "char": "🚹", "name": "mens", "keywords": ["toilet", "restroom", "wc", "blue-square", "gender", "male"] }, - { "category": "symbols", "char": "🚺", "name": "womens", "keywords": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"] }, - { "category": "symbols", "char": "🚼", "name": "baby_symbol", "keywords": ["orange-square", "child"] }, - { "category": "symbols", "char": "🚻", "name": "restroom", "keywords": ["blue-square", "toilet", "refresh", "wc", "gender"] }, - { "category": "symbols", "char": "🚮", "name": "put_litter_in_its_place", "keywords": ["blue-square", "sign", "human", "info"] }, - { "category": "symbols", "char": "🎦", "name": "cinema", "keywords": ["blue-square", "record", "film", "movie", "curtain", "stage", "theater"] }, - { "category": "symbols", "char": "📶", "name": "signal_strength", "keywords": ["blue-square", "reception", "phone", "internet", "connection", "wifi", "bluetooth", "bars"] }, - { "category": "symbols", "char": "ðŸˆ", "name": "koko", "keywords": ["blue-square", "here", "katakana", "japanese", "destination"] }, - { "category": "symbols", "char": "🆖", "name": "ng", "keywords": ["blue-square", "words", "shape", "icon"] }, - { "category": "symbols", "char": "🆗", "name": "ok", "keywords": ["good", "agree", "yes", "blue-square"] }, - { "category": "symbols", "char": "🆙", "name": "up", "keywords": ["blue-square", "above", "high"] }, - { "category": "symbols", "char": "🆒", "name": "cool", "keywords": ["words", "blue-square"] }, - { "category": "symbols", "char": "🆕", "name": "new", "keywords": ["blue-square", "words", "start"] }, - { "category": "symbols", "char": "🆓", "name": "free", "keywords": ["blue-square", "words"] }, - { "category": "symbols", "char": "0ï¸âƒ£", "name": "zero", "keywords": ["0", "numbers", "blue-square", "null"] }, - { "category": "symbols", "char": "1ï¸âƒ£", "name": "one", "keywords": ["blue-square", "numbers", "1"] }, - { "category": "symbols", "char": "2ï¸âƒ£", "name": "two", "keywords": ["numbers", "2", "prime", "blue-square"] }, - { "category": "symbols", "char": "3ï¸âƒ£", "name": "three", "keywords": ["3", "numbers", "prime", "blue-square"] }, - { "category": "symbols", "char": "4ï¸âƒ£", "name": "four", "keywords": ["4", "numbers", "blue-square"] }, - { "category": "symbols", "char": "5ï¸âƒ£", "name": "five", "keywords": ["5", "numbers", "blue-square", "prime"] }, - { "category": "symbols", "char": "6ï¸âƒ£", "name": "six", "keywords": ["6", "numbers", "blue-square"] }, - { "category": "symbols", "char": "7ï¸âƒ£", "name": "seven", "keywords": ["7", "numbers", "blue-square", "prime"] }, - { "category": "symbols", "char": "8ï¸âƒ£", "name": "eight", "keywords": ["8", "blue-square", "numbers"] }, - { "category": "symbols", "char": "9ï¸âƒ£", "name": "nine", "keywords": ["blue-square", "numbers", "9"] }, - { "category": "symbols", "char": "🔟", "name": "keycap_ten", "keywords": ["numbers", "10", "blue-square"] }, - { "category": "symbols", "char": "*⃣", "name": "asterisk", "keywords": ["star", "keycap"] }, - { "category": "symbols", "char": "âï¸", "name": "eject_button", "keywords": ["blue-square"] }, - { "category": "symbols", "char": "â–¶ï¸", "name": "arrow_forward", "keywords": ["blue-square", "right", "direction", "play"] }, - { "category": "symbols", "char": "â¸", "name": "pause_button", "keywords": ["pause", "blue-square"] }, - { "category": "symbols", "char": "â", "name": "next_track_button", "keywords": ["forward", "next", "blue-square"] }, - { "category": "symbols", "char": "â¹", "name": "stop_button", "keywords": ["blue-square"] }, - { "category": "symbols", "char": "âº", "name": "record_button", "keywords": ["blue-square"] }, - { "category": "symbols", "char": "â¯", "name": "play_or_pause_button", "keywords": ["blue-square", "play", "pause"] }, - { "category": "symbols", "char": "â®", "name": "previous_track_button", "keywords": ["backward"] }, - { "category": "symbols", "char": "â©", "name": "fast_forward", "keywords": ["blue-square", "play", "speed", "continue"] }, - { "category": "symbols", "char": "âª", "name": "rewind", "keywords": ["play", "blue-square"] }, - { "category": "symbols", "char": "🔀", "name": "twisted_rightwards_arrows", "keywords": ["blue-square", "shuffle", "music", "random"] }, - { "category": "symbols", "char": "ðŸ”", "name": "repeat", "keywords": ["loop", "record"] }, - { "category": "symbols", "char": "🔂", "name": "repeat_one", "keywords": ["blue-square", "loop"] }, - { "category": "symbols", "char": "â—€ï¸", "name": "arrow_backward", "keywords": ["blue-square", "left", "direction"] }, - { "category": "symbols", "char": "🔼", "name": "arrow_up_small", "keywords": ["blue-square", "triangle", "direction", "point", "forward", "top"] }, - { "category": "symbols", "char": "🔽", "name": "arrow_down_small", "keywords": ["blue-square", "direction", "bottom"] }, - { "category": "symbols", "char": "â«", "name": "arrow_double_up", "keywords": ["blue-square", "direction", "top"] }, - { "category": "symbols", "char": "â¬", "name": "arrow_double_down", "keywords": ["blue-square", "direction", "bottom"] }, - { "category": "symbols", "char": "âž¡ï¸", "name": "arrow_right", "keywords": ["blue-square", "next"] }, - { "category": "symbols", "char": "⬅ï¸", "name": "arrow_left", "keywords": ["blue-square", "previous", "back"] }, - { "category": "symbols", "char": "⬆ï¸", "name": "arrow_up", "keywords": ["blue-square", "continue", "top", "direction"] }, - { "category": "symbols", "char": "⬇ï¸", "name": "arrow_down", "keywords": ["blue-square", "direction", "bottom"] }, - { "category": "symbols", "char": "↗ï¸", "name": "arrow_upper_right", "keywords": ["blue-square", "point", "direction", "diagonal", "northeast"] }, - { "category": "symbols", "char": "↘ï¸", "name": "arrow_lower_right", "keywords": ["blue-square", "direction", "diagonal", "southeast"] }, - { "category": "symbols", "char": "↙ï¸", "name": "arrow_lower_left", "keywords": ["blue-square", "direction", "diagonal", "southwest"] }, - { "category": "symbols", "char": "↖ï¸", "name": "arrow_upper_left", "keywords": ["blue-square", "point", "direction", "diagonal", "northwest"] }, - { "category": "symbols", "char": "↕ï¸", "name": "arrow_up_down", "keywords": ["blue-square", "direction", "way", "vertical"] }, - { "category": "symbols", "char": "↔ï¸", "name": "left_right_arrow", "keywords": ["shape", "direction", "horizontal", "sideways"] }, - { "category": "symbols", "char": "🔄", "name": "arrows_counterclockwise", "keywords": ["blue-square", "sync", "cycle"] }, - { "category": "symbols", "char": "↪ï¸", "name": "arrow_right_hook", "keywords": ["blue-square", "return", "rotate", "direction"] }, - { "category": "symbols", "char": "↩ï¸", "name": "leftwards_arrow_with_hook", "keywords": ["back", "return", "blue-square", "undo", "enter"] }, - { "category": "symbols", "char": "⤴ï¸", "name": "arrow_heading_up", "keywords": ["blue-square", "direction", "top"] }, - { "category": "symbols", "char": "⤵ï¸", "name": "arrow_heading_down", "keywords": ["blue-square", "direction", "bottom"] }, - { "category": "symbols", "char": "#ï¸âƒ£", "name": "hash", "keywords": ["symbol", "blue-square", "twitter"] }, - { "category": "symbols", "char": "ℹï¸", "name": "information_source", "keywords": ["blue-square", "alphabet", "letter"] }, - { "category": "symbols", "char": "🔤", "name": "abc", "keywords": ["blue-square", "alphabet"] }, - { "category": "symbols", "char": "🔡", "name": "abcd", "keywords": ["blue-square", "alphabet"] }, - { "category": "symbols", "char": "🔠", "name": "capital_abcd", "keywords": ["alphabet", "words", "blue-square"] }, - { "category": "symbols", "char": "🔣", "name": "symbols", "keywords": ["blue-square", "music", "note", "ampersand", "percent", "glyphs", "characters"] }, - { "category": "symbols", "char": "🎵", "name": "musical_note", "keywords": ["score", "tone", "sound"] }, - { "category": "symbols", "char": "🎶", "name": "notes", "keywords": ["music", "score"] }, - { "category": "symbols", "char": "〰ï¸", "name": "wavy_dash", "keywords": ["draw", "line", "moustache", "mustache", "squiggle", "scribble"] }, - { "category": "symbols", "char": "âž°", "name": "curly_loop", "keywords": ["scribble", "draw", "shape", "squiggle"] }, - { "category": "symbols", "char": "✔ï¸", "name": "heavy_check_mark", "keywords": ["ok", "nike", "answer", "yes", "tick"] }, - { "category": "symbols", "char": "🔃", "name": "arrows_clockwise", "keywords": ["sync", "cycle", "round", "repeat"] }, - { "category": "symbols", "char": "âž•", "name": "heavy_plus_sign", "keywords": ["math", "calculation", "addition", "more", "increase"] }, - { "category": "symbols", "char": "âž–", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] }, - { "category": "symbols", "char": "âž—", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] }, - { "category": "symbols", "char": "✖ï¸", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] }, - { "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] }, - { "category": "symbols", "char": "♾", "name": "infinity", "keywords": ["forever"] }, - { "category": "symbols", "char": "💲", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] }, - { "category": "symbols", "char": "💱", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] }, - { "category": "symbols", "char": "©ï¸", "name": "copyright", "keywords": ["ip", "license", "circle", "law", "legal"] }, - { "category": "symbols", "char": "®ï¸", "name": "registered", "keywords": ["alphabet", "circle"] }, - { "category": "symbols", "char": "â„¢ï¸", "name": "tm", "keywords": ["trademark", "brand", "law", "legal"] }, - { "category": "symbols", "char": "🔚", "name": "end", "keywords": ["words", "arrow"] }, - { "category": "symbols", "char": "🔙", "name": "back", "keywords": ["arrow", "words", "return"] }, - { "category": "symbols", "char": "🔛", "name": "on", "keywords": ["arrow", "words"] }, - { "category": "symbols", "char": "ðŸ”", "name": "top", "keywords": ["words", "blue-square"] }, - { "category": "symbols", "char": "🔜", "name": "soon", "keywords": ["arrow", "words"] }, - { "category": "symbols", "char": "☑ï¸", "name": "ballot_box_with_check", "keywords": ["ok", "agree", "confirm", "black-square", "vote", "election", "yes", "tick"] }, - { "category": "symbols", "char": "🔘", "name": "radio_button", "keywords": ["input", "old", "music", "circle"] }, - { "category": "symbols", "char": "âš«", "name": "black_circle", "keywords": ["shape", "button", "round"] }, - { "category": "symbols", "char": "⚪", "name": "white_circle", "keywords": ["shape", "round"] }, - { "category": "symbols", "char": "🔴", "name": "red_circle", "keywords": ["shape", "error", "danger"] }, - { "category": "symbols", "char": "🟠", "name": "orange_circle", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟡", "name": "yellow_circle", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟢", "name": "green_circle", "keywords": ["shape"] }, - { "category": "symbols", "char": "🔵", "name": "large_blue_circle", "keywords": ["shape", "icon", "button"] }, - { "category": "symbols", "char": "🟣", "name": "purple_circle", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟤", "name": "brown_circle", "keywords": ["shape"] }, - { "category": "symbols", "char": "🔸", "name": "small_orange_diamond", "keywords": ["shape", "jewel", "gem"] }, - { "category": "symbols", "char": "🔹", "name": "small_blue_diamond", "keywords": ["shape", "jewel", "gem"] }, - { "category": "symbols", "char": "🔶", "name": "large_orange_diamond", "keywords": ["shape", "jewel", "gem"] }, - { "category": "symbols", "char": "🔷", "name": "large_blue_diamond", "keywords": ["shape", "jewel", "gem"] }, - { "category": "symbols", "char": "🔺", "name": "small_red_triangle", "keywords": ["shape", "direction", "up", "top"] }, - { "category": "symbols", "char": "â–ªï¸", "name": "black_small_square", "keywords": ["shape", "icon"] }, - { "category": "symbols", "char": "â–«ï¸", "name": "white_small_square", "keywords": ["shape", "icon"] }, - { "category": "symbols", "char": "⬛", "name": "black_large_square", "keywords": ["shape", "icon", "button"] }, - { "category": "symbols", "char": "⬜", "name": "white_large_square", "keywords": ["shape", "icon", "stone", "button"] }, - { "category": "symbols", "char": "🟥", "name": "red_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟧", "name": "orange_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟨", "name": "yellow_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟩", "name": "green_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟦", "name": "blue_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟪", "name": "purple_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🟫", "name": "brown_square", "keywords": ["shape"] }, - { "category": "symbols", "char": "🔻", "name": "small_red_triangle_down", "keywords": ["shape", "direction", "bottom"] }, - { "category": "symbols", "char": "â—¼ï¸", "name": "black_medium_square", "keywords": ["shape", "button", "icon"] }, - { "category": "symbols", "char": "â—»ï¸", "name": "white_medium_square", "keywords": ["shape", "stone", "icon"] }, - { "category": "symbols", "char": "â—¾", "name": "black_medium_small_square", "keywords": ["icon", "shape", "button"] }, - { "category": "symbols", "char": "â—½", "name": "white_medium_small_square", "keywords": ["shape", "stone", "icon", "button"] }, - { "category": "symbols", "char": "🔲", "name": "black_square_button", "keywords": ["shape", "input", "frame"] }, - { "category": "symbols", "char": "🔳", "name": "white_square_button", "keywords": ["shape", "input"] }, - { "category": "symbols", "char": "🔈", "name": "speaker", "keywords": ["sound", "volume", "silence", "broadcast"] }, - { "category": "symbols", "char": "🔉", "name": "sound", "keywords": ["volume", "speaker", "broadcast"] }, - { "category": "symbols", "char": "🔊", "name": "loud_sound", "keywords": ["volume", "noise", "noisy", "speaker", "broadcast"] }, - { "category": "symbols", "char": "🔇", "name": "mute", "keywords": ["sound", "volume", "silence", "quiet"] }, - { "category": "symbols", "char": "📣", "name": "mega", "keywords": ["sound", "speaker", "volume"] }, - { "category": "symbols", "char": "📢", "name": "loudspeaker", "keywords": ["volume", "sound"] }, - { "category": "symbols", "char": "🔔", "name": "bell", "keywords": ["sound", "notification", "christmas", "xmas", "chime"] }, - { "category": "symbols", "char": "🔕", "name": "no_bell", "keywords": ["sound", "volume", "mute", "quiet", "silent"] }, - { "category": "symbols", "char": "ðŸƒ", "name": "black_joker", "keywords": ["poker", "cards", "game", "play", "magic"] }, - { "category": "symbols", "char": "🀄", "name": "mahjong", "keywords": ["game", "play", "chinese", "kanji"] }, - { "category": "symbols", "char": "â™ ï¸", "name": "spades", "keywords": ["poker", "cards", "suits", "magic"] }, - { "category": "symbols", "char": "♣ï¸", "name": "clubs", "keywords": ["poker", "cards", "magic", "suits"] }, - { "category": "symbols", "char": "♥ï¸", "name": "hearts", "keywords": ["poker", "cards", "magic", "suits"] }, - { "category": "symbols", "char": "♦ï¸", "name": "diamonds", "keywords": ["poker", "cards", "magic", "suits"] }, - { "category": "symbols", "char": "🎴", "name": "flower_playing_cards", "keywords": ["game", "sunset", "red"] }, - { "category": "symbols", "char": "ðŸ’", "name": "thought_balloon", "keywords": ["bubble", "cloud", "speech", "thinking", "dream"] }, - { "category": "symbols", "char": "🗯", "name": "right_anger_bubble", "keywords": ["caption", "speech", "thinking", "mad"] }, - { "category": "symbols", "char": "💬", "name": "speech_balloon", "keywords": ["bubble", "words", "message", "talk", "chatting"] }, - { "category": "symbols", "char": "🗨", "name": "left_speech_bubble", "keywords": ["words", "message", "talk", "chatting"] }, - { "category": "symbols", "char": "ðŸ•", "name": "clock1", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕑", "name": "clock2", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕒", "name": "clock3", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕓", "name": "clock4", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕔", "name": "clock5", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕕", "name": "clock6", "keywords": ["time", "late", "early", "schedule", "dawn", "dusk"] }, - { "category": "symbols", "char": "🕖", "name": "clock7", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕗", "name": "clock8", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕘", "name": "clock9", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕙", "name": "clock10", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕚", "name": "clock11", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕛", "name": "clock12", "keywords": ["time", "noon", "midnight", "midday", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕜", "name": "clock130", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "ðŸ•", "name": "clock230", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕞", "name": "clock330", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕟", "name": "clock430", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕠", "name": "clock530", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕡", "name": "clock630", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕢", "name": "clock730", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕣", "name": "clock830", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕤", "name": "clock930", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕥", "name": "clock1030", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕦", "name": "clock1130", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "symbols", "char": "🕧", "name": "clock1230", "keywords": ["time", "late", "early", "schedule"] }, - { "category": "flags", "char": "🇦🇫", "name": "afghanistan", "keywords": ["af", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇽", "name": "aland_islands", "keywords": ["Ã…land", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇱", "name": "albania", "keywords": ["al", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇩🇿", "name": "algeria", "keywords": ["dz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇸", "name": "american_samoa", "keywords": ["american", "ws", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇩", "name": "andorra", "keywords": ["ad", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇴", "name": "angola", "keywords": ["ao", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇮", "name": "anguilla", "keywords": ["ai", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇶", "name": "antarctica", "keywords": ["aq", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇬", "name": "antigua_barbuda", "keywords": ["antigua", "barbuda", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇷", "name": "argentina", "keywords": ["ar", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇲", "name": "armenia", "keywords": ["am", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇼", "name": "aruba", "keywords": ["aw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇨", "name": "ascension_island", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇺", "name": "australia", "keywords": ["au", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇹", "name": "austria", "keywords": ["at", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇿", "name": "azerbaijan", "keywords": ["az", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇸", "name": "bahamas", "keywords": ["bs", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧ðŸ‡", "name": "bahrain", "keywords": ["bh", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇩", "name": "bangladesh", "keywords": ["bd", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇧", "name": "barbados", "keywords": ["bb", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇾", "name": "belarus", "keywords": ["by", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇪", "name": "belgium", "keywords": ["be", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇿", "name": "belize", "keywords": ["bz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇯", "name": "benin", "keywords": ["bj", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇲", "name": "bermuda", "keywords": ["bm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇹", "name": "bhutan", "keywords": ["bt", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇴", "name": "bolivia", "keywords": ["bo", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇶", "name": "caribbean_netherlands", "keywords": ["bonaire", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇦", "name": "bosnia_herzegovina", "keywords": ["bosnia", "herzegovina", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇼", "name": "botswana", "keywords": ["bw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇷", "name": "brazil", "keywords": ["br", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇴", "name": "british_indian_ocean_territory", "keywords": ["british", "indian", "ocean", "territory", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇬", "name": "british_virgin_islands", "keywords": ["british", "virgin", "islands", "bvi", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇳", "name": "brunei", "keywords": ["bn", "darussalam", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇬", "name": "bulgaria", "keywords": ["bg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇫", "name": "burkina_faso", "keywords": ["burkina", "faso", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇮", "name": "burundi", "keywords": ["bi", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇻", "name": "cape_verde", "keywords": ["cabo", "verde", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰ðŸ‡", "name": "cambodia", "keywords": ["kh", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇲", "name": "cameroon", "keywords": ["cm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇦", "name": "canada", "keywords": ["ca", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇨", "name": "canary_islands", "keywords": ["canary", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇾", "name": "cayman_islands", "keywords": ["cayman", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇫", "name": "central_african_republic", "keywords": ["central", "african", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇩", "name": "chad", "keywords": ["td", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇱", "name": "chile", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇳", "name": "cn", "keywords": ["china", "chinese", "prc", "flag", "country", "nation", "banner"] }, - { "category": "flags", "char": "🇨🇽", "name": "christmas_island", "keywords": ["christmas", "island", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇨", "name": "cocos_islands", "keywords": ["cocos", "keeling", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇴", "name": "colombia", "keywords": ["co", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇲", "name": "comoros", "keywords": ["km", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇬", "name": "congo_brazzaville", "keywords": ["congo", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇩", "name": "congo_kinshasa", "keywords": ["congo", "democratic", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇰", "name": "cook_islands", "keywords": ["cook", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇷", "name": "costa_rica", "keywords": ["costa", "rica", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "ðŸ‡ðŸ‡·", "name": "croatia", "keywords": ["hr", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇺", "name": "cuba", "keywords": ["cu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇼", "name": "curacao", "keywords": ["curaçao", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇾", "name": "cyprus", "keywords": ["cy", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇿", "name": "czech_republic", "keywords": ["cz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇩🇰", "name": "denmark", "keywords": ["dk", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇩🇯", "name": "djibouti", "keywords": ["dj", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇩🇲", "name": "dominica", "keywords": ["dm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇩🇴", "name": "dominican_republic", "keywords": ["dominican", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇨", "name": "ecuador", "keywords": ["ec", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇬", "name": "egypt", "keywords": ["eg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇻", "name": "el_salvador", "keywords": ["el", "salvador", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇶", "name": "equatorial_guinea", "keywords": ["equatorial", "gn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇷", "name": "eritrea", "keywords": ["er", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇪", "name": "estonia", "keywords": ["ee", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇹", "name": "ethiopia", "keywords": ["et", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇺", "name": "eu", "keywords": ["european", "union", "flag", "banner"] }, - { "category": "flags", "char": "🇫🇰", "name": "falkland_islands", "keywords": ["falkland", "islands", "malvinas", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇫🇴", "name": "faroe_islands", "keywords": ["faroe", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇫🇯", "name": "fiji", "keywords": ["fj", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇫🇮", "name": "finland", "keywords": ["fi", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇫🇷", "name": "fr", "keywords": ["banner", "flag", "nation", "france", "french", "country"] }, - { "category": "flags", "char": "🇬🇫", "name": "french_guiana", "keywords": ["french", "guiana", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇫", "name": "french_polynesia", "keywords": ["french", "polynesia", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇫", "name": "french_southern_territories", "keywords": ["french", "southern", "territories", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇦", "name": "gabon", "keywords": ["ga", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇲", "name": "gambia", "keywords": ["gm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇪", "name": "georgia", "keywords": ["ge", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇩🇪", "name": "de", "keywords": ["german", "nation", "flag", "country", "banner"] }, - { "category": "flags", "char": "🇬ðŸ‡", "name": "ghana", "keywords": ["gh", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇮", "name": "gibraltar", "keywords": ["gi", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇷", "name": "greece", "keywords": ["gr", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇱", "name": "greenland", "keywords": ["gl", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇩", "name": "grenada", "keywords": ["gd", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇵", "name": "guadeloupe", "keywords": ["gp", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇺", "name": "guam", "keywords": ["gu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇹", "name": "guatemala", "keywords": ["gt", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇬", "name": "guernsey", "keywords": ["gg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇳", "name": "guinea", "keywords": ["gn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇼", "name": "guinea_bissau", "keywords": ["gw", "bissau", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇾", "name": "guyana", "keywords": ["gy", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "ðŸ‡ðŸ‡¹", "name": "haiti", "keywords": ["ht", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "ðŸ‡ðŸ‡³", "name": "honduras", "keywords": ["hn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "ðŸ‡ðŸ‡°", "name": "hong_kong", "keywords": ["hong", "kong", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "ðŸ‡ðŸ‡º", "name": "hungary", "keywords": ["hu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇸", "name": "iceland", "keywords": ["is", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇳", "name": "india", "keywords": ["in", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇩", "name": "indonesia", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇷", "name": "iran", "keywords": ["iran, ", "islamic", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇶", "name": "iraq", "keywords": ["iq", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇪", "name": "ireland", "keywords": ["ie", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇲", "name": "isle_of_man", "keywords": ["isle", "man", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇱", "name": "israel", "keywords": ["il", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇮🇹", "name": "it", "keywords": ["italy", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨🇮", "name": "cote_divoire", "keywords": ["ivory", "coast", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇯🇲", "name": "jamaica", "keywords": ["jm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇯🇵", "name": "jp", "keywords": ["japanese", "nation", "flag", "country", "banner"] }, - { "category": "flags", "char": "🇯🇪", "name": "jersey", "keywords": ["je", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇯🇴", "name": "jordan", "keywords": ["jo", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇿", "name": "kazakhstan", "keywords": ["kz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇪", "name": "kenya", "keywords": ["ke", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇮", "name": "kiribati", "keywords": ["ki", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇽🇰", "name": "kosovo", "keywords": ["xk", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇼", "name": "kuwait", "keywords": ["kw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇬", "name": "kyrgyzstan", "keywords": ["kg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇦", "name": "laos", "keywords": ["lao", "democratic", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇻", "name": "latvia", "keywords": ["lv", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇧", "name": "lebanon", "keywords": ["lb", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇸", "name": "lesotho", "keywords": ["ls", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇷", "name": "liberia", "keywords": ["lr", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇾", "name": "libya", "keywords": ["ly", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇮", "name": "liechtenstein", "keywords": ["li", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇹", "name": "lithuania", "keywords": ["lt", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇺", "name": "luxembourg", "keywords": ["lu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇴", "name": "macau", "keywords": ["macao", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇰", "name": "macedonia", "keywords": ["macedonia, ", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇬", "name": "madagascar", "keywords": ["mg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇼", "name": "malawi", "keywords": ["mw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇾", "name": "malaysia", "keywords": ["my", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇻", "name": "maldives", "keywords": ["mv", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇱", "name": "mali", "keywords": ["ml", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇹", "name": "malta", "keywords": ["mt", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲ðŸ‡", "name": "marshall_islands", "keywords": ["marshall", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇶", "name": "martinique", "keywords": ["mq", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇷", "name": "mauritania", "keywords": ["mr", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇺", "name": "mauritius", "keywords": ["mu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇾🇹", "name": "mayotte", "keywords": ["yt", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇽", "name": "mexico", "keywords": ["mx", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇫🇲", "name": "micronesia", "keywords": ["micronesia, ", "federated", "states", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇩", "name": "moldova", "keywords": ["moldova, ", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇨", "name": "monaco", "keywords": ["mc", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇳", "name": "mongolia", "keywords": ["mn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇪", "name": "montenegro", "keywords": ["me", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇸", "name": "montserrat", "keywords": ["ms", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇦", "name": "morocco", "keywords": ["ma", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇿", "name": "mozambique", "keywords": ["mz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇲", "name": "myanmar", "keywords": ["mm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇦", "name": "namibia", "keywords": ["na", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇷", "name": "nauru", "keywords": ["nr", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇵", "name": "nepal", "keywords": ["np", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇱", "name": "netherlands", "keywords": ["nl", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇨", "name": "new_caledonia", "keywords": ["new", "caledonia", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇿", "name": "new_zealand", "keywords": ["new", "zealand", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇮", "name": "nicaragua", "keywords": ["ni", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇪", "name": "niger", "keywords": ["ne", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇬", "name": "nigeria", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇺", "name": "niue", "keywords": ["nu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇳🇫", "name": "norfolk_island", "keywords": ["norfolk", "island", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇲🇵", "name": "northern_mariana_islands", "keywords": ["northern", "mariana", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇵", "name": "north_korea", "keywords": ["north", "korea", "nation", "flag", "country", "banner"] }, - { "category": "flags", "char": "🇳🇴", "name": "norway", "keywords": ["no", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇴🇲", "name": "oman", "keywords": ["om_symbol", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇰", "name": "pakistan", "keywords": ["pk", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇼", "name": "palau", "keywords": ["pw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇸", "name": "palestinian_territories", "keywords": ["palestine", "palestinian", "territories", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇦", "name": "panama", "keywords": ["pa", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇬", "name": "papua_new_guinea", "keywords": ["papua", "new", "guinea", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇾", "name": "paraguay", "keywords": ["py", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇪", "name": "peru", "keywords": ["pe", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵ðŸ‡", "name": "philippines", "keywords": ["ph", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇳", "name": "pitcairn_islands", "keywords": ["pitcairn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇱", "name": "poland", "keywords": ["pl", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇹", "name": "portugal", "keywords": ["pt", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇷", "name": "puerto_rico", "keywords": ["puerto", "rico", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇶🇦", "name": "qatar", "keywords": ["qa", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇷🇪", "name": "reunion", "keywords": ["réunion", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇷🇴", "name": "romania", "keywords": ["ro", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇷🇺", "name": "ru", "keywords": ["russian", "federation", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇷🇼", "name": "rwanda", "keywords": ["rw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇧🇱", "name": "st_barthelemy", "keywords": ["saint", "barthélemy", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸ðŸ‡", "name": "st_helena", "keywords": ["saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇳", "name": "st_kitts_nevis", "keywords": ["saint", "kitts", "nevis", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇨", "name": "st_lucia", "keywords": ["saint", "lucia", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇵🇲", "name": "st_pierre_miquelon", "keywords": ["saint", "pierre", "miquelon", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇨", "name": "st_vincent_grenadines", "keywords": ["saint", "vincent", "grenadines", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇼🇸", "name": "samoa", "keywords": ["ws", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇲", "name": "san_marino", "keywords": ["san", "marino", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇹", "name": "sao_tome_principe", "keywords": ["sao", "tome", "principe", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇦", "name": "saudi_arabia", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇳", "name": "senegal", "keywords": ["sn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇷🇸", "name": "serbia", "keywords": ["rs", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇨", "name": "seychelles", "keywords": ["sc", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇱", "name": "sierra_leone", "keywords": ["sierra", "leone", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇬", "name": "singapore", "keywords": ["sg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇽", "name": "sint_maarten", "keywords": ["sint", "maarten", "dutch", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇰", "name": "slovakia", "keywords": ["sk", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇮", "name": "slovenia", "keywords": ["si", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇧", "name": "solomon_islands", "keywords": ["solomon", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇴", "name": "somalia", "keywords": ["so", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇿🇦", "name": "south_africa", "keywords": ["south", "africa", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇸", "name": "south_georgia_south_sandwich_islands", "keywords": ["south", "georgia", "sandwich", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇰🇷", "name": "kr", "keywords": ["south", "korea", "nation", "flag", "country", "banner"] }, - { "category": "flags", "char": "🇸🇸", "name": "south_sudan", "keywords": ["south", "sd", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪🇸", "name": "es", "keywords": ["spain", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇱🇰", "name": "sri_lanka", "keywords": ["sri", "lanka", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇩", "name": "sudan", "keywords": ["sd", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇷", "name": "suriname", "keywords": ["sr", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇿", "name": "swaziland", "keywords": ["sz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇪", "name": "sweden", "keywords": ["se", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇨ðŸ‡", "name": "switzerland", "keywords": ["ch", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇸🇾", "name": "syria", "keywords": ["syrian", "arab", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇼", "name": "taiwan", "keywords": ["tw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇯", "name": "tajikistan", "keywords": ["tj", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇿", "name": "tanzania", "keywords": ["tanzania, ", "united", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹ðŸ‡", "name": "thailand", "keywords": ["th", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇱", "name": "timor_leste", "keywords": ["timor", "leste", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇬", "name": "togo", "keywords": ["tg", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇰", "name": "tokelau", "keywords": ["tk", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇴", "name": "tonga", "keywords": ["to", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇹", "name": "trinidad_tobago", "keywords": ["trinidad", "tobago", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇦", "name": "tristan_da_cunha", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇳", "name": "tunisia", "keywords": ["tn", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇷", "name": "tr", "keywords": ["turkey", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇲", "name": "turkmenistan", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇨", "name": "turks_caicos_islands", "keywords": ["turks", "caicos", "islands", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇹🇻", "name": "tuvalu", "keywords": ["flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇺🇬", "name": "uganda", "keywords": ["ug", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇺🇦", "name": "ukraine", "keywords": ["ua", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇦🇪", "name": "united_arab_emirates", "keywords": ["united", "arab", "emirates", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇬🇧", "name": "uk", "keywords": ["united", "kingdom", "great", "britain", "northern", "ireland", "flag", "nation", "country", "banner", "british", "UK", "english", "england", "union jack"] }, - { "category": "flags", "char": "ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿", "name": "england", "keywords": ["flag", "english"] }, - { "category": "flags", "char": "ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿", "name": "scotland", "keywords": ["flag", "scottish"] }, - { "category": "flags", "char": "ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿", "name": "wales", "keywords": ["flag", "welsh"] }, - { "category": "flags", "char": "🇺🇸", "name": "us", "keywords": ["united", "states", "america", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇮", "name": "us_virgin_islands", "keywords": ["virgin", "islands", "us", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇺🇾", "name": "uruguay", "keywords": ["uy", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇺🇿", "name": "uzbekistan", "keywords": ["uz", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇺", "name": "vanuatu", "keywords": ["vu", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇦", "name": "vatican_city", "keywords": ["vatican", "city", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇪", "name": "venezuela", "keywords": ["ve", "bolivarian", "republic", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇻🇳", "name": "vietnam", "keywords": ["viet", "nam", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇼🇫", "name": "wallis_futuna", "keywords": ["wallis", "futuna", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇪ðŸ‡", "name": "western_sahara", "keywords": ["western", "sahara", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇾🇪", "name": "yemen", "keywords": ["ye", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇿🇲", "name": "zambia", "keywords": ["zm", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇿🇼", "name": "zimbabwe", "keywords": ["zw", "flag", "nation", "country", "banner"] }, - { "category": "flags", "char": "🇺🇳", "name": "united_nations", "keywords": ["un", "flag", "banner"] }, - { "category": "flags", "char": "ðŸ´â€â˜ ï¸", "name": "pirate_flag", "keywords": ["skull", "crossbones", "flag", "banner"] } + ["😀", "grinning", 0], + ["😬", "grimacing", 0], + ["ðŸ˜", "grin", 0], + ["😂", "joy", 0], + ["🤣", "rofl", 0], + ["🥳", "partying", 0], + ["😃", "smiley", 0], + ["😄", "smile", 0], + ["😅", "sweat_smile", 0], + ["🥲", "smiling_face_with_tear", 0], + ["😆", "laughing", 0], + ["😇", "innocent", 0], + ["😉", "wink", 0], + ["😊", "blush", 0], + ["🙂", "slightly_smiling_face", 0], + ["🙃", "upside_down_face", 0], + ["☺ï¸", "relaxed", 0], + ["😋", "yum", 0], + ["😌", "relieved", 0], + ["ðŸ˜", "heart_eyes", 0], + ["🥰", "smiling_face_with_three_hearts", 0], + ["😘", "kissing_heart", 0], + ["😗", "kissing", 0], + ["😙", "kissing_smiling_eyes", 0], + ["😚", "kissing_closed_eyes", 0], + ["😜", "stuck_out_tongue_winking_eye", 0], + ["🤪", "zany", 0], + ["🤨", "raised_eyebrow", 0], + ["ðŸ§", "monocle", 0], + ["ðŸ˜", "stuck_out_tongue_closed_eyes", 0], + ["😛", "stuck_out_tongue", 0], + ["🤑", "money_mouth_face", 0], + ["🤓", "nerd_face", 0], + ["🥸", "disguised_face", 0], + ["😎", "sunglasses", 0], + ["🤩", "star_struck", 0], + ["🤡", "clown_face", 0], + ["🤠", "cowboy_hat_face", 0], + ["🤗", "hugs", 0], + ["ðŸ˜", "smirk", 0], + ["😶", "no_mouth", 0], + ["ðŸ˜", "neutral_face", 0], + ["😑", "expressionless", 0], + ["😒", "unamused", 0], + ["🙄", "roll_eyes", 0], + ["🤔", "thinking", 0], + ["🤥", "lying_face", 0], + ["ðŸ¤", "hand_over_mouth", 0], + ["🤫", "shushing", 0], + ["🤬", "symbols_over_mouth", 0], + ["🤯", "exploding_head", 0], + ["😳", "flushed", 0], + ["😞", "disappointed", 0], + ["😟", "worried", 0], + ["😠", "angry", 0], + ["😡", "rage", 0], + ["😔", "pensive", 0], + ["😕", "confused", 0], + ["ðŸ™", "slightly_frowning_face", 0], + ["☹", "frowning_face", 0], + ["😣", "persevere", 0], + ["😖", "confounded", 0], + ["😫", "tired_face", 0], + ["😩", "weary", 0], + ["🥺", "pleading", 0], + ["😤", "triumph", 0], + ["😮", "open_mouth", 0], + ["😱", "scream", 0], + ["😨", "fearful", 0], + ["😰", "cold_sweat", 0], + ["😯", "hushed", 0], + ["😦", "frowning", 0], + ["😧", "anguished", 0], + ["😢", "cry", 0], + ["😥", "disappointed_relieved", 0], + ["🤤", "drooling_face", 0], + ["😪", "sleepy", 0], + ["😓", "sweat", 0], + ["🥵", "hot", 0], + ["🥶", "cold", 0], + ["ðŸ˜", "sob", 0], + ["😵", "dizzy_face", 0], + ["😲", "astonished", 0], + ["ðŸ¤", "zipper_mouth_face", 0], + ["🤢", "nauseated_face", 0], + ["🤧", "sneezing_face", 0], + ["🤮", "vomiting", 0], + ["😷", "mask", 0], + ["🤒", "face_with_thermometer", 0], + ["🤕", "face_with_head_bandage", 0], + ["🥴", "woozy", 0], + ["🥱", "yawning", 0], + ["😴", "sleeping", 0], + ["💤", "zzz", 0], + ["😶â€ðŸŒ«ï¸", "face_in_clouds", 0], + ["😮â€ðŸ’¨", "face_exhaling", 0], + ["😵â€ðŸ’«", "face_with_spiral_eyes", 0], + ["🫠", "melting_face", 0], + ["🫢", "face_with_open_eyes_and_hand_over_mouth", 0], + ["🫣", "face_with_peeking_eye", 0], + ["🫡", "saluting_face", 0], + ["🫥", "dotted_line_face", 0], + ["🫤", "face_with_diagonal_mouth", 0], + ["🥹", "face_holding_back_tears", 0], + ["💩", "poop", 0], + ["😈", "smiling_imp", 0], + ["👿", "imp", 0], + ["👹", "japanese_ogre", 0], + ["👺", "japanese_goblin", 0], + ["💀", "skull", 0], + ["👻", "ghost", 0], + ["👽", "alien", 0], + ["🤖", "robot", 0], + ["😺", "smiley_cat", 0], + ["😸", "smile_cat", 0], + ["😹", "joy_cat", 0], + ["😻", "heart_eyes_cat", 0], + ["😼", "smirk_cat", 0], + ["😽", "kissing_cat", 0], + ["🙀", "scream_cat", 0], + ["😿", "crying_cat_face", 0], + ["😾", "pouting_cat", 0], + ["🤲", "palms_up", 1], + ["🙌", "raised_hands", 1], + ["ðŸ‘", "clap", 1], + ["👋", "wave", 1], + ["🤙", "call_me_hand", 1], + ["ðŸ‘", "+1", 1], + ["👎", "-1", 1], + ["👊", "facepunch", 1], + ["✊", "fist", 1], + ["🤛", "fist_left", 1], + ["🤜", "fist_right", 1], + ["✌", "v", 1], + ["👌", "ok_hand", 1], + ["✋", "raised_hand", 1], + ["🤚", "raised_back_of_hand", 1], + ["ðŸ‘", "open_hands", 1], + ["💪", "muscle", 1], + ["🦾", "mechanical_arm", 1], + ["ðŸ™", "pray", 1], + ["🦶", "foot", 1], + ["🦵", "leg", 1], + ["🦿", "mechanical_leg", 1], + ["ðŸ¤", "handshake", 1], + ["â˜", "point_up", 1], + ["👆", "point_up_2", 1], + ["👇", "point_down", 1], + ["👈", "point_left", 1], + ["👉", "point_right", 1], + ["🖕", "fu", 1], + ["ðŸ–", "raised_hand_with_fingers_splayed", 1], + ["🤟", "love_you", 1], + ["🤘", "metal", 1], + ["🤞", "crossed_fingers", 1], + ["🖖", "vulcan_salute", 1], + ["âœ", "writing_hand", 1], + ["🫰", "hand_with_index_finger_and_thumb_crossed", 1], + ["🫱", "rightwards_hand", 1], + ["🫲", "leftwards_hand", 1], + ["🫳", "palm_down_hand", 1], + ["🫴", "palm_up_hand", 1], + ["🫵", "index_pointing_at_the_viewer", 1], + ["🫶", "heart_hands", 1], + ["ðŸ¤", "pinching_hand", 1], + ["🤌", "pinched_fingers", 1], + ["🤳", "selfie", 1], + ["💅", "nail_care", 1], + ["👄", "lips", 1], + ["🫦", "biting_lip", 1], + ["🦷", "tooth", 1], + ["👅", "tongue", 1], + ["👂", "ear", 1], + ["🦻", "ear_with_hearing_aid", 1], + ["👃", "nose", 1], + ["ðŸ‘", "eye", 1], + ["👀", "eyes", 1], + ["🧠", "brain", 1], + ["🫀", "anatomical_heart", 1], + ["ðŸ«", "lungs", 1], + ["👤", "bust_in_silhouette", 1], + ["👥", "busts_in_silhouette", 1], + ["🗣", "speaking_head", 1], + ["👶", "baby", 1], + ["🧒", "child", 1], + ["👦", "boy", 1], + ["👧", "girl", 1], + ["🧑", "adult", 1], + ["👨", "man", 1], + ["👩", "woman", 1], + ["🧑â€ðŸ¦±", "curly_hair", 1], + ["👩â€ðŸ¦±", "curly_hair_woman", 1], + ["👨â€ðŸ¦±", "curly_hair_man", 1], + ["🧑â€ðŸ¦°", "red_hair", 1], + ["👩â€ðŸ¦°", "red_hair_woman", 1], + ["👨â€ðŸ¦°", "red_hair_man", 1], + ["👱â€â™€ï¸", "blonde_woman", 1], + ["👱", "blonde_man", 1], + ["🧑â€ðŸ¦³", "white_hair", 1], + ["👩â€ðŸ¦³", "white_hair_woman", 1], + ["👨â€ðŸ¦³", "white_hair_man", 1], + ["🧑â€ðŸ¦²", "bald", 1], + ["👩â€ðŸ¦²", "bald_woman", 1], + ["👨â€ðŸ¦²", "bald_man", 1], + ["🧔", "bearded_person", 1], + ["🧓", "older_adult", 1], + ["👴", "older_man", 1], + ["👵", "older_woman", 1], + ["👲", "man_with_gua_pi_mao", 1], + ["🧕", "woman_with_headscarf", 1], + ["👳â€â™€ï¸", "woman_with_turban", 1], + ["👳", "man_with_turban", 1], + ["👮â€â™€ï¸", "policewoman", 1], + ["👮", "policeman", 1], + ["👷â€â™€ï¸", "construction_worker_woman", 1], + ["👷", "construction_worker_man", 1], + ["💂â€â™€ï¸", "guardswoman", 1], + ["💂", "guardsman", 1], + ["🕵ï¸â€â™€ï¸", "female_detective", 1], + ["🕵", "male_detective", 1], + ["🧑â€âš•ï¸", "health_worker", 1], + ["👩â€âš•ï¸", "woman_health_worker", 1], + ["👨â€âš•ï¸", "man_health_worker", 1], + ["🧑â€ðŸŒ¾", "farmer", 1], + ["👩â€ðŸŒ¾", "woman_farmer", 1], + ["👨â€ðŸŒ¾", "man_farmer", 1], + ["🧑â€ðŸ³", "cook", 1], + ["👩â€ðŸ³", "woman_cook", 1], + ["👨â€ðŸ³", "man_cook", 1], + ["🧑â€ðŸŽ“", "student", 1], + ["👩â€ðŸŽ“", "woman_student", 1], + ["👨â€ðŸŽ“", "man_student", 1], + ["🧑â€ðŸŽ¤", "singer", 1], + ["👩â€ðŸŽ¤", "woman_singer", 1], + ["👨â€ðŸŽ¤", "man_singer", 1], + ["🧑â€ðŸ«", "teacher", 1], + ["👩â€ðŸ«", "woman_teacher", 1], + ["👨â€ðŸ«", "man_teacher", 1], + ["🧑â€ðŸ", "factory_worker", 1], + ["👩â€ðŸ", "woman_factory_worker", 1], + ["👨â€ðŸ", "man_factory_worker", 1], + ["🧑â€ðŸ’»", "technologist", 1], + ["👩â€ðŸ’»", "woman_technologist", 1], + ["👨â€ðŸ’»", "man_technologist", 1], + ["🧑â€ðŸ’¼", "office_worker", 1], + ["👩â€ðŸ’¼", "woman_office_worker", 1], + ["👨â€ðŸ’¼", "man_office_worker", 1], + ["🧑â€ðŸ”§", "mechanic", 1], + ["👩â€ðŸ”§", "woman_mechanic", 1], + ["👨â€ðŸ”§", "man_mechanic", 1], + ["🧑â€ðŸ”¬", "scientist", 1], + ["👩â€ðŸ”¬", "woman_scientist", 1], + ["👨â€ðŸ”¬", "man_scientist", 1], + ["🧑â€ðŸŽ¨", "artist", 1], + ["👩â€ðŸŽ¨", "woman_artist", 1], + ["👨â€ðŸŽ¨", "man_artist", 1], + ["🧑â€ðŸš’", "firefighter", 1], + ["👩â€ðŸš’", "woman_firefighter", 1], + ["👨â€ðŸš’", "man_firefighter", 1], + ["🧑â€âœˆï¸", "pilot", 1], + ["👩â€âœˆï¸", "woman_pilot", 1], + ["👨â€âœˆï¸", "man_pilot", 1], + ["🧑â€ðŸš€", "astronaut", 1], + ["👩â€ðŸš€", "woman_astronaut", 1], + ["👨â€ðŸš€", "man_astronaut", 1], + ["🧑â€âš–ï¸", "judge", 1], + ["👩â€âš–ï¸", "woman_judge", 1], + ["👨â€âš–ï¸", "man_judge", 1], + ["🦸â€â™€ï¸", "woman_superhero", 1], + ["🦸â€â™‚ï¸", "man_superhero", 1], + ["🦹â€â™€ï¸", "woman_supervillain", 1], + ["🦹â€â™‚ï¸", "man_supervillain", 1], + ["🤶", "mrs_claus", 1], + ["🧑â€ðŸŽ„", "mx_claus", 1], + ["🎅", "santa", 1], + ["🥷", "ninja", 1], + ["🧙â€â™€ï¸", "sorceress", 1], + ["🧙â€â™‚ï¸", "wizard", 1], + ["ðŸ§â€â™€ï¸", "woman_elf", 1], + ["ðŸ§â€â™‚ï¸", "man_elf", 1], + ["🧛â€â™€ï¸", "woman_vampire", 1], + ["🧛â€â™‚ï¸", "man_vampire", 1], + ["🧟â€â™€ï¸", "woman_zombie", 1], + ["🧟â€â™‚ï¸", "man_zombie", 1], + ["🧞â€â™€ï¸", "woman_genie", 1], + ["🧞â€â™‚ï¸", "man_genie", 1], + ["🧜â€â™€ï¸", "mermaid", 1], + ["🧜â€â™‚ï¸", "merman", 1], + ["🧚â€â™€ï¸", "woman_fairy", 1], + ["🧚â€â™‚ï¸", "man_fairy", 1], + ["👼", "angel", 1], + ["🧌", "troll", 1], + ["🤰", "pregnant_woman", 1], + ["🫃", "pregnant_man", 1], + ["🫄", "pregnant_person", 1], + ["🫅", "person_with_crown", 1], + ["🤱", "breastfeeding", 1], + ["👩â€ðŸ¼", "woman_feeding_baby", 1], + ["👨â€ðŸ¼", "man_feeding_baby", 1], + ["🧑â€ðŸ¼", "person_feeding_baby", 1], + ["👸", "princess", 1], + ["🤴", "prince", 1], + ["👰", "person_with_veil", 1], + ["👰", "bride_with_veil", 1], + ["🤵", "person_in_tuxedo", 1], + ["🤵", "man_in_tuxedo", 1], + ["ðŸƒâ€â™€ï¸", "running_woman", 1], + ["ðŸƒ", "running_man", 1], + ["🚶â€â™€ï¸", "walking_woman", 1], + ["🚶", "walking_man", 1], + ["💃", "dancer", 1], + ["🕺", "man_dancing", 1], + ["👯", "dancing_women", 1], + ["👯â€â™‚ï¸", "dancing_men", 1], + ["👫", "couple", 1], + ["🧑â€ðŸ¤â€ðŸ§‘", "people_holding_hands", 1], + ["👬", "two_men_holding_hands", 1], + ["ðŸ‘", "two_women_holding_hands", 1], + ["🫂", "people_hugging", 1], + ["🙇â€â™€ï¸", "bowing_woman", 1], + ["🙇", "bowing_man", 1], + ["🤦â€â™‚ï¸", "man_facepalming", 1], + ["🤦â€â™€ï¸", "woman_facepalming", 1], + ["🤷", "woman_shrugging", 1], + ["🤷â€â™‚ï¸", "man_shrugging", 1], + ["ðŸ’", "tipping_hand_woman", 1], + ["ðŸ’â€â™‚ï¸", "tipping_hand_man", 1], + ["🙅", "no_good_woman", 1], + ["🙅â€â™‚ï¸", "no_good_man", 1], + ["🙆", "ok_woman", 1], + ["🙆â€â™‚ï¸", "ok_man", 1], + ["🙋", "raising_hand_woman", 1], + ["🙋â€â™‚ï¸", "raising_hand_man", 1], + ["🙎", "pouting_woman", 1], + ["🙎â€â™‚ï¸", "pouting_man", 1], + ["ðŸ™", "frowning_woman", 1], + ["ðŸ™â€â™‚ï¸", "frowning_man", 1], + ["💇", "haircut_woman", 1], + ["💇â€â™‚ï¸", "haircut_man", 1], + ["💆", "massage_woman", 1], + ["💆â€â™‚ï¸", "massage_man", 1], + ["🧖â€â™€ï¸", "woman_in_steamy_room", 1], + ["🧖â€â™‚ï¸", "man_in_steamy_room", 1], + ["ðŸ§â€â™€ï¸", "woman_deaf", 1], + ["ðŸ§â€â™‚ï¸", "man_deaf", 1], + ["ðŸ§â€â™€ï¸", "woman_standing", 1], + ["ðŸ§â€â™‚ï¸", "man_standing", 1], + ["🧎â€â™€ï¸", "woman_kneeling", 1], + ["🧎â€â™‚ï¸", "man_kneeling", 1], + ["🧑â€ðŸ¦¯", "person_with_probing_cane", 1], + ["👩â€ðŸ¦¯", "woman_with_probing_cane", 1], + ["👨â€ðŸ¦¯", "man_with_probing_cane", 1], + ["🧑â€ðŸ¦¼", "person_in_motorized_wheelchair", 1], + ["👩â€ðŸ¦¼", "woman_in_motorized_wheelchair", 1], + ["👨â€ðŸ¦¼", "man_in_motorized_wheelchair", 1], + ["🧑â€ðŸ¦½", "person_in_manual_wheelchair", 1], + ["👩â€ðŸ¦½", "woman_in_manual_wheelchair", 1], + ["👨â€ðŸ¦½", "man_in_manual_wheelchair", 1], + ["💑", "couple_with_heart_woman_man", 1], + ["👩â€â¤ï¸â€ðŸ‘©", "couple_with_heart_woman_woman", 1], + ["👨â€â¤ï¸â€ðŸ‘¨", "couple_with_heart_man_man", 1], + ["ðŸ’", "couplekiss_man_woman", 1], + ["👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©", "couplekiss_woman_woman", 1], + ["👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨", "couplekiss_man_man", 1], + ["👪", "family_man_woman_boy", 1], + ["👨â€ðŸ‘©â€ðŸ‘§", "family_man_woman_girl", 1], + ["👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦", "family_man_woman_girl_boy", 1], + ["👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦", "family_man_woman_boy_boy", 1], + ["👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§", "family_man_woman_girl_girl", 1], + ["👩â€ðŸ‘©â€ðŸ‘¦", "family_woman_woman_boy", 1], + ["👩â€ðŸ‘©â€ðŸ‘§", "family_woman_woman_girl", 1], + ["👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦", "family_woman_woman_girl_boy", 1], + ["👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦", "family_woman_woman_boy_boy", 1], + ["👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§", "family_woman_woman_girl_girl", 1], + ["👨â€ðŸ‘¨â€ðŸ‘¦", "family_man_man_boy", 1], + ["👨â€ðŸ‘¨â€ðŸ‘§", "family_man_man_girl", 1], + ["👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦", "family_man_man_girl_boy", 1], + ["👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦", "family_man_man_boy_boy", 1], + ["👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§", "family_man_man_girl_girl", 1], + ["👩â€ðŸ‘¦", "family_woman_boy", 1], + ["👩â€ðŸ‘§", "family_woman_girl", 1], + ["👩â€ðŸ‘§â€ðŸ‘¦", "family_woman_girl_boy", 1], + ["👩â€ðŸ‘¦â€ðŸ‘¦", "family_woman_boy_boy", 1], + ["👩â€ðŸ‘§â€ðŸ‘§", "family_woman_girl_girl", 1], + ["👨â€ðŸ‘¦", "family_man_boy", 1], + ["👨â€ðŸ‘§", "family_man_girl", 1], + ["👨â€ðŸ‘§â€ðŸ‘¦", "family_man_girl_boy", 1], + ["👨â€ðŸ‘¦â€ðŸ‘¦", "family_man_boy_boy", 1], + ["👨â€ðŸ‘§â€ðŸ‘§", "family_man_girl_girl", 1], + ["🧶", "yarn", 1], + ["🧵", "thread", 1], + ["🧥", "coat", 1], + ["🥼", "labcoat", 1], + ["👚", "womans_clothes", 1], + ["👕", "tshirt", 1], + ["👖", "jeans", 1], + ["👔", "necktie", 1], + ["👗", "dress", 1], + ["👙", "bikini", 1], + ["🩱", "one_piece_swimsuit", 1], + ["👘", "kimono", 1], + ["🥻", "sari", 1], + ["🩲", "briefs", 1], + ["🩳", "shorts", 1], + ["💄", "lipstick", 1], + ["💋", "kiss", 1], + ["👣", "footprints", 1], + ["🥿", "flat_shoe", 1], + ["👠", "high_heel", 1], + ["👡", "sandal", 1], + ["👢", "boot", 1], + ["👞", "mans_shoe", 1], + ["👟", "athletic_shoe", 1], + ["🩴", "thong_sandal", 1], + ["🩰", "ballet_shoes", 1], + ["🧦", "socks", 1], + ["🧤", "gloves", 1], + ["🧣", "scarf", 1], + ["👒", "womans_hat", 1], + ["🎩", "tophat", 1], + ["🧢", "billed_hat", 1], + ["⛑", "rescue_worker_helmet", 1], + ["🪖", "military_helmet", 1], + ["🎓", "mortar_board", 1], + ["👑", "crown", 1], + ["🎒", "school_satchel", 1], + ["🧳", "luggage", 1], + ["ðŸ‘", "pouch", 1], + ["👛", "purse", 1], + ["👜", "handbag", 1], + ["💼", "briefcase", 1], + ["👓", "eyeglasses", 1], + ["🕶", "dark_sunglasses", 1], + ["🥽", "goggles", 1], + ["ðŸ’", "ring", 1], + ["🌂", "closed_umbrella", 1], + ["ðŸ¶", "dog", 2], + ["ðŸ±", "cat", 2], + ["ðŸˆâ€â¬›", "black_cat", 2], + ["ðŸ", "mouse", 2], + ["ðŸ¹", "hamster", 2], + ["ðŸ°", "rabbit", 2], + ["🦊", "fox_face", 2], + ["ðŸ»", "bear", 2], + ["ðŸ¼", "panda_face", 2], + ["ðŸ¨", "koala", 2], + ["ðŸ¯", "tiger", 2], + ["ðŸ¦", "lion", 2], + ["ðŸ®", "cow", 2], + ["ðŸ·", "pig", 2], + ["ðŸ½", "pig_nose", 2], + ["ðŸ¸", "frog", 2], + ["🦑", "squid", 2], + ["ðŸ™", "octopus", 2], + ["ðŸ¦", "shrimp", 2], + ["ðŸµ", "monkey_face", 2], + ["ðŸ¦", "gorilla", 2], + ["🙈", "see_no_evil", 2], + ["🙉", "hear_no_evil", 2], + ["🙊", "speak_no_evil", 2], + ["ðŸ’", "monkey", 2], + ["ðŸ”", "chicken", 2], + ["ðŸ§", "penguin", 2], + ["ðŸ¦", "bird", 2], + ["ðŸ¤", "baby_chick", 2], + ["ðŸ£", "hatching_chick", 2], + ["ðŸ¥", "hatched_chick", 2], + ["🦆", "duck", 2], + ["🦅", "eagle", 2], + ["🦉", "owl", 2], + ["🦇", "bat", 2], + ["ðŸº", "wolf", 2], + ["ðŸ—", "boar", 2], + ["ðŸ´", "horse", 2], + ["🦄", "unicorn", 2], + ["ðŸ", "honeybee", 2], + ["ðŸ›", "bug", 2], + ["🦋", "butterfly", 2], + ["ðŸŒ", "snail", 2], + ["ðŸž", "lady_beetle", 2], + ["ðŸœ", "ant", 2], + ["🦗", "grasshopper", 2], + ["🕷", "spider", 2], + ["🪲", "beetle", 2], + ["🪳", "cockroach", 2], + ["🪰", "fly", 2], + ["🪱", "worm", 2], + ["🦂", "scorpion", 2], + ["🦀", "crab", 2], + ["ðŸ", "snake", 2], + ["🦎", "lizard", 2], + ["🦖", "t-rex", 2], + ["🦕", "sauropod", 2], + ["ðŸ¢", "turtle", 2], + ["ðŸ ", "tropical_fish", 2], + ["ðŸŸ", "fish", 2], + ["ðŸ¡", "blowfish", 2], + ["ðŸ¬", "dolphin", 2], + ["🦈", "shark", 2], + ["ðŸ³", "whale", 2], + ["ðŸ‹", "whale2", 2], + ["ðŸŠ", "crocodile", 2], + ["ðŸ†", "leopard", 2], + ["🦓", "zebra", 2], + ["ðŸ…", "tiger2", 2], + ["ðŸƒ", "water_buffalo", 2], + ["ðŸ‚", "ox", 2], + ["ðŸ„", "cow2", 2], + ["🦌", "deer", 2], + ["ðŸª", "dromedary_camel", 2], + ["ðŸ«", "camel", 2], + ["🦒", "giraffe", 2], + ["ðŸ˜", "elephant", 2], + ["ðŸ¦", "rhinoceros", 2], + ["ðŸ", "goat", 2], + ["ðŸ", "ram", 2], + ["ðŸ‘", "sheep", 2], + ["ðŸŽ", "racehorse", 2], + ["ðŸ–", "pig2", 2], + ["ðŸ€", "rat", 2], + ["ðŸ", "mouse2", 2], + ["ðŸ“", "rooster", 2], + ["🦃", "turkey", 2], + ["🕊", "dove", 2], + ["ðŸ•", "dog2", 2], + ["ðŸ©", "poodle", 2], + ["ðŸˆ", "cat2", 2], + ["ðŸ‡", "rabbit2", 2], + ["ðŸ¿", "chipmunk", 2], + ["🦔", "hedgehog", 2], + ["ðŸ¦", "raccoon", 2], + ["🦙", "llama", 2], + ["🦛", "hippopotamus", 2], + ["🦘", "kangaroo", 2], + ["🦡", "badger", 2], + ["🦢", "swan", 2], + ["🦚", "peacock", 2], + ["🦜", "parrot", 2], + ["🦞", "lobster", 2], + ["🦠", "microbe", 2], + ["🦟", "mosquito", 2], + ["🦬", "bison", 2], + ["🦣", "mammoth", 2], + ["🦫", "beaver", 2], + ["ðŸ»â€â„ï¸", "polar_bear", 2], + ["🦤", "dodo", 2], + ["🪶", "feather", 2], + ["ðŸ¦", "seal", 2], + ["ðŸ¾", "paw_prints", 2], + ["ðŸ‰", "dragon", 2], + ["ðŸ²", "dragon_face", 2], + ["🦧", "orangutan", 2], + ["🦮", "guide_dog", 2], + ["ðŸ•â€ðŸ¦º", "service_dog", 2], + ["🦥", "sloth", 2], + ["🦦", "otter", 2], + ["🦨", "skunk", 2], + ["🦩", "flamingo", 2], + ["🌵", "cactus", 2], + ["🎄", "christmas_tree", 2], + ["🌲", "evergreen_tree", 2], + ["🌳", "deciduous_tree", 2], + ["🌴", "palm_tree", 2], + ["🌱", "seedling", 2], + ["🌿", "herb", 2], + ["☘", "shamrock", 2], + ["ðŸ€", "four_leaf_clover", 2], + ["ðŸŽ", "bamboo", 2], + ["🎋", "tanabata_tree", 2], + ["ðŸƒ", "leaves", 2], + ["ðŸ‚", "fallen_leaf", 2], + ["ðŸ", "maple_leaf", 2], + ["🌾", "ear_of_rice", 2], + ["🌺", "hibiscus", 2], + ["🌻", "sunflower", 2], + ["🌹", "rose", 2], + ["🥀", "wilted_flower", 2], + ["🌷", "tulip", 2], + ["🌼", "blossom", 2], + ["🌸", "cherry_blossom", 2], + ["ðŸ’", "bouquet", 2], + ["ðŸ„", "mushroom", 2], + ["🪴", "potted_plant", 2], + ["🌰", "chestnut", 2], + ["🎃", "jack_o_lantern", 2], + ["ðŸš", "shell", 2], + ["🕸", "spider_web", 2], + ["🌎", "earth_americas", 2], + ["ðŸŒ", "earth_africa", 2], + ["ðŸŒ", "earth_asia", 2], + ["ðŸª", "ringed_planet", 2], + ["🌕", "full_moon", 2], + ["🌖", "waning_gibbous_moon", 2], + ["🌗", "last_quarter_moon", 2], + ["🌘", "waning_crescent_moon", 2], + ["🌑", "new_moon", 2], + ["🌒", "waxing_crescent_moon", 2], + ["🌓", "first_quarter_moon", 2], + ["🌔", "waxing_gibbous_moon", 2], + ["🌚", "new_moon_with_face", 2], + ["ðŸŒ", "full_moon_with_face", 2], + ["🌛", "first_quarter_moon_with_face", 2], + ["🌜", "last_quarter_moon_with_face", 2], + ["🌞", "sun_with_face", 2], + ["🌙", "crescent_moon", 2], + ["â", "star", 2], + ["🌟", "star2", 2], + ["💫", "dizzy", 2], + ["✨", "sparkles", 2], + ["☄", "comet", 2], + ["☀ï¸", "sunny", 2], + ["🌤", "sun_behind_small_cloud", 2], + ["â›…", "partly_sunny", 2], + ["🌥", "sun_behind_large_cloud", 2], + ["🌦", "sun_behind_rain_cloud", 2], + ["â˜ï¸", "cloud", 2], + ["🌧", "cloud_with_rain", 2], + ["⛈", "cloud_with_lightning_and_rain", 2], + ["🌩", "cloud_with_lightning", 2], + ["âš¡", "zap", 2], + ["🔥", "fire", 2], + ["💥", "boom", 2], + ["â„ï¸", "snowflake", 2], + ["🌨", "cloud_with_snow", 2], + ["⛄", "snowman", 2], + ["☃", "snowman_with_snow", 2], + ["🌬", "wind_face", 2], + ["💨", "dash", 2], + ["🌪", "tornado", 2], + ["🌫", "fog", 2], + ["☂", "open_umbrella", 2], + ["☔", "umbrella", 2], + ["💧", "droplet", 2], + ["💦", "sweat_drops", 2], + ["🌊", "ocean", 2], + ["🪷", "lotus", 2], + ["🪸", "coral", 2], + ["🪹", "empty_nest", 2], + ["🪺", "nest_with_eggs", 2], + ["ðŸ", "green_apple", 3], + ["ðŸŽ", "apple", 3], + ["ðŸ", "pear", 3], + ["ðŸŠ", "tangerine", 3], + ["ðŸ‹", "lemon", 3], + ["ðŸŒ", "banana", 3], + ["ðŸ‰", "watermelon", 3], + ["ðŸ‡", "grapes", 3], + ["ðŸ“", "strawberry", 3], + ["ðŸˆ", "melon", 3], + ["ðŸ’", "cherries", 3], + ["ðŸ‘", "peach", 3], + ["ðŸ", "pineapple", 3], + ["🥥", "coconut", 3], + ["ðŸ¥", "kiwi_fruit", 3], + ["ðŸ¥", "mango", 3], + ["🥑", "avocado", 3], + ["🥦", "broccoli", 3], + ["ðŸ…", "tomato", 3], + ["ðŸ†", "eggplant", 3], + ["🥒", "cucumber", 3], + ["ðŸ«", "blueberries", 3], + ["🫒", "olive", 3], + ["🫑", "bell_pepper", 3], + ["🥕", "carrot", 3], + ["🌶", "hot_pepper", 3], + ["🥔", "potato", 3], + ["🌽", "corn", 3], + ["🥬", "leafy_greens", 3], + ["ðŸ ", "sweet_potato", 3], + ["🥜", "peanuts", 3], + ["🧄", "garlic", 3], + ["🧅", "onion", 3], + ["ðŸ¯", "honey_pot", 3], + ["ðŸ¥", "croissant", 3], + ["ðŸž", "bread", 3], + ["🥖", "baguette_bread", 3], + ["🥯", "bagel", 3], + ["🥨", "pretzel", 3], + ["🧀", "cheese", 3], + ["🥚", "egg", 3], + ["🥓", "bacon", 3], + ["🥩", "steak", 3], + ["🥞", "pancakes", 3], + ["ðŸ—", "poultry_leg", 3], + ["ðŸ–", "meat_on_bone", 3], + ["🦴", "bone", 3], + ["ðŸ¤", "fried_shrimp", 3], + ["ðŸ³", "fried_egg", 3], + ["ðŸ”", "hamburger", 3], + ["ðŸŸ", "fries", 3], + ["🥙", "stuffed_flatbread", 3], + ["ðŸŒ", "hotdog", 3], + ["ðŸ•", "pizza", 3], + ["🥪", "sandwich", 3], + ["🥫", "canned_food", 3], + ["ðŸ", "spaghetti", 3], + ["🌮", "taco", 3], + ["🌯", "burrito", 3], + ["🥗", "green_salad", 3], + ["🥘", "shallow_pan_of_food", 3], + ["ðŸœ", "ramen", 3], + ["ðŸ²", "stew", 3], + ["ðŸ¥", "fish_cake", 3], + ["🥠", "fortune_cookie", 3], + ["ðŸ£", "sushi", 3], + ["ðŸ±", "bento", 3], + ["ðŸ›", "curry", 3], + ["ðŸ™", "rice_ball", 3], + ["ðŸš", "rice", 3], + ["ðŸ˜", "rice_cracker", 3], + ["ðŸ¢", "oden", 3], + ["ðŸ¡", "dango", 3], + ["ðŸ§", "shaved_ice", 3], + ["ðŸ¨", "ice_cream", 3], + ["ðŸ¦", "icecream", 3], + ["🥧", "pie", 3], + ["ðŸ°", "cake", 3], + ["ðŸ§", "cupcake", 3], + ["🥮", "moon_cake", 3], + ["🎂", "birthday", 3], + ["ðŸ®", "custard", 3], + ["ðŸ¬", "candy", 3], + ["ðŸ", "lollipop", 3], + ["ðŸ«", "chocolate_bar", 3], + ["ðŸ¿", "popcorn", 3], + ["🥟", "dumpling", 3], + ["ðŸ©", "doughnut", 3], + ["ðŸª", "cookie", 3], + ["🧇", "waffle", 3], + ["🧆", "falafel", 3], + ["🧈", "butter", 3], + ["🦪", "oyster", 3], + ["🫓", "flatbread", 3], + ["🫔", "tamale", 3], + ["🫕", "fondue", 3], + ["🥛", "milk_glass", 3], + ["ðŸº", "beer", 3], + ["ðŸ»", "beers", 3], + ["🥂", "clinking_glasses", 3], + ["ðŸ·", "wine_glass", 3], + ["🥃", "tumbler_glass", 3], + ["ðŸ¸", "cocktail", 3], + ["ðŸ¹", "tropical_drink", 3], + ["ðŸ¾", "champagne", 3], + ["ðŸ¶", "sake", 3], + ["ðŸµ", "tea", 3], + ["🥤", "cup_with_straw", 3], + ["☕", "coffee", 3], + ["🫖", "teapot", 3], + ["🧋", "bubble_tea", 3], + ["ðŸ¼", "baby_bottle", 3], + ["🧃", "beverage_box", 3], + ["🧉", "mate", 3], + ["🧊", "ice_cube", 3], + ["🧂", "salt", 3], + ["🥄", "spoon", 3], + ["ðŸ´", "fork_and_knife", 3], + ["ðŸ½", "plate_with_cutlery", 3], + ["🥣", "bowl_with_spoon", 3], + ["🥡", "takeout_box", 3], + ["🥢", "chopsticks", 3], + ["🫗", "pouring_liquid", 3], + ["🫘", "beans", 3], + ["🫙", "jar", 3], + ["âš½", "soccer", 4], + ["ðŸ€", "basketball", 4], + ["ðŸˆ", "football", 4], + ["âš¾", "baseball", 4], + ["🥎", "softball", 4], + ["🎾", "tennis", 4], + ["ðŸ", "volleyball", 4], + ["ðŸ‰", "rugby_football", 4], + ["ðŸ¥", "flying_disc", 4], + ["🎱", "8ball", 4], + ["⛳", "golf", 4], + ["ðŸŒï¸â€â™€ï¸", "golfing_woman", 4], + ["ðŸŒ", "golfing_man", 4], + ["ðŸ“", "ping_pong", 4], + ["ðŸ¸", "badminton", 4], + ["🥅", "goal_net", 4], + ["ðŸ’", "ice_hockey", 4], + ["ðŸ‘", "field_hockey", 4], + ["ðŸ¥", "lacrosse", 4], + ["ðŸ", "cricket", 4], + ["🎿", "ski", 4], + ["â›·", "skier", 4], + ["ðŸ‚", "snowboarder", 4], + ["🤺", "person_fencing", 4], + ["🤼â€â™€ï¸", "women_wrestling", 4], + ["🤼â€â™‚ï¸", "men_wrestling", 4], + ["🤸â€â™€ï¸", "woman_cartwheeling", 4], + ["🤸â€â™‚ï¸", "man_cartwheeling", 4], + ["🤾â€â™€ï¸", "woman_playing_handball", 4], + ["🤾â€â™‚ï¸", "man_playing_handball", 4], + ["⛸", "ice_skate", 4], + ["🥌", "curling_stone", 4], + ["🛹", "skateboard", 4], + ["🛷", "sled", 4], + ["ðŸ¹", "bow_and_arrow", 4], + ["🎣", "fishing_pole_and_fish", 4], + ["🥊", "boxing_glove", 4], + ["🥋", "martial_arts_uniform", 4], + ["🚣â€â™€ï¸", "rowing_woman", 4], + ["🚣", "rowing_man", 4], + ["🧗â€â™€ï¸", "climbing_woman", 4], + ["🧗â€â™‚ï¸", "climbing_man", 4], + ["ðŸŠâ€â™€ï¸", "swimming_woman", 4], + ["ðŸŠ", "swimming_man", 4], + ["🤽â€â™€ï¸", "woman_playing_water_polo", 4], + ["🤽â€â™‚ï¸", "man_playing_water_polo", 4], + ["🧘â€â™€ï¸", "woman_in_lotus_position", 4], + ["🧘â€â™‚ï¸", "man_in_lotus_position", 4], + ["ðŸ„â€â™€ï¸", "surfing_woman", 4], + ["ðŸ„", "surfing_man", 4], + ["🛀", "bath", 4], + ["⛹ï¸â€â™€ï¸", "basketball_woman", 4], + ["⛹", "basketball_man", 4], + ["ðŸ‹ï¸â€â™€ï¸", "weight_lifting_woman", 4], + ["ðŸ‹", "weight_lifting_man", 4], + ["🚴â€â™€ï¸", "biking_woman", 4], + ["🚴", "biking_man", 4], + ["🚵â€â™€ï¸", "mountain_biking_woman", 4], + ["🚵", "mountain_biking_man", 4], + ["ðŸ‡", "horse_racing", 4], + ["🤿", "diving_mask", 4], + ["🪀", "yo_yo", 4], + ["ðŸª", "kite", 4], + ["🦺", "safety_vest", 4], + ["🪡", "sewing_needle", 4], + ["🪢", "knot", 4], + ["🕴", "business_suit_levitating", 4], + ["ðŸ†", "trophy", 4], + ["🎽", "running_shirt_with_sash", 4], + ["ðŸ…", "medal_sports", 4], + ["🎖", "medal_military", 4], + ["🥇", "1st_place_medal", 4], + ["🥈", "2nd_place_medal", 4], + ["🥉", "3rd_place_medal", 4], + ["🎗", "reminder_ribbon", 4], + ["ðŸµ", "rosette", 4], + ["🎫", "ticket", 4], + ["🎟", "tickets", 4], + ["ðŸŽ", "performing_arts", 4], + ["🎨", "art", 4], + ["🎪", "circus_tent", 4], + ["🤹â€â™€ï¸", "woman_juggling", 4], + ["🤹â€â™‚ï¸", "man_juggling", 4], + ["🎤", "microphone", 4], + ["🎧", "headphones", 4], + ["🎼", "musical_score", 4], + ["🎹", "musical_keyboard", 4], + ["ðŸ¥", "drum", 4], + ["🎷", "saxophone", 4], + ["🎺", "trumpet", 4], + ["🎸", "guitar", 4], + ["🎻", "violin", 4], + ["🪕", "banjo", 4], + ["🪗", "accordion", 4], + ["🪘", "long_drum", 4], + ["🎬", "clapper", 4], + ["🎮", "video_game", 4], + ["👾", "space_invader", 4], + ["🎯", "dart", 4], + ["🎲", "game_die", 4], + ["♟ï¸", "chess_pawn", 4], + ["🎰", "slot_machine", 4], + ["🧩", "jigsaw", 4], + ["🎳", "bowling", 4], + ["🪄", "magic_wand", 4], + ["🪅", "pinata", 4], + ["🪆", "nesting_dolls", 4], + ["🪬", "hamsa", 4], + ["🪩", "mirror_ball", 4], + ["🚗", "red_car", 5], + ["🚕", "taxi", 5], + ["🚙", "blue_car", 5], + ["🚌", "bus", 5], + ["🚎", "trolleybus", 5], + ["ðŸŽ", "racing_car", 5], + ["🚓", "police_car", 5], + ["🚑", "ambulance", 5], + ["🚒", "fire_engine", 5], + ["ðŸš", "minibus", 5], + ["🚚", "truck", 5], + ["🚛", "articulated_lorry", 5], + ["🚜", "tractor", 5], + ["🛴", "kick_scooter", 5], + ["ðŸ", "motorcycle", 5], + ["🚲", "bike", 5], + ["🛵", "motor_scooter", 5], + ["🦽", "manual_wheelchair", 5], + ["🦼", "motorized_wheelchair", 5], + ["🛺", "auto_rickshaw", 5], + ["🪂", "parachute", 5], + ["🚨", "rotating_light", 5], + ["🚔", "oncoming_police_car", 5], + ["ðŸš", "oncoming_bus", 5], + ["🚘", "oncoming_automobile", 5], + ["🚖", "oncoming_taxi", 5], + ["🚡", "aerial_tramway", 5], + ["🚠", "mountain_cableway", 5], + ["🚟", "suspension_railway", 5], + ["🚃", "railway_car", 5], + ["🚋", "train", 5], + ["ðŸš", "monorail", 5], + ["🚄", "bullettrain_side", 5], + ["🚅", "bullettrain_front", 5], + ["🚈", "light_rail", 5], + ["🚞", "mountain_railway", 5], + ["🚂", "steam_locomotive", 5], + ["🚆", "train2", 5], + ["🚇", "metro", 5], + ["🚊", "tram", 5], + ["🚉", "station", 5], + ["🛸", "flying_saucer", 5], + ["ðŸš", "helicopter", 5], + ["🛩", "small_airplane", 5], + ["✈ï¸", "airplane", 5], + ["🛫", "flight_departure", 5], + ["🛬", "flight_arrival", 5], + ["⛵", "sailboat", 5], + ["🛥", "motor_boat", 5], + ["🚤", "speedboat", 5], + ["â›´", "ferry", 5], + ["🛳", "passenger_ship", 5], + ["🚀", "rocket", 5], + ["🛰", "artificial_satellite", 5], + ["🛻", "pickup_truck", 5], + ["🛼", "roller_skate", 5], + ["💺", "seat", 5], + ["🛶", "canoe", 5], + ["âš“", "anchor", 5], + ["🚧", "construction", 5], + ["⛽", "fuelpump", 5], + ["ðŸš", "busstop", 5], + ["🚦", "vertical_traffic_light", 5], + ["🚥", "traffic_light", 5], + ["ðŸ", "checkered_flag", 5], + ["🚢", "ship", 5], + ["🎡", "ferris_wheel", 5], + ["🎢", "roller_coaster", 5], + ["🎠", "carousel_horse", 5], + ["ðŸ—", "building_construction", 5], + ["ðŸŒ", "foggy", 5], + ["ðŸ", "factory", 5], + ["⛲", "fountain", 5], + ["🎑", "rice_scene", 5], + ["â›°", "mountain", 5], + ["ðŸ”", "mountain_snow", 5], + ["🗻", "mount_fuji", 5], + ["🌋", "volcano", 5], + ["🗾", "japan", 5], + ["ðŸ•", "camping", 5], + ["⛺", "tent", 5], + ["ðŸž", "national_park", 5], + ["🛣", "motorway", 5], + ["🛤", "railway_track", 5], + ["🌅", "sunrise", 5], + ["🌄", "sunrise_over_mountains", 5], + ["ðŸœ", "desert", 5], + ["ðŸ–", "beach_umbrella", 5], + ["ðŸ", "desert_island", 5], + ["🌇", "city_sunrise", 5], + ["🌆", "city_sunset", 5], + ["ðŸ™", "cityscape", 5], + ["🌃", "night_with_stars", 5], + ["🌉", "bridge_at_night", 5], + ["🌌", "milky_way", 5], + ["🌠", "stars", 5], + ["🎇", "sparkler", 5], + ["🎆", "fireworks", 5], + ["🌈", "rainbow", 5], + ["ðŸ˜", "houses", 5], + ["ðŸ°", "european_castle", 5], + ["ðŸ¯", "japanese_castle", 5], + ["🗼", "tokyo_tower", 5], + ["", "shibuya_109", 5], + ["ðŸŸ", "stadium", 5], + ["🗽", "statue_of_liberty", 5], + ["ðŸ ", "house", 5], + ["ðŸ¡", "house_with_garden", 5], + ["ðŸš", "derelict_house", 5], + ["ðŸ¢", "office", 5], + ["ðŸ¬", "department_store", 5], + ["ðŸ£", "post_office", 5], + ["ðŸ¤", "european_post_office", 5], + ["ðŸ¥", "hospital", 5], + ["ðŸ¦", "bank", 5], + ["ðŸ¨", "hotel", 5], + ["ðŸª", "convenience_store", 5], + ["ðŸ«", "school", 5], + ["ðŸ©", "love_hotel", 5], + ["💒", "wedding", 5], + ["ðŸ›", "classical_building", 5], + ["⛪", "church", 5], + ["🕌", "mosque", 5], + ["ðŸ•", "synagogue", 5], + ["🕋", "kaaba", 5], + ["⛩", "shinto_shrine", 5], + ["🛕", "hindu_temple", 5], + ["🪨", "rock", 5], + ["🪵", "wood", 5], + ["🛖", "hut", 5], + ["ðŸ›", "playground_slide", 5], + ["🛞", "wheel", 5], + ["🛟", "ring_buoy", 5], + ["⌚", "watch", 6], + ["📱", "iphone", 6], + ["📲", "calling", 6], + ["💻", "computer", 6], + ["⌨", "keyboard", 6], + ["🖥", "desktop_computer", 6], + ["🖨", "printer", 6], + ["🖱", "computer_mouse", 6], + ["🖲", "trackball", 6], + ["🕹", "joystick", 6], + ["🗜", "clamp", 6], + ["💽", "minidisc", 6], + ["💾", "floppy_disk", 6], + ["💿", "cd", 6], + ["📀", "dvd", 6], + ["📼", "vhs", 6], + ["📷", "camera", 6], + ["📸", "camera_flash", 6], + ["📹", "video_camera", 6], + ["🎥", "movie_camera", 6], + ["📽", "film_projector", 6], + ["🎞", "film_strip", 6], + ["📞", "telephone_receiver", 6], + ["☎ï¸", "phone", 6], + ["📟", "pager", 6], + ["📠", "fax", 6], + ["📺", "tv", 6], + ["📻", "radio", 6], + ["🎙", "studio_microphone", 6], + ["🎚", "level_slider", 6], + ["🎛", "control_knobs", 6], + ["ðŸ§", "compass", 6], + ["â±", "stopwatch", 6], + ["â²", "timer_clock", 6], + ["â°", "alarm_clock", 6], + ["🕰", "mantelpiece_clock", 6], + ["â³", "hourglass_flowing_sand", 6], + ["⌛", "hourglass", 6], + ["📡", "satellite", 6], + ["🔋", "battery", 6], + ["🪫", "battery", 6], + ["🔌", "electric_plug", 6], + ["💡", "bulb", 6], + ["🔦", "flashlight", 6], + ["🕯", "candle", 6], + ["🧯", "fire_extinguisher", 6], + ["🗑", "wastebasket", 6], + ["🛢", "oil_drum", 6], + ["💸", "money_with_wings", 6], + ["💵", "dollar", 6], + ["💴", "yen", 6], + ["💶", "euro", 6], + ["💷", "pound", 6], + ["💰", "moneybag", 6], + ["🪙", "coin", 6], + ["💳", "credit_card", 6], + ["🪫", "identification_card", 6], + ["💎", "gem", 6], + ["âš–", "balance_scale", 6], + ["🧰", "toolbox", 6], + ["🔧", "wrench", 6], + ["🔨", "hammer", 6], + ["âš’", "hammer_and_pick", 6], + ["🛠", "hammer_and_wrench", 6], + ["â›", "pick", 6], + ["🪓", "axe", 6], + ["🦯", "probing_cane", 6], + ["🔩", "nut_and_bolt", 6], + ["âš™", "gear", 6], + ["🪃", "boomerang", 6], + ["🪚", "carpentry_saw", 6], + ["🪛", "screwdriver", 6], + ["ðŸª", "hook", 6], + ["🪜", "ladder", 6], + ["🧱", "brick", 6], + ["⛓", "chains", 6], + ["🧲", "magnet", 6], + ["🔫", "gun", 6], + ["💣", "bomb", 6], + ["🧨", "firecracker", 6], + ["🔪", "hocho", 6], + ["🗡", "dagger", 6], + ["âš”", "crossed_swords", 6], + ["🛡", "shield", 6], + ["🚬", "smoking", 6], + ["☠", "skull_and_crossbones", 6], + ["âš°", "coffin", 6], + ["âš±", "funeral_urn", 6], + ["ðŸº", "amphora", 6], + ["🔮", "crystal_ball", 6], + ["📿", "prayer_beads", 6], + ["🧿", "nazar_amulet", 6], + ["💈", "barber", 6], + ["âš—", "alembic", 6], + ["ðŸ”", "telescope", 6], + ["🔬", "microscope", 6], + ["🕳", "hole", 6], + ["💊", "pill", 6], + ["💉", "syringe", 6], + ["🩸", "drop_of_blood", 6], + ["🩹", "adhesive_bandage", 6], + ["🩺", "stethoscope", 6], + ["🪒", "razor", 6], + ["🩻", "xray", 6], + ["🩼", "crutch", 6], + ["🧬", "dna", 6], + ["🧫", "petri_dish", 6], + ["🧪", "test_tube", 6], + ["🌡", "thermometer", 6], + ["🧹", "broom", 6], + ["🧺", "basket", 6], + ["🧻", "toilet_paper", 6], + ["ðŸ·", "label", 6], + ["🔖", "bookmark", 6], + ["🚽", "toilet", 6], + ["🚿", "shower", 6], + ["ðŸ›", "bathtub", 6], + ["🧼", "soap", 6], + ["🧽", "sponge", 6], + ["🧴", "lotion_bottle", 6], + ["🔑", "key", 6], + ["ðŸ—", "old_key", 6], + ["🛋", "couch_and_lamp", 6], + ["🪔", "diya_Lamp", 6], + ["🛌", "sleeping_bed", 6], + ["ðŸ›", "bed", 6], + ["🚪", "door", 6], + ["🪑", "chair", 6], + ["🛎", "bellhop_bell", 6], + ["🧸", "teddy_bear", 6], + ["🖼", "framed_picture", 6], + ["🗺", "world_map", 6], + ["🛗", "elevator", 6], + ["🪞", "mirror", 6], + ["🪟", "window", 6], + ["🪠", "plunger", 6], + ["🪤", "mouse_trap", 6], + ["🪣", "bucket", 6], + ["🪥", "toothbrush", 6], + ["🫧", "bubbles", 6], + ["â›±", "parasol_on_ground", 6], + ["🗿", "moyai", 6], + ["ðŸ›", "shopping", 6], + ["🛒", "shopping_cart", 6], + ["🎈", "balloon", 6], + ["ðŸŽ", "flags", 6], + ["🎀", "ribbon", 6], + ["ðŸŽ", "gift", 6], + ["🎊", "confetti_ball", 6], + ["🎉", "tada", 6], + ["🎎", "dolls", 6], + ["ðŸŽ", "wind_chime", 6], + ["🎌", "crossed_flags", 6], + ["ðŸ®", "izakaya_lantern", 6], + ["🧧", "red_envelope", 6], + ["✉ï¸", "email", 6], + ["📩", "envelope_with_arrow", 6], + ["📨", "incoming_envelope", 6], + ["📧", "e-mail", 6], + ["💌", "love_letter", 6], + ["📮", "postbox", 6], + ["📪", "mailbox_closed", 6], + ["📫", "mailbox", 6], + ["📬", "mailbox_with_mail", 6], + ["ðŸ“", "mailbox_with_no_mail", 6], + ["📦", "package", 6], + ["📯", "postal_horn", 6], + ["📥", "inbox_tray", 6], + ["📤", "outbox_tray", 6], + ["📜", "scroll", 6], + ["📃", "page_with_curl", 6], + ["📑", "bookmark_tabs", 6], + ["🧾", "receipt", 6], + ["📊", "bar_chart", 6], + ["📈", "chart_with_upwards_trend", 6], + ["📉", "chart_with_downwards_trend", 6], + ["📄", "page_facing_up", 6], + ["📅", "date", 6], + ["📆", "calendar", 6], + ["🗓", "spiral_calendar", 6], + ["📇", "card_index", 6], + ["🗃", "card_file_box", 6], + ["🗳", "ballot_box", 6], + ["🗄", "file_cabinet", 6], + ["📋", "clipboard", 6], + ["🗒", "spiral_notepad", 6], + ["ðŸ“", "file_folder", 6], + ["📂", "open_file_folder", 6], + ["🗂", "card_index_dividers", 6], + ["🗞", "newspaper_roll", 6], + ["📰", "newspaper", 6], + ["📓", "notebook", 6], + ["📕", "closed_book", 6], + ["📗", "green_book", 6], + ["📘", "blue_book", 6], + ["📙", "orange_book", 6], + ["📔", "notebook_with_decorative_cover", 6], + ["📒", "ledger", 6], + ["📚", "books", 6], + ["📖", "open_book", 6], + ["🧷", "safety_pin", 6], + ["🔗", "link", 6], + ["📎", "paperclip", 6], + ["🖇", "paperclips", 6], + ["✂ï¸", "scissors", 6], + ["ðŸ“", "triangular_ruler", 6], + ["ðŸ“", "straight_ruler", 6], + ["🧮", "abacus", 6], + ["📌", "pushpin", 6], + ["ðŸ“", "round_pushpin", 6], + ["🚩", "triangular_flag_on_post", 6], + ["ðŸ³", "white_flag", 6], + ["ðŸ´", "black_flag", 6], + ["ðŸ³ï¸â€ðŸŒˆ", "rainbow_flag", 6], + ["ðŸ³ï¸â€âš§ï¸", "transgender_flag", 6], + ["ðŸ”", "closed_lock_with_key", 6], + ["🔒", "lock", 6], + ["🔓", "unlock", 6], + ["ðŸ”", "lock_with_ink_pen", 6], + ["🖊", "pen", 6], + ["🖋", "fountain_pen", 6], + ["✒ï¸", "black_nib", 6], + ["ðŸ“", "memo", 6], + ["âœï¸", "pencil2", 6], + ["ðŸ–", "crayon", 6], + ["🖌", "paintbrush", 6], + ["ðŸ”", "mag", 6], + ["🔎", "mag_right", 6], + ["🪦", "headstone", 6], + ["🪧", "placard", 6], + ["💯", "100", 7], + ["🔢", "1234", 7], + ["â¤ï¸", "heart", 7], + ["🧡", "orange_heart", 7], + ["💛", "yellow_heart", 7], + ["💚", "green_heart", 7], + ["💙", "blue_heart", 7], + ["💜", "purple_heart", 7], + ["🤎", "brown_heart", 7], + ["🖤", "black_heart", 7], + ["ðŸ¤", "white_heart", 7], + ["💔", "broken_heart", 7], + ["â£", "heavy_heart_exclamation", 7], + ["💕", "two_hearts", 7], + ["💞", "revolving_hearts", 7], + ["💓", "heartbeat", 7], + ["💗", "heartpulse", 7], + ["💖", "sparkling_heart", 7], + ["💘", "cupid", 7], + ["ðŸ’", "gift_heart", 7], + ["💟", "heart_decoration", 7], + ["â¤ï¸â€ðŸ”¥", "heart_on_fire", 7], + ["â¤ï¸â€ðŸ©¹", "mending_heart", 7], + ["☮", "peace_symbol", 7], + ["âœ", "latin_cross", 7], + ["☪", "star_and_crescent", 7], + ["🕉", "om", 7], + ["☸", "wheel_of_dharma", 7], + ["✡", "star_of_david", 7], + ["🔯", "six_pointed_star", 7], + ["🕎", "menorah", 7], + ["☯", "yin_yang", 7], + ["☦", "orthodox_cross", 7], + ["ðŸ›", "place_of_worship", 7], + ["⛎", "ophiuchus", 7], + ["♈", "aries", 7], + ["♉", "taurus", 7], + ["♊", "gemini", 7], + ["♋", "cancer", 7], + ["♌", "leo", 7], + ["â™", "virgo", 7], + ["♎", "libra", 7], + ["â™", "scorpius", 7], + ["â™", "sagittarius", 7], + ["♑", "capricorn", 7], + ["â™’", "aquarius", 7], + ["♓", "pisces", 7], + ["🆔", "id", 7], + ["âš›", "atom_symbol", 7], + ["⚧ï¸", "transgender_symbol", 7], + ["🈳", "u7a7a", 7], + ["🈹", "u5272", 7], + ["☢", "radioactive", 7], + ["☣", "biohazard", 7], + ["📴", "mobile_phone_off", 7], + ["📳", "vibration_mode", 7], + ["🈶", "u6709", 7], + ["🈚", "u7121", 7], + ["🈸", "u7533", 7], + ["🈺", "u55b6", 7], + ["🈷ï¸", "u6708", 7], + ["✴ï¸", "eight_pointed_black_star", 7], + ["🆚", "vs", 7], + ["🉑", "accept", 7], + ["💮", "white_flower", 7], + ["ðŸ‰", "ideograph_advantage", 7], + ["㊙ï¸", "secret", 7], + ["㊗ï¸", "congratulations", 7], + ["🈴", "u5408", 7], + ["🈵", "u6e80", 7], + ["🈲", "u7981", 7], + ["🅰ï¸", "a", 7], + ["🅱ï¸", "b", 7], + ["🆎", "ab", 7], + ["🆑", "cl", 7], + ["🅾ï¸", "o2", 7], + ["🆘", "sos", 7], + ["â›”", "no_entry", 7], + ["📛", "name_badge", 7], + ["🚫", "no_entry_sign", 7], + ["âŒ", "x", 7], + ["â•", "o", 7], + ["🛑", "stop_sign", 7], + ["💢", "anger", 7], + ["♨ï¸", "hotsprings", 7], + ["🚷", "no_pedestrians", 7], + ["🚯", "do_not_litter", 7], + ["🚳", "no_bicycles", 7], + ["🚱", "non-potable_water", 7], + ["🔞", "underage", 7], + ["📵", "no_mobile_phones", 7], + ["â—", "exclamation", 7], + ["â•", "grey_exclamation", 7], + ["â“", "question", 7], + ["â”", "grey_question", 7], + ["‼ï¸", "bangbang", 7], + ["â‰ï¸", "interrobang", 7], + ["🔅", "low_brightness", 7], + ["🔆", "high_brightness", 7], + ["🔱", "trident", 7], + ["âšœ", "fleur_de_lis", 7], + ["〽ï¸", "part_alternation_mark", 7], + ["âš ï¸", "warning", 7], + ["🚸", "children_crossing", 7], + ["🔰", "beginner", 7], + ["â™»ï¸", "recycle", 7], + ["🈯", "u6307", 7], + ["💹", "chart", 7], + ["â‡ï¸", "sparkle", 7], + ["✳ï¸", "eight_spoked_asterisk", 7], + ["âŽ", "negative_squared_cross_mark", 7], + ["✅", "white_check_mark", 7], + ["💠", "diamond_shape_with_a_dot_inside", 7], + ["🌀", "cyclone", 7], + ["âž¿", "loop", 7], + ["ðŸŒ", "globe_with_meridians", 7], + ["â“‚ï¸", "m", 7], + ["ðŸ§", "atm", 7], + ["🈂ï¸", "sa", 7], + ["🛂", "passport_control", 7], + ["🛃", "customs", 7], + ["🛄", "baggage_claim", 7], + ["🛅", "left_luggage", 7], + ["♿", "wheelchair", 7], + ["ðŸš", "no_smoking", 7], + ["🚾", "wc", 7], + ["🅿ï¸", "parking", 7], + ["🚰", "potable_water", 7], + ["🚹", "mens", 7], + ["🚺", "womens", 7], + ["🚼", "baby_symbol", 7], + ["🚻", "restroom", 7], + ["🚮", "put_litter_in_its_place", 7], + ["🎦", "cinema", 7], + ["📶", "signal_strength", 7], + ["ðŸˆ", "koko", 7], + ["🆖", "ng", 7], + ["🆗", "ok", 7], + ["🆙", "up", 7], + ["🆒", "cool", 7], + ["🆕", "new", 7], + ["🆓", "free", 7], + ["0ï¸âƒ£", "zero", 7], + ["1ï¸âƒ£", "one", 7], + ["2ï¸âƒ£", "two", 7], + ["3ï¸âƒ£", "three", 7], + ["4ï¸âƒ£", "four", 7], + ["5ï¸âƒ£", "five", 7], + ["6ï¸âƒ£", "six", 7], + ["7ï¸âƒ£", "seven", 7], + ["8ï¸âƒ£", "eight", 7], + ["9ï¸âƒ£", "nine", 7], + ["🔟", "keycap_ten", 7], + ["*⃣", "asterisk", 7], + ["âï¸", "eject_button", 7], + ["â–¶ï¸", "arrow_forward", 7], + ["â¸", "pause_button", 7], + ["â", "next_track_button", 7], + ["â¹", "stop_button", 7], + ["âº", "record_button", 7], + ["â¯", "play_or_pause_button", 7], + ["â®", "previous_track_button", 7], + ["â©", "fast_forward", 7], + ["âª", "rewind", 7], + ["🔀", "twisted_rightwards_arrows", 7], + ["ðŸ”", "repeat", 7], + ["🔂", "repeat_one", 7], + ["â—€ï¸", "arrow_backward", 7], + ["🔼", "arrow_up_small", 7], + ["🔽", "arrow_down_small", 7], + ["â«", "arrow_double_up", 7], + ["â¬", "arrow_double_down", 7], + ["âž¡ï¸", "arrow_right", 7], + ["⬅ï¸", "arrow_left", 7], + ["⬆ï¸", "arrow_up", 7], + ["⬇ï¸", "arrow_down", 7], + ["↗ï¸", "arrow_upper_right", 7], + ["↘ï¸", "arrow_lower_right", 7], + ["↙ï¸", "arrow_lower_left", 7], + ["↖ï¸", "arrow_upper_left", 7], + ["↕ï¸", "arrow_up_down", 7], + ["↔ï¸", "left_right_arrow", 7], + ["🔄", "arrows_counterclockwise", 7], + ["↪ï¸", "arrow_right_hook", 7], + ["↩ï¸", "leftwards_arrow_with_hook", 7], + ["⤴ï¸", "arrow_heading_up", 7], + ["⤵ï¸", "arrow_heading_down", 7], + ["#ï¸âƒ£", "hash", 7], + ["ℹï¸", "information_source", 7], + ["🔤", "abc", 7], + ["🔡", "abcd", 7], + ["🔠", "capital_abcd", 7], + ["🔣", "symbols", 7], + ["🎵", "musical_note", 7], + ["🎶", "notes", 7], + ["〰ï¸", "wavy_dash", 7], + ["âž°", "curly_loop", 7], + ["✔ï¸", "heavy_check_mark", 7], + ["🔃", "arrows_clockwise", 7], + ["âž•", "heavy_plus_sign", 7], + ["âž–", "heavy_minus_sign", 7], + ["âž—", "heavy_division_sign", 7], + ["✖ï¸", "heavy_multiplication_x", 7], + ["🟰", "heavy_equals_sign", 7], + ["♾", "infinity", 7], + ["💲", "heavy_dollar_sign", 7], + ["💱", "currency_exchange", 7], + ["©ï¸", "copyright", 7], + ["®ï¸", "registered", 7], + ["â„¢ï¸", "tm", 7], + ["🔚", "end", 7], + ["🔙", "back", 7], + ["🔛", "on", 7], + ["ðŸ”", "top", 7], + ["🔜", "soon", 7], + ["☑ï¸", "ballot_box_with_check", 7], + ["🔘", "radio_button", 7], + ["âš«", "black_circle", 7], + ["⚪", "white_circle", 7], + ["🔴", "red_circle", 7], + ["🟠", "orange_circle", 7], + ["🟡", "yellow_circle", 7], + ["🟢", "green_circle", 7], + ["🔵", "large_blue_circle", 7], + ["🟣", "purple_circle", 7], + ["🟤", "brown_circle", 7], + ["🔸", "small_orange_diamond", 7], + ["🔹", "small_blue_diamond", 7], + ["🔶", "large_orange_diamond", 7], + ["🔷", "large_blue_diamond", 7], + ["🔺", "small_red_triangle", 7], + ["â–ªï¸", "black_small_square", 7], + ["â–«ï¸", "white_small_square", 7], + ["⬛", "black_large_square", 7], + ["⬜", "white_large_square", 7], + ["🟥", "red_square", 7], + ["🟧", "orange_square", 7], + ["🟨", "yellow_square", 7], + ["🟩", "green_square", 7], + ["🟦", "blue_square", 7], + ["🟪", "purple_square", 7], + ["🟫", "brown_square", 7], + ["🔻", "small_red_triangle_down", 7], + ["â—¼ï¸", "black_medium_square", 7], + ["â—»ï¸", "white_medium_square", 7], + ["â—¾", "black_medium_small_square", 7], + ["â—½", "white_medium_small_square", 7], + ["🔲", "black_square_button", 7], + ["🔳", "white_square_button", 7], + ["🔈", "speaker", 7], + ["🔉", "sound", 7], + ["🔊", "loud_sound", 7], + ["🔇", "mute", 7], + ["📣", "mega", 7], + ["📢", "loudspeaker", 7], + ["🔔", "bell", 7], + ["🔕", "no_bell", 7], + ["ðŸƒ", "black_joker", 7], + ["🀄", "mahjong", 7], + ["â™ ï¸", "spades", 7], + ["♣ï¸", "clubs", 7], + ["♥ï¸", "hearts", 7], + ["♦ï¸", "diamonds", 7], + ["🎴", "flower_playing_cards", 7], + ["ðŸ’", "thought_balloon", 7], + ["🗯", "right_anger_bubble", 7], + ["💬", "speech_balloon", 7], + ["🗨", "left_speech_bubble", 7], + ["ðŸ•", "clock1", 7], + ["🕑", "clock2", 7], + ["🕒", "clock3", 7], + ["🕓", "clock4", 7], + ["🕔", "clock5", 7], + ["🕕", "clock6", 7], + ["🕖", "clock7", 7], + ["🕗", "clock8", 7], + ["🕘", "clock9", 7], + ["🕙", "clock10", 7], + ["🕚", "clock11", 7], + ["🕛", "clock12", 7], + ["🕜", "clock130", 7], + ["ðŸ•", "clock230", 7], + ["🕞", "clock330", 7], + ["🕟", "clock430", 7], + ["🕠", "clock530", 7], + ["🕡", "clock630", 7], + ["🕢", "clock730", 7], + ["🕣", "clock830", 7], + ["🕤", "clock930", 7], + ["🕥", "clock1030", 7], + ["🕦", "clock1130", 7], + ["🕧", "clock1230", 7], + ["🇦🇫", "afghanistan", 8], + ["🇦🇽", "aland_islands", 8], + ["🇦🇱", "albania", 8], + ["🇩🇿", "algeria", 8], + ["🇦🇸", "american_samoa", 8], + ["🇦🇩", "andorra", 8], + ["🇦🇴", "angola", 8], + ["🇦🇮", "anguilla", 8], + ["🇦🇶", "antarctica", 8], + ["🇦🇬", "antigua_barbuda", 8], + ["🇦🇷", "argentina", 8], + ["🇦🇲", "armenia", 8], + ["🇦🇼", "aruba", 8], + ["🇦🇨", "ascension_island", 8], + ["🇦🇺", "australia", 8], + ["🇦🇹", "austria", 8], + ["🇦🇿", "azerbaijan", 8], + ["🇧🇸", "bahamas", 8], + ["🇧ðŸ‡", "bahrain", 8], + ["🇧🇩", "bangladesh", 8], + ["🇧🇧", "barbados", 8], + ["🇧🇾", "belarus", 8], + ["🇧🇪", "belgium", 8], + ["🇧🇿", "belize", 8], + ["🇧🇯", "benin", 8], + ["🇧🇲", "bermuda", 8], + ["🇧🇹", "bhutan", 8], + ["🇧🇴", "bolivia", 8], + ["🇧🇶", "caribbean_netherlands", 8], + ["🇧🇦", "bosnia_herzegovina", 8], + ["🇧🇼", "botswana", 8], + ["🇧🇷", "brazil", 8], + ["🇮🇴", "british_indian_ocean_territory", 8], + ["🇻🇬", "british_virgin_islands", 8], + ["🇧🇳", "brunei", 8], + ["🇧🇬", "bulgaria", 8], + ["🇧🇫", "burkina_faso", 8], + ["🇧🇮", "burundi", 8], + ["🇨🇻", "cape_verde", 8], + ["🇰ðŸ‡", "cambodia", 8], + ["🇨🇲", "cameroon", 8], + ["🇨🇦", "canada", 8], + ["🇮🇨", "canary_islands", 8], + ["🇰🇾", "cayman_islands", 8], + ["🇨🇫", "central_african_republic", 8], + ["🇹🇩", "chad", 8], + ["🇨🇱", "chile", 8], + ["🇨🇳", "cn", 8], + ["🇨🇽", "christmas_island", 8], + ["🇨🇨", "cocos_islands", 8], + ["🇨🇴", "colombia", 8], + ["🇰🇲", "comoros", 8], + ["🇨🇬", "congo_brazzaville", 8], + ["🇨🇩", "congo_kinshasa", 8], + ["🇨🇰", "cook_islands", 8], + ["🇨🇷", "costa_rica", 8], + ["ðŸ‡ðŸ‡·", "croatia", 8], + ["🇨🇺", "cuba", 8], + ["🇨🇼", "curacao", 8], + ["🇨🇾", "cyprus", 8], + ["🇨🇿", "czech_republic", 8], + ["🇩🇰", "denmark", 8], + ["🇩🇯", "djibouti", 8], + ["🇩🇲", "dominica", 8], + ["🇩🇴", "dominican_republic", 8], + ["🇪🇨", "ecuador", 8], + ["🇪🇬", "egypt", 8], + ["🇸🇻", "el_salvador", 8], + ["🇬🇶", "equatorial_guinea", 8], + ["🇪🇷", "eritrea", 8], + ["🇪🇪", "estonia", 8], + ["🇪🇹", "ethiopia", 8], + ["🇪🇺", "eu", 8], + ["🇫🇰", "falkland_islands", 8], + ["🇫🇴", "faroe_islands", 8], + ["🇫🇯", "fiji", 8], + ["🇫🇮", "finland", 8], + ["🇫🇷", "fr", 8], + ["🇬🇫", "french_guiana", 8], + ["🇵🇫", "french_polynesia", 8], + ["🇹🇫", "french_southern_territories", 8], + ["🇬🇦", "gabon", 8], + ["🇬🇲", "gambia", 8], + ["🇬🇪", "georgia", 8], + ["🇩🇪", "de", 8], + ["🇬ðŸ‡", "ghana", 8], + ["🇬🇮", "gibraltar", 8], + ["🇬🇷", "greece", 8], + ["🇬🇱", "greenland", 8], + ["🇬🇩", "grenada", 8], + ["🇬🇵", "guadeloupe", 8], + ["🇬🇺", "guam", 8], + ["🇬🇹", "guatemala", 8], + ["🇬🇬", "guernsey", 8], + ["🇬🇳", "guinea", 8], + ["🇬🇼", "guinea_bissau", 8], + ["🇬🇾", "guyana", 8], + ["ðŸ‡ðŸ‡¹", "haiti", 8], + ["ðŸ‡ðŸ‡³", "honduras", 8], + ["ðŸ‡ðŸ‡°", "hong_kong", 8], + ["ðŸ‡ðŸ‡º", "hungary", 8], + ["🇮🇸", "iceland", 8], + ["🇮🇳", "india", 8], + ["🇮🇩", "indonesia", 8], + ["🇮🇷", "iran", 8], + ["🇮🇶", "iraq", 8], + ["🇮🇪", "ireland", 8], + ["🇮🇲", "isle_of_man", 8], + ["🇮🇱", "israel", 8], + ["🇮🇹", "it", 8], + ["🇨🇮", "cote_divoire", 8], + ["🇯🇲", "jamaica", 8], + ["🇯🇵", "jp", 8], + ["🇯🇪", "jersey", 8], + ["🇯🇴", "jordan", 8], + ["🇰🇿", "kazakhstan", 8], + ["🇰🇪", "kenya", 8], + ["🇰🇮", "kiribati", 8], + ["🇽🇰", "kosovo", 8], + ["🇰🇼", "kuwait", 8], + ["🇰🇬", "kyrgyzstan", 8], + ["🇱🇦", "laos", 8], + ["🇱🇻", "latvia", 8], + ["🇱🇧", "lebanon", 8], + ["🇱🇸", "lesotho", 8], + ["🇱🇷", "liberia", 8], + ["🇱🇾", "libya", 8], + ["🇱🇮", "liechtenstein", 8], + ["🇱🇹", "lithuania", 8], + ["🇱🇺", "luxembourg", 8], + ["🇲🇴", "macau", 8], + ["🇲🇰", "macedonia", 8], + ["🇲🇬", "madagascar", 8], + ["🇲🇼", "malawi", 8], + ["🇲🇾", "malaysia", 8], + ["🇲🇻", "maldives", 8], + ["🇲🇱", "mali", 8], + ["🇲🇹", "malta", 8], + ["🇲ðŸ‡", "marshall_islands", 8], + ["🇲🇶", "martinique", 8], + ["🇲🇷", "mauritania", 8], + ["🇲🇺", "mauritius", 8], + ["🇾🇹", "mayotte", 8], + ["🇲🇽", "mexico", 8], + ["🇫🇲", "micronesia", 8], + ["🇲🇩", "moldova", 8], + ["🇲🇨", "monaco", 8], + ["🇲🇳", "mongolia", 8], + ["🇲🇪", "montenegro", 8], + ["🇲🇸", "montserrat", 8], + ["🇲🇦", "morocco", 8], + ["🇲🇿", "mozambique", 8], + ["🇲🇲", "myanmar", 8], + ["🇳🇦", "namibia", 8], + ["🇳🇷", "nauru", 8], + ["🇳🇵", "nepal", 8], + ["🇳🇱", "netherlands", 8], + ["🇳🇨", "new_caledonia", 8], + ["🇳🇿", "new_zealand", 8], + ["🇳🇮", "nicaragua", 8], + ["🇳🇪", "niger", 8], + ["🇳🇬", "nigeria", 8], + ["🇳🇺", "niue", 8], + ["🇳🇫", "norfolk_island", 8], + ["🇲🇵", "northern_mariana_islands", 8], + ["🇰🇵", "north_korea", 8], + ["🇳🇴", "norway", 8], + ["🇴🇲", "oman", 8], + ["🇵🇰", "pakistan", 8], + ["🇵🇼", "palau", 8], + ["🇵🇸", "palestinian_territories", 8], + ["🇵🇦", "panama", 8], + ["🇵🇬", "papua_new_guinea", 8], + ["🇵🇾", "paraguay", 8], + ["🇵🇪", "peru", 8], + ["🇵ðŸ‡", "philippines", 8], + ["🇵🇳", "pitcairn_islands", 8], + ["🇵🇱", "poland", 8], + ["🇵🇹", "portugal", 8], + ["🇵🇷", "puerto_rico", 8], + ["🇶🇦", "qatar", 8], + ["🇷🇪", "reunion", 8], + ["🇷🇴", "romania", 8], + ["🇷🇺", "ru", 8], + ["🇷🇼", "rwanda", 8], + ["🇧🇱", "st_barthelemy", 8], + ["🇸ðŸ‡", "st_helena", 8], + ["🇰🇳", "st_kitts_nevis", 8], + ["🇱🇨", "st_lucia", 8], + ["🇵🇲", "st_pierre_miquelon", 8], + ["🇻🇨", "st_vincent_grenadines", 8], + ["🇼🇸", "samoa", 8], + ["🇸🇲", "san_marino", 8], + ["🇸🇹", "sao_tome_principe", 8], + ["🇸🇦", "saudi_arabia", 8], + ["🇸🇳", "senegal", 8], + ["🇷🇸", "serbia", 8], + ["🇸🇨", "seychelles", 8], + ["🇸🇱", "sierra_leone", 8], + ["🇸🇬", "singapore", 8], + ["🇸🇽", "sint_maarten", 8], + ["🇸🇰", "slovakia", 8], + ["🇸🇮", "slovenia", 8], + ["🇸🇧", "solomon_islands", 8], + ["🇸🇴", "somalia", 8], + ["🇿🇦", "south_africa", 8], + ["🇬🇸", "south_georgia_south_sandwich_islands", 8], + ["🇰🇷", "kr", 8], + ["🇸🇸", "south_sudan", 8], + ["🇪🇸", "es", 8], + ["🇱🇰", "sri_lanka", 8], + ["🇸🇩", "sudan", 8], + ["🇸🇷", "suriname", 8], + ["🇸🇿", "swaziland", 8], + ["🇸🇪", "sweden", 8], + ["🇨ðŸ‡", "switzerland", 8], + ["🇸🇾", "syria", 8], + ["🇹🇼", "taiwan", 8], + ["🇹🇯", "tajikistan", 8], + ["🇹🇿", "tanzania", 8], + ["🇹ðŸ‡", "thailand", 8], + ["🇹🇱", "timor_leste", 8], + ["🇹🇬", "togo", 8], + ["🇹🇰", "tokelau", 8], + ["🇹🇴", "tonga", 8], + ["🇹🇹", "trinidad_tobago", 8], + ["🇹🇦", "tristan_da_cunha", 8], + ["🇹🇳", "tunisia", 8], + ["🇹🇷", "tr", 8], + ["🇹🇲", "turkmenistan", 8], + ["🇹🇨", "turks_caicos_islands", 8], + ["🇹🇻", "tuvalu", 8], + ["🇺🇬", "uganda", 8], + ["🇺🇦", "ukraine", 8], + ["🇦🇪", "united_arab_emirates", 8], + ["🇬🇧", "uk", 8], + ["ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿", "england", 8], + ["ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿", "scotland", 8], + ["ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿", "wales", 8], + ["🇺🇸", "us", 8], + ["🇻🇮", "us_virgin_islands", 8], + ["🇺🇾", "uruguay", 8], + ["🇺🇿", "uzbekistan", 8], + ["🇻🇺", "vanuatu", 8], + ["🇻🇦", "vatican_city", 8], + ["🇻🇪", "venezuela", 8], + ["🇻🇳", "vietnam", 8], + ["🇼🇫", "wallis_futuna", 8], + ["🇪ðŸ‡", "western_sahara", 8], + ["🇾🇪", "yemen", 8], + ["🇿🇲", "zambia", 8], + ["🇿🇼", "zimbabwe", 8], + ["🇺🇳", "united_nations", 8], + ["ðŸ´â€â˜ ï¸", "pirate_flag", 8] ] - diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts index 220c6210c067c7cca13149322ffc5d94efdf269f..30771ec1b37913d9adc4999959fef55fac3d3485 100644 --- a/packages/frontend/src/i18n.ts +++ b/packages/frontend/src/i18n.ts @@ -1,8 +1,9 @@ import { markRaw } from 'vue'; +import type { Locale } from '../../../locales'; import { locale } from '@/config'; import { I18n } from '@/scripts/i18n'; -export const i18n = markRaw(new I18n(locale)); +export const i18n = markRaw(new I18n<Locale>(locale)); export function updateI18n(newLocale) { i18n.ts = newLocale; diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts deleted file mode 100644 index 49e7bb4008545a4032f60b342f8096fe55151427..0000000000000000000000000000000000000000 --- a/packages/frontend/src/init.ts +++ /dev/null @@ -1,527 +0,0 @@ -/** - * Client entry point - */ -// https://vitejs.dev/config/build-options.html#build-modulepreload -import 'vite/modulepreload-polyfill'; - -import '@/style.scss'; - -import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; -import { compareVersions } from 'compare-versions'; -import JSON5 from 'json5'; - -import widgets from '@/widgets'; -import directives from '@/directives'; -import components from '@/components'; -import { version, ui, lang, updateLocale } from '@/config'; -import { applyTheme } from '@/scripts/theme'; -import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; -import { i18n, updateI18n } from '@/i18n'; -import { confirm, alert, post, popup, toast } from '@/os'; -import { stream } from '@/stream'; -import * as sound from '@/scripts/sound'; -import { $i, refreshAccount, login, updateAccount, signout } from '@/account'; -import { defaultStore, ColdDeviceStorage } from '@/store'; -import { fetchInstance, instance } from '@/instance'; -import { makeHotkey } from '@/scripts/hotkey'; -import { deviceKind } from '@/scripts/device-kind'; -import { initializeSw } from '@/scripts/initialize-sw'; -import { reloadChannel } from '@/scripts/unison-reload'; -import { reactionPicker } from '@/scripts/reaction-picker'; -import { getUrlWithoutLoginId } from '@/scripts/login-id'; -import { getAccountFromId } from '@/scripts/get-account-from-id'; -import { deckStore } from '@/ui/deck/deck-store'; -import { miLocalStorage } from '@/local-storage'; -import { claimAchievement, claimedAchievements } from '@/scripts/achievements'; -import { fetchCustomEmojis } from '@/custom-emojis'; -import { mainRouter } from '@/router'; - -console.info(`Misskey v${version}`); - -if (_DEV_) { - console.warn('Development mode!!!'); - - console.info(`vue ${vueVersion}`); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).$i = $i; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).$store = defaultStore; - - window.addEventListener('error', event => { - console.error(event); - /* - alert({ - type: 'error', - title: 'DEV: Unhandled error', - text: event.message - }); - */ - }); - - window.addEventListener('unhandledrejection', event => { - console.error(event); - /* - alert({ - type: 'error', - title: 'DEV: Unhandled promise rejection', - text: event.reason - }); - */ - }); -} - -//#region Detect language & fetch translations -const localeVersion = miLocalStorage.getItem('localeVersion'); -const localeOutdated = (localeVersion == null || localeVersion !== version); -if (localeOutdated) { - const res = await window.fetch(`/assets/locales/${lang}.${version}.json`); - if (res.status === 200) { - const newLocale = await res.text(); - const parsedNewLocale = JSON.parse(newLocale); - miLocalStorage.setItem('locale', newLocale); - miLocalStorage.setItem('localeVersion', version); - updateLocale(parsedNewLocale); - updateI18n(parsedNewLocale); - } -} -//#endregion - -// タッãƒãƒ‡ãƒã‚¤ã‚¹ã§CSSã®:hoverを機能ã•ã›ã‚‹ -document.addEventListener('touchend', () => {}, { passive: true }); - -// 一斉リãƒãƒ¼ãƒ‰ -reloadChannel.addEventListener('message', path => { - if (path !== null) location.href = path; - else location.reload(); -}); - -// If mobile, insert the viewport meta tag -if (['smartphone', 'tablet'].includes(deviceKind)) { - const viewport = document.getElementsByName('viewport').item(0); - viewport.setAttribute('content', - `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); -} - -//#region Set lang attr -const html = document.documentElement; -html.setAttribute('lang', lang); -//#endregion - -//#region loginId -const params = new URLSearchParams(location.search); -const loginId = params.get('loginId'); - -if (loginId) { - const target = getUrlWithoutLoginId(location.href); - - if (!$i || $i.id !== loginId) { - const account = await getAccountFromId(loginId); - if (account) { - await login(account.token, target); - } - } - - history.replaceState({ misskey: 'loginId' }, '', target); -} - -//#endregion - -//#region Fetch user -if ($i && $i.token) { - if (_DEV_) { - console.log('account cache found. refreshing...'); - } - - refreshAccount(); -} else { - if (_DEV_) { - console.log('no account cache found.'); - } - - // 連æºãƒã‚°ã‚¤ãƒ³ã®å ´åˆç”¨ã«Cookieã‚’å‚ç…§ã™ã‚‹ - const i = (document.cookie.match(/igi=(\w+)/) ?? [null, null])[1]; - - if (i != null && i !== 'null') { - if (_DEV_) { - console.log('signing...'); - } - - try { - document.body.innerHTML = '<div>Please wait...</div>'; - await login(i); - } catch (err) { - // Render the error screen - // TODO: ã¡ã‚ƒã‚“ã¨ã—ãŸã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã‚’レンダリングã™ã‚‹(v10ã¨ã‹ã®ãƒˆãƒ©ãƒ–ルシューティングゲーム付ãã®ã‚„ã¤ã¿ãŸã„ãª) - document.body.innerHTML = '<div id="err">Oops!</div>'; - } - } else { - if (_DEV_) { - console.log('not signed in'); - } - } -} -//#endregion - -const fetchInstanceMetaPromise = fetchInstance(); - -fetchInstanceMetaPromise.then(() => { - miLocalStorage.setItem('v', instance.version); - - // Init service worker - initializeSw(); -}); - -try { - await fetchCustomEmojis(); -} catch (err) { /* empty */ } - -const app = createApp( - new URLSearchParams(window.location.search).has('zen') ? defineAsyncComponent(() => import('@/ui/zen.vue')) : - !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : - ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : - ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : - defineAsyncComponent(() => import('@/ui/universal.vue')), -); - -if (_DEV_) { - app.config.performance = true; -} - -widgets(app); -directives(app); -components(app); - -const splash = document.getElementById('splash'); -// 念ã®ãŸã‚nullãƒã‚§ãƒƒã‚¯(HTMLãŒå¤ã„å ´åˆãŒã‚ã‚‹ãŸã‚(ãã®ã†ã¡æ¶ˆã™)) -if (splash) splash.addEventListener('transitionend', () => { - splash.remove(); -}); - -await deckStore.ready; - -// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 -// ãªãœã‹init.tsã®å†…容ãŒ2回実行ã•ã‚Œã‚‹ã“ã¨ãŒã‚ã‚‹ãŸã‚ã€mountã™ã‚‹divã‚’1ã¤ã«åˆ¶é™ã™ã‚‹ -const rootEl = ((): HTMLElement => { - const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - - const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); - - if (currentRoot) { - console.warn('multiple import detected'); - return currentRoot; - } - - const root = document.createElement('div'); - root.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(root); - return root; -})(); - -app.mount(rootEl); - -// boot.jsã®ã‚„ã¤ã‚’解除 -window.onerror = null; -window.onunhandledrejection = null; - -reactionPicker.init(); - -if (splash) { - splash.style.opacity = '0'; - splash.style.pointerEvents = 'none'; -} - -// クライアントãŒæ›´æ–°ã•ã‚ŒãŸã‹ï¼Ÿ -const lastVersion = miLocalStorage.getItem('lastVersion'); -if (lastVersion !== version) { - miLocalStorage.setItem('lastVersion', version); - - // テーマリビルドã™ã‚‹ãŸã‚ - miLocalStorage.removeItem('theme'); - - try { // 変ãªãƒãƒ¼ã‚¸ãƒ§ãƒ³æ–‡å—列æ¥ã‚‹ã¨compareVersionsã§ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ãŸã‚ - if (lastVersion != null && compareVersions(version, lastVersion) === 1) { - // ãƒã‚°ã‚¤ãƒ³ã—ã¦ã‚‹å ´åˆã ã‘ - if ($i) { - popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed'); - } - } - } catch (err) { /* empty */ } -} - -await defaultStore.ready; - -// NOTE: ã“ã®å‡¦ç†ã¯å¿…ãšâ†‘ã®ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆæ›´æ–°æ™‚処ç†ã‚ˆã‚Šå¾Œã«æ¥ã‚‹ã“ã¨(テーマå†æ§‹ç¯‰ã®ãŸã‚) -watch(defaultStore.reactiveState.darkMode, (darkMode) => { - applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); -}, { immediate: miLocalStorage.getItem('theme') == null }); - -const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); -const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); - -watch(darkTheme, (theme) => { - if (defaultStore.state.darkMode) { - applyTheme(theme); - } -}); - -watch(lightTheme, (theme) => { - if (!defaultStore.state.darkMode) { - applyTheme(theme); - } -}); - -//#region Sync dark mode -if (ColdDeviceStorage.get('syncDeviceDarkMode')) { - defaultStore.set('darkMode', isDeviceDarkmode()); -} - -window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { - if (ColdDeviceStorage.get('syncDeviceDarkMode')) { - defaultStore.set('darkMode', mql.matches); - } -}); -//#endregion - -fetchInstanceMetaPromise.then(() => { - if (defaultStore.state.themeInitial) { - if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON5.parse(instance.defaultDarkTheme)); - defaultStore.set('themeInitial', false); - } -}); - -watch(defaultStore.reactiveState.useBlurEffectForModal, v => { - document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); -}, { immediate: true }); - -watch(defaultStore.reactiveState.useBlurEffect, v => { - if (v) { - document.documentElement.style.removeProperty('--blur'); - } else { - document.documentElement.style.setProperty('--blur', 'none'); - } -}, { immediate: true }); - -let reloadDialogShowing = false; -stream.on('_disconnected_', async () => { - if (defaultStore.state.serverDisconnectedBehavior === 'reload') { - location.reload(); - } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { - if (reloadDialogShowing) return; - reloadDialogShowing = true; - const { canceled } = await confirm({ - type: 'warning', - title: i18n.ts.disconnectedFromServer, - text: i18n.ts.reloadConfirm, - }); - reloadDialogShowing = false; - if (!canceled) { - location.reload(); - } - } -}); - -for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { - import('./plugin').then(async ({ install }) => { - // Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740 - await new Promise(r => setTimeout(r, 0)); - install(plugin); - }); -} - -const hotkeys = { - 'd': (): void => { - defaultStore.set('darkMode', !defaultStore.state.darkMode); - }, - 's': (): void => { - mainRouter.push('/search'); - }, -}; - -if ($i) { - // only add post shortcuts if logged in - hotkeys['p|n'] = post; - - if (defaultStore.state.accountSetupWizard !== -1) { - // ã“ã®ã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ãŒå®Ÿè£…ã•ã‚Œã‚‹å‰ã«ç™»éŒ²ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã¯è¡¨ç¤ºã•ã›ãªã„ãŸã‚ - // TODO: ãã®ã†ã¡æ¶ˆã™ - if (Date.now() - new Date($i.createdAt).getTime() < 1000 * 60 * 60 * 24) { - popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed'); - } else { - defaultStore.set('accountSetupWizard', -1); - } - } - - if ($i.isDeleted) { - alert({ - type: 'warning', - text: i18n.ts.accountDeletionInProgress, - }); - } - - const now = new Date(); - const m = now.getMonth() + 1; - const d = now.getDate(); - - if ($i.birthday) { - const bm = parseInt($i.birthday.split('-')[1]); - const bd = parseInt($i.birthday.split('-')[2]); - if (m === bm && d === bd) { - claimAchievement('loggedInOnBirthday'); - } - } - - if (m === 1 && d === 1) { - claimAchievement('loggedInOnNewYearsDay'); - } - - if ($i.loggedInDays >= 3) claimAchievement('login3'); - if ($i.loggedInDays >= 7) claimAchievement('login7'); - if ($i.loggedInDays >= 15) claimAchievement('login15'); - if ($i.loggedInDays >= 30) claimAchievement('login30'); - if ($i.loggedInDays >= 60) claimAchievement('login60'); - if ($i.loggedInDays >= 100) claimAchievement('login100'); - if ($i.loggedInDays >= 200) claimAchievement('login200'); - if ($i.loggedInDays >= 300) claimAchievement('login300'); - if ($i.loggedInDays >= 400) claimAchievement('login400'); - if ($i.loggedInDays >= 500) claimAchievement('login500'); - if ($i.loggedInDays >= 600) claimAchievement('login600'); - if ($i.loggedInDays >= 700) claimAchievement('login700'); - if ($i.loggedInDays >= 800) claimAchievement('login800'); - if ($i.loggedInDays >= 900) claimAchievement('login900'); - if ($i.loggedInDays >= 1000) claimAchievement('login1000'); - - if ($i.notesCount > 0) claimAchievement('notes1'); - if ($i.notesCount >= 10) claimAchievement('notes10'); - if ($i.notesCount >= 100) claimAchievement('notes100'); - if ($i.notesCount >= 500) claimAchievement('notes500'); - if ($i.notesCount >= 1000) claimAchievement('notes1000'); - if ($i.notesCount >= 5000) claimAchievement('notes5000'); - if ($i.notesCount >= 10000) claimAchievement('notes10000'); - if ($i.notesCount >= 20000) claimAchievement('notes20000'); - if ($i.notesCount >= 30000) claimAchievement('notes30000'); - if ($i.notesCount >= 40000) claimAchievement('notes40000'); - if ($i.notesCount >= 50000) claimAchievement('notes50000'); - if ($i.notesCount >= 60000) claimAchievement('notes60000'); - if ($i.notesCount >= 70000) claimAchievement('notes70000'); - if ($i.notesCount >= 80000) claimAchievement('notes80000'); - if ($i.notesCount >= 90000) claimAchievement('notes90000'); - if ($i.notesCount >= 100000) claimAchievement('notes100000'); - - if ($i.followersCount > 0) claimAchievement('followers1'); - if ($i.followersCount >= 10) claimAchievement('followers10'); - if ($i.followersCount >= 50) claimAchievement('followers50'); - if ($i.followersCount >= 100) claimAchievement('followers100'); - if ($i.followersCount >= 300) claimAchievement('followers300'); - if ($i.followersCount >= 500) claimAchievement('followers500'); - if ($i.followersCount >= 1000) claimAchievement('followers1000'); - - if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) { - claimAchievement('passedSinceAccountCreated1'); - } - if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) { - claimAchievement('passedSinceAccountCreated2'); - } - if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) { - claimAchievement('passedSinceAccountCreated3'); - } - - if (claimedAchievements.length >= 30) { - claimAchievement('collectAchievements30'); - } - - window.setInterval(() => { - if (Math.floor(Math.random() * 20000) === 0) { - claimAchievement('justPlainLucky'); - } - }, 1000 * 10); - - window.setTimeout(() => { - claimAchievement('client30min'); - }, 1000 * 60 * 30); - - window.setTimeout(() => { - claimAchievement('client60min'); - }, 1000 * 60 * 60); - - const lastUsed = miLocalStorage.getItem('lastUsed'); - if (lastUsed) { - const lastUsedDate = parseInt(lastUsed, 10); - // 二時間以上å‰ãªã‚‰ - if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { - toast(i18n.t('welcomeBackWithName', { - name: $i.name || $i.username, - })); - } - } - miLocalStorage.setItem('lastUsed', Date.now().toString()); - - const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); - const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); - if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { - if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { - popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed'); - } - } - - if ('Notification' in window) { - // 許å¯ã‚’å¾—ã¦ã„ãªã‹ã£ãŸã‚‰ãƒªã‚¯ã‚¨ã‚¹ãƒˆ - if (Notification.permission === 'default') { - Notification.requestPermission(); - } - } - - const main = markRaw(stream.useChannel('main', null, 'System')); - - // 自分ã®æƒ…å ±ãŒæ›´æ–°ã•ã‚ŒãŸã¨ã - main.on('meUpdated', i => { - updateAccount(i); - }); - - main.on('readAllNotifications', () => { - updateAccount({ hasUnreadNotification: false }); - }); - - main.on('unreadNotification', () => { - updateAccount({ hasUnreadNotification: true }); - }); - - main.on('unreadMention', () => { - updateAccount({ hasUnreadMentions: true }); - }); - - main.on('readAllUnreadMentions', () => { - updateAccount({ hasUnreadMentions: false }); - }); - - main.on('unreadSpecifiedNote', () => { - updateAccount({ hasUnreadSpecifiedNotes: true }); - }); - - main.on('readAllUnreadSpecifiedNotes', () => { - updateAccount({ hasUnreadSpecifiedNotes: false }); - }); - - main.on('readAllAntennas', () => { - updateAccount({ hasUnreadAntenna: false }); - }); - - main.on('unreadAntenna', () => { - updateAccount({ hasUnreadAntenna: true }); - sound.play('antenna'); - }); - - main.on('readAllAnnouncements', () => { - updateAccount({ hasUnreadAnnouncement: false }); - }); - - // トークンãŒå†ç”Ÿæˆã•ã‚ŒãŸã¨ã - // ã“ã®ã¾ã¾ã§ã¯MisskeyãŒåˆ©ç”¨ã§ããªã„ã®ã§å¼·åˆ¶çš„ã«ã‚µã‚¤ãƒ³ã‚¢ã‚¦ãƒˆã•ã›ã‚‹ - main.on('myTokenRegenerated', () => { - signout(); - }); -} - -// shortcut -document.addEventListener('keydown', makeHotkey(hotkeys)); diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 441a35747a3be50a9684c5547e6d234adc4542f2..ca4f21f79b0681a2933112cc1d760825218e4f3e 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -13,7 +13,7 @@ type Keys = 'hashtags' | 'wallpaper' | 'theme' | - 'colorSchema' | + 'colorScheme' | 'useSystemFont' | 'fontSize' | 'ui' | diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index c4f9d47d7d35ba639516b877bd838e14e9888b6a..c44d3480462adb1b8de7861a428649de0c0df812 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -172,12 +172,6 @@ export function pageWindow(path: string) { }, {}, 'closed'); } -export function modalPageWindow(path: string) { - popup(defineAsyncComponent(() => import('@/components/MkModalPageWindow.vue')), { - initialPath: path, - }, {}, 'closed'); -} - export function toast(message: string) { popup(MkToast, { message, diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue index f53fec7d94a0649a15f9775aa668918561e57139..f27d2df336f88b4582866a6da7dbad4d46ce13e5 100644 --- a/packages/frontend/src/pages/_error_.vue +++ b/packages/frontend/src/pages/_error_.vue @@ -1,18 +1,20 @@ <template> <MkLoading v-if="!loaded"/> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> - <div v-show="loaded" class="mjndxjch"> - <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> - <p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p> - <p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p> - <p v-else-if="serverIsDead">{{ i18n.ts.serverIsDead }}</p> - <template v-else> - <p>{{ i18n.ts.newVersionOfClientAvailable }}</p> - <p>{{ i18n.ts.youShouldUpgradeClient }}</p> - <MkButton class="button primary" @click="reload">{{ i18n.ts.reload }}</MkButton> - </template> - <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></p> - <p v-if="error" class="error">ERROR: {{ error }}</p> + <div v-show="loaded" :class="$style.root"> + <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost" :class="$style.img"/> + <div class="_gaps"> + <p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p> + <p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p> + <p v-else-if="serverIsDead">{{ i18n.ts.serverIsDead }}</p> + <template v-else> + <p>{{ i18n.ts.newVersionOfClientAvailable }}</p> + <p>{{ i18n.ts.youShouldUpgradeClient }}</p> + <MkButton style="margin: 8px auto;" @click="reload">{{ i18n.ts.reload }}</MkButton> + </template> + <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></p> + <p v-if="error" style="opacity: 0.7;">ERROR: {{ error }}</p> + </div> </div> </Transition> </template> @@ -64,28 +66,16 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.mjndxjch { +<style lang="scss" module> +.root { padding: 32px; text-align: center; +} - > p { - margin: 0 0 12px 0; - } - - > .button { - margin: 8px auto; - } - - > img { - vertical-align: bottom; - height: 128px; - margin-bottom: 24px; - border-radius: 16px; - } - - > .error { - opacity: 0.7; - } +.img { + vertical-align: bottom; + height: 128px; + margin-bottom: 24px; + border-radius: 16px; } </style> diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 9e0594db3c4937663dfcda08a9b02a32f2f97d1b..0017145fa151797f28a8af83aae1503d59bf61fb 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -2,7 +2,7 @@ <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <div style="overflow: clip;"> - <MkSpacer :content-max="600" :margin-min="20"> + <MkSpacer :contentMax="600" :marginMin="20"> <div class="_gaps_m znqjceqz"> <div v-panel class="about"> <div ref="containerEl" class="container" :class="{ playing: easterEggEngine != null }"> @@ -10,8 +10,8 @@ <div class="misskey">Misskey</div> <div class="version">v{{ version }}</div> <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"> - <MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :no-style="true"/> - <MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :no-style="true"/> + <MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :noStyle="true"/> + <MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :noStyle="true"/> </span> </div> <button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button> @@ -86,8 +86,13 @@ </FormSection> <FormSection> <template #label>Special thanks</template> - <div style="text-align: center;"> - <a style="display: inline-block;" class="dcadvirth" title="DC Advirth" href="https://www.dotchain.ltd/advirth" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/dcadvirth.png" alt="DC Advirth"></a> + <div class="_gaps" style="text-align: center;"> + <div> + <a style="display: inline-block;" class="masknetwork" title="Mask Network" href="https://mask.io/" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/masknetwork.png" alt="Mask Network"></a> + </div> + <div> + <a style="display: inline-block;" class="dcadvirth" title="DC Advirth" href="https://www.dotchain.ltd/advirth" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/dcadvirth.png" alt="DC Advirth"></a> + </div> </div> </FormSection> </div> @@ -144,6 +149,12 @@ const patronsWithIcon = [{ }, { name: 'ã‹ã¿ã‚‰ãˆã£ã¨', icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg', +}, { + name: 'ã¸ã¦ã¦', + icon: 'https://misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg', +}, { + name: 'spinlock', + icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg', }]; const patrons = [ diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index 2d82fcf2771371242922f76672559cd1366f6f59..3744bed10ff639266784cb27245050ffafe515cc 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -1,5 +1,5 @@ <template> -<div class="driuhtrh _gaps"> +<div class="_gaps"> <MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton> <div class="query"> @@ -14,17 +14,17 @@ --> </div> - <MkFoldableSection v-if="searchEmojis" class="emojis"> + <MkFoldableSection v-if="searchEmojis"> <template #header>{{ i18n.ts.searchResult }}</template> - <div class="zuvgdzyt"> - <XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/> + <div :class="$style.emojis"> + <XEmoji v-for="emoji in searchEmojis" :key="emoji.name" :emoji="emoji"/> </div> </MkFoldableSection> - <MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category" class="emojis"> + <MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category"> <template #header>{{ category || i18n.ts.other }}</template> - <div class="zuvgdzyt"> - <XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/> + <div :class="$style.emojis"> + <XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/> </div> </MkFoldableSection> </div> @@ -57,7 +57,7 @@ function search() { if (queryarry) { searchEmojis = customEmojis.value.filter(emoji => - queryarry.includes(`:${emoji.name}:`) + queryarry.includes(`:${emoji.name}:`), ); } else { searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); @@ -84,36 +84,10 @@ watch($$(selectedTags), () => { }, { deep: true }); </script> -<style lang="scss" scoped> -.driuhtrh { - background: var(--bg); - - > .query { - background: var(--bg); - - > .tags { - > .tag { - display: inline-block; - margin: 8px 8px 0 0; - padding: 4px 8px; - font-size: 0.9em; - background: var(--accentedBg); - border-radius: 5px; - - &.active { - background: var(--accent); - color: var(--fgOnAccent); - } - } - } - } - - > .emojis { - .zuvgdzyt { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); - grid-gap: 12px; - } - } +<style lang="scss" module> +.emojis { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); + grid-gap: 12px; } </style> diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index 8fe613a9a86751d7482c5581b4bb3d56945e8820..a8c6c05d8b74ffae8070b2a44ca914b0e60584ae 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -1,6 +1,6 @@ <template> -<div class="taeiyria"> - <div class="query"> +<div> + <div> <MkInput v-model="host" :debounce="true" class=""> <template #prefix><i class="ti ti-search"></i></template> <template #label>{{ i18n.ts.host }}</template> @@ -35,8 +35,8 @@ </div> <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> - <div class="dqokceoi"> - <MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`"> + <div :class="$style.items"> + <MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.item" :to="`/instance-info/${instance.host}`"> <MkInstanceCardMini :instance="instance"/> </MkA> </div> @@ -82,21 +82,14 @@ function getStatus(instance) { } </script> -<style lang="scss" scoped> -.taeiyria { - > .query { - background: var(--bg); - margin-bottom: 16px; - } -} - -.dqokceoi { +<style lang="scss" module> +.items { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px; +} - > .instance:hover { - text-decoration: none; - } +.item:hover { + text-decoration: none; } </style> diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index 8e29990426fe3eaf0a136a15661418c49461c36e..693d369b891327e7611ef14d520bf76d4c02da5f 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer v-if="tab === 'overview'" :content-max="600" :margin-min="20"> + <MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20"> <div class="_gaps_m"> <div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"> <div style="overflow: clip;"> @@ -80,13 +80,13 @@ </FormSection> </div> </MkSpacer> - <MkSpacer v-else-if="tab === 'emojis'" :content-max="1000" :margin-min="20"> + <MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20"> <XEmojis/> </MkSpacer> - <MkSpacer v-else-if="tab === 'federation'" :content-max="1000" :margin-min="20"> + <MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20"> <XFederation/> </MkSpacer> - <MkSpacer v-else-if="tab === 'charts'" :content-max="1000" :margin-min="20"> + <MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20"> <MkInstanceStats/> </MkSpacer> </MkStickyContainer> diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue index 1eef7a53fe37a328b09a66389488ab90184357bf..dc47d8dde0ab342333cd9f56d9ca6cce80f4c2eb 100644 --- a/packages/frontend/src/pages/achievements.vue +++ b/packages/frontend/src/pages/achievements.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader/></template> - <MkSpacer :content-max="1200"> + <MkSpacer :contentMax="1200"> <MkAchievements :user="$i"/> </MkSpacer> </MkStickyContainer> diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 1d309a7377880f65cbea59ecd270384bd0a04acd..24c863ba626e92a2b562df9d0777f5c69f161b3d 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer v-if="file" :content-max="600" :margin-min="16" :margin-max="32"> + <MkSpacer v-if="file" :contentMax="600" :marginMin="16" :marginMax="32"> <div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m"> <a class="thumbnail" :href="file.url" target="_blank"> <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> @@ -32,7 +32,7 @@ <MkUserCardMini :user="file.user"/> </MkA> <div> - <MkSwitch v-model="isSensitive" @update:model-value="toggleIsSensitive">NSFW</MkSwitch> + <MkSwitch v-model="isSensitive" @update:modelValue="toggleIsSensitive">NSFW</MkSwitch> </div> <div> diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue index 343d2c4c5cf7e0b6d8089d8506011ad844d6b9b2..9530b27bad0fdfdf7cc8feea6dd7300056e22988 100644 --- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -1,5 +1,5 @@ <template> -<div :class="$style.root" class="_gaps"> +<div class="_gaps"> <div :class="$style.header"> <MkSelect v-model="type" :class="$style.typeSelect"> <option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option> @@ -24,12 +24,12 @@ </button> </div> - <div v-if="type === 'and' || type === 'or'" :class="$style.values" class="_gaps"> - <Sortable v-model="v.values" tag="div" class="_gaps" item-key="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swap-threshold="0.5"> + <div v-if="type === 'and' || type === 'or'" class="_gaps"> + <Sortable v-model="v.values" tag="div" class="_gaps" itemKey="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swapThreshold="0.5"> <template #item="{element}"> <div :class="$style.item"> <!-- divãŒç„¡ã„ã¨ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ https://github.com/SortableJS/vue.draggable.next/issues/189 --> - <RolesEditorFormula :model-value="element" draggable @update:model-value="updated => valuesItemUpdated(updated)" @remove="removeItem(element)"/> + <RolesEditorFormula :modelValue="element" draggable @update:modelValue="updated => valuesItemUpdated(updated)" @remove="removeItem(element)"/> </div> </template> </Sortable> @@ -118,10 +118,6 @@ function removeSelf() { </script> <style lang="scss" module> -.root { - -} - .header { display: flex; } @@ -148,8 +144,4 @@ function removeSelf() { border-color: var(--accent); } } - -.values { - -} </style> diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 9e8af43024e6eca006fe08db606a2ad5401bb7e8..3bc5ee97235849a705c46c1f34ea2130452099d3 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -1,8 +1,8 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="900"> - <div class="lcixvhis"> + <MkSpacer :contentMax="900"> + <div> <div class="reports"> <div class=""> <div class="inputs" style="display: flex;"> @@ -87,9 +87,3 @@ definePageMetadata({ icon: 'ti ti-exclamation-circle', }); </script> - -<style lang="scss" scoped> -.lcixvhis { - margin: var(--margin); -} -</style> diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index 803e8cb7b063f8e22436c57d880f092d0368c218..2c9e18b0bffb03312a47c44b75595687c8762968 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -1,9 +1,9 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="900"> - <div class="uqshojas"> - <div v-for="ad in ads" class="_panel _gaps_m ad"> + <MkSpacer :contentMax="900"> + <div> + <div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad"> <MkAd v-if="ad.url" :specify="ad"/> <MkInput v-model="ad.url" type="url"> <template #label>URL</template> @@ -196,14 +196,12 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.uqshojas { - > .ad { - padding: 32px; +<style lang="scss" module> +.ad { + padding: 32px; - &:not(:last-child) { - margin-bottom: var(--margin); - } + &:not(:last-child) { + margin-bottom: var(--margin); } } </style> diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index b76e4b911491f854b29d5d9ceb5edb9259db46cc..3cb32c1d9daa316af6d940a5b17087c107af4727 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -1,8 +1,8 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="900"> - <div class="ztgjmzrw _gaps_m"> + <MkSpacer :contentMax="900"> + <div class="_gaps_m"> <section v-for="announcement in announcements" class=""> <div class="_panel _gaps_m" style="padding: 24px;"> <MkInput v-model="announcement.title"> @@ -113,9 +113,3 @@ definePageMetadata({ icon: 'ti ti-speakerphone', }); </script> - -<style lang="scss" scoped> -.ztgjmzrw { - margin: var(--margin); -} -</style> diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue index 5a0d3d5e51a182f26b8a5453dc55c5302018d89a..131e586afd0795a285a3d27763f7f512ec76fa31 100644 --- a/packages/frontend/src/pages/admin/database.vue +++ b/packages/frontend/src/pages/admin/database.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="800" :marginMin="16" :marginMax="32"> <FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory"> <MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;"> <template #key>{{ table[0] }}</template> diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue index d51bf6230a9a02d47332d04dd3eea943a92980a2..4f5bb379ad89164f86ffbd45118ae032fb212501 100644 --- a/packages/frontend/src/pages/admin/email-settings.vue +++ b/packages/frontend/src/pages/admin/email-settings.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> <MkSwitch v-model="enableEmail"> @@ -18,7 +18,7 @@ <template #label>{{ i18n.ts.smtpConfig }}</template> <div class="_gaps_m"> - <FormSplit :min-width="280"> + <FormSplit :minWidth="280"> <MkInput v-model="smtpHost"> <template #label>{{ i18n.ts.smtpHost }}</template> </MkInput> @@ -26,7 +26,7 @@ <template #label>{{ i18n.ts.smtpPort }}</template> </MkInput> </FormSplit> - <FormSplit :min-width="280"> + <FormSplit :minWidth="280"> <MkInput v-model="smtpUser"> <template #label>{{ i18n.ts.smtpUser }}</template> </MkInput> @@ -47,7 +47,7 @@ </MkSpacer> <template #footer> <div :class="$style.footer"> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="16"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> <div class="_buttons"> <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> <MkButton rounded @click="testEmail"><i class="ti ti-send"></i> {{ i18n.ts.testEmail }}</MkButton> diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue index 6af1610431b4c2018108cf67b48197e2f534a5a2..a8d6dcf3de6228420111088bed5e55c5a11fcfd1 100644 --- a/packages/frontend/src/pages/admin/federation.vue +++ b/packages/frontend/src/pages/admin/federation.vue @@ -2,9 +2,9 @@ <div> <MkStickyContainer> <template #header><XHeader :actions="headerActions"/></template> - <MkSpacer :content-max="900"> - <div class="taeiyrib"> - <div class="query"> + <MkSpacer :contentMax="900"> + <div class="_gaps"> + <div> <MkInput v-model="host" :debounce="true" class=""> <template #prefix><i class="ti ti-search"></i></template> <template #label>{{ i18n.ts.host }}</template> @@ -39,8 +39,8 @@ </div> <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> - <div class="dqokceoj"> - <MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`"> + <div :class="$style.instances"> + <MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`"> <MkInstanceCardMini :instance="instance"/> </MkA> </div> @@ -100,21 +100,14 @@ definePageMetadata(computed(() => ({ }))); </script> -<style lang="scss" scoped> -.taeiyrib { - > .query { - background: var(--bg); - margin-bottom: 16px; - } -} - -.dqokceoj { +<style lang="scss" module> +.instances { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px; +} - > .instance:hover { - text-decoration: none; - } +.instance:hover { + text-decoration: none; } </style> diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue index c189437246abc1d6b005bdfd7cb44246d918e98f..b204a1a64a5faec670992512b7a54edb6e7bec0f 100644 --- a/packages/frontend/src/pages/admin/files.vue +++ b/packages/frontend/src/pages/admin/files.vue @@ -2,30 +2,28 @@ <div> <MkStickyContainer> <template #header><XHeader :actions="headerActions"/></template> - <MkSpacer :content-max="900"> - <div class="xrmjdkdw"> - <div> - <div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> - <MkSelect v-model="origin" style="margin: 0; flex: 1;"> - <template #label>{{ i18n.ts.instance }}</template> - <option value="combined">{{ i18n.ts.all }}</option> - <option value="local">{{ i18n.ts.local }}</option> - <option value="remote">{{ i18n.ts.remote }}</option> - </MkSelect> - <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> - <template #label>{{ i18n.ts.host }}</template> - </MkInput> - </div> - <div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap; padding-top: 1.2em;"> - <MkInput v-model="userId" :debounce="true" type="search" style="margin: 0; flex: 1;"> - <template #label>User ID</template> - </MkInput> - <MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> - <template #label>MIME type</template> - </MkInput> - </div> - <MkFileListForAdmin :pagination="pagination" :view-mode="viewMode"/> + <MkSpacer :contentMax="900"> + <div class="_gaps"> + <div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkSelect v-model="origin" style="margin: 0; flex: 1;"> + <template #label>{{ i18n.ts.instance }}</template> + <option value="combined">{{ i18n.ts.all }}</option> + <option value="local">{{ i18n.ts.local }}</option> + <option value="remote">{{ i18n.ts.remote }}</option> + </MkSelect> + <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> + <template #label>{{ i18n.ts.host }}</template> + </MkInput> </div> + <div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkInput v-model="userId" :debounce="true" type="search" style="margin: 0; flex: 1;"> + <template #label>User ID</template> + </MkInput> + <MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> + <template #label>MIME type</template> + </MkInput> + </div> + <MkFileListForAdmin :pagination="pagination" :viewMode="viewMode"/> </div> </MkSpacer> </MkStickyContainer> @@ -109,9 +107,3 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-cloud', }))); </script> - -<style lang="scss" scoped> -.xrmjdkdw { - margin: var(--margin); -} -</style> diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 963393d7e535186247a7f54805c24c463300aa16..5cbbcaa44cdc19323a0d23a0e8ff9e41de90f64a 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -1,7 +1,7 @@ <template> <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }"> <div v-if="!narrow || currentPage?.route.name == null" class="nav"> - <MkSpacer :content-max="700" :margin-min="16"> + <MkSpacer :contentMax="700" :marginMin="16"> <div class="lxpfedzu"> <div class="banner"> <img :src="instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue index 7a4937093ee14e781836a7c579ec9768cac0a04a..e5f3816c82d4d6c784559d067f3bf63538d5a3e1 100644 --- a/packages/frontend/src/pages/admin/instance-block.vue +++ b/packages/frontend/src/pages/admin/instance-block.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <MkTextarea v-model="blockedHosts"> <span>{{ i18n.ts.blockedInstances }}</span> diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index bf788e3609876587ac4d5a4f465ee7a32405a911..e36c9ac91d64baf4ece3e8bd1ad40d0640ae1437 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -2,7 +2,7 @@ <div> <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> <MkSwitch v-model="enableRegistration"> @@ -34,7 +34,7 @@ </MkSpacer> <template #footer> <div :class="$style.footer"> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="16"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </MkSpacer> </div> diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue index 704b27c1745304500a401e2d35471f07c93c1708..e569aad1b88ce50fbe4e42217b0faf26bfdeda3d 100644 --- a/packages/frontend/src/pages/admin/object-storage.vue +++ b/packages/frontend/src/pages/admin/object-storage.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> <MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch> @@ -33,7 +33,7 @@ <template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template> </MkInput> - <FormSplit :min-width="280"> + <FormSplit :minWidth="280"> <MkInput v-model="objectStorageAccessKey"> <template #prefix><i class="ti ti-key"></i></template> <template #label>Access key</template> @@ -69,7 +69,7 @@ </MkSpacer> <template #footer> <div :class="$style.footer"> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="16"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </MkSpacer> </div> diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue index 62dff6ce7fb9424b063d6853a9aede73d7a75b4a..15d720a070367280a8daddc42646c280b127e5e3 100644 --- a/packages/frontend/src/pages/admin/other-settings.vue +++ b/packages/frontend/src/pages/admin/other-settings.vue @@ -1,9 +1,17 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> - none + <div class="_gaps_s"> + <MkSwitch v-model="enableChartsForRemoteUser"> + <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template> + </MkSwitch> + + <MkSwitch v-model="enableChartsForFederatedInstances"> + <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template> + </MkSwitch> + </div> </FormSuspense> </MkSpacer> </MkStickyContainer> @@ -17,13 +25,22 @@ import * as os from '@/os'; import { fetchInstance } from '@/instance'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import MkSwitch from '@/components/MkSwitch.vue'; + +let enableChartsForRemoteUser: boolean = $ref(false); +let enableChartsForFederatedInstances: boolean = $ref(false); async function init() { - await os.api('admin/meta'); + const meta = await os.api('admin/meta'); + enableChartsForRemoteUser = meta.enableChartsForRemoteUser; + enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances; } function save() { - os.apiWithDialog('admin/update-meta').then(() => { + os.apiWithDialog('admin/update-meta', { + enableChartsForRemoteUser, + enableChartsForFederatedInstances, + }).then(() => { fetchInstance(); }); } diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue index 6c2ffd474242517227d662ca5be333f7af13b711..d349b32322aa15cac3a47d8faf07909bef9ac902 100644 --- a/packages/frontend/src/pages/admin/overview.instances.vue +++ b/packages/frontend/src/pages/admin/overview.instances.vue @@ -1,9 +1,9 @@ <template> -<div class="wbrkwale"> +<div> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> <MkLoading v-if="fetching"/> - <div v-else class="instances"> - <MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" class="instance"> + <div v-else :class="$style.instances"> + <MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" :class="$style.instance"> <MkInstanceCardMini :instance="instance"/> </MkA> </div> @@ -36,16 +36,14 @@ useInterval(fetch, 1000 * 60, { }); </script> -<style lang="scss" scoped> -.wbrkwale { - > .instances { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - grid-gap: 12px; +<style lang="scss" module> +.instances { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + grid-gap: 12px; +} - > .instance:hover { - text-decoration: none; - } - } +.instance:hover { + text-decoration: none; } </style> diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue index 08a29bf550373933f6c13cee800f352069c7d84e..af7bc70551fbb0c1ca1a26e0fbf0149b78a7b088 100644 --- a/packages/frontend/src/pages/admin/overview.pie.vue +++ b/packages/frontend/src/pages/admin/overview.pie.vue @@ -67,7 +67,3 @@ onMounted(() => { }); }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue index 6a11e8b768fa622c0ab6232aeb9f508c92b7b387..a3c8659ce5e3cac71cc2138386642bdad7469625 100644 --- a/packages/frontend/src/pages/admin/overview.queue.chart.vue +++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue @@ -132,7 +132,3 @@ defineExpose({ pushData, }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue index 1f56a2826aca0ac8ae6c14853b7f680f74b3a29e..69ca89e226a7f2ecadf2558c8a0e305ec1244dff 100644 --- a/packages/frontend/src/pages/admin/overview.queue.vue +++ b/packages/frontend/src/pages/admin/overview.queue.vue @@ -33,9 +33,9 @@ import { markRaw, onMounted, onUnmounted, ref } from 'vue'; import XChart from './overview.queue.chart.vue'; import number from '@/filters/number'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; -const connection = markRaw(stream.useChannel('queueStats')); +const connection = markRaw(useStream().useChannel('queueStats')); const activeSincePrevTick = ref(0); const active = ref(0); diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue index 5c96c07bfbe2a5256f71a64c131d0c7a6ea0a0fb..e8295c81b5aa242f8ae4dcd50d08cf451349711e 100644 --- a/packages/frontend/src/pages/admin/overview.vue +++ b/packages/frontend/src/pages/admin/overview.vue @@ -1,6 +1,6 @@ <template> -<MkSpacer :content-max="1000"> - <div ref="rootEl" class="edbbcaef"> +<MkSpacer :contentMax="1000"> + <div ref="rootEl" :class="$style.root"> <MkFoldableSection class="item"> <template #header>Stats</template> <XStats/> @@ -72,7 +72,7 @@ import XRetention from './overview.retention.vue'; import XModerators from './overview.moderators.vue'; import XHeatmap from './overview.heatmap.vue'; import * as os from '@/os'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; @@ -87,7 +87,7 @@ let federationSubActive = $ref<number | null>(null); let federationSubActiveDiff = $ref<number | null>(null); let newUsers = $ref(null); let activeInstances = $shallowRef(null); -const queueStatsConnection = markRaw(stream.useChannel('queueStats')); +const queueStatsConnection = markRaw(useStream().useChannel('queueStats')); const now = new Date(); const filesPagination = { endpoint: 'admin/drive/files' as const, @@ -176,8 +176,8 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.edbbcaef { +<style lang="scss" module> +.root { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); grid-gap: 16px; diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue index 6ad566187a4b9723b518ed816906a63ce65bd096..c81f50a0d238541a7afcc8fc4c31535d00746f5d 100644 --- a/packages/frontend/src/pages/admin/proxy-account.vue +++ b/packages/frontend/src/pages/admin/proxy-account.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo> <MkKeyValue> diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue index 1a1f6a9db4dd434426d8a6ba50a5f0824547f348..9bc0eee2124fdce9806d66a7eed071eec5791800 100644 --- a/packages/frontend/src/pages/admin/queue.chart.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue @@ -132,7 +132,3 @@ defineExpose({ pushData, }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue index 100d1ea545892cf3eb25e1badd6992b1a681e14b..8e6856fdddeb9dda07c4f56e01ab875e1025a111 100644 --- a/packages/frontend/src/pages/admin/queue.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.vue @@ -1,35 +1,35 @@ <template> -<div class="pumxzjhg _gaps"> +<div class="_gaps"> <div :class="$style.status"> - <div class="item _panel"><div class="label">Process</div>{{ number(activeSincePrevTick) }}</div> - <div class="item _panel"><div class="label">Active</div>{{ number(active) }}</div> - <div class="item _panel"><div class="label">Waiting</div>{{ number(waiting) }}</div> - <div class="item _panel"><div class="label">Delayed</div>{{ number(delayed) }}</div> + <div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Process</div>{{ number(activeSincePrevTick) }}</div> + <div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Active</div>{{ number(active) }}</div> + <div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Waiting</div>{{ number(waiting) }}</div> + <div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Delayed</div>{{ number(delayed) }}</div> </div> - <div class="charts"> - <div class="chart"> - <div class="title">Process</div> + <div :class="$style.charts"> + <div :class="$style.chart"> + <div :class="$style.chartTitle">Process</div> <XChart ref="chartProcess" type="process"/> </div> - <div class="chart"> - <div class="title">Active</div> + <div :class="$style.chart"> + <div :class="$style.chartTitle">Active</div> <XChart ref="chartActive" type="active"/> </div> - <div class="chart"> - <div class="title">Delayed</div> + <div :class="$style.chart"> + <div :class="$style.chartTitle">Delayed</div> <XChart ref="chartDelayed" type="delayed"/> </div> - <div class="chart"> - <div class="title">Waiting</div> + <div :class="$style.chart"> + <div :class="$style.chartTitle">Waiting</div> <XChart ref="chartWaiting" type="waiting"/> </div> </div> - <MkFolder :default-open="true" :max-height="250"> + <MkFolder :defaultOpen="true" :max-height="250"> <template #icon><i class="ti ti-alert-triangle"></i></template> <template #label>Errored instances</template> <template #suffix>({{ number(jobs.reduce((a, b) => a + b[1], 0)) }} jobs)</template> - - <div :class="$style.jobs"> + + <div> <div v-if="jobs.length > 0"> <div v-for="job in jobs" :key="job[0]"> <MkA :to="`/instance-info/${job[0]}`" behavior="window">{{ job[0] }}</MkA> @@ -47,11 +47,11 @@ import { markRaw, onMounted, onUnmounted, ref } from 'vue'; import XChart from './queue.chart.chart.vue'; import number from '@/filters/number'; import * as os from '@/os'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; import MkFolder from '@/components/MkFolder.vue'; -const connection = markRaw(stream.useChannel('queueStats')); +const connection = markRaw(useStream().useChannel('queueStats')); const activeSincePrevTick = ref(0); const active = ref(0); @@ -118,45 +118,36 @@ onUnmounted(() => { }); </script> -<style lang="scss" scoped> -.pumxzjhg { - > .charts { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - - > .chart { - min-width: 0; - padding: 16px; - background: var(--panel); - border-radius: var(--radius); - - > .title { - margin-bottom: 8px; - } - } - } +<style lang="scss" module> +.charts { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.chart { + min-width: 0; + padding: 16px; + background: var(--panel); + border-radius: var(--radius); +} + +.chartTitle { + margin-bottom: 8px; } -</style> -<style lang="scss" module> .status { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); grid-gap: 10px; +} - &:global { - > .item { - padding: 12px 16px; - - > .label { - font-size: 80%; - opacity: 0.6; - } - } - } +.statusItem { + padding: 12px 16px; } -.jobs { +.statusLabel { + font-size: 80%; + opacity: 0.6; } </style> diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue index 509d329eb16ced178d5ef5e31e6293355f7c3ed3..1282a4b49f3f25eb4888131749fafa162e1d2da1 100644 --- a/packages/frontend/src/pages/admin/queue.vue +++ b/packages/frontend/src/pages/admin/queue.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><XHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <XQueue v-if="tab === 'deliver'" domain="deliver"/> <XQueue v-else-if="tab === 'inbox'" domain="inbox"/> <br> diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue index 7ebcdfc58340b31a6617abe7c938763c8acee0f1..119439c958e3f3ba102622013e2f6a6549e79431 100644 --- a/packages/frontend/src/pages/admin/relays.vue +++ b/packages/frontend/src/pages/admin/relays.vue @@ -1,14 +1,14 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <div class="_gaps"> <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;"> <div>{{ relay.inbox }}</div> - <div class="status"> - <i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i> - <i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i> - <i v-else class="ti ti-clock icon requesting"></i> + <div style="margin: 8px 0;"> + <i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--success);"></i> + <i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--error);"></i> + <i v-else class="ti ti-clock" :class="$style.icon"></i> <span>{{ i18n.t(`_relayStatus.${relay.status}`) }}</span> </div> <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> @@ -83,23 +83,9 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.relaycxt { - > .status { - margin: 8px 0; - - > .icon { - width: 1em; - margin-right: 0.75em; - - &.accepted { - color: var(--success); - } - - &.rejected { - color: var(--error); - } - } - } +<style lang="scss" module> +.icon { + width: 1em; + margin-right: 0.75em; } </style> diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue index c211ef2f055f1f73f79c2c2596066c16121d1baf..c7a34ac77b8ad853e92586b338ba52480de3cde2 100644 --- a/packages/frontend/src/pages/admin/roles.edit.vue +++ b/packages/frontend/src/pages/admin/roles.edit.vue @@ -2,12 +2,12 @@ <div> <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> - <MkSpacer :content-max="600" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="600" :marginMin="16" :marginMax="32"> <XEditor v-if="data" v-model="data"/> </MkSpacer> <template #footer> <div :class="$style.footer"> - <MkSpacer :content-max="600" :margin-min="16" :margin-max="16"> + <MkSpacer :contentMax="600" :marginMin="16" :marginMax="16"> <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </MkSpacer> </div> diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 49942c87ce58aec4531441e5b90674f294768530..a1fa9d2932d6973a314041cb995c1257877c9a4c 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -36,7 +36,7 @@ <option value="conditional">{{ i18n.ts._role.conditional }}</option> </MkSelect> - <MkFolder v-if="role.target === 'conditional'" default-open> + <MkFolder v-if="role.target === 'conditional'" defaultOpen> <template #label>{{ i18n.ts._role.condition }}</template> <div class="_gaps"> <RolesEditorFormula v-model="role.condFormula"/> @@ -81,11 +81,11 @@ <MkSwitch v-model="role.policies.rateLimitFactor.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts._role.useBaseValue }}</template> </MkSwitch> - <MkRange :model-value="role.policies.rateLimitFactor.value * 100" :min="0" :max="400" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => role.policies.rateLimitFactor.value = (v / 100)"> + <MkRange :modelValue="role.policies.rateLimitFactor.value * 100" :min="0" :max="400" :step="10" :textConverter="(v) => `${v}%`" @update:modelValue="v => role.policies.rateLimitFactor.value = (v / 100)"> <template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template> <template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template> </MkRange> - <MkRange v-model="role.policies.rateLimitFactor.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.rateLimitFactor.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -105,7 +105,7 @@ <MkSwitch v-model="role.policies.gtlAvailable.value" :disabled="role.policies.gtlAvailable.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.gtlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.gtlAvailable.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -125,7 +125,7 @@ <MkSwitch v-model="role.policies.ltlAvailable.value" :disabled="role.policies.ltlAvailable.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.ltlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.ltlAvailable.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -145,7 +145,7 @@ <MkSwitch v-model="role.policies.canPublicNote.value" :disabled="role.policies.canPublicNote.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.canPublicNote.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.canPublicNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -165,7 +165,7 @@ <MkSwitch v-model="role.policies.canInvite.value" :disabled="role.policies.canInvite.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.canInvite.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.canInvite.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -185,7 +185,7 @@ <MkSwitch v-model="role.policies.canManageCustomEmojis.value" :disabled="role.policies.canManageCustomEmojis.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.canManageCustomEmojis.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.canManageCustomEmojis.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -205,7 +205,7 @@ <MkSwitch v-model="role.policies.canSearchNotes.value" :disabled="role.policies.canSearchNotes.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.canSearchNotes.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.canSearchNotes.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -225,7 +225,7 @@ <MkInput v-model="role.policies.driveCapacityMb.value" :disabled="role.policies.driveCapacityMb.useDefault" type="number" :readonly="readonly"> <template #suffix>MB</template> </MkInput> - <MkRange v-model="role.policies.driveCapacityMb.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.driveCapacityMb.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -245,7 +245,7 @@ <MkSwitch v-model="role.policies.alwaysMarkNsfw.value" :disabled="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.alwaysMarkNsfw.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.alwaysMarkNsfw.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -264,7 +264,7 @@ </MkSwitch> <MkInput v-model="role.policies.pinLimit.value" :disabled="role.policies.pinLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.pinLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.pinLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -283,7 +283,7 @@ </MkSwitch> <MkInput v-model="role.policies.antennaLimit.value" :disabled="role.policies.antennaLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.antennaLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.antennaLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -303,7 +303,7 @@ <MkInput v-model="role.policies.wordMuteLimit.value" :disabled="role.policies.wordMuteLimit.useDefault" type="number" :readonly="readonly"> <template #suffix>chars</template> </MkInput> - <MkRange v-model="role.policies.wordMuteLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.wordMuteLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -322,7 +322,7 @@ </MkSwitch> <MkInput v-model="role.policies.webhookLimit.value" :disabled="role.policies.webhookLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.webhookLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.webhookLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -341,7 +341,7 @@ </MkSwitch> <MkInput v-model="role.policies.clipLimit.value" :disabled="role.policies.clipLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.clipLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.clipLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -360,7 +360,7 @@ </MkSwitch> <MkInput v-model="role.policies.noteEachClipsLimit.value" :disabled="role.policies.noteEachClipsLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.noteEachClipsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.noteEachClipsLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -379,7 +379,7 @@ </MkSwitch> <MkInput v-model="role.policies.userListLimit.value" :disabled="role.policies.userListLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.userListLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.userListLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -398,7 +398,7 @@ </MkSwitch> <MkInput v-model="role.policies.userEachUserListsLimit.value" :disabled="role.policies.userEachUserListsLimit.useDefault" type="number" :readonly="readonly"> </MkInput> - <MkRange v-model="role.policies.userEachUserListsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.userEachUserListsLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> @@ -418,7 +418,7 @@ <MkSwitch v-model="role.policies.canHideAds.value" :disabled="role.policies.canHideAds.useDefault" :readonly="readonly"> <template #label>{{ i18n.ts.enable }}</template> </MkSwitch> - <MkRange v-model="role.policies.canHideAds.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <MkRange v-model="role.policies.canHideAds.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> <template #label>{{ i18n.ts._role.priority }}</template> </MkRange> </div> diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index 6eac902577b2b244638dd7e1f3c7b3d625a4f50c..4ed6abf200a04c602d6b73ac9aaf1c95cb9b7396 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -2,7 +2,7 @@ <div> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div class="_gaps"> <div class="_buttons"> <MkButton primary rounded @click="edit"><i class="ti ti-pencil"></i> {{ i18n.ts.edit }}</MkButton> @@ -11,9 +11,9 @@ <MkFolder> <template #icon><i class="ti ti-info-circle"></i></template> <template #label>{{ i18n.ts.info }}</template> - <XEditor :model-value="role" readonly/> + <XEditor :modelValue="role" readonly/> </MkFolder> - <MkFolder v-if="role.target === 'manual'" default-open> + <MkFolder v-if="role.target === 'manual'" defaultOpen> <template #icon><i class="ti ti-users"></i></template> <template #label>{{ i18n.ts.users }}</template> <template #suffix>{{ role.usersCount }}</template> diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index e8dbe1c5f0ed6c16fb3d557266c899d227c2fd91..6634d9cba94caa0c517f5ff2132e59e1d40e3f57 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -2,7 +2,7 @@ <div> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div class="_gaps"> <MkFolder> <template #label>{{ i18n.ts._role.baseRole }}</template> @@ -14,7 +14,7 @@ <MkFolder v-if="matchQuery([i18n.ts._role._options.rateLimitFactor, 'rateLimitFactor'])"> <template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template> <template #suffix>{{ Math.floor(policies.rateLimitFactor * 100) }}%</template> - <MkRange :model-value="policies.rateLimitFactor * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => policies.rateLimitFactor = (v / 100)"> + <MkRange :modelValue="policies.rateLimitFactor * 100" :min="30" :max="300" :step="10" :textConverter="(v) => `${v}%`" @update:modelValue="v => policies.rateLimitFactor = (v / 100)"> <template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template> </MkRange> </MkFolder> @@ -156,13 +156,13 @@ <MkFoldableSection> <template #header>Manual roles</template> <div class="_gaps_s"> - <MkRolePreview v-for="role in roles.filter(x => x.target === 'manual')" :key="role.id" :role="role" :for-moderation="true"/> + <MkRolePreview v-for="role in roles.filter(x => x.target === 'manual')" :key="role.id" :role="role" :forModeration="true"/> </div> </MkFoldableSection> <MkFoldableSection> <template #header>Conditional roles</template> <div class="_gaps_s"> - <MkRolePreview v-for="role in roles.filter(x => x.target === 'conditional')" :key="role.id" :role="role" :for-moderation="true"/> + <MkRolePreview v-for="role in roles.filter(x => x.target === 'conditional')" :key="role.id" :role="role" :forModeration="true"/> </div> </MkFoldableSection> </div> diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index cd8ef9e68ba383d3e0cf11e7d6d1a924b1fa9097..efb9f81f25964626781a3d6b68fa80305309a5dc 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> <MkFolder> @@ -33,7 +33,7 @@ <option value="remote">{{ i18n.ts.remoteOnly }}</option> </MkRadios> - <MkRange v-model="sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :text-converter="(v) => `${v + 1}`"> + <MkRange v-model="sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`"> <template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template> <template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template> </MkRange> @@ -65,7 +65,7 @@ <div class="_gaps_m"> <span>{{ i18n.ts.activeEmailValidationDescription }}</span> - <MkSwitch v-model="enableActiveEmailValidation" @update:model-value="save"> + <MkSwitch v-model="enableActiveEmailValidation" @update:modelValue="save"> <template #label>Enable</template> </MkSwitch> </div> @@ -77,7 +77,7 @@ <template v-else #suffix>Disabled</template> <div class="_gaps_m"> - <MkSwitch v-model="enableIpLogging" @update:model-value="save"> + <MkSwitch v-model="enableIpLogging" @update:modelValue="save"> <template #label>Enable</template> </MkSwitch> </div> diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue index 85781c0bd00cd128a503f2b0d922dd7d964fa398..fdba4f464e4c40e808ca71afc8d40f053c18e2ea 100644 --- a/packages/frontend/src/pages/admin/server-rules.vue +++ b/packages/frontend/src/pages/admin/server-rules.vue @@ -2,13 +2,13 @@ <div> <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <div class="_gaps_m"> <div>{{ i18n.ts._serverRules.description }}</div> <Sortable v-model="serverRules" class="_gaps_m" - :item-key="(_, i) => i" + :itemKey="(_, i) => i" :animation="150" :handle="'.' + $style.itemHandle" @start="e => e.item.classList.add('active')" @@ -27,7 +27,7 @@ </Sortable> <div :class="$style.commands"> <MkButton rounded @click="serverRules.push('')"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> - <MkButton primary rounded :class="$style.buttonSave" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </div> </div> </MkSpacer> diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 7ec3c381f3a33815a24a4330b0d0c8d2db093c6f..39d5ae8607546420c08350f4ebb1415deb90a144 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -2,7 +2,7 @@ <div> <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> <MkInput v-model="name"> @@ -13,7 +13,7 @@ <template #label>{{ i18n.ts.instanceDescription }}</template> </MkTextarea> - <FormSplit :min-width="300"> + <FormSplit :minWidth="300"> <MkInput v-model="maintainerName"> <template #label>{{ i18n.ts.maintainerName }}</template> </MkInput> @@ -29,18 +29,6 @@ <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> </MkTextarea> - <FormSection> - <div class="_gaps_s"> - <MkSwitch v-model="enableChartsForRemoteUser"> - <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template> - </MkSwitch> - - <MkSwitch v-model="enableChartsForFederatedInstances"> - <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template> - </MkSwitch> - </div> - </FormSection> - <FormSection> <template #label>{{ i18n.ts.theme }}</template> @@ -128,7 +116,7 @@ </MkSpacer> <template #footer> <div :class="$style.footer"> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="16"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </MkSpacer> </div> @@ -166,8 +154,6 @@ let defaultDarkTheme: any = $ref(null); let pinnedUsers: string = $ref(''); let cacheRemoteFiles: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false); -let enableChartsForRemoteUser: boolean = $ref(false); -let enableChartsForFederatedInstances: boolean = $ref(false); let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); let deeplAuthKey: string = $ref(''); @@ -188,8 +174,6 @@ async function init() { pinnedUsers = meta.pinnedUsers.join('\n'); cacheRemoteFiles = meta.cacheRemoteFiles; enableServiceWorker = meta.enableServiceWorker; - enableChartsForRemoteUser = meta.enableChartsForRemoteUser; - enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances; swPublicKey = meta.swPublickey; swPrivateKey = meta.swPrivateKey; deeplAuthKey = meta.deeplAuthKey; @@ -211,8 +195,6 @@ function save() { pinnedUsers: pinnedUsers.split('\n'), cacheRemoteFiles, enableServiceWorker, - enableChartsForRemoteUser, - enableChartsForFederatedInstances, swPublicKey, swPrivateKey, deeplAuthKey, diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index 819ced826d4ad87fc0c4aa646b71d3d0493c3ae6..1af661a475bad934cd6f7243cb9b6453c8c01939 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -2,7 +2,7 @@ <div> <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="900"> + <MkSpacer :contentMax="900"> <div class="_gaps"> <div :class="$style.inputs"> <MkSelect v-model="sort" style="flex: 1;"> @@ -28,11 +28,11 @@ </MkSelect> </div> <div :class="$style.inputs"> - <MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false" @update:model-value="$refs.users.reload()"> + <MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false" @update:modelValue="$refs.users.reload()"> <template #prefix>@</template> <template #label>{{ i18n.ts.username }}</template> </MkInput> - <MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:model-value="$refs.users.reload()"> + <MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()"> <template #prefix>@</template> <template #label>{{ i18n.ts.host }}</template> </MkInput> diff --git a/packages/frontend/src/pages/ads.vue b/packages/frontend/src/pages/ads.vue index 728ef3c0b120a66f7ca4fd7c3550b6aacd07931d..4cf2e4b2e5f918b759a2dace40be216dfae2bae3 100644 --- a/packages/frontend/src/pages/ads.vue +++ b/packages/frontend/src/pages/ads.vue @@ -2,7 +2,7 @@ <MkStickyContainer> <template #header><MkPageHeader/></template> - <MkSpacer :content-max="500"> + <MkSpacer :contentMax="500"> <div class="_gaps"> <MkAd v-for="ad in instance.ads" :key="ad.id" :specify="ad"/> </div> diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 16a0ee837346f1af886c1f0a32545eaceb8dd35b..3dfb9e5554ce23eb16933464eb5b0dd60be817ee 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <MkPagination v-slot="{items}" :pagination="pagination" class="ruryvtyk _gaps_m"> <section v-for="(announcement, i) in items" :key="announcement.id" class="announcement _panel"> <div class="header"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div> diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue index 62e8178af1d8e6cf14d080a9542dd02fc8cde6eb..a22714791fd000926d241d2d877b3c25741e5e0a 100644 --- a/packages/frontend/src/pages/antenna-timeline.vue +++ b/packages/frontend/src/pages/antenna-timeline.vue @@ -1,19 +1,20 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <div ref="rootEl" v-hotkey.global="keymap" class="tqmomfks"> - <div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> - <div class="tl"> - <MkTimeline - ref="tlEl" :key="antennaId" - class="tl" - src="antenna" - :antenna="antennaId" - :sound="true" - @queue="queueUpdated" - /> + <MkSpacer :contentMax="800"> + <div ref="rootEl" v-hotkey.global="keymap"> + <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> + <div :class="$style.tl"> + <MkTimeline + ref="tlEl" :key="antennaId" + src="antenna" + :antenna="antennaId" + :sound="true" + @queue="queueUpdated" + /> + </div> </div> - </div> + </MkSpacer> </MkStickyContainer> </template> @@ -89,36 +90,29 @@ definePageMetadata(computed(() => antenna ? { } : null)); </script> -<style lang="scss" scoped> -.tqmomfks { - padding: var(--margin); - - > .new { - position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); - z-index: 1000; - width: 100%; - margin: calc(-0.675em - 8px - var(--margin)) 0 calc(-0.675em - 8px); - - > button { - display: block; - margin: var(--margin) auto 0 auto; - padding: 8px 16px; - border-radius: 32px; - } - } +<style lang="scss" module> +.new { + position: sticky; + top: calc(var(--stickyTop, 0px) + 16px); + z-index: 1000; + width: 100%; + margin: calc(-0.675em - 8px) 0; - > .tl { - background: var(--bg); - border-radius: var(--radius); - overflow: clip; + &:first-child { + margin-top: calc(-0.675em - 8px - var(--margin)); } } -@container (min-width: 800px) { - .tqmomfks { - max-width: 800px; - margin: 0 auto; - } +.newButton { + display: block; + margin: var(--margin) auto 0 auto; + padding: 8px 16px; + border-radius: 32px; +} + +.tl { + background: var(--bg); + border-radius: var(--radius); + overflow: clip; } </style> diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue index 7d2828e91dc9775c77f72cf878667d4299b7dfdc..3a3cb3d7d37e9e3c4ca07d2e275a4dce1f720b99 100644 --- a/packages/frontend/src/pages/api-console.vue +++ b/packages/frontend/src/pages/api-console.vue @@ -1,10 +1,10 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div class="_gaps_m"> <div class="_gaps_m"> - <MkInput v-model="endpoint" :datalist="endpoints" @update:model-value="onEndpointChange()"> + <MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()"> <template #label>Endpoint</template> </MkInput> <MkTextarea v-model="body" code> diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue index 2f40e7ded68a22abe60495afafb85ad37cce3f31..54e76805bfd33b04565e7aba70cc4114495aafc0 100644 --- a/packages/frontend/src/pages/auth.vue +++ b/packages/frontend/src/pages/auth.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="500"> + <MkSpacer :contentMax="500"> <div v-if="state == 'fetch-session-error'"> <p>{{ i18n.ts.somethingHappened }}</p> </div> diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue index a74ab40473a5b6e05be491db1a85fbcf0c28ef8d..0a358a141b620ffe88837a75b739b72dee3e0f8f 100644 --- a/packages/frontend/src/pages/channel-editor.vue +++ b/packages/frontend/src/pages/channel-editor.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div v-if="channelId == null || channel != null" class="_gaps_m"> <MkInput v-model="name"> <template #label>{{ i18n.ts.name }}</template> @@ -23,7 +23,7 @@ </div> </div> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.pinnedNotes }}</template> <div class="_gaps"> @@ -31,7 +31,7 @@ <Sortable v-model="pinnedNotes" - item-key="id" + itemKey="id" :handle="'.' + $style.pinnedNoteHandle" :animation="150" > diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 9aa564a7dac7ad66cfa66f76b2412098dd85edb7..bcc0fc6860c07447628922a08954568e0e2682dd 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -1,10 +1,12 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :class="$style.main"> + <MkSpacer :contentMax="700" :class="$style.main"> <div v-if="channel && tab === 'overview'" class="_gaps"> <div class="_panel" :class="$style.bannerContainer"> <XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/> + <MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton> + <MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton> <div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" :class="$style.banner"> <div :class="$style.bannerStatus"> <div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div> @@ -13,13 +15,10 @@ <div :class="$style.bannerFade"></div> </div> <div v-if="channel.description" :class="$style.description"> - <Mfm :text="channel.description" :is-note="false" :i="$i"/> + <Mfm :text="channel.description" :isNote="false" :i="$i"/> </div> </div> - <MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-star"></i></MkButton> - <MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-star"></i></MkButton> - <MkFoldableSection> <template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template> <div v-if="channel.pinnedNotes.length > 0" class="_gaps"> @@ -52,7 +51,7 @@ </MkSpacer> <template #footer> <div :class="$style.footer"> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="16"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> <div class="_buttonsCenter"> <MkButton inline rounded primary gradate @click="openPostForm()"><i class="ti ti-pencil"></i> {{ i18n.ts.postToTheChannel }}</MkButton> </div> @@ -229,6 +228,13 @@ definePageMetadata(computed(() => channel ? { left: 16px; } +.favorite { + position: absolute; + z-index: 1; + top: 16px; + right: 16px; +} + .banner { position: relative; height: 200px; diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index e670cdd864787e89bffa4121d0e1b42c62e7a77f..0c4ccc1bcdbaeb35767ae5650a30aa1409934092 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -1,13 +1,13 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div v-if="tab === 'search'"> <div class="_gaps"> <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> <template #prefix><i class="ti ti-search"></i></template> </MkInput> - <MkRadios v-model="searchType" @update:model-value="search()"> + <MkRadios v-model="searchType" @update:modelValue="search()"> <option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option> <option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option> </MkRadios> diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue index 24eae32e1310729b8597070a8c768122c89d2b72..69ecc9e7721e5a4d81543b59fb3d791e5d662067 100644 --- a/packages/frontend/src/pages/clicker.vue +++ b/packages/frontend/src/pages/clicker.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <MkClickerGame/> </MkSpacer> </MkStickyContainer> diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index e3ac3f4c9bb722cfe38210308d67c71c4d790cd0..d5313099da8d8eb714e16443e784a02c457ac549 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -1,16 +1,16 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions"/></template> - <MkSpacer :content-max="800"> - <div v-if="clip"> - <div class="okzinsic _panel"> - <div v-if="clip.description" class="description"> - <Mfm :text="clip.description" :is-note="false" :i="$i"/> + <MkSpacer :contentMax="800"> + <div v-if="clip" class="_gaps"> + <div class="_panel"> + <div v-if="clip.description" :class="$style.description"> + <Mfm :text="clip.description" :isNote="false" :i="$i"/> </div> - <MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton> - <MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton> - <div class="user"> - <MkAvatar :user="clip.user" class="avatar" indicator link preview/> <MkUserName :user="clip.user" :nowrap="false"/> + <MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton> + <MkButton v-else v-tooltip="i18n.ts.favorite" asLike rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton> + <div :class="$style.user"> + <MkAvatar :user="clip.user" :class="$style.avatar" indicator link preview/> <MkUserName :user="clip.user" :nowrap="false"/> </div> </div> @@ -147,25 +147,20 @@ definePageMetadata(computed(() => clip ? { } : null)); </script> -<style lang="scss" scoped> -.okzinsic { - position: relative; - margin-bottom: var(--margin); - - > .description { - padding: 16px; - } - - > .user { - $height: 32px; - padding: 16px; - border-top: solid 0.5px var(--divider); - line-height: $height; - - > .avatar { - width: $height; - height: $height; - } - } +<style lang="scss" module> +.description { + padding: 16px; +} + +.user { + --height: 32px; + padding: 16px; + border-top: solid 0.5px var(--divider); + line-height: var(--height); +} + +.avatar { + width: var(--height); + height: var(--height); } </style> diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 3f13f0787d6a0d17ee8b2bea9aff448b156eb277..3da6a0d9cb1ac38915d2dc56f3117f870fec085e 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -2,7 +2,7 @@ <div> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="900"> + <MkSpacer :contentMax="900"> <div class="ogwlenmc"> <div v-if="tab === 'local'" class="local"> <MkInput v-model="query" :debounce="true" type="search"> @@ -123,15 +123,14 @@ const toggleSelect = (emoji) => { }; const add = async (ev: MouseEvent) => { - const files = await selectFiles(ev.currentTarget ?? ev.target, null); - - const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { - fileId: file.id, - }))); - promise.then(() => { - emojisPaginationComponent.value.reload(); - }); - os.promiseDialog(promise); + os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { + }, { + done: result => { + if (result.created) { + emojisPaginationComponent.value.prepend(result.created); + } + }, + }, 'closed'); }; const edit = (emoji) => { diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 84bc153b71d4d75fcbb7a669655f4bd3ff68ce4f..3208c92738d8fd8efdedaf7d23fadc991d3387df 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -1,84 +1,171 @@ <template> <MkModalWindow ref="dialog" - :width="370" - :with-ok-button="true" - @close="$refs.dialog.close()" + :width="400" + @close="dialog.close()" @closed="$emit('closed')" - @ok="ok()" > - <template #header>:{{ emoji.name }}:</template> - - <MkSpacer :margin-min="20" :margin-max="28"> - <div class="yigymqpb _gaps_m"> - <img :src="`/emoji/${emoji.name}.webp`" class="img"/> - <MkInput v-model="name"> - <template #label>{{ i18n.ts.name }}</template> - </MkInput> - <MkInput v-model="category" :datalist="customEmojiCategories"> - <template #label>{{ i18n.ts.category }}</template> - </MkInput> - <MkInput v-model="aliases"> - <template #label>{{ i18n.ts.tags }}</template> - <template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template> - </MkInput> - <MkInput v-model="license"> - <template #label>{{ i18n.ts.license }}</template> - </MkInput> - <MkButton danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + <template v-if="emoji" #header>:{{ emoji.name }}:</template> + <template v-else #header>New emoji</template> + + <div> + <MkSpacer :marginMin="20" :marginMax="28"> + <div class="_gaps_m"> + <div v-if="imgUrl != null" :class="$style.imgs"> + <div style="background: #000;" :class="$style.imgContainer"> + <img :src="imgUrl" :class="$style.img"/> + </div> + <div style="background: #222;" :class="$style.imgContainer"> + <img :src="imgUrl" :class="$style.img"/> + </div> + <div style="background: #ddd;" :class="$style.imgContainer"> + <img :src="imgUrl" :class="$style.img"/> + </div> + <div style="background: #fff;" :class="$style.imgContainer"> + <img :src="imgUrl" :class="$style.img"/> + </div> + </div> + <MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton> + <MkInput v-model="name"> + <template #label>{{ i18n.ts.name }}</template> + </MkInput> + <MkInput v-model="category" :datalist="customEmojiCategories"> + <template #label>{{ i18n.ts.category }}</template> + </MkInput> + <MkInput v-model="aliases"> + <template #label>{{ i18n.ts.tags }}</template> + <template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template> + </MkInput> + <MkInput v-model="license"> + <template #label>{{ i18n.ts.license }}</template> + </MkInput> + <MkFolder> + <template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template> + <template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template> + + <div class="_gaps"> + <MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + + <div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem"> + <MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/> + <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button> + <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> + </div> + + <MkInfo>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription }}</MkInfo> + <MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo> + </div> + </MkFolder> + <MkSwitch v-model="isSensitive">isSensitive</MkSwitch> + <MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch> + <MkButton danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </MkSpacer> + <div :class="$style.footer"> + <MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton> </div> - </MkSpacer> + </div> </MkModalWindow> </template> <script lang="ts" setup> -import { } from 'vue'; +import { computed, watch } from 'vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { customEmojiCategories } from '@/custom-emojis'; +import MkSwitch from '@/components/MkSwitch.vue'; +import { selectFile, selectFiles } from '@/scripts/select-file'; +import MkRolePreview from '@/components/MkRolePreview.vue'; const props = defineProps<{ - emoji: any, + emoji?: any, }>(); let dialog = $ref(null); -let name: string = $ref(props.emoji.name); -let category: string = $ref(props.emoji.category); -let aliases: string = $ref(props.emoji.aliases.join(' ')); -let license: string = $ref(props.emoji.license ?? ''); +let name: string = $ref(props.emoji ? props.emoji.name : ''); +let category: string = $ref(props.emoji ? props.emoji.category : ''); +let aliases: string = $ref(props.emoji ? props.emoji.aliases.join(' ') : ''); +let license: string = $ref(props.emoji ? (props.emoji.license ?? '') : ''); +let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false); +let localOnly = $ref(props.emoji ? props.emoji.localOnly : false); +let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []); +let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]); +let file = $ref(); + +watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => { + rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null); +}, { immediate: true }); + +const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null); const emit = defineEmits<{ - (ev: 'done', v: { deleted?: boolean, updated?: any }): void, + (ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, (ev: 'closed'): void }>(); -function ok() { - update(); +async function changeImage(ev) { + file = await selectFile(ev.currentTarget ?? ev.target, null); } -async function update() { - await os.apiWithDialog('admin/emoji/update', { - id: props.emoji.id, +async function addRole() { + const roles = await os.api('admin/roles/list'); + const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id); + + const { canceled, result: role } = await os.select({ + items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })), + }); + if (canceled) return; + + rolesThatCanBeUsedThisEmojiAsReaction.push(role); +} + +async function removeRole(role, ev) { + rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id); +} + +async function done() { + const params = { name, - category, - aliases: aliases.split(' '), + category: category === '' ? null : category, + aliases: aliases.split(' ').filter(x => x !== ''), license: license === '' ? null : license, - }); + isSensitive, + localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id), + }; - emit('done', { - updated: { + if (file) { + params.fileId = file.id; + } + + if (props.emoji) { + await os.apiWithDialog('admin/emoji/update', { id: props.emoji.id, - name, - category, - aliases: aliases.split(' '), - license: license === '' ? null : license, - }, - }); + ...params, + }); - dialog.close(); + emit('done', { + updated: { + id: props.emoji.id, + ...params, + }, + }); + + dialog.close(); + } else { + const created = await os.apiWithDialog('admin/emoji/add', params); + + emit('done', { + created: created, + }); + + dialog.close(); + } } async function del() { @@ -99,12 +186,48 @@ async function del() { } </script> -<style lang="scss" scoped> -.yigymqpb { - > .img { - display: block; - height: 64px; - margin: 0 auto; - } +<style lang="scss" module> +.imgs { + display: flex; + gap: 8px; + flex-wrap: wrap; + justify-content: center; +} + +.imgContainer { + padding: 8px; + border-radius: 6px; +} + +.img { + display: block; + height: 64px; + width: 64px; + object-fit: contain; +} + +.roleItem { + display: flex; +} + +.role { + flex: 1; +} + +.roleUnassign { + width: 32px; + height: 32px; + margin-left: 8px; + align-self: center; +} + +.footer { + position: sticky; + bottom: 0; + left: 0; + padding: 12px; + border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue index bdd21b29eeda3930a9c62b05ddbf074a2d576dc0..e9fab6a313fefeb4504e54d5e7709b17f60bf773 100644 --- a/packages/frontend/src/pages/emojis.emoji.vue +++ b/packages/frontend/src/pages/emojis.emoji.vue @@ -1,9 +1,9 @@ <template> -<button class="zuvgdzyu _button" @click="menu"> - <img :src="emoji.url" class="img" loading="lazy"/> - <div class="body"> - <div class="name _monospace">{{ emoji.name }}</div> - <div class="info">{{ emoji.aliases.join(' ') }}</div> +<button class="_button" :class="$style.root" @click="menu"> + <img :src="emoji.url" :class="$style.img" loading="lazy"/> + <div :class="$style.body"> + <div :class="$style.name" class="_monospace">{{ emoji.name }}</div> + <div :class="$style.info">{{ emoji.aliases.join(' ') }}</div> </div> </button> </template> @@ -49,8 +49,8 @@ function menu(ev) { } </script> -<style lang="scss" scoped> -.zuvgdzyu { +<style lang="scss" module> +.root { display: flex; align-items: center; padding: 12px; @@ -61,29 +61,29 @@ function menu(ev) { &:hover { border-color: var(--accent); } +} - > .img { - width: 42px; - height: 42px; - object-fit: contain; - } +.img { + width: 42px; + height: 42px; + object-fit: contain; +} - > .body { - padding: 0 0 0 8px; - white-space: nowrap; - overflow: hidden; +.body { + padding: 0 0 0 8px; + white-space: nowrap; + overflow: hidden; +} - > .name { - text-overflow: ellipsis; - overflow: hidden; - } +.name { + text-overflow: ellipsis; + overflow: hidden; +} - > .info { - opacity: 0.5; - font-size: 0.9em; - text-overflow: ellipsis; - overflow: hidden; - } - } +.info { + opacity: 0.5; + font-size: 0.9em; + text-overflow: ellipsis; + overflow: hidden; } </style> diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue index a972ae04ec32926332dbd4e62eed330eda9925eb..5c713137384636bbf7f0ea0a47789cbbacafd1bf 100644 --- a/packages/frontend/src/pages/explore.featured.vue +++ b/packages/frontend/src/pages/explore.featured.vue @@ -1,5 +1,5 @@ <template> -<MkSpacer :content-max="800"> +<MkSpacer :contentMax="800"> <MkTab v-model="tab" style="margin-bottom: var(--margin);"> <option value="notes">{{ i18n.ts.notes }}</option> <option value="polls">{{ i18n.ts.poll }}</option> diff --git a/packages/frontend/src/pages/explore.roles.vue b/packages/frontend/src/pages/explore.roles.vue index 6ac469f7bae3b6ef9762c05bc44604dd80f947e4..c855d79f45e5a84b6f544603bd82ba8c6af2fc93 100644 --- a/packages/frontend/src/pages/explore.roles.vue +++ b/packages/frontend/src/pages/explore.roles.vue @@ -1,7 +1,7 @@ <template> -<MkSpacer :content-max="700"> +<MkSpacer :contentMax="700"> <div class="_gaps_s"> - <MkRolePreview v-for="role in roles" :key="role.id" :role="role" :for-moderation="false"/> + <MkRolePreview v-for="role in roles" :key="role.id" :role="role" :forModeration="false"/> </div> </MkSpacer> </template> diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue index f9c833dd29fc2792aaaaaf40eac859a6af2786d0..785dbaa343af96265e901dfc4d3161b9e3027f50 100644 --- a/packages/frontend/src/pages/explore.users.vue +++ b/packages/frontend/src/pages/explore.users.vue @@ -1,24 +1,24 @@ <template> -<MkSpacer :content-max="1200"> +<MkSpacer :contentMax="1200"> <MkTab v-model="origin" style="margin-bottom: var(--margin);"> <option value="local">{{ i18n.ts.local }}</option> <option value="remote">{{ i18n.ts.remote }}</option> </MkTab> <div v-if="origin === 'local'"> <template v-if="tag == null"> - <MkFoldableSection class="_margin" persist-key="explore-pinned-users"> + <MkFoldableSection class="_margin" persistKey="explore-pinned-users"> <template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template> <MkUserList :pagination="pinnedUsers"/> </MkFoldableSection> - <MkFoldableSection class="_margin" persist-key="explore-popular-users"> + <MkFoldableSection class="_margin" persistKey="explore-popular-users"> <template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template> <MkUserList :pagination="popularUsers"/> </MkFoldableSection> - <MkFoldableSection class="_margin" persist-key="explore-recently-updated-users"> + <MkFoldableSection class="_margin" persistKey="explore-recently-updated-users"> <template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template> <MkUserList :pagination="recentlyUpdatedUsers"/> </MkFoldableSection> - <MkFoldableSection class="_margin" persist-key="explore-recently-registered-users"> + <MkFoldableSection class="_margin" persistKey="explore-recently-registered-users"> <template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template> <MkUserList :pagination="recentlyRegisteredUsers"/> </MkFoldableSection> diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue index 0dc9b9dc8f68a69a492bc079be310e0c28671ed7..460bf65d1ea522b965cb22d255898548ccfc0c8b 100644 --- a/packages/frontend/src/pages/favorites.vue +++ b/packages/frontend/src/pages/favorites.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <MkPagination :pagination="pagination"> <template #empty> <div class="_fullinfo"> @@ -11,7 +11,7 @@ </template> <template #default="{ items }"> - <MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false"> + <MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false"> <MkNote :key="item.id" :note="item.note" :class="$style.note"/> </MkDateSeparatedList> </template> diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index 816825e5b6e5649d7062cb20e2939196664a8b87..6a16cd1c4afc739df2e122e046840402212a5753 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div class="_gaps"> <MkInput v-model="title"> <template #label>{{ i18n.ts._play.title }}</template> @@ -33,7 +33,7 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkInput from '@/components/MkInput.vue'; import { useRouter } from '@/router'; -const PRESET_DEFAULT = `/// @ 0.13.2 +const PRESET_DEFAULT = `/// @ 0.13.3 var name = "" @@ -51,7 +51,7 @@ Ui:render([ ]) `; -const PRESET_OMIKUJI = `/// @ 0.13.2 +const PRESET_OMIKUJI = `/// @ 0.13.3 // ユーザーã”ã¨ã«æ—¥æ›¿ã‚ã‚Šã®ãŠã¿ãã˜ã®ãƒ—リセット // é¸æŠžè‚¢ @@ -94,7 +94,7 @@ Ui:render([ ]) `; -const PRESET_SHUFFLE = `/// @ 0.13.2 +const PRESET_SHUFFLE = `/// @ 0.13.3 // å·»ã戻ã—å¯èƒ½ãªæ–‡å—シャッフルã®ãƒ—リセット let string = "ペペãƒãƒ³ãƒãƒ¼ãƒŽ" @@ -173,7 +173,7 @@ var cursor = 0 do() `; -const PRESET_QUIZ = `/// @ 0.13.2 +const PRESET_QUIZ = `/// @ 0.13.3 let title = '地ç†ã‚¯ã‚¤ã‚º' let qas = [{ @@ -286,7 +286,7 @@ qaEls.push(Ui:C:container({ Ui:render(qaEls) `; -const PRESET_TIMELINE = `/// @ 0.13.2 +const PRESET_TIMELINE = `/// @ 0.13.3 // APIリクエストを行ã„ãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ラインを表示ã™ã‚‹ãƒ—リセット @fetch() { @@ -442,7 +442,3 @@ definePageMetadata(computed(() => flash ? { title: i18n.ts._play.new, })); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue index f1dca5f240726ea1ba59c71c382cfffe23407fba..1f933c23466fafdbfb9fadd5e8f263b35d02ce98 100644 --- a/packages/frontend/src/pages/flash/flash-index.vue +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -1,30 +1,30 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> - <div v-if="tab === 'featured'" class=""> + <MkSpacer :contentMax="700"> + <div v-if="tab === 'featured'"> <MkPagination v-slot="{items}" :pagination="featuredFlashsPagination"> <div class="_gaps_s"> - <MkFlashPreview v-for="flash in items" :key="flash.id" class="" :flash="flash"/> + <MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/> </div> </MkPagination> </div> - <div v-else-if="tab === 'my'" class="my"> + <div v-else-if="tab === 'my'"> <div class="_gaps"> - <MkButton class="new" gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton> + <MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton> <MkPagination v-slot="{items}" :pagination="myFlashsPagination"> <div class="_gaps_s"> - <MkFlashPreview v-for="flash in items" :key="flash.id" class="" :flash="flash"/> + <MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/> </div> </MkPagination> </div> </div> - <div v-else-if="tab === 'liked'" class=""> + <div v-else-if="tab === 'liked'"> <MkPagination v-slot="{items}" :pagination="likedFlashsPagination"> <div class="_gaps_s"> - <MkFlashPreview v-for="like in items" :key="like.flash.id" class="" :flash="like.flash"/> + <MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/> </div> </MkPagination> </div> @@ -87,21 +87,3 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-player-play', }))); </script> - -<style lang="scss" scoped> -.rknalgpo { - &.my .ckltabjg:first-child { - margin-top: 16px; - } - - .ckltabjg:not(:last-child) { - margin-bottom: 8px; - } - - @media (min-width: 500px) { - .ckltabjg:not(:last-child) { - margin-bottom: 16px; - } - } -} -</style> diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 961ef4b751288b39f235c54c9908fe8d7e3cddf4..2e1532b9f3e64e4a0fc419ec0aec21c21512fdac 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="flash" :key="flash.id"> <Transition :name="defaultStore.state.animation ? 'zoom' : ''" mode="out-in"> @@ -10,8 +10,8 @@ <MkAsUi v-if="root" :component="root" :components="components"/> </div> <div class="actions _panel"> - <MkButton v-if="flash.isLiked" v-tooltip="i18n.ts.unlike" as-like class="button" rounded primary @click="unlike()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> - <MkButton v-else v-tooltip="i18n.ts.like" as-like class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> + <MkButton v-if="flash.isLiked" v-tooltip="i18n.ts.unlike" asLike class="button" rounded primary @click="unlike()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> + <MkButton v-else v-tooltip="i18n.ts.like" asLike class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton> <MkButton v-tooltip="i18n.ts.shareWithNote" class="button" rounded @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></MkButton> <MkButton v-tooltip="i18n.ts.share" class="button" rounded @click="share"><i class="ti ti-share ti-fw"></i></MkButton> </div> @@ -27,7 +27,7 @@ </div> </div> </Transition> - <MkFolder :default-open="false" :max-height="280" class="_margin"> + <MkFolder :defaultOpen="false" :max-height="280" class="_margin"> <template #icon><i class="ti ti-code"></i></template> <template #label>{{ i18n.ts._play.viewSource }}</template> diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index a51d1c78a4f722d359ffe73302afa857be8b8f32..1452942a1e549d85d45064c33d590cdfa8081efd 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <MkPagination ref="paginationComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> diff --git a/packages/frontend/src/pages/follow.vue b/packages/frontend/src/pages/follow.vue index 828246d67862e5899882af5191bb64128cdf6488..d14b66336476298be0619d5e7afbb4f23288ea81 100644 --- a/packages/frontend/src/pages/follow.vue +++ b/packages/frontend/src/pages/follow.vue @@ -1,5 +1,5 @@ <template> -<div class="mk-follow-page"> +<div> </div> </template> diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index cafcee0c33981e76337557dab26c8a8f45b760d8..f381636a785e205328d5ede367a893c1b6b78e4d 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="800" :marginMin="16" :marginMax="32"> <FormSuspense :p="init" class="_gaps"> <MkInput v-model="title"> <template #label>{{ i18n.ts.title }}</template> diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index fc9cc7ae9e997f4345783a510bbe293aee69bdd6..3c9c21a2ffdc65043b22c1e1ec5a59552f3a70ce 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -1,21 +1,21 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="1400"> + <MkSpacer :contentMax="1400"> <div class="_root"> <div v-if="tab === 'explore'"> <MkFoldableSection class="_margin"> <template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template> - <MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disable-auto-load="true"> - <div class="vfpdbgtk"> + <MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true"> + <div :class="$style.items"> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> </div> </MkPagination> </MkFoldableSection> <MkFoldableSection class="_margin"> <template #header><i class="ti ti-comet"></i>{{ i18n.ts.popularPosts }}</template> - <MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disable-auto-load="true"> - <div class="vfpdbgtk"> + <MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disableAutoLoad="true"> + <div :class="$style.items"> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> </div> </MkPagination> @@ -23,7 +23,7 @@ </div> <div v-else-if="tab === 'liked'"> <MkPagination v-slot="{items}" :pagination="likedPostsPagination"> - <div class="vfpdbgtk"> + <div :class="$style.items"> <MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/> </div> </MkPagination> @@ -31,7 +31,7 @@ <div v-else-if="tab === 'my'"> <MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA> <MkPagination v-slot="{items}" :pagination="myPostsPagination"> - <div class="vfpdbgtk"> + <div :class="$style.items"> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> </div> </MkPagination> @@ -119,15 +119,11 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.vfpdbgtk { +<style lang="scss" module> +.items { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-gap: 12px; margin: 0 var(--margin); - - > .post { - - } } </style> diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index e0f3c105e159392d4884affc44bb171cea537bb7..dfa6c0bac0e5a29fe8b15630483e627a10747e13 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="1000" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="1000" :marginMin="16" :marginMax="32"> <div class="_root"> <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="post" class="rkxwuolj"> diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index ba5fda137a4c978e490e6eff08db6e20d3e1bfe3..83997b255549825d86b0e1e7de49b6092b162da1 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer v-if="instance" :content-max="600" :margin-min="16" :margin-max="32"> + <MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32"> <div v-if="tab === 'overview'" class="_gaps_m"> <div class="fnfelxur"> <img :src="faviconUrl" alt="" class="icon"/> @@ -29,8 +29,8 @@ <FormSection v-if="iAmModerator"> <template #label>Moderation</template> <div class="_gaps_s"> - <MkSwitch v-model="suspended" @update:model-value="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch> - <MkSwitch v-model="isBlocked" @update:model-value="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch> + <MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch> + <MkSwitch v-model="isBlocked" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch> <MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton> </div> </FormSection> diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue new file mode 100644 index 0000000000000000000000000000000000000000..f92c06d1c53b4e6f72aeedbe3677244c5572b3e7 --- /dev/null +++ b/packages/frontend/src/pages/list.vue @@ -0,0 +1,148 @@ +<template> +<MkStickyContainer> + <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> + <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> + <div :class="$style.root"> + <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> + <p :class="$style.text"> + <i class="ti ti-alert-triangle"></i> + {{ i18n.ts.nothing }} + </p> + </div> + </MKSpacer> + <MkSpacer v-else-if="list" :contentMax="700" :class="$style.main"> + <div v-if="list" class="members _margin"> + <div :class="$style.member_text">{{ i18n.ts.members }}</div> + <div class="_gaps_s"> + <div v-for="user in users" :key="user.id" :class="$style.userItem"> + <MkA :class="$style.userItemBody" :to="`${userPage(user)}`"> + <MkUserCardMini :user="user"/> + </MkA> + </div> + </div> + </div> + <MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton> + <MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton> + <MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { watch, computed } from 'vue'; +import * as os from '@/os'; +import { userPage } from '@/filters/user'; +import { i18n } from '@/i18n'; +import MkUserCardMini from '@/components/MkUserCardMini.vue'; +import MkButton from '@/components/MkButton.vue'; +import { definePageMetadata } from '@/scripts/page-metadata'; + +const props = defineProps<{ + listId: string; +}>(); + +let list = $ref(null); +let error = $ref(); +let users = $ref([]); + +function fetchList(): void { + os.api('users/lists/show', { + listId: props.listId, + forPublic: true, + }).then(_list => { + list = _list; + os.api('users/show', { + userIds: list.userIds, + }).then(_users => { + users = _users; + }); + }).catch(err => { + error = err; + }); +} + +function like() { + os.apiWithDialog('users/lists/favorite', { + listId: list.id, + }).then(() => { + list.isLiked = true; + list.likedCount++; + }); +} + +function unlike() { + os.apiWithDialog('users/lists/unfavorite', { + listId: list.id, + }).then(() => { + list.isLiked = false; + list.likedCount--; + }); +} + +async function create() { + const { canceled, result: name } = await os.inputText({ + title: i18n.ts.enterListName, + }); + if (canceled) return; + await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.id }); +} + +watch(() => props.listId, fetchList, { immediate: true }); + +const headerActions = $computed(() => []); + +const headerTabs = $computed(() => []); + +definePageMetadata(computed(() => list ? { + title: list.name, + icon: 'ti ti-list', +} : null)); +</script> +<style lang="scss" module> +.main { + min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); +} + +.userItem { + display: flex; +} + +.userItemBody { + flex: 1; + min-width: 0; + margin-right: 8px; + + &:hover { + text-decoration: none; + } +} +.member_text { + margin: 5px; +} + +.root { + padding: 32px; + text-align: center; + align-items: center; +} + +.text { + margin: 0 0 8px 0; +} + +.img { + vertical-align: bottom; + width: 128px; + height: 128px; + margin-bottom: 16px; + border-radius: 16px; +} + +.button { + margin-right: 10px; +} + +.import { + margin-right: 4px; +} +</style> diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue index 8e0624f555b957a3914d2a12bfb118d7566a4fa2..553946cd9e67d171a895d85b2c1bf5c58958f98f 100644 --- a/packages/frontend/src/pages/miauth.vue +++ b/packages/frontend/src/pages/miauth.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <div v-if="$i"> <div v-if="state == 'waiting'"> <MkLoading/> diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue index c35af3e22a8c04ea84bfd090e9485d9ee2f75fe7..355d18fdb527d804a4cd42ce6299afa48cf7fd49 100644 --- a/packages/frontend/src/pages/my-antennas/create.vue +++ b/packages/frontend/src/pages/my-antennas/create.vue @@ -1,5 +1,5 @@ <template> -<div class="geegznzt"> +<div> <XAntenna :antenna="draft" @created="onAntennaCreated"/> </div> </template> @@ -38,7 +38,3 @@ definePageMetadata({ icon: 'ti ti-antenna', }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue index 913fbde8e95620fe1b1c51f60d899b1c9c964712..da9b2de48f1d67b54ecbeb34c715d6194bc15e29 100644 --- a/packages/frontend/src/pages/my-antennas/edit.vue +++ b/packages/frontend/src/pages/my-antennas/edit.vue @@ -36,7 +36,3 @@ definePageMetadata({ icon: 'ti ti-antenna', }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue index 26b7bcc71b6614e017a510cc6b075b588fcd392f..ed92208c42f584d14d8ade3694842ef27ec34ef5 100644 --- a/packages/frontend/src/pages/my-antennas/editor.vue +++ b/packages/frontend/src/pages/my-antennas/editor.vue @@ -1,6 +1,6 @@ <template> -<MkSpacer :content-max="700"> - <div class="shaynizk"> +<MkSpacer :contentMax="700"> + <div> <div class="_gaps_m"> <MkInput v-model="name"> <template #label>{{ i18n.ts.name }}</template> @@ -33,7 +33,7 @@ <MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch> <MkSwitch v-model="notify">{{ i18n.ts.notifyAntenna }}</MkSwitch> </div> - <div class="actions"> + <div :class="$style.actions"> <MkButton inline primary @click="saveAntenna()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> <MkButton v-if="antenna.id != null" inline danger @click="deleteAntenna()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> </div> @@ -128,12 +128,10 @@ function addUser() { } </script> -<style lang="scss" scoped> -.shaynizk { - > .actions { - margin-top: 16px; - padding: 24px 0; - border-top: solid 0.5px var(--divider); - } +<style lang="scss" module> +.actions { + margin-top: 16px; + padding: 24px 0; + border-top: solid 0.5px var(--divider); } </style> diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue index f1764b1aadf15382aa0b41a244371204661d7751..2ca026b9a15ab75ec36db921b2d089be2711f5ca 100644 --- a/packages/frontend/src/pages/my-antennas/index.vue +++ b/packages/frontend/src/pages/my-antennas/index.vue @@ -1,18 +1,20 @@ -<template><MkStickyContainer> +<template> +<MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> - <div class="ieepwinx"> - <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkSpacer :contentMax="700"> + <div class="ieepwinx"> + <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> - <div class=""> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> - <MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`"> - <div class="name">{{ antenna.name }}</div> - </MkA> - </MkPagination> + <div class=""> + <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> + <MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`"> + <div class="name">{{ antenna.name }}</div> + </MkA> + </MkPagination> + </div> </div> - </div> -</MkSpacer></MkStickyContainer> + </MkSpacer> +</MkStickyContainer> </template> <script lang="ts" setup> diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue index ccffa7b5631c47307078884d20f738865fc58b8c..a769f8ee9761a28d47ff3d77c9d63362dc68f2d6 100644 --- a/packages/frontend/src/pages/my-clips/index.vue +++ b/packages/frontend/src/pages/my-clips/index.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div v-if="tab === 'my'" class="_gaps"> <MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index 47437f3e57a73428e1b22df06d2fa637e7b07f75..cee241c489632c54bbb5a81124827172d21a204d 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -1,15 +1,17 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> - <div class="qkcjvfiv"> - <MkButton primary class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.createList }}</MkButton> + <MkSpacer :contentMax="700"> + <div class="_gaps"> + <MkButton primary rounded style="margin: 0 auto;" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.createList }}</MkButton> - <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists"> - <MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`"> - <div class="name">{{ list.name }}</div> - <MkAvatars :user-ids="list.userIds"/> - </MkA> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination"> + <div class="_gaps"> + <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`"> + <div style="margin-bottom: 4px;">{{ list.name }}</div> + <MkAvatars :userIds="list.userIds"/> + </MkA> + </div> </MkPagination> </div> </MkSpacer> @@ -58,28 +60,17 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.qkcjvfiv { - > .add { - margin: 0 auto var(--margin) auto; - } - - > .lists { - > .list { - display: block; - padding: 16px; - border: solid 1px var(--divider); - border-radius: 6px; - - &:hover { - border: solid 1px var(--accent); - text-decoration: none; - } +<style lang="scss" module> +.list { + display: block; + padding: 16px; + border: solid 1px var(--divider); + border-radius: 6px; + margin-bottom: 8px; - > .name { - margin-bottom: 4px; - } - } + &:hover { + border: solid 1px var(--accent); + text-decoration: none; } } </style> diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index 86201e8e0c87748b8e97c0d69fdeac9f7375c90b..dd431e8dc0426fc30fa2b131f3ea4c91e97b1756 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -1,35 +1,43 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700" :class="$style.main"> - <div v-if="list" class="members _margin"> - <div class="">{{ i18n.ts.members }}</div> - <div class="_gaps_s"> - <div v-for="user in users" :key="user.id" :class="$style.userItem"> - <MkA :class="$style.userItemBody" :to="`${userPage(user)}`"> - <MkUserCardMini :user="user"/> - </MkA> - <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button> + <MkSpacer :contentMax="700" :class="$style.main"> + <div v-if="list" class="_gaps"> + <MkFolder> + <template #label>{{ i18n.ts.settings }}</template> + + <div class="_gaps"> + <MkInput v-model="name"> + <template #label>{{ i18n.ts.name }}</template> + </MkInput> + <MkSwitch v-model="isPublic">{{ i18n.ts.public }}</MkSwitch> + <div class="_buttons"> + <MkButton rounded primary @click="updateSettings">{{ i18n.ts.save }}</MkButton> + <MkButton rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton> + </div> </div> - </div> - </div> - </MkSpacer> - <template #footer> - <div :class="$style.footer"> - <MkSpacer :content-max="700" :margin-min="16" :margin-max="16"> - <div class="_buttons"> - <MkButton inline rounded primary @click="addUser()">{{ i18n.ts.addUser }}</MkButton> - <MkButton inline rounded @click="renameList()">{{ i18n.ts.rename }}</MkButton> - <MkButton inline rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton> + </MkFolder> + + <MkFolder defaultOpen> + <template #label>{{ i18n.ts.members }}</template> + + <div class="_gaps_s"> + <MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton> + <div v-for="user in users" :key="user.id" :class="$style.userItem"> + <MkA :class="$style.userItemBody" :to="`${userPage(user)}`"> + <MkUserCardMini :user="user"/> + </MkA> + <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button> + </div> </div> - </MkSpacer> + </MkFolder> </div> - </template> + </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> -import { computed, watch } from 'vue'; +import { computed, ref, watch } from 'vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { mainRouter } from '@/router'; @@ -37,6 +45,9 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import { userPage } from '@/filters/user'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkInput from '@/components/MkInput.vue'; import { userListsCache } from '@/cache'; const props = defineProps<{ @@ -45,12 +56,17 @@ const props = defineProps<{ let list = $ref(null); let users = $ref([]); +const isPublic = ref(false); +const name = ref(''); function fetchList() { os.api('users/lists/show', { listId: props.listId, }).then(_list => { list = _list; + name.value = list.name; + isPublic.value = list.isPublic; + os.api('users/show', { userIds: list.userIds, }).then(_users => { @@ -86,23 +102,6 @@ async function removeUser(user, ev) { }], ev.currentTarget ?? ev.target); } -async function renameList() { - const { canceled, result: name } = await os.inputText({ - title: i18n.ts.enterListName, - default: list.name, - }); - if (canceled) return; - - await os.api('users/lists/update', { - listId: list.id, - name: name, - }); - - userListsCache.delete(); - - list.name = name; -} - async function deleteList() { const { canceled } = await os.confirm({ type: 'warning', @@ -117,6 +116,19 @@ async function deleteList() { mainRouter.push('/my/lists'); } +async function updateSettings() { + await os.apiWithDialog('users/lists/update', { + listId: list.id, + name: name.value, + isPublic: isPublic.value, + }); + + userListsCache.delete(); + + list.name = name.value; + list.isPublic = isPublic.value; +} + watch(() => props.listId, fetchList, { immediate: true }); const headerActions = $computed(() => []); diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue index e58e44ef79abc27ae601fafde2d751a6b7b771cb..2c9d949017ede433fda55cb6719a37f08fbaff8e 100644 --- a/packages/frontend/src/pages/not-found.vue +++ b/packages/frontend/src/pages/not-found.vue @@ -1,5 +1,5 @@ <template> -<div class="ipledcug"> +<div> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/not-found.jpg" class="_ghost"/> <div>{{ i18n.ts.notFoundDescription }}</div> diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index d9baa1096a27b21e13938ffde31f658e987801b4..c519cefbaf44d1b49623bc1ce4bd7452510f796d 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -1,33 +1,33 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> - <div class="fcuexfpr"> + <MkSpacer :contentMax="800"> + <div> <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> - <div v-if="note" class="note"> + <div v-if="note"> <div v-if="showNext" class="_margin"> - <MkNotes class="" :pagination="nextPagination" :no-gap="true"/> + <MkNotes class="" :pagination="nextPagination" :noGap="true"/> </div> - <div class="main _margin"> - <MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton> - <div class="note _margin _gaps_s"> + <div class="_margin"> + <MkButton v-if="!showNext && hasNext" :class="$style.loadNext" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton> + <div class="_margin _gaps_s"> <MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/> - <MkNoteDetailed :key="note.id" v-model:note="note" class="note"/> + <MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note"/> </div> - <div v-if="clips && clips.length > 0" class="clips _margin"> - <div class="title">{{ i18n.ts.clip }}</div> + <div v-if="clips && clips.length > 0" class="_margin"> + <div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div> <div class="_gaps"> <MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`"> <MkClipPreview :clip="item"/> </MkA> </div> </div> - <MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="ti ti-chevron-down"></i></MkButton> + <MkButton v-if="!showPrev && hasPrev" :class="$style.loadPrev" @click="showPrev = true"><i class="ti ti-chevron-down"></i></MkButton> </div> <div v-if="showPrev" class="_margin"> - <MkNotes class="" :pagination="prevPagination" :no-gap="true"/> + <MkNotes class="" :pagination="prevPagination" :noGap="true"/> </div> </div> <MkError v-else-if="error" @retry="fetchNote()"/> @@ -137,7 +137,7 @@ definePageMetadata(computed(() => note ? { } : null)); </script> -<style lang="scss" scoped> +<style lang="scss" module> .fade-enter-active, .fade-leave-active { transition: opacity 0.125s ease; @@ -147,39 +147,23 @@ definePageMetadata(computed(() => note ? { opacity: 0; } -.fcuexfpr { - background: var(--bg); - - > .note { - > .main { - > .load { - min-width: 0; - margin: 0 auto; - border-radius: 999px; - - &.next { - margin-bottom: var(--margin); - } - - &.prev { - margin-top: var(--margin); - } - } - - > .note { - > .note { - border-radius: var(--radius); - background: var(--panel); - } - } - - > .clips { - > .title { - font-weight: bold; - padding: 12px; - } - } - } - } +.loadNext, +.loadPrev { + min-width: 0; + margin: 0 auto; + border-radius: 999px; +} + +.loadNext { + margin-bottom: var(--margin); +} + +.loadPrev { + margin-top: var(--margin); +} + +.note { + border-radius: var(--radius); + background: var(--panel); } </style> diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index 1789606cd83106d6f3f1112b7f21c1106ae09bbc..8196f918684129e39057c81b29c5e11fb738d5b5 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -1,9 +1,9 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <div v-if="tab === 'all'"> - <XNotifications class="notifications" :include-types="includeTypes"/> + <XNotifications class="notifications" :includeTypes="includeTypes"/> </div> <div v-else-if="tab === 'mentions'"> <MkNotes :pagination="mentionsPagination"/> diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue index 1b292e8f3c8e8394772638ffeaf53f526d23c687..eca3feda62f02bf306681660514b7be94f71bc89 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue @@ -8,8 +8,8 @@ </button> </template> - <section class="oyyftmcf"> - <MkDriveFileThumbnail v-if="file" class="preview" :file="file" fit="contain" @click="choose()"/> + <section> + <MkDriveFileThumbnail v-if="file" style="height: 150px;" :file="file" fit="contain" @click="choose()"/> </section> </XContainer> </template> @@ -54,11 +54,3 @@ onMounted(async () => { } }); </script> - -<style lang="scss" scoped> -.oyyftmcf { - > .preview { - height: 150px; - } -} -</style> diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue index bf21ae3c676e64577a4a8872d6bfd172356d62e5..3b15c17747a48d6913bb8eb8897ff9706d763c7f 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue @@ -3,8 +3,8 @@ <XContainer :draggable="true" @remove="() => $emit('remove')"> <template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template> - <section class="vckmsadr"> - <textarea v-model="text"></textarea> + <section> + <textarea v-model="text" :class="$style.textarea"></textarea> </section> </XContainer> </template> @@ -33,23 +33,21 @@ watch($$(text), () => { }); </script> -<style lang="scss" scoped> -.vckmsadr { - > textarea { - display: block; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - width: 100%; - min-width: 100%; - min-height: 150px; - border: none; - box-shadow: none; - padding: 16px; - background: transparent; - color: var(--fg); - font-size: 14px; - box-sizing: border-box; - } +<style lang="scss" module> +.textarea { + display: block; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: 100%; + min-width: 100%; + min-height: 150px; + border: none; + box-shadow: none; + padding: 16px; + background: transparent; + color: var(--fg); + font-size: 14px; + box-sizing: border-box; } </style> diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue index 97bdcfe80f1ee6aabe48b328dad9a21f01490580..fc945b3d63c46967b2d83098883670e4be0f65f0 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue @@ -1,57 +1,59 @@ <template> -<Sortable :model-value="modelValue" tag="div" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swap-threshold="0.5" @update:model-value="v => $emit('update:modelValue', v)"> +<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => $emit('update:modelValue', v)"> <template #item="{element}"> <div :class="$style.item"> <!-- divãŒç„¡ã„ã¨ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ https://github.com/SortableJS/vue.draggable.next/issues/189 --> - <component :is="'x-' + element.type" :model-value="element" @update:model-value="updateItem" @remove="() => removeItem(element)"/> + <component :is="getComponent(element.type)" :modelValue="element" @update:modelValue="updateItem" @remove="() => removeItem(element)"/> </div> </template> </Sortable> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import XSection from './els/page-editor.el.section.vue'; import XText from './els/page-editor.el.text.vue'; import XImage from './els/page-editor.el.image.vue'; import XNote from './els/page-editor.el.note.vue'; -export default defineComponent({ - components: { - Sortable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), - XSection, XText, XImage, XNote, - }, - - props: { - modelValue: { - type: Array, - required: true, - }, - }, - - emits: ['update:modelValue'], - - methods: { - updateItem(v) { - const i = this.modelValue.findIndex(x => x.id === v.id); - const newValue = [ - ...this.modelValue.slice(0, i), - v, - ...this.modelValue.slice(i + 1), - ]; - this.$emit('update:modelValue', newValue); - }, - - removeItem(el) { - const i = this.modelValue.findIndex(x => x.id === el.id); - const newValue = [ - ...this.modelValue.slice(0, i), - ...this.modelValue.slice(i + 1), - ]; - this.$emit('update:modelValue', newValue); - }, - }, -}); +function getComponent(type: string) { + switch (type) { + case 'section': return XSection; + case 'text': return XText; + case 'image': return XImage; + case 'note': return XNote; + default: return null; + } +} + +const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); + +const props = defineProps<{ + modelValue: any[]; +}>(); + +const emit = defineEmits<{ + (ev: 'update:modelValue', value: any[]): void; +}>(); + +function updateItem(v) { + const i = props.modelValue.findIndex(x => x.id === v.id); + const newValue = [ + ...props.modelValue.slice(0, i), + v, + ...props.modelValue.slice(i + 1), + ]; + emit('update:modelValue', newValue); +} + +function removeItem(el) { + const i = props.modelValue.findIndex(x => x.id === el.id); + const newValue = [ + ...props.modelValue.slice(0, i), + ...props.modelValue.slice(i + 1), + ]; + emit('update:modelValue', newValue); +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue index dd733403af750d34a5fdb65a84c85bfbeaa713d2..0842b4fd26097dd060f68711774579e6278d2282 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.container.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue @@ -1,5 +1,5 @@ <template> -<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }"> +<div class="cpjygsrt"> <header> <div class="title"><slot name="header"></slot></div> <div class="buttons"> @@ -16,58 +16,40 @@ </button> </div> </header> - <p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p> - <p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p> <div v-show="showBody" class="body"> <slot></slot> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - expanded: { - type: Boolean, - default: true, - }, - removable: { - type: Boolean, - default: true, - }, - draggable: { - type: Boolean, - default: false, - }, - error: { - required: false, - default: null, - }, - warn: { - required: false, - default: null, - }, - }, - emits: ['toggle', 'remove'], - data() { - return { - showBody: this.expanded, - i18n, - }; - }, - methods: { - toggleContent(show: boolean) { - this.showBody = show; - this.$emit('toggle', show); - }, - remove() { - this.$emit('remove'); - }, - }, +const props = withDefaults(defineProps<{ + expanded?: boolean; + removable?: boolean; + draggable?: boolean; +}>(), { + expanded: true, + removable: true, }); + +const emit = defineEmits<{ + (ev: 'toggle', show: boolean): void; + (ev: 'remove'): void; +}>(); + +const showBody = ref(props.expanded); + +function toggleContent(show: boolean) { + showBody.value = show; + emit('toggle', show); +} + +function remove() { + emit('remove'); +} </script> <style lang="scss" scoped> @@ -128,20 +110,6 @@ export default defineComponent({ } } - > .warn { - color: #b19e49; - margin: 0; - padding: 16px 16px 0 16px; - font-size: 14px; - } - - > .error { - color: #f00; - margin: 0; - padding: 16px 16px 0 16px; - font-size: 14px; - } - > .body { ::v-deep(.juejbjww), ::v-deep(.eiipwacr) { &:not(.inline):first-child { diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index bcf30e23a7b6070d0b3f9c8233c14f365ebeecfc..bd54699dc44a876da2ee927f97f8e0c271d71e1d 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <div class="jqqmcavi"> <MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton> <MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 5a0f58c8df8b3b58f2a92a20d66476b495cd24f3..27a4cd059550dabedcc0253af0fba36c3c4df326 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> + <MkSpacer :contentMax="700"> <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="page" :key="page.id" class="xcukqgmh"> <div class="main"> @@ -18,8 +18,8 @@ </div> <div class="actions"> <div class="like"> - <MkButton v-if="page.isLiked" v-tooltip="i18n.ts._pages.unlike" class="button" as-like primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton> - <MkButton v-else v-tooltip="i18n.ts._pages.like" class="button" as-like @click="like()"><i class="ti ti-heart"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton> + <MkButton v-if="page.isLiked" v-tooltip="i18n.ts._pages.unlike" class="button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton> + <MkButton v-else v-tooltip="i18n.ts._pages.like" class="button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton> </div> <div class="other"> <button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button> diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue index 0427332ab274d47e4ed5f9d84e50419caa944157..4f67bda11fc6c9e9b111a522459ec9bb608a4315 100644 --- a/packages/frontend/src/pages/pages.vue +++ b/packages/frontend/src/pages/pages.vue @@ -1,23 +1,29 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="700"> - <div v-if="tab === 'featured'" class="rknalgpo"> + <MkSpacer :contentMax="700"> + <div v-if="tab === 'featured'"> <MkPagination v-slot="{items}" :pagination="featuredPagesPagination"> - <MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/> + <div class="_gaps"> + <MkPagePreview v-for="page in items" :key="page.id" :page="page"/> + </div> </MkPagination> </div> - <div v-else-if="tab === 'my'" class="rknalgpo my"> + <div v-else-if="tab === 'my'" class="_gaps"> <MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton> <MkPagination v-slot="{items}" :pagination="myPagesPagination"> - <MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/> + <div class="_gaps"> + <MkPagePreview v-for="page in items" :key="page.id" :page="page"/> + </div> </MkPagination> </div> - <div v-else-if="tab === 'liked'" class="rknalgpo"> + <div v-else-if="tab === 'liked'"> <MkPagination v-slot="{items}" :pagination="likedPagesPagination"> - <MkPagePreview v-for="like in items" :key="like.page.id" class="ckltabjg" :page="like.page"/> + <div class="_gaps"> + <MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/> + </div> </MkPagination> </div> </MkSpacer> @@ -79,21 +85,3 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-note', }))); </script> - -<style lang="scss" scoped> -.rknalgpo { - &.my .ckltabjg:first-child { - margin-top: 16px; - } - - .ckltabjg:not(:last-child) { - margin-bottom: 8px; - } - - @media (min-width: 500px) { - .ckltabjg:not(:last-child) { - margin-bottom: 16px; - } - } -} -</style> diff --git a/packages/frontend/src/pages/preview.vue b/packages/frontend/src/pages/preview.vue deleted file mode 100644 index 354f686e467c70138ce7f39d190686fae0b16ae4..0000000000000000000000000000000000000000 --- a/packages/frontend/src/pages/preview.vue +++ /dev/null @@ -1,27 +0,0 @@ -<template> -<div class="graojtoi"> - <MkSample/> -</div> -</template> - -<script lang="ts" setup> -import { computed } from 'vue'; -import MkSample from '@/components/MkSample.vue'; -import { i18n } from '@/i18n'; -import { definePageMetadata } from '@/scripts/page-metadata'; - -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - -definePageMetadata(computed(() => ({ - title: i18n.ts.preview, - icon: 'ti ti-eye', -}))); -</script> - -<style lang="scss" scoped> -.graojtoi { - padding: var(--margin); -} -</style> diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue index c687b89eab09c2f947b118c7ad2be84e50ba8ecc..b1d41fe2c7790157be018bebd9470cc2f4fef2d1 100644 --- a/packages/frontend/src/pages/registry.keys.vue +++ b/packages/frontend/src/pages/registry.keys.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="600" :margin-min="16"> + <MkSpacer :contentMax="600" :marginMin="16"> <div class="_gaps_m"> <FormSplit> <MkKeyValue> @@ -93,6 +93,3 @@ definePageMetadata({ icon: 'ti ti-adjustments', }); </script> - -<style lang="scss" scoped> -</style> diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue index 00e2ca5e036dbf130ae2cf02ff2ce43358d12a34..513a2f8febc9bd26214cc3650bf3e59c6c49b563 100644 --- a/packages/frontend/src/pages/registry.value.vue +++ b/packages/frontend/src/pages/registry.value.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="600" :margin-min="16"> + <MkSpacer :contentMax="600" :marginMin="16"> <div class="_gaps_m"> <FormInfo warn>{{ i18n.ts.editTheseSettingsMayBreakAccount }}</FormInfo> @@ -118,6 +118,3 @@ definePageMetadata({ icon: 'ti ti-adjustments', }); </script> - -<style lang="scss" scoped> -</style> diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue index 5a029cb0c7e7462035e63bc55b347b8ba3edadcf..6bfb9bce58c78a5b8454f7a9288cd7b827408a69 100644 --- a/packages/frontend/src/pages/registry.vue +++ b/packages/frontend/src/pages/registry.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="600" :margin-min="16"> + <MkSpacer :contentMax="600" :marginMin="16"> <MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton> <FormSection v-if="scopes"> @@ -68,6 +68,3 @@ definePageMetadata({ icon: 'ti ti-adjustments', }); </script> - -<style lang="scss" scoped> -</style> diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue index 38c88cc650ea676863dfc76b3ff822ba56b51289..9d5730731433b38d0e3d4181b1feb36dd278d70d 100644 --- a/packages/frontend/src/pages/reset-password.vue +++ b/packages/frontend/src/pages/reset-password.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer v-if="token" :content-max="700" :margin-min="16" :margin-max="32"> + <MkSpacer v-if="token" :contentMax="700" :marginMin="16" :marginMax="32"> <div class="_gaps_m"> <MkInput v-model="password" type="password"> <template #prefix><i class="ti ti-lock"></i></template> @@ -53,7 +53,3 @@ definePageMetadata({ icon: 'ti ti-lock', }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue index fe39c594ba868b10ac348e49607a70718fa4f2e4..e85ab0917a8434bd90136ea77e92ca9aa38d4d2d 100644 --- a/packages/frontend/src/pages/role.vue +++ b/packages/frontend/src/pages/role.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template> - <MKSpacer v-if="!(typeof error === 'undefined')" :content-max="1200"> + <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> <div :class="$style.root"> <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> <p :class="$style.text"> @@ -10,17 +10,18 @@ </p> </div> </MKSpacer> - <MkSpacer v-else-if="tab === 'users'" :content-max="1200"> + <MkSpacer v-else-if="tab === 'users'" :contentMax="1200"> <div class="_gaps_s"> <div v-if="role">{{ role.description }}</div> <MkUserList :pagination="users" :extractor="(item) => item.user"/> </div> </MkSpacer> - <MkSpacer v-else-if="tab === 'timeline'" :content-max="700"> + <MkSpacer v-else-if="tab === 'timeline'" :contentMax="700"> <MkTimeline ref="timeline" src="role" :role="props.role"/> </MkSpacer> </MkStickyContainer> </template> + <script lang="ts" setup> import { computed, watch } from 'vue'; import * as os from '@/os'; @@ -80,6 +81,7 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-badge', }))); </script> + <style lang="scss" module> .root { padding: 32px; diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index fb78546cb1086eeb120910e6a8761d290956e70b..22eb00dad4ad33eda18b339f450cdcde9b1d3058 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -1,8 +1,8 @@ <template> -<MkSpacer :content-max="800"> +<MkSpacer :contentMax="800"> <div :class="$style.root"> <div :class="$style.editor" class="_panel"> - <PrismEditor v-model="code" class="_code code" :highlight="highlighter" :line-numbers="false"/> + <PrismEditor v-model="code" class="_code code" :highlight="highlighter" :lineNumbers="false"/> <MkButton style="position: absolute; top: 8px; right: 8px;" primary @click="run()"><i class="ti ti-player-play"></i></MkButton> </div> diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue index 23a8978fd15eaefbf96cd486b480b52fb67545fa..bd1389ffef97e9268f6d3c52ac4ec6eec6584610 100644 --- a/packages/frontend/src/pages/search.user.vue +++ b/packages/frontend/src/pages/search.user.vue @@ -4,7 +4,7 @@ <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> <template #prefix><i class="ti ti-search"></i></template> </MkInput> - <MkRadios v-model="searchOrigin" @update:model-value="search()"> + <MkRadios v-model="searchOrigin" @update:modelValue="search()"> <option value="combined">{{ i18n.ts.all }}</option> <option value="local">{{ i18n.ts.local }}</option> <option value="remote">{{ i18n.ts.remote }}</option> diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index 9f3d8da5602aeebb27cdee0d840f19548420131e..dcaf42e6488d33bbd938712f086a79d4537bc7e6 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -2,7 +2,7 @@ <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer v-if="tab === 'note'" :content-max="800"> + <MkSpacer v-if="tab === 'note'" :contentMax="800"> <div v-if="notesSearchAvailable"> <XNote/> </div> @@ -11,7 +11,7 @@ </div> </MkSpacer> - <MkSpacer v-else-if="tab === 'user'" :content-max="800"> + <MkSpacer v-else-if="tab === 'user'" :contentMax="800"> <XUser/> </MkSpacer> </MkStickyContainer> diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue index 1d836db5f5d78da89329c8b5f5925f8343438bb1..6a798b562671639734838f5ae20266bab88acfe2 100644 --- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue +++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue @@ -1,8 +1,8 @@ <template> <MkModal ref="dialogEl" - :prefer-type="'dialog'" - :z-priority="'low'" + :preferType="'dialog'" + :zPriority="'low'" @click="cancel" @close="cancel" @closed="emit('closed')" diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index 891934d706a83c6efa1b8c956b5f48184064a200..aff7765ed50953d6c5391a7d27b8b3b7adc925d8 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -51,7 +51,7 @@ </div> </MkFolder> - <MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :model-value="usePasswordLessLogin" @update:model-value="v => updatePasswordLessLogin(v)"> + <MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)"> <template #label>{{ i18n.ts.passwordLessLogin }}</template> <template #caption>{{ i18n.ts.passwordLessLoginDescription }}</template> </MkSwitch> diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index a58e74fe69eab15f765f150a92404573c877300a..78479be973eb5aaded5bfebd88487f952e549e8b 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -15,13 +15,13 @@ <script lang="ts" setup> import { defineAsyncComponent, ref } from 'vue'; +import type * as Misskey from 'misskey-js'; import FormSuspense from '@/components/form/suspense.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import type * as Misskey from 'misskey-js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; const storedAccounts = ref<any>(null); diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue index 599d6329e2c62a62ebb200fbae45960700cd387a..fbb78200d46238a523644006878c39be78b8fff1 100644 --- a/packages/frontend/src/pages/settings/apps.vue +++ b/packages/frontend/src/pages/settings/apps.vue @@ -9,11 +9,11 @@ </template> <template #default="{items}"> <div class="_gaps"> - <div v-for="token in items" :key="token.id" class="_panel bfomjevm"> - <img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/> - <div class="body"> - <div class="name">{{ token.name }}</div> - <div class="description">{{ token.description }}</div> + <div v-for="token in items" :key="token.id" class="_panel" :class="$style.app"> + <img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/> + <div :class="$style.appBody"> + <div :class="$style.appName">{{ token.name }}</div> + <div>{{ token.description }}</div> <MkKeyValue oneline> <template #key>{{ i18n.ts.installedDate }}</template> <template #value><MkTime :time="token.createdAt"/></template> @@ -28,7 +28,7 @@ <li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li> </ul> </details> - <div class="actions"> + <div> <MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton> </div> </div> @@ -75,27 +75,27 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.bfomjevm { +<style lang="scss" module> +.app { display: flex; padding: 16px; +} - > .icon { - display: block; - flex-shrink: 0; - margin: 0 12px 0 0; - width: 50px; - height: 50px; - border-radius: 8px; - } +.appIcon { + display: block; + flex-shrink: 0; + margin: 0 12px 0 0; + width: 50px; + height: 50px; + border-radius: 8px; +} - > .body { - width: calc(100% - 62px); - position: relative; +.appBody { + width: calc(100% - 62px); + position: relative; +} - > .name { - font-weight: bold; - } - } +.appName { + font-weight: bold; } </style> diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue index 456c3742c5e480d43c671e32f12d4ae98622eb27..970d5689b4bd3d101b4b920337eba60cd2bbd99e 100644 --- a/packages/frontend/src/pages/settings/custom-css.vue +++ b/packages/frontend/src/pages/settings/custom-css.vue @@ -2,7 +2,7 @@ <div class="_gaps_m"> <FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo> - <MkTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;"> + <MkTextarea v-model="localCustomCss" manualSave tall class="_monospace" style="tab-size: 2;"> <template #label>CSS</template> </MkTextarea> </div> diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index 73c2b2e6040e44c6b2c7a6418f47e0539131bd33..8d7b30dc6eeb14925bbf3ecbd6aa4ad5a1cd7e02 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -4,8 +4,8 @@ <template #label>{{ i18n.ts.usageAmount }}</template> <div class="_gaps_m"> - <div class="uawsfosz"> - <div class="meter"><div :style="meterStyle"></div></div> + <div> + <div :class="$style.meter"><div :class="$style.meterValue" :style="meterStyle"></div></div> </div> <FormSplit> <MkKeyValue> @@ -22,7 +22,7 @@ <FormSection> <template #label>{{ i18n.ts.statistics }}</template> - <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/> + <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspectRatio="6"/> </FormSection> <FormSection> @@ -39,10 +39,10 @@ <template #label>{{ i18n.ts.keepOriginalUploading }}</template> <template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template> </MkSwitch> - <MkSwitch v-model="alwaysMarkNsfw" @update:model-value="saveProfile()"> + <MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()"> <template #label>{{ i18n.ts.alwaysMarkSensitive }}</template> </MkSwitch> - <MkSwitch v-model="autoSensitive" @update:model-value="saveProfile()"> + <MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()"> <template #label>{{ i18n.ts.enableAutoSensitive }}<span class="_beta">{{ i18n.ts.beta }}</span></template> <template #caption>{{ i18n.ts.enableAutoSensitiveDescription }}</template> </MkSwitch> @@ -139,22 +139,16 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> - -@use "sass:math"; - -.uawsfosz { - - > .meter { - $size: 12px; - background: rgba(0, 0, 0, 0.1); - border-radius: math.div($size, 2); - overflow: hidden; +<style lang="scss" module> +.meter { + height: 10px; + background: rgba(0, 0, 0, 0.1); + border-radius: 999px; + overflow: clip; +} - > div { - height: $size; - border-radius: math.div($size, 2); - } - } +.meterValue { + height: 100%; + border-radius: 999px; } </style> diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue index b1e6f223b6417dcde2dbe0297d6931556d757c5f..d015cec1541ec1140c99ff3e8ac50e52c875f379 100644 --- a/packages/frontend/src/pages/settings/email.vue +++ b/packages/frontend/src/pages/settings/email.vue @@ -2,7 +2,7 @@ <div v-if="instance.enableEmail" class="_gaps_m"> <FormSection first> <template #label>{{ i18n.ts.emailAddress }}</template> - <MkInput v-model="emailAddress" type="email" manual-save> + <MkInput v-model="emailAddress" type="email" manualSave> <template #prefix><i class="ti ti-mail"></i></template> <template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template> <template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--success);"></i> {{ i18n.ts.emailVerified }}</template> @@ -10,7 +10,7 @@ </FormSection> <FormSection> - <MkSwitch :model-value="$i.receiveAnnouncementEmail" @update:model-value="onChangeReceiveAnnouncementEmail"> + <MkSwitch :modelValue="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail"> {{ i18n.ts.receiveAnnouncementFromInstance }} </MkSwitch> </FormSection> diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index ba0f3274fc827826b7efac411bb189c1134d715a..20b36f0fcb549dedb5c356af82056fe8e9139ff6 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -24,6 +24,7 @@ <div class="_gaps_s"> <MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch> <MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch> + <MkSwitch v-model="showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch> </div> </FormSection> @@ -56,7 +57,7 @@ <option value="ignore">{{ i18n.ts._nsfw.ignore }}</option> <option value="force">{{ i18n.ts._nsfw.force }}</option> </MkSelect> - <!-- + <MkRadios v-model="mediaListWithOneImageAppearance"> <template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template> <option value="expand">{{ i18n.ts.default }}</option> @@ -64,7 +65,6 @@ <option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option> <option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option> </MkRadios> - --> </div> </FormSection> @@ -145,12 +145,20 @@ </FormSection> <FormSection> - <MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch> - </FormSection> + <template #label>{{ i18n.ts.other }}</template> - <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink> - - <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> + <div class="_gaps"> + <MkFolder> + <template #label>{{ i18n.ts.additionalEmojiDictionary }}</template> + <div v-for="lang in emojiIndexLangs" class="_buttons"> + <MkButton @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ lang }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> + <MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> + </div> + </MkFolder> + <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink> + <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> + </div> + </FormSection> </div> </template> @@ -160,6 +168,8 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkRange from '@/components/MkRange.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkButton from '@/components/MkButton.vue'; import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/MkLink.vue'; @@ -212,10 +222,10 @@ const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker')) const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll')); const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu')); const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); -const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode')); const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance')); const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition')); const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis')); +const showTimelineReplies = computed(defaultStore.makeGetterSetter('showTimelineReplies')); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -244,7 +254,6 @@ watch([ useSystemFont, enableInfiniteScroll, squareAvatars, - aiChanMode, showNoteActionsOnlyHover, showGapBetweenNotesInTimeline, instanceTicker, @@ -253,6 +262,34 @@ watch([ await reloadAsk(); }); +const emojiIndexLangs = ['en-US']; + +function downloadEmojiIndex(lang: string) { + async function main() { + const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes; + function download() { + switch (lang) { + case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default); + default: throw new Error('unrecognized lang: ' + lang); + } + } + currentIndexes[lang] = await download(); + await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes); + } + + os.promiseDialog(main()); +} + +function removeEmojiIndex(lang: string) { + async function main() { + const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes; + delete currentIndexes[lang]; + await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes); + } + + os.promiseDialog(main()); +} + const headerActions = $computed(() => []); const headerTabs = $computed(() => []); diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 34a962ef4cf86849170c2a56ffa67065df8ba1f5..b4f056d8a6880e1872e0b515304726137898f13c 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="900" :margin-min="20" :margin-max="32"> + <MkSpacer :contentMax="900" :marginMin="20" :marginMax="32"> <div ref="el" class="vvcocwet" :class="{ wide: !narrow }"> <div class="body"> <div v-if="!narrow || currentPage?.route.name == null" class="nav"> diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue index 541992875e5bd009cb753d2fb415aa6f0ffb0af1..102bc68523a8d95dd982e09f8366ea19173a6576 100644 --- a/packages/frontend/src/pages/settings/migration.vue +++ b/packages/frontend/src/pages/settings/migration.vue @@ -3,7 +3,7 @@ <FormInfo warn> {{ i18n.ts.thisIsExperimentalFeature }} </FormInfo> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #icon><i class="ti ti-plane-arrival"></i></template> <template #label>{{ i18n.ts._accountMigration.moveFrom }}</template> <template #caption>{{ i18n.ts._accountMigration.moveFromSub }}</template> @@ -25,7 +25,7 @@ </div> </MkFolder> - <MkFolder :default-open="!!$i?.movedTo"> + <MkFolder :defaultOpen="!!$i?.movedTo"> <template #icon><i class="ti ti-plane-departure"></i></template> <template #label>{{ i18n.ts._accountMigration.moveTo }}</template> @@ -48,7 +48,7 @@ <FormInfo>{{ i18n.ts._accountMigration.postMigrationNote }}</FormInfo> <FormInfo warn>{{ i18n.ts._accountMigration.movedAndCannotBeUndone }}</FormInfo> <div>{{ i18n.ts._accountMigration.movedTo }}</div> - <MkUserInfo v-if="movedTo" :user="movedTo" class="_panel _shadow" /> + <MkUserInfo v-if="movedTo" :user="movedTo" class="_panel _shadow"/> </template> </div> </MkFolder> @@ -57,6 +57,8 @@ <script lang="ts" setup> import { ref } from 'vue'; +import { toString } from 'misskey-js/built/acct'; +import { UserDetailed } from 'misskey-js/built/entities'; import FormInfo from '@/components/MkInfo.vue'; import MkInput from '@/components/MkInput.vue'; import MkButton from '@/components/MkButton.vue'; @@ -66,8 +68,6 @@ import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import { $i } from '@/account'; -import { toString } from 'misskey-js/built/acct'; -import { UserDetailed } from 'misskey-js/built/entities'; import { unisonReload } from '@/scripts/unison-reload'; const moveToAccount = ref(''); diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index b3b33b8026d1667a90f7f0f0a32dc428751b2114..8780bfbc1e4ecd4de037ef0f333a63bc665decc8 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -2,10 +2,10 @@ <div class="_gaps_m"> <FormSlot> <template #label>{{ i18n.ts.navbar }}</template> - <MkContainer :show-header="false"> + <MkContainer :showHeader="false"> <Sortable v-model="items" - item-key="id" + itemKey="id" :animation="150" :handle="'.' + $style.itemHandle" @start="e => e.item.classList.add('active')" diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 2cf2f6d7f65080a38c403f8b5fdc5ba1a70e40ce..2552db4198999f8ee3c9204e64fa8152f310de93 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -12,7 +12,7 @@ <div class="_gaps_m"> <MkPushNotificationAllowButton ref="allowButton"/> - <MkSwitch :disabled="!pushRegistrationInServer" :model-value="sendReadMessage" @update:model-value="onChangeSendReadMessage"> + <MkSwitch :disabled="!pushRegistrationInServer" :modelValue="sendReadMessage" @update:modelValue="onChangeSendReadMessage"> <template #label>{{ i18n.ts.sendPushNotificationReadMessage }}</template> <template #caption> <I18n :src="i18n.ts.sendPushNotificationReadMessageCaption"> diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 776305d723c087986b38d0f644384847e48e138d..0b73780a8bd7ec05e9a07278a85d5ef8899cbc6a 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -53,6 +53,17 @@ </MkSwitch> </div> </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-code"></i></template> + <template #label>{{ i18n.ts.developer }}</template> + + <div class="_gaps_m"> + <MkSwitch v-model="devMode"> + <template #label>{{ i18n.ts.devMode }}</template> + </MkSwitch> + </div> + </MkFolder> </div> </FormSection> @@ -80,6 +91,7 @@ import FormSection from '@/components/form/section.vue'; const reportError = computed(defaultStore.makeGetterSetter('reportError')); const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct')); +const devMode = computed(defaultStore.makeGetterSetter('devMode')); function onChangeInjectFeaturedNote(v) { os.api('i/update', { diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 8b57dceefbff3f6d3b3e46ee625f98381e3e6f0a..75fae014f85598721a11894c0cbe33d5e9220566 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -8,7 +8,7 @@ <div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_s" style="padding: 20px;"> <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> - <MkSwitch :model-value="plugin.active" @update:model-value="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch> + <MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch> <MkKeyValue> <template #key>{{ i18n.ts.author }}</template> @@ -94,7 +94,3 @@ definePageMetadata({ icon: 'ti ti-plug', }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 6613ce4c1d8639758668f1276e4d48c1c11c001c..e34901cd11fe89540a22a64f7b2f716e948e7b8a 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -32,7 +32,7 @@ </template> <script lang="ts" setup> -import { computed, onMounted, onUnmounted, useCssModule } from 'vue'; +import { computed, onMounted, onUnmounted } from 'vue'; import { v4 as uuid } from 'uuid'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/MkButton.vue'; @@ -40,7 +40,7 @@ import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os'; import { ColdDeviceStorage, defaultStore } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { version, host } from '@/config'; @@ -48,8 +48,6 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { miLocalStorage } from '@/local-storage'; const { t, ts } = i18n; -useCssModule(); - const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'menu', 'visibility', @@ -125,7 +123,7 @@ type Profile = { }; }; -const connection = $i && stream.useChannel('main'); +const connection = $i && useStream().useChannel('main'); let profiles = $ref<Record<string, Profile> | null>(null); diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index a1af0ba80bbb200bc22a7b846c36d27fd49d16c8..7fd4d6d34e1e94dd5eec06c6993325c60fd87724 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -1,14 +1,14 @@ <template> <div class="_gaps_m"> - <MkSwitch v-model="isLocked" @update:model-value="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch> - <MkSwitch v-if="isLocked" v-model="autoAcceptFollowed" @update:model-value="save()">{{ i18n.ts.autoAcceptFollowed }}</MkSwitch> + <MkSwitch v-model="isLocked" @update:modelValue="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch> + <MkSwitch v-if="isLocked" v-model="autoAcceptFollowed" @update:modelValue="save()">{{ i18n.ts.autoAcceptFollowed }}</MkSwitch> - <MkSwitch v-model="publicReactions" @update:model-value="save()"> + <MkSwitch v-model="publicReactions" @update:modelValue="save()"> {{ i18n.ts.makeReactionsPublic }} <template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template> </MkSwitch> - <MkSelect v-model="ffVisibility" @update:model-value="save()"> + <MkSelect v-model="ffVisibility" @update:modelValue="save()"> <template #label>{{ i18n.ts.ffVisibility }}</template> <option value="public">{{ i18n.ts._ffVisibility.public }}</option> <option value="followers">{{ i18n.ts._ffVisibility.followers }}</option> @@ -16,26 +16,26 @@ <template #caption>{{ i18n.ts.ffVisibilityDescription }}</template> </MkSelect> - <MkSwitch v-model="hideOnlineStatus" @update:model-value="save()"> + <MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()"> {{ i18n.ts.hideOnlineStatus }} <template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template> </MkSwitch> - <MkSwitch v-model="noCrawle" @update:model-value="save()"> + <MkSwitch v-model="noCrawle" @update:modelValue="save()"> {{ i18n.ts.noCrawle }} <template #caption>{{ i18n.ts.noCrawleDescription }}</template> </MkSwitch> - <MkSwitch v-model="preventAiLearning" @update:model-value="save()"> + <MkSwitch v-model="preventAiLearning" @update:modelValue="save()"> {{ i18n.ts.preventAiLearning }}<span class="_beta">{{ i18n.ts.beta }}</span> <template #caption>{{ i18n.ts.preventAiLearningDescription }}</template> </MkSwitch> - <MkSwitch v-model="isExplorable" @update:model-value="save()"> + <MkSwitch v-model="isExplorable" @update:modelValue="save()"> {{ i18n.ts.makeExplorable }} <template #caption>{{ i18n.ts.makeExplorableDescription }}</template> </MkSwitch> <FormSection> <div class="_gaps_m"> - <MkSwitch v-model="rememberNoteVisibility" @update:model-value="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch> + <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch> <MkFolder v-if="!rememberNoteVisibility"> <template #label>{{ i18n.ts.defaultNoteVisibility }}</template> <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> @@ -56,7 +56,7 @@ </div> </FormSection> - <MkSwitch v-model="keepCw" @update:model-value="save()">{{ i18n.ts.keepCw }}</MkSwitch> + <MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch> </div> </template> diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 6ffd6826108839dbe650f317d9f0f62c96ba0d2b..58217d047546dc8c71536c17442bfb1a67d51efb 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -1,28 +1,28 @@ <template> <div class="_gaps_m"> - <div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> - <div class="avatar"> - <MkAvatar class="avatar" :user="$i" @click="changeAvatar"/> - <MkButton primary rounded class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton> + <div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> + <div :class="$style.avatarContainer"> + <MkAvatar :class="$style.avatar" :user="$i" @click="changeAvatar"/> + <MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton> </div> - <MkButton primary rounded class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton> + <MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton> </div> - <MkInput v-model="profile.name" :max="30" manual-save> + <MkInput v-model="profile.name" :max="30" manualSave> <template #label>{{ i18n.ts._profile.name }}</template> </MkInput> - <MkTextarea v-model="profile.description" :max="500" tall manual-save> + <MkTextarea v-model="profile.description" :max="500" tall manualSave> <template #label>{{ i18n.ts._profile.description }}</template> <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template> </MkTextarea> - <MkInput v-model="profile.location" manual-save> + <MkInput v-model="profile.location" manualSave> <template #label>{{ i18n.ts.location }}</template> <template #prefix><i class="ti ti-map-pin"></i></template> </MkInput> - <MkInput v-model="profile.birthday" type="date" manual-save> + <MkInput v-model="profile.birthday" type="date" manualSave> <template #label>{{ i18n.ts.birthday }}</template> <template #prefix><i class="ti ti-cake"></i></template> </MkInput> @@ -48,7 +48,7 @@ <Sortable v-model="fields" class="_gaps_s" - item-key="id" + itemKey="id" :animation="150" :handle="'.' + $style.dragItemHandle" @start="e => e.item.classList.add('active')" @@ -59,7 +59,7 @@ <button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button> <button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button> <div :class="$style.dragItemForm"> - <FormSplit :min-width="200"> + <FormSplit :minWidth="200"> <MkInput v-model="element.name" small> <template #label>{{ i18n.ts._profile.metadataLabel }}</template> </MkInput> @@ -88,11 +88,11 @@ <MkSelect v-model="reactionAcceptance"> <template #label>{{ i18n.ts.reactionAcceptance }}</template> <option :value="null">{{ i18n.ts.all }}</option> - <option value="likeOnly">{{ i18n.ts.likeOnly }}</option> <option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option> + <option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option> + <option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option> + <option value="likeOnly">{{ i18n.ts.likeOnly }}</option> </MkSelect> - - <MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch> </div> </template> @@ -248,36 +248,35 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.llvierxe { +<style lang="scss" module> +.avatarAndBanner { position: relative; background-size: cover; background-position: center; border: solid 1px var(--divider); border-radius: 10px; overflow: clip; +} - > .avatar { - display: inline-block; - text-align: center; - padding: 16px; +.avatarContainer { + display: inline-block; + text-align: center; + padding: 16px; +} - > .avatar { - display: inline-block; - width: 72px; - height: 72px; - margin: 0 auto 16px auto; - } - } +.avatar { + display: inline-block; + width: 72px; + height: 72px; + margin: 0 auto 16px auto; +} - > .bannerEdit { - position: absolute; - top: 16px; - right: 16px; - } +.bannerEdit { + position: absolute; + top: 16px; + right: 16px; } -</style> -<style lang="scss" module> + .metadataRoot { container-type: inline-size; } diff --git a/packages/frontend/src/pages/settings/reaction.vue b/packages/frontend/src/pages/settings/reaction.vue index ed913731d36b41afe81c4076eb8c50e5bfa15a62..cb483e34b540cc0054093867484216dd5f8271a5 100644 --- a/packages/frontend/src/pages/settings/reaction.vue +++ b/packages/frontend/src/pages/settings/reaction.vue @@ -3,15 +3,15 @@ <FromSlot> <template #label>{{ i18n.ts.reactionSettingDescription }}</template> <div v-panel style="border-radius: 6px;"> - <Sortable v-model="reactions" class="zoaiodol" :item-key="item => item" :animation="150" :delay="100" :delay-on-touch-only="true"> + <Sortable v-model="reactions" :class="$style.reactions" :itemKey="item => item" :animation="150" :delay="100" :delayOnTouchOnly="true"> <template #item="{element}"> - <button class="_button item" @click="remove(element, $event)"> + <button class="_button" :class="$style.reactionsItem" @click="remove(element, $event)"> <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/> <MkEmoji v-else :emoji="element" :normal="true"/> </button> </template> <template #footer> - <button class="_button add" @click="chooseEmoji"><i class="ti ti-plus"></i></button> + <button class="_button" :class="$style.reactionsAdd" @click="chooseEmoji"><i class="ti ti-plus"></i></button> </template> </Sortable> </div> @@ -135,20 +135,20 @@ definePageMetadata({ }); </script> -<style lang="scss" scoped> -.zoaiodol { +<style lang="scss" module> +.reactions { padding: 12px; font-size: 1.1em; +} - > .item { - display: inline-block; - padding: 8px; - cursor: move; - } +.reactionsItem { + display: inline-block; + padding: 8px; + cursor: move; +} - > .add { - display: inline-block; - padding: 8px; - } +.reactionsAdd { + display: inline-block; + padding: 8px; } </style> diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue index ba510dced3eb8df4ede8c65a9bf391bbeedd234e..05753c9b60d7341f64576506f63ae07f9a7a54eb 100644 --- a/packages/frontend/src/pages/settings/roles.vue +++ b/packages/frontend/src/pages/settings/roles.vue @@ -3,7 +3,7 @@ <FormSection first> <template #label>{{ i18n.ts.rolesAssignedToMe }}</template> <div class="_gaps_s"> - <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :for-moderation="false"/> + <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/> </div> </FormSection> <FormSection> diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue index 0cc2df09c5e6c0e8524c3b579c7b7e08a7ebf5b8..2da84763a3d68f7175a5a7df557cf9abe8255c4c 100644 --- a/packages/frontend/src/pages/settings/security.vue +++ b/packages/frontend/src/pages/settings/security.vue @@ -9,7 +9,7 @@ <FormSection> <template #label>{{ i18n.ts.signinHistory }}</template> - <MkPagination :pagination="pagination" disable-auto-load> + <MkPagination :pagination="pagination" disableAutoLoad> <template #default="{items}"> <div> <div v-for="item in items" :key="item.id" v-panel class="timnmucd"> diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index aa9f52800608e64acc2be0ade2b5baf721cc8079..c1a333548de9e907c9abfe0c2050b36dd8257064 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -4,7 +4,7 @@ <template #label>{{ i18n.ts.sound }}</template> <option v-for="x in soundsTypes" :key="x" :value="x">{{ x == null ? i18n.ts.none : x }}</option> </MkSelect> - <MkRange v-model="volume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`"> + <MkRange v-model="volume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`"> <template #label>{{ i18n.ts.volume }}</template> </MkRange> diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index 724fe4347857098647de6cbdc278b861952ef2fc..c2bf3f8cd57a2802672397a82c336309bcee7ca5 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -1,6 +1,6 @@ <template> <div class="_gaps_m"> - <MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`"> + <MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`"> <template #label>{{ i18n.ts.masterVolume }}</template> </MkRange> diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue index 81ff873e9e55f50c7265f97669e203c9290d0252..c73ff7c0751d9e532536df82cc6ff230840142f4 100644 --- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue @@ -7,7 +7,7 @@ <option value="userList">User list timeline</option> </MkSelect> - <MkInput v-model="statusbar.name" manual-save> + <MkInput v-model="statusbar.name" manualSave> <template #label>{{ i18n.ts.label }}</template> </MkInput> @@ -25,13 +25,13 @@ </MkRadios> <template v-if="statusbar.type === 'rss'"> - <MkInput v-model="statusbar.props.url" manual-save type="url"> + <MkInput v-model="statusbar.props.url" manualSave type="url"> <template #label>URL</template> </MkInput> <MkSwitch v-model="statusbar.props.shuffle"> <template #label>{{ i18n.ts.shuffle }}</template> </MkSwitch> - <MkInput v-model="statusbar.props.refreshIntervalSec" manual-save type="number"> + <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number"> <template #label>{{ i18n.ts.refreshInterval }}</template> </MkInput> <MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1"> @@ -43,7 +43,7 @@ </MkSwitch> </template> <template v-else-if="statusbar.type === 'federation'"> - <MkInput v-model="statusbar.props.refreshIntervalSec" manual-save type="number"> + <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number"> <template #label>{{ i18n.ts.refreshInterval }}</template> </MkInput> <MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1"> @@ -62,7 +62,7 @@ <template #label>{{ i18n.ts.userList }}</template> <option v-for="list in userLists" :value="list.id">{{ list.name }}</option> </MkSelect> - <MkInput v-model="statusbar.props.refreshIntervalSec" manual-save type="number"> + <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number"> <template #label>{{ i18n.ts.refreshInterval }}</template> </MkInput> <MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1"> diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue index f5a090a63b1ca251bb3865f5e326620ff69fad31..bfb69936e13423a311bab3321deadc1db3a05f30 100644 --- a/packages/frontend/src/pages/settings/statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.vue @@ -3,7 +3,7 @@ <MkFolder v-for="x in statusbars" :key="x.id"> <template #label>{{ x.type ?? i18n.ts.notSet }}</template> <template #suffix>{{ x.name }}</template> - <XStatusbar :_id="x.id" :user-lists="userLists"/> + <XStatusbar :_id="x.id" :userLists="userLists"/> </MkFolder> <MkButton primary @click="add">{{ i18n.ts.add }}</MkButton> </div> diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue index d1821a00d47019497c57e020312fe20b07cf9200..0255435112be234c7c62650d2273d3bef533905f 100644 --- a/packages/frontend/src/pages/settings/theme.manage.vue +++ b/packages/frontend/src/pages/settings/theme.manage.vue @@ -10,13 +10,13 @@ </optgroup> </MkSelect> <template v-if="selectedTheme"> - <MkInput readonly :model-value="selectedTheme.author"> + <MkInput readonly :modelValue="selectedTheme.author"> <template #label>{{ i18n.ts.author }}</template> </MkInput> - <MkTextarea v-if="selectedTheme.desc" readonly :model-value="selectedTheme.desc"> + <MkTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc"> <template #label>{{ i18n.ts._theme.description }}</template> </MkTextarea> - <MkTextarea readonly tall :model-value="selectedThemeCode"> + <MkTextarea readonly tall :modelValue="selectedThemeCode"> <template #label>{{ i18n.ts._theme.code }}</template> <template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template> </MkTextarea> diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue index 78e0710162aff4edd01393002ea79b4cba1ea9d8..e0ac899230f9db90f8782f33f5d953ee5c42dbf8 100644 --- a/packages/frontend/src/pages/share.vue +++ b/packages/frontend/src/pages/share.vue @@ -1,22 +1,25 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> + <MkSpacer :contentMax="800"> <MkPostForm v-if="state === 'writing'" fixed :instant="true" - :initial-text="initialText" - :initial-visibility="visibility" - :initial-files="files" - :initial-local-only="localOnly" + :initialText="initialText" + :initialVisibility="visibility" + :initialFiles="files" + :initialLocalOnly="localOnly" :reply="reply" :renote="renote" - :initial-visible-users="visibleUsers" + :initialVisibleUsers="visibleUsers" class="_panel" @posted="state = 'posted'" /> - <MkButton v-else-if="state === 'posted'" primary class="close" @click="close()">{{ i18n.ts.close }}</MkButton> + <div v-else-if="state === 'posted'" class="_buttonsCenter"> + <MkButton primary @click="close">{{ i18n.ts.close }}</MkButton> + <MkButton @click="goToMisskey">{{ i18n.ts.goToMisskey }}</MkButton> + </div> </MkSpacer> </MkStickyContainer> </template> @@ -148,10 +151,14 @@ function close(): void { // é–‰ã˜ãªã‘ã‚Œã°100ms後タイムライン㫠window.setTimeout(() => { - mainRouter.push('/'); + location.href = '/'; }, 100); } +function goToMisskey(): void { + location.href = '/'; +} + const headerActions = $computed(() => []); const headerTabs = $computed(() => []); @@ -161,9 +168,3 @@ definePageMetadata({ icon: 'ti ti-share', }); </script> - -<style lang="scss" scoped> -.close { - margin: 16px auto; -} -</style> diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue index 545953231032da2655ec717c9906fee373311714..61d7eb24fd2fbe9aff5fbd3eb7eaf75b607af56c 100644 --- a/packages/frontend/src/pages/signup-complete.vue +++ b/packages/frontend/src/pages/signup-complete.vue @@ -1,41 +1,80 @@ <template> <div> - {{ i18n.ts.processing }} + <MkAnimBg style="position: fixed; top: 0;"/> + <div :class="$style.formContainer"> + <form :class="$style.form" class="_panel" @submit.prevent="submit()"> + <div :class="$style.banner"> + <i class="ti ti-user-check"></i> + </div> + <div class="_gaps_m" style="padding: 32px;"> + <div>{{ i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }) }}</div> + <div> + <MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;"> + {{ submitting ? i18n.ts.processing : i18n.ts.gotIt }}<MkEllipsis v-if="submitting"/> + </MkButton> + </div> + </div> + </form> + </div> </div> </template> <script lang="ts" setup> -import { onMounted } from 'vue'; -import * as os from '@/os'; +import { } from 'vue'; +import MkButton from '@/components/MkButton.vue'; +import MkAnimBg from '@/components/MkAnimBg.vue'; import { login } from '@/account'; import { i18n } from '@/i18n'; -import { definePageMetadata } from '@/scripts/page-metadata'; +import * as os from '@/os'; + +let submitting = $ref(false); const props = defineProps<{ code: string; }>(); -onMounted(async () => { - await os.alert({ - type: 'info', - text: i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }), - }); - const res = await os.apiWithDialog('signup-pending', { - code: props.code, - }); - login(res.i, '/'); -}); - -const headerActions = $computed(() => []); +function submit() { + if (submitting) return; + submitting = true; -const headerTabs = $computed(() => []); + os.api('signup-pending', { + code: props.code, + }).then(res => { + return login(res.i, '/'); + }).catch(() => { + submitting = false; -definePageMetadata({ - title: i18n.ts.signup, - icon: 'ti ti-user', -}); + os.alert({ + type: 'error', + text: i18n.ts.somethingHappened, + }); + }); +} </script> -<style lang="scss" scoped> +<style lang="scss" module> +.formContainer { + min-height: 100svh; + padding: 32px 32px 64px 32px; + box-sizing: border-box; +display: grid; +place-content: center; +} + +.form { + position: relative; + z-index: 10; + border-radius: var(--radius); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + overflow: clip; + max-width: 500px; +} +.banner { + padding: 16px; + text-align: center; + font-size: 26px; + background-color: var(--accentedBg); + color: var(--accent); +} </style> diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 511052c4242395afc593e62303860e992bed3190..104e73886655f364f80eb93a715b9a8a92742466 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -1,16 +1,28 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> - <MkNotes class="" :pagination="pagination"/> + <MkSpacer :contentMax="800"> + <MkNotes ref="notes" class="" :pagination="pagination"/> </MkSpacer> + <template v-if="$i" #footer> + <div :class="$style.footer"> + <MkSpacer :contentMax="800" :marginMin="16" :marginMax="16"> + <MkButton rounded primary :class="$style.button" @click="post()"><i class="ti ti-pencil"></i>{{ i18n.ts.postToHashtag }}</MkButton> + </MkSpacer> + </div> + </template> </MkStickyContainer> </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, ref } from 'vue'; import MkNotes from '@/components/MkNotes.vue'; +import MkButton from '@/components/MkButton.vue'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; +import { defaultStore } from '@/store'; +import * as os from '@/os'; const props = defineProps<{ tag: string; @@ -23,6 +35,16 @@ const pagination = { tag: props.tag, })), }; +const notes = ref<InstanceType<typeof MkNotes>>(); + +async function post() { + defaultStore.set('postFormHashtags', props.tag); + defaultStore.set('postFormWithHashtags', true); + await os.post(); + defaultStore.set('postFormHashtags', ''); + defaultStore.set('postFormWithHashtags', false); + notes.value?.pagingComponent?.reload(); +} const headerActions = $computed(() => []); @@ -33,3 +55,16 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-hash', }))); </script> + +<style lang="scss" module> +.footer { + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--divider); + display: flex; +} + +.button { + margin: 0 auto var(--margin) auto; +} +</style> diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index 56fdfdf7820ca5ca0a62938ec6f9ca3c37eecbf8..f942b5005b5a160a44e3464671a1867407116fb1 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -1,9 +1,9 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="800" :marginMin="16" :marginMax="32"> <div class="cwepdizn _gaps_m"> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.backgroundColor }}</template> <div class="cwepdizn-colors"> <div class="row"> @@ -19,7 +19,7 @@ </div> </MkFolder> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.accentColor }}</template> <div class="cwepdizn-colors"> <div class="row"> @@ -30,7 +30,7 @@ </div> </MkFolder> - <MkFolder :default-open="true"> + <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.textColor }}</template> <div class="cwepdizn-colors"> <div class="row"> @@ -41,7 +41,7 @@ </div> </MkFolder> - <MkFolder :default-open="false"> + <MkFolder :defaultOpen="false"> <template #icon><i class="ti ti-code"></i></template> <template #label>{{ i18n.ts.editCode }}</template> @@ -53,7 +53,7 @@ </div> </MkFolder> - <MkFolder :default-open="false"> + <MkFolder :defaultOpen="false"> <template #label>{{ i18n.ts.addDescription }}</template> <div class="_gaps_m"> diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 1bf4cdc99ae4ee1ae6222fff1b65133a3c8f5b48..a441c6f728666e97cb304c9e4678e4da29e562e6 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -1,12 +1,12 @@ <template> <MkStickyContainer> - <template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :display-my-avatar="true"/></template> - <MkSpacer :content-max="800"> + <template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template> + <MkSpacer :contentMax="800"> <div ref="rootEl" v-hotkey.global="keymap"> <XTutorial v-if="$i && defaultStore.reactiveState.timelineTutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/> <MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/> - <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> + <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div :class="$style.tl"> <MkTimeline ref="tlComponent" @@ -187,13 +187,13 @@ definePageMetadata(computed(() => ({ &:first-child { margin-top: calc(-0.675em - 8px - var(--margin)); } +} - > button { - display: block; - margin: var(--margin) auto 0 auto; - padding: 8px 16px; - border-radius: 32px; - } +.newButton { + display: block; + margin: var(--margin) auto 0 auto; + padding: 8px 16px; + border-radius: 32px; } .postForm { diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue index 94718d1533324a783f3c7bc27dfa60608e8ddc5a..56e8737e1caa7f01203eac55b118e5efabfcd70c 100644 --- a/packages/frontend/src/pages/user-info.vue +++ b/packages/frontend/src/pages/user-info.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="600" :margin-min="16" :margin-max="32"> + <MkSpacer :contentMax="600" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div v-if="tab === 'overview'" class="_gaps_m"> <div class="aeakzknw"> @@ -88,7 +88,7 @@ </div> <div v-else-if="tab === 'moderation'" class="_gaps_m"> - <MkSwitch v-model="suspended" @update:model-value="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch> + <MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch> <div> <MkButton v-if="user.host == null && iAmModerator" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton> @@ -112,7 +112,7 @@ <MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> <div v-for="role in info.roles" :key="role.id" :class="$style.roleItem"> - <MkRolePreview :class="$style.role" :role="role" :for-moderation="true"/> + <MkRolePreview :class="$style.role" :role="role" :forModeration="true"/> <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button> <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> </div> @@ -135,10 +135,10 @@ <MkFolder> <template #icon><i class="ti ti-cloud"></i></template> <template #label>{{ i18n.ts.files }}</template> - <MkFileListForAdmin :pagination="filesPagination" view-mode="grid"/> + <MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/> </MkFolder> - <MkTextarea v-model="moderationNote" manual-save> + <MkTextarea v-model="moderationNote" manualSave> <template #label>Moderation note</template> </MkTextarea> </div> diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue index acf7ea9b2c2abf70ee60726ef8beb62b609a7c9b..f66670e1f6fd7230cb6ec018c7da9a131fa0d31b 100644 --- a/packages/frontend/src/pages/user-list-timeline.vue +++ b/packages/frontend/src/pages/user-list-timeline.vue @@ -1,19 +1,20 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <div ref="rootEl" class="eqqrhokj"> - <div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> - <div class="tl"> - <MkTimeline - ref="tlEl" :key="listId" - class="tl" - src="list" - :list="listId" - :sound="true" - @queue="queueUpdated" - /> + <MkSpacer :contentMax="800"> + <div ref="rootEl"> + <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> + <div :class="$style.tl"> + <MkTimeline + ref="tlEl" :key="listId" + src="list" + :list="listId" + :sound="true" + @queue="queueUpdated" + /> + </div> </div> - </div> + </MkSpacer> </MkStickyContainer> </template> @@ -82,36 +83,29 @@ definePageMetadata(computed(() => list ? { } : null)); </script> -<style lang="scss" scoped> -.eqqrhokj { - padding: var(--margin); - - > .new { - position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); - z-index: 1000; - width: 100%; - margin: calc(-0.675em - 8px - var(--margin)) 0 calc(-0.675em - 8px); - - > button { - display: block; - margin: var(--margin) auto 0 auto; - padding: 8px 16px; - border-radius: 32px; - } - } +<style lang="scss" module> +.new { + position: sticky; + top: calc(var(--stickyTop, 0px) + 16px); + z-index: 1000; + width: 100%; + margin: calc(-0.675em - 8px) 0; - > .tl { - background: var(--bg); - border-radius: var(--radius); - overflow: clip; + &:first-child { + margin-top: calc(-0.675em - 8px - var(--margin)); } } -@container (min-width: 800px) { - .eqqrhokj { - max-width: 800px; - margin: 0 auto; - } +.newButton { + display: block; + margin: var(--margin) auto 0 auto; + padding: 8px 16px; + border-radius: 32px; +} + +.tl { + background: var(--bg); + border-radius: var(--radius); + overflow: clip; } </style> diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue index fac7593e9cac082e76a60e99968028b959f979b2..01ef1126cb62ff77ca67a02dde727a03554904df 100644 --- a/packages/frontend/src/pages/user-tag.vue +++ b/packages/frontend/src/pages/user-tag.vue @@ -2,7 +2,7 @@ <MkStickyContainer> <template #header><MkPageHeader/></template> - <MkSpacer :content-max="1200"> + <MkSpacer :contentMax="1200"> <div class="_gaps_s"> <MkUserList :pagination="tagUsers"/> </div> diff --git a/packages/frontend/src/pages/user/achievements.vue b/packages/frontend/src/pages/user/achievements.vue index 1b3a6e24b3cf1d2d760c47db114b5952ab0116a2..7d5993c265230937babf7952d9a18253a45c4bdc 100644 --- a/packages/frontend/src/pages/user/achievements.vue +++ b/packages/frontend/src/pages/user/achievements.vue @@ -1,6 +1,6 @@ <template> -<MkSpacer :content-max="1200"> - <MkAchievements :user="user" :with-locked="false" :with-description="$i != null && (props.user.id === $i.id)"/> +<MkSpacer :contentMax="1200"> + <MkAchievements :user="user" :withLocked="false" :withDescription="$i != null && (props.user.id === $i.id)"/> </MkSpacer> </template> diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue index cd538ad61fdd6679fea36f5840e75348b4a2a81a..655371ac1d554d824d51e3c24474b464633de2e9 100644 --- a/packages/frontend/src/pages/user/activity.vue +++ b/packages/frontend/src/pages/user/activity.vue @@ -1,5 +1,5 @@ <template> -<MkSpacer :content-max="700"> +<MkSpacer :contentMax="700"> <div class="_gaps"> <MkFoldableSection class="item"> <template #header><i class="ti ti-activity"></i> Heatmap</template> @@ -34,7 +34,3 @@ const props = defineProps<{ }>(); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/user/clips.vue b/packages/frontend/src/pages/user/clips.vue index 95f8cbc296b4f02c39475df339bc3675590018f9..08b7b9a71f13bff7983aa7b2e0d15870c0c8b328 100644 --- a/packages/frontend/src/pages/user/clips.vue +++ b/packages/frontend/src/pages/user/clips.vue @@ -1,10 +1,10 @@ <template> -<MkSpacer :content-max="700"> - <div class="pages-user-clips"> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list"> - <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _margin"> +<MkSpacer :contentMax="700"> + <div> + <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> + <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" :class="$style.item" class="_panel _margin"> <b>{{ item.name }}</b> - <div v-if="item.description" class="description">{{ item.description }}</div> + <div v-if="item.description" :class="$style.description">{{ item.description }}</div> </MkA> </MkPagination> </div> @@ -29,19 +29,15 @@ const pagination = { }; </script> -<style lang="scss" scoped> -.pages-user-clips { - > .list { - > .item { - display: block; - padding: 16px; +<style lang="scss" module> +.item { + display: block; + padding: 16px; +} - > .description { - margin-top: 8px; - padding-top: 8px; - border-top: solid 0.5px var(--divider); - } - } - } +.description { + margin-top: 8px; + padding-top: 8px; + border-top: solid 0.5px var(--divider); } </style> diff --git a/packages/frontend/src/pages/user/follow-list.vue b/packages/frontend/src/pages/user/follow-list.vue index d42acd838f854afc2dfe4810717cf2630564c0de..4e76ddfe795b9baefb6b9525b8e23afb07388409 100644 --- a/packages/frontend/src/pages/user/follow-list.vue +++ b/packages/frontend/src/pages/user/follow-list.vue @@ -1,8 +1,8 @@ <template> <div> - <MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination" class="mk-following-or-followers"> - <div class="users"> - <MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/> + <MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination"> + <div :class="$style.users"> + <MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :user="user"/> </div> </MkPagination> </div> @@ -36,12 +36,10 @@ const followersPagination = { }; </script> -<style lang="scss" scoped> -.mk-following-or-followers { - > .users { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - } +<style lang="scss" module> +.users { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: var(--margin); } </style> diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue index 20573e67e946514d93a3d059977c04139524012a..b330f7863774cf7450bd636409c52ae95be0f2c9 100644 --- a/packages/frontend/src/pages/user/followers.vue +++ b/packages/frontend/src/pages/user/followers.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="1000"> + <MkSpacer :contentMax="1000"> <Transition name="fade" mode="out-in"> <div v-if="user"> <XFollowList :user="user" type="followers"/> @@ -56,6 +56,3 @@ definePageMetadata(computed(() => user ? { avatar: user, } : null)); </script> - -<style lang="scss" scoped> -</style> diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue index 3825f138cfcf9819194073c36a87e4cb68cd2661..9544cf76ca29d1b562d43c1b637b0ab19170c280 100644 --- a/packages/frontend/src/pages/user/following.vue +++ b/packages/frontend/src/pages/user/following.vue @@ -1,7 +1,7 @@ <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="1000"> + <MkSpacer :contentMax="1000"> <Transition name="fade" mode="out-in"> <div v-if="user"> <XFollowList :user="user" type="following"/> @@ -56,6 +56,3 @@ definePageMetadata(computed(() => user ? { avatar: user, } : null)); </script> - -<style lang="scss" scoped> -</style> diff --git a/packages/frontend/src/pages/user/gallery.vue b/packages/frontend/src/pages/user/gallery.vue index b80e83fb11f29fd68b5c00b54c2d7baa4cc1a401..b4bbab16fd35e41b61144e96db58be2b48bb0cf7 100644 --- a/packages/frontend/src/pages/user/gallery.vue +++ b/packages/frontend/src/pages/user/gallery.vue @@ -1,7 +1,7 @@ <template> -<MkSpacer :content-max="700"> +<MkSpacer :contentMax="700"> <MkPagination v-slot="{items}" :pagination="pagination"> - <div class="jrnovfpt"> + <div :class="$style.root"> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> </div> </MkPagination> @@ -28,8 +28,8 @@ const pagination = { }; </script> -<style lang="scss" scoped> -.jrnovfpt { +<style lang="scss" module> +.root { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-gap: 12px; diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 9c133346d5440ce0d2077b02e44fe2999b5600a1..2e69eb367ba9c06c066a55cbe410e2ff4e945090 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -1,5 +1,5 @@ <template> -<MkSpacer :content-max="narrow ? 800 : 1100"> +<MkSpacer :contentMax="narrow ? 800 : 1100"> <div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;"> <div class="main _gaps"> <!-- TODO --> @@ -7,7 +7,7 @@ <!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> --> <div class="profile _gaps"> - <MkAccountMoved v-if="user.movedTo" :moved-to="user.movedTo"/> + <MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/> <MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/> <div :key="user.id" class="main _panel"> @@ -49,7 +49,7 @@ </span> </div> <div v-if="iAmModerator" class="moderationNote"> - <MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manual-save> + <MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave> <template #label>Moderation note</template> </MkTextarea> <div v-else> @@ -69,7 +69,7 @@ </div> <div class="description"> <MkOmit> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/> + <Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" :i="$i"/> <p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p> </MkOmit> </div> @@ -123,7 +123,7 @@ <XPhotos :key="user.id" :user="user"/> <XActivity :key="user.id" :user="user"/> </template> - <MkNotes v-if="!disableNotes" :class="$style.tl" :no-gap="true" :pagination="pagination"/> + <MkNotes v-if="!disableNotes" :class="$style.tl" :noGap="true" :pagination="pagination"/> </div> </div> <div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;"> diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue index 2d9ee85bc489dd42ce8e6cea13a35c10d3c6494d..64d36307e9957ae74c67d8f5a0f2452cb80df00b 100644 --- a/packages/frontend/src/pages/user/index.activity.vue +++ b/packages/frontend/src/pages/user/index.activity.vue @@ -9,7 +9,7 @@ </template> <div style="padding: 8px;"> - <MkChart :src="chartSrc" :args="{ user, withoutAll: true }" span="day" :limit="limit" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="5"/> + <MkChart :src="chartSrc" :args="{ user, withoutAll: true }" span="day" :limit="limit" :bar="true" :stacked="true" :detailed="false" :aspectRatio="5"/> </div> </MkContainer> </template> diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue index d8fc253910b350f87864c8d1c939dd84793c547d..91c580ce96862e79300135fa8b01201c213a6880 100644 --- a/packages/frontend/src/pages/user/index.timeline.vue +++ b/packages/frontend/src/pages/user/index.timeline.vue @@ -1,5 +1,5 @@ <template> -<MkSpacer :content-max="800" style="padding-top: 0"> +<MkSpacer :contentMax="800" style="padding-top: 0"> <MkStickyContainer> <template #header> <MkTab v-model="include" :class="$style.tab"> @@ -8,7 +8,7 @@ <option value="files">{{ i18n.ts.withFiles }}</option> </MkTab> </template> - <MkNotes :no-gap="true" :pagination="pagination" :class="$style.tl"/> + <MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/> </MkStickyContainer> </MkSpacer> </template> diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 03a226cc095c5565e1a677f715357edebcd23da5..6aba815e9d74d68da4a322031f59c58d30af9c42 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -2,20 +2,19 @@ <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> <div> - <Transition name="fade" mode="out-in"> - <div v-if="user"> - <XHome v-if="tab === 'home'" :user="user"/> - <XTimeline v-else-if="tab === 'notes'" :user="user" /> - <XActivity v-else-if="tab === 'activity'" :user="user"/> - <XAchievements v-else-if="tab === 'achievements'" :user="user"/> - <XReactions v-else-if="tab === 'reactions'" :user="user"/> - <XClips v-else-if="tab === 'clips'" :user="user"/> - <XPages v-else-if="tab === 'pages'" :user="user"/> - <XGallery v-else-if="tab === 'gallery'" :user="user"/> - </div> - <MkError v-else-if="error" @retry="fetchUser()"/> - <MkLoading v-else/> - </Transition> + <div v-if="user"> + <XHome v-if="tab === 'home'" :user="user"/> + <XTimeline v-else-if="tab === 'notes'" :user="user"/> + <XActivity v-else-if="tab === 'activity'" :user="user"/> + <XAchievements v-else-if="tab === 'achievements'" :user="user"/> + <XReactions v-else-if="tab === 'reactions'" :user="user"/> + <XClips v-else-if="tab === 'clips'" :user="user"/> + <XLists v-else-if="tab === 'lists'" :user="user"/> + <XPages v-else-if="tab === 'pages'" :user="user"/> + <XGallery v-else-if="tab === 'gallery'" :user="user"/> + </div> + <MkError v-else-if="error" @retry="fetchUser()"/> + <MkLoading v-else/> </div> </MkStickyContainer> </template> @@ -36,6 +35,7 @@ const XActivity = defineAsyncComponent(() => import('./activity.vue')); const XAchievements = defineAsyncComponent(() => import('./achievements.vue')); const XReactions = defineAsyncComponent(() => import('./reactions.vue')); const XClips = defineAsyncComponent(() => import('./clips.vue')); +const XLists = defineAsyncComponent(() => import('./lists.vue')); const XPages = defineAsyncComponent(() => import('./pages.vue')); const XGallery = defineAsyncComponent(() => import('./gallery.vue')); @@ -90,6 +90,10 @@ const headerTabs = $computed(() => user ? [{ key: 'clips', title: i18n.ts.clips, icon: 'ti ti-paperclip', +}, { + key: 'lists', + title: i18n.ts.lists, + icon: 'ti ti-list', }, { key: 'pages', title: i18n.ts.pages, @@ -112,14 +116,3 @@ definePageMetadata(computed(() => user ? { }, } : null)); </script> - -<style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} -</style> diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue new file mode 100644 index 0000000000000000000000000000000000000000..78f03d2b38ba0d8739fd06eb7061b082cf18d52e --- /dev/null +++ b/packages/frontend/src/pages/user/lists.vue @@ -0,0 +1,51 @@ +<template> +<MkStickyContainer> + <MkSpacer :contentMax="700"> + <div> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists"> + <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`"> + <div>{{ list.name }}</div> + <MkAvatars :userIds="list.userIds"/> + </MkA> + </MkPagination> + </div> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import {} from 'vue'; +import * as misskey from 'misskey-js'; +import MkPagination from '@/components/MkPagination.vue'; +import MkStickyContainer from '@/components/global/MkStickyContainer.vue'; +import MkSpacer from '@/components/global/MkSpacer.vue'; +import MkAvatars from '@/components/MkAvatars.vue'; + +const props = defineProps<{ + user: misskey.entities.UserDetailed; +}>(); + +const pagination = { + endpoint: 'users/lists/list' as const, + noPaging: true, + limit: 10, + params: { + userId: props.user.id, + }, +}; +</script> + +<style lang="scss" module> +.list { + display: block; + padding: 16px; + border: solid 1px var(--divider); + border-radius: 6px; + margin-bottom: 8px; + + &:hover { + border: solid 1px var(--accent); + text-decoration: none; + } +} +</style> diff --git a/packages/frontend/src/pages/user/pages.vue b/packages/frontend/src/pages/user/pages.vue index 7ea1d75f4304424318557482d025729d2ca8770d..a2975c707910bfdb64cf19d86e4bbd1864299ed5 100644 --- a/packages/frontend/src/pages/user/pages.vue +++ b/packages/frontend/src/pages/user/pages.vue @@ -1,5 +1,5 @@ <template> -<MkSpacer :content-max="700"> +<MkSpacer :contentMax="700"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> <MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_margin"/> </MkPagination> @@ -24,7 +24,3 @@ const pagination = { })), }; </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/pages/user/reactions.vue b/packages/frontend/src/pages/user/reactions.vue index 24129ec024670cba3e0e5207a606da8b4155549a..228160339472f516302833b1d5408200d655c241 100644 --- a/packages/frontend/src/pages/user/reactions.vue +++ b/packages/frontend/src/pages/user/reactions.vue @@ -1,11 +1,11 @@ <template> -<MkSpacer :content-max="700"> +<MkSpacer :contentMax="700"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> - <div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _margin afdcfbfb"> - <div class="header"> - <MkAvatar class="avatar" :user="user"/> - <MkReactionIcon class="reaction" :reaction="item.type" :no-style="true"/> - <MkTime :time="item.createdAt" class="createdAt"/> + <div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="_panel _margin"> + <div :class="$style.header"> + <MkAvatar :class="$style.avatar" :user="user"/> + <MkReactionIcon :class="$style.reaction" :reaction="item.type" :noStyle="true"/> + <MkTime :time="item.createdAt" :class="$style.createdAt"/> </div> <MkNote :key="item.id" :note="item.note"/> </div> @@ -33,29 +33,27 @@ const pagination = { }; </script> -<style lang="scss" scoped> -.afdcfbfb { - > .header { - display: flex; - align-items: center; - padding: 8px 16px; - margin-bottom: 8px; - border-bottom: solid 2px var(--divider); +<style lang="scss" module> +.header { + display: flex; + align-items: center; + padding: 8px 16px; + margin-bottom: 8px; + border-bottom: solid 2px var(--divider); +} - > .avatar { - width: 24px; - height: 24px; - margin-right: 8px; - } +.avatar { + width: 24px; + height: 24px; + margin-right: 8px; +} - > .reaction { - width: 32px; - height: 32px; - } +.reaction { + width: 32px; + height: 32px; +} - > .createdAt { - margin-left: auto; - } - } +.createdAt { + margin-left: auto; } </style> diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index 929152bd5abcf86f8e0f960c03f6003864b96ea4..f082b4b3c79cecf1a759404bfd3f4036f563c82e 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -6,11 +6,11 @@ <div class="shape2"></div> <img src="/client-assets/misskey.svg" class="misskey"/> <div class="emojis"> - <MkEmoji :normal="true" :no-style="true" emoji="ðŸ‘"/> - <MkEmoji :normal="true" :no-style="true" emoji="â¤"/> - <MkEmoji :normal="true" :no-style="true" emoji="😆"/> - <MkEmoji :normal="true" :no-style="true" emoji="🎉"/> - <MkEmoji :normal="true" :no-style="true" emoji="ðŸ®"/> + <MkEmoji :normal="true" :noStyle="true" emoji="ðŸ‘"/> + <MkEmoji :normal="true" :noStyle="true" emoji="â¤"/> + <MkEmoji :normal="true" :noStyle="true" emoji="😆"/> + <MkEmoji :normal="true" :noStyle="true" emoji="🎉"/> + <MkEmoji :normal="true" :noStyle="true" emoji="ðŸ®"/> </div> <div class="contents"> <MkVisitorDashboard/> diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 7728d97a6554f348bf763994b521ceaeb0d429a6..2081cb9c93b2fdc8c7a337c65858f6e0bd49e2ea 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -1,27 +1,32 @@ <template> -<form :class="$style.root" class="_panel" @submit.prevent="submit()"> - <div :class="$style.title"> - <div>Welcome to Misskey!</div> - <div :class="$style.version">v{{ version }}</div> +<div> + <MkAnimBg style="position: fixed; top: 0;"/> + <div :class="$style.formContainer"> + <form :class="$style.form" class="_panel" @submit.prevent="submit()"> + <div :class="$style.title"> + <div>Welcome to Misskey!</div> + <div :class="$style.version">v{{ version }}</div> + </div> + <div class="_gaps_m" style="padding: 32px;"> + <div>{{ i18n.ts.intro }}</div> + <MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username> + <template #label>{{ i18n.ts.username }}</template> + <template #prefix>@</template> + <template #suffix>@{{ host }}</template> + </MkInput> + <MkInput v-model="password" type="password" data-cy-admin-password> + <template #label>{{ i18n.ts.password }}</template> + <template #prefix><i class="ti ti-lock"></i></template> + </MkInput> + <div> + <MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;"> + {{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/> + </MkButton> + </div> + </div> + </form> </div> - <div class="_gaps_m" style="padding: 32px;"> - <div>{{ i18n.ts.intro }}</div> - <MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username> - <template #label>{{ i18n.ts.username }}</template> - <template #prefix>@</template> - <template #suffix>@{{ host }}</template> - </MkInput> - <MkInput v-model="password" type="password" data-cy-admin-password> - <template #label>{{ i18n.ts.password }}</template> - <template #prefix><i class="ti ti-lock"></i></template> - </MkInput> - <div> - <MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;"> - {{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/> - </MkButton> - </div> - </div> -</form> +</div> </template> <script lang="ts" setup> @@ -32,6 +37,7 @@ import { host, version } from '@/config'; import * as os from '@/os'; import { login } from '@/account'; import { i18n } from '@/i18n'; +import MkAnimBg from '@/components/MkAnimBg.vue'; let username = $ref(''); let password = $ref(''); @@ -58,12 +64,21 @@ function submit() { </script> <style lang="scss" module> -.root { +.formContainer { + min-height: 100svh; + padding: 32px 32px 64px 32px; + box-sizing: border-box; +display: grid; +place-content: center; +} + +.form { + position: relative; + z-index: 10; border-radius: var(--radius); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); - overflow: hidden; + overflow: clip; max-width: 500px; - margin: 32px auto; } .title { diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue index 6ec6e3f8633ef977c137b7fcb0c5ecb35aec95bd..a93d103d4feb6c5936279f5984867ed515a6a677 100644 --- a/packages/frontend/src/pages/welcome.timeline.vue +++ b/packages/frontend/src/pages/welcome.timeline.vue @@ -3,16 +3,16 @@ <div ref="scrollEl" :class="[$style.scrollbox, { [$style.scroll]: isScrolling }]"> <div v-for="note in notes" :key="note.id" :class="$style.note"> <div class="_panel" :class="$style.content"> - <div :class="$style.body"> + <div> <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/> <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> </div> <div v-if="note.files.length > 0" :class="$style.richcontent"> - <MkMediaList :media-list="note.files"/> + <MkMediaList :mediaList="note.files"/> </div> <div v-if="note.poll"> - <MkPoll :note="note" :read-only="true"/> + <MkPoll :note="note" :readOnly="true"/> </div> </div> <MkReactionsViewer ref="reactionsViewer" :note="note"/> diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index 2616a8a1d5f1d26206eb90f176c9bf789e135e5f..d97bd4be62e9a996c077b06e150b1d393faa5e1b 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -6,7 +6,7 @@ import { $i } from './account'; import { api } from './os'; import { get, set } from './scripts/idb-proxy'; import { defaultStore } from './store'; -import { stream } from './stream'; +import { useStream } from './stream'; import { deepClone } from './scripts/clone'; type StateDef = Record<string, { @@ -26,8 +26,6 @@ type PizzaxChannelMessage<T extends StateDef> = { userId?: string; }; -const connection = $i && stream.useChannel('main'); - export class Storage<T extends StateDef> { public readonly ready: Promise<void>; public readonly loaded: Promise<void>; @@ -105,8 +103,10 @@ export class Storage<T extends StateDef> { }); if ($i) { + const connection = useStream().useChannel('main'); + // streamingã®user storage updateイベントを監視ã—ã¦æ›´æ–° - connection?.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => { + connection.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => { if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return; this.reactiveState[key].value = this.state[key] = value; diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index e46c1eeb77ebd70fdedc64cc68a463cfbfb6df22..6b11137d799158792be486f02a08ad087168ab92 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -30,6 +30,10 @@ export const routes = [{ name: 'note', path: '/notes/:noteId', component: page(() => import('./pages/note.vue')), +}, { + name: 'list', + path: '/list/:listId', + component: page(() => import('./pages/list.vue')), }, { path: '/clips/:clipId', component: page(() => import('./pages/clip.vue')), @@ -242,9 +246,6 @@ export const routes = [{ }, { path: '/scratchpad', component: page(() => import('./pages/scratchpad.vue')), -}, { - path: '/preview', - component: page(() => import('./pages/preview.vue')), }, { path: '/auth/:token', component: page(() => import('./pages/auth.vue')), diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend/src/scripts/emojilist.ts index 2e853b58b50947540e30d155b00d52d9a5ba1619..79661b7ce9472f60410fa717093e4bb2c8c2b8a5 100644 --- a/packages/frontend/src/scripts/emojilist.ts +++ b/packages/frontend/src/scripts/emojilist.ts @@ -2,7 +2,6 @@ export const unicodeEmojiCategories = ['face', 'people', 'animals_and_nature', ' export type UnicodeEmojiDef = { name: string; - keywords: string[]; char: string; category: typeof unicodeEmojiCategories[number]; } @@ -10,11 +9,16 @@ export type UnicodeEmojiDef = { // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb import _emojilist from '../emojilist.json'; -export const emojilist = _emojilist as UnicodeEmojiDef[]; +export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({ + name: x[1] as string, + char: x[0] as string, + category: unicodeEmojiCategories[x[2]], +})); const _indexByChar = new Map<string, number>(); const _charGroupByCategory = new Map<string, string[]>(); -emojilist.forEach((emo, i) => { +for (let i = 0; i < emojilist.length; i++) { + const emo = emojilist[i]; _indexByChar.set(emo.char, i); if (_charGroupByCategory.has(emo.category)) { @@ -22,14 +26,14 @@ emojilist.forEach((emo, i) => { } else { _charGroupByCategory.set(emo.category, [emo.char]); } -}); +} export const emojiCharByCategory = _charGroupByCategory; -export function getEmojiName(char: string): string | undefined { +export function getEmojiName(char: string): string | null { const idx = _indexByChar.get(char); - if (typeof idx === 'undefined') { - return undefined; + if (idx == null) { + return null; } else { return emojilist[idx].name; } diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts index ed01b490547c186e44abfabf7cabaee1107d3d32..060c8a1a110915ffd35de4369f30fda24f75e518 100644 --- a/packages/frontend/src/scripts/get-drive-file-menu.ts +++ b/packages/frontend/src/scripts/get-drive-file-menu.ts @@ -73,7 +73,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile) { action: () => rename(file), }, { text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, - icon: file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-off', + icon: file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation', action: () => toggleSensitive(file), }, { text: i18n.ts.describeFile, diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index c8a610025385a3cebb940abbeefc9a07885a5962..960f26ca675b501ce168ad495f280434a2cd158a 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -7,7 +7,7 @@ import { instance } from '@/instance'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { url } from '@/config'; -import { noteActions } from '@/store'; +import { defaultStore, noteActions } from '@/store'; import { miLocalStorage } from '@/local-storage'; import { getUserMenu } from '@/scripts/get-user-menu'; import { clipsCache } from '@/cache'; @@ -396,5 +396,15 @@ export function getNoteMenu(props: { }))]); } + if (defaultStore.state.devMode) { + menu = menu.concat([null, { + icon: 'ti ti-id', + text: i18n.ts.copyNoteId, + action: () => { + copyToClipboard(appearNote.id); + }, + }]); + } + return menu; } diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 6ff9fb63f1d92ab1717da4461be98aaeca9f3bc2..b055d264737b3fc58f4f365528b82410349cfe2a 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -4,7 +4,7 @@ import { i18n } from '@/i18n'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { host } from '@/config'; import * as os from '@/os'; -import { userActions } from '@/store'; +import { defaultStore, userActions } from '@/store'; import { $i, iAmModerator } from '@/account'; import { mainRouter } from '@/router'; import { Router } from '@/nirax'; @@ -240,6 +240,16 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router }]); } + if (defaultStore.state.devMode) { + menu = menu.concat([null, { + icon: 'ti ti-id', + text: i18n.ts.copyUserId, + action: () => { + copyToClipboard(user.id); + }, + }]); + } + if ($i && meId === user.id) { menu = menu.concat([null, { icon: 'ti ti-pencil', diff --git a/packages/frontend/src/scripts/hpml/block.ts b/packages/frontend/src/scripts/hpml/block.ts deleted file mode 100644 index 804c5c1124c036e476403650a9ee8a4e1a64dc51..0000000000000000000000000000000000000000 --- a/packages/frontend/src/scripts/hpml/block.ts +++ /dev/null @@ -1,109 +0,0 @@ -// blocks - -export type BlockBase = { - id: string; - type: string; -}; - -export type TextBlock = BlockBase & { - type: 'text'; - text: string; -}; - -export type SectionBlock = BlockBase & { - type: 'section'; - title: string; - children: (Block | VarBlock)[]; -}; - -export type ImageBlock = BlockBase & { - type: 'image'; - fileId: string | null; -}; - -export type ButtonBlock = BlockBase & { - type: 'button'; - text: any; - primary: boolean; - action: string; - content: string; - event: string; - message: string; - var: string; - fn: string; -}; - -export type IfBlock = BlockBase & { - type: 'if'; - var: string; - children: Block[]; -}; - -export type TextareaBlock = BlockBase & { - type: 'textarea'; - text: string; -}; - -export type PostBlock = BlockBase & { - type: 'post'; - text: string; - attachCanvasImage: boolean; - canvasId: string; -}; - -export type CanvasBlock = BlockBase & { - type: 'canvas'; - name: string; // canvas id - width: number; - height: number; -}; - -export type NoteBlock = BlockBase & { - type: 'note'; - detailed: boolean; - note: string | null; -}; - -export type Block = - TextBlock | SectionBlock | ImageBlock | ButtonBlock | IfBlock | TextareaBlock | PostBlock | CanvasBlock | NoteBlock | VarBlock; - -// variable blocks - -export type VarBlockBase = BlockBase & { - name: string; -}; - -export type NumberInputVarBlock = VarBlockBase & { - type: 'numberInput'; - text: string; -}; - -export type TextInputVarBlock = VarBlockBase & { - type: 'textInput'; - text: string; -}; - -export type SwitchVarBlock = VarBlockBase & { - type: 'switch'; - text: string; -}; - -export type RadioButtonVarBlock = VarBlockBase & { - type: 'radioButton'; - title: string; - values: string[]; -}; - -export type CounterVarBlock = VarBlockBase & { - type: 'counter'; - text: string; - inc: number; -}; - -export type VarBlock = - NumberInputVarBlock | TextInputVarBlock | SwitchVarBlock | RadioButtonVarBlock | CounterVarBlock; - -const varBlock = ['numberInput', 'textInput', 'switch', 'radioButton', 'counter']; -export function isVarBlock(block: Block): block is VarBlock { - return varBlock.includes(block.type); -} diff --git a/packages/frontend/src/scripts/hpml/evaluator.ts b/packages/frontend/src/scripts/hpml/evaluator.ts deleted file mode 100644 index 9adfba7f27b84065f68c81a54692bf6e10122a10..0000000000000000000000000000000000000000 --- a/packages/frontend/src/scripts/hpml/evaluator.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { ref, Ref, unref } from 'vue'; -import { collectPageVars } from '../collect-page-vars'; -import { initHpmlLib } from './lib'; -import { Expr, isLiteralValue, Variable } from './expr'; -import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.'; -import { version } from '@/config'; - -/** - * Hpml evaluator - */ -export class Hpml { - private variables: Variable[]; - private pageVars: PageVar[]; - private envVars: Record<keyof typeof envVarsDef, any>; - public pageVarUpdatedCallback?: values.VFn; - public canvases: Record<string, HTMLCanvasElement> = {}; - public vars: Ref<Record<string, any>> = ref({}); - public page: Record<string, any>; - - private opts: { - randomSeed: string; visitor?: any; url?: string; - }; - - constructor(page: Hpml['page'], opts: Hpml['opts']) { - this.page = page; - this.variables = this.page.variables; - this.pageVars = collectPageVars(this.page.content); - this.opts = opts; - - const date = new Date(); - - this.envVars = { - AI: 'kawaii', - VERSION: version, - URL: this.page ? `${opts.url}/@${this.page.user.username}/pages/${this.page.name}` : '', - LOGIN: opts.visitor != null, - NAME: opts.visitor ? opts.visitor.name || opts.visitor.username : '', - USERNAME: opts.visitor ? opts.visitor.username : '', - USERID: opts.visitor ? opts.visitor.id : '', - NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0, - FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0, - FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0, - IS_CAT: opts.visitor ? opts.visitor.isCat : false, - SEED: opts.randomSeed ? opts.randomSeed : '', - YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`, - AISCRIPT_DISABLED: true, - NULL: null, - }; - - this.eval(); - } - - public eval() { - try { - this.vars.value = this.evaluateVars(); - } catch (err) { - //this.onError(e); - } - } - - public interpolate(str: string) { - if (str == null) return null; - return str.replace(/{(.+?)}/g, match => { - const v = unref(this.vars)[match.slice(1, -1).trim()]; - return v == null ? 'NULL' : v.toString(); - }); - } - - public registerCanvas(id: string, canvas: any) { - this.canvases[id] = canvas; - } - - public updatePageVar(name: string, value: any) { - const pageVar = this.pageVars.find(v => v.name === name); - if (pageVar !== undefined) { - pageVar.value = value; - } else { - throw new HpmlError(`No such page var '${name}'`); - } - } - - public updateRandomSeed(seed: string) { - this.opts.randomSeed = seed; - this.envVars.SEED = seed; - } - - private _interpolateScope(str: string, scope: HpmlScope) { - return str.replace(/{(.+?)}/g, match => { - const v = scope.getState(match.slice(1, -1).trim()); - return v == null ? 'NULL' : v.toString(); - }); - } - - public evaluateVars(): Record<string, any> { - const values: Record<string, any> = {}; - - for (const [k, v] of Object.entries(this.envVars)) { - values[k] = v; - } - - for (const v of this.pageVars) { - values[v.name] = v.value; - } - - for (const v of this.variables) { - values[v.name] = this.evaluate(v, new HpmlScope([values])); - } - - return values; - } - - private evaluate(expr: Expr, scope: HpmlScope): any { - if (isLiteralValue(expr)) { - if (expr.type === null) { - return null; - } - - if (expr.type === 'number') { - return parseInt((expr.value as any), 10); - } - - if (expr.type === 'text' || expr.type === 'multiLineText') { - return this._interpolateScope(expr.value || '', scope); - } - - if (expr.type === 'textList') { - return this._interpolateScope(expr.value || '', scope).trim().split('\n'); - } - - if (expr.type === 'ref') { - return scope.getState(expr.value); - } - - // Define user function - if (expr.type === 'fn') { - return { - slots: expr.value.slots.map(x => x.name), - exec: (slotArg: Record<string, any>) => { - return this.evaluate(expr.value.expression, scope.createChildScope(slotArg, expr.id)); - }, - } as Fn; - } - return; - } - - // Call user function - if (expr.type.startsWith('fn:')) { - const fnName = expr.type.split(':')[1]; - const fn = scope.getState(fnName); - const args = {} as Record<string, any>; - for (let i = 0; i < fn.slots.length; i++) { - const name = fn.slots[i]; - args[name] = this.evaluate(expr.args[i], scope); - } - return fn.exec(args); - } - - if (expr.args === undefined) return null; - - const funcs = initHpmlLib(expr, scope, this.opts.randomSeed, this.opts.visitor); - - // Call function - const fnName = expr.type; - const fn = (funcs as any)[fnName]; - if (fn == null) { - throw new HpmlError(`No such function '${fnName}'`); - } else { - return fn(...expr.args.map(x => this.evaluate(x, scope))); - } - } -} diff --git a/packages/frontend/src/scripts/hpml/expr.ts b/packages/frontend/src/scripts/hpml/expr.ts deleted file mode 100644 index 18c7c2a14b6da8704b252f6455cdf145c3d1be7c..0000000000000000000000000000000000000000 --- a/packages/frontend/src/scripts/hpml/expr.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { literalDefs, Type } from '.'; - -export type ExprBase = { - id: string; -}; - -// value - -export type EmptyValue = ExprBase & { - type: null; - value: null; -}; - -export type TextValue = ExprBase & { - type: 'text'; - value: string; -}; - -export type MultiLineTextValue = ExprBase & { - type: 'multiLineText'; - value: string; -}; - -export type TextListValue = ExprBase & { - type: 'textList'; - value: string; -}; - -export type NumberValue = ExprBase & { - type: 'number'; - value: number; -}; - -export type RefValue = ExprBase & { - type: 'ref'; - value: string; // value is variable name -}; - -export type AiScriptRefValue = ExprBase & { - type: 'aiScriptVar'; - value: string; // value is variable name -}; - -export type UserFnValue = ExprBase & { - type: 'fn'; - value: UserFnInnerValue; -}; -type UserFnInnerValue = { - slots: { - name: string; - type: Type; - }[]; - expression: Expr; -}; - -export type Value = - EmptyValue | TextValue | MultiLineTextValue | TextListValue | NumberValue | RefValue | AiScriptRefValue | UserFnValue; - -export function isLiteralValue(expr: Expr): expr is Value { - if (expr.type == null) return true; - if (literalDefs[expr.type]) return true; - return false; -} - -// call function - -export type CallFn = ExprBase & { // "fn:hoge" or string - type: string; - args: Expr[]; - value: null; -}; - -// variable -export type Variable = (Value | CallFn) & { - name: string; -}; - -// expression -export type Expr = Variable | Value | CallFn; diff --git a/packages/frontend/src/scripts/hpml/index.ts b/packages/frontend/src/scripts/hpml/index.ts deleted file mode 100644 index 994f286b9ff690e0c85caf65c252bea99668b9a0..0000000000000000000000000000000000000000 --- a/packages/frontend/src/scripts/hpml/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Hpml - */ - -import { Hpml } from './evaluator'; -import { funcDefs } from './lib'; - -export type Fn = { - slots: string[]; - exec: (args: Record<string, any>) => ReturnType<Hpml['evaluate']>; -}; - -export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null; - -export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = { - text: { out: 'string', category: 'value', icon: 'ti ti-quote' }, - multiLineText: { out: 'string', category: 'value', icon: 'ti ti-align-left' }, - textList: { out: 'stringArray', category: 'value', icon: 'ti ti-list' }, - number: { out: 'number', category: 'value', icon: 'ti ti-list-numbers' }, - ref: { out: null, category: 'value', icon: 'ti ti-wand' }, - aiScriptVar: { out: null, category: 'value', icon: 'ti ti-wand' }, - fn: { out: 'function', category: 'value', icon: 'ti ti-math-function' }, -}; - -export const blockDefs = [ - ...Object.entries(literalDefs).map(([k, v]) => ({ - type: k, out: v.out, category: v.category, icon: v.icon, - })), - ...Object.entries(funcDefs).map(([k, v]) => ({ - type: k, out: v.out, category: v.category, icon: v.icon, - })), -]; - -export type PageVar = { name: string; value: any; type: Type; }; - -export const envVarsDef: Record<string, Type> = { - AI: 'string', - URL: 'string', - VERSION: 'string', - LOGIN: 'boolean', - NAME: 'string', - USERNAME: 'string', - USERID: 'string', - NOTES_COUNT: 'number', - FOLLOWERS_COUNT: 'number', - FOLLOWING_COUNT: 'number', - IS_CAT: 'boolean', - SEED: null, - YMD: 'string', - AISCRIPT_DISABLED: 'boolean', - NULL: null, -}; - -export class HpmlScope { - private layerdStates: Record<string, any>[]; - public name: string; - - constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) { - this.layerdStates = layerdStates; - this.name = name ?? 'anonymous'; - } - - public createChildScope(states: Record<string, any>, name?: HpmlScope['name']): HpmlScope { - const layer = [states, ...this.layerdStates]; - return new HpmlScope(layer, name); - } - - /** - * 指定ã—ãŸåå‰ã®å¤‰æ•°ã®å€¤ã‚’å–å¾—ã—ã¾ã™ - * @param name 変数å - */ - public getState(name: string): any { - for (const later of this.layerdStates) { - const state = later[name]; - if (state !== undefined) { - return state; - } - } - - throw new HpmlError( - `No such variable '${name}' in scope '${this.name}'`, { - scope: this.layerdStates, - }); - } -} - -export class HpmlError extends Error { - public info?: any; - - constructor(message: string, info?: any) { - super(message); - - this.info = info; - - // Maintains proper stack trace for where our error was thrown (only available on V8) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, HpmlError); - } - } -} diff --git a/packages/frontend/src/scripts/hpml/lib.ts b/packages/frontend/src/scripts/hpml/lib.ts deleted file mode 100644 index 88db82dd272946371fb5be80d77bfea4d3b42d96..0000000000000000000000000000000000000000 --- a/packages/frontend/src/scripts/hpml/lib.ts +++ /dev/null @@ -1,245 +0,0 @@ -import seedrandom from 'seedrandom'; -import { Hpml } from './evaluator'; -import { Expr } from './expr'; -import { Fn, HpmlScope } from '.'; - -/* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color -// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs -Chart.pluginService.register({ - beforeDraw: (chart, easing) => { - if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) { - const ctx = chart.chart.ctx; - ctx.save(); - ctx.fillStyle = chart.config.options.chartArea.backgroundColor; - ctx.fillRect(0, 0, chart.chart.width, chart.chart.height); - ctx.restore(); - } - } -}); -*/ - -export function initAiLib(hpml: Hpml) { - return { - 'MkPages:updated': values.FN_NATIVE(([callback]) => { - hpml.pageVarUpdatedCallback = (callback as values.VFn); - }), - 'MkPages:get_canvas': values.FN_NATIVE(([id]) => { - utils.assertString(id); - const canvas = hpml.canvases[id.value]; - const ctx = canvas.getContext('2d'); - return values.OBJ(new Map([ - ['clear_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.clearRect(x.value, y.value, width.value, height.value); })], - ['fill_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.fillRect(x.value, y.value, width.value, height.value); })], - ['stroke_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.strokeRect(x.value, y.value, width.value, height.value); })], - ['fill_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.fillText(text.value, x.value, y.value, width ? width.value : undefined); })], - ['stroke_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.strokeText(text.value, x.value, y.value, width ? width.value : undefined); })], - ['set_line_width', values.FN_NATIVE(([width]) => { ctx.lineWidth = width.value; })], - ['set_font', values.FN_NATIVE(([font]) => { ctx.font = font.value; })], - ['set_fill_style', values.FN_NATIVE(([style]) => { ctx.fillStyle = style.value; })], - ['set_stroke_style', values.FN_NATIVE(([style]) => { ctx.strokeStyle = style.value; })], - ['begin_path', values.FN_NATIVE(() => { ctx.beginPath(); })], - ['close_path', values.FN_NATIVE(() => { ctx.closePath(); })], - ['move_to', values.FN_NATIVE(([x, y]) => { ctx.moveTo(x.value, y.value); })], - ['line_to', values.FN_NATIVE(([x, y]) => { ctx.lineTo(x.value, y.value); })], - ['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value); })], - ['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value); })], - ['fill', values.FN_NATIVE(() => { ctx.fill(); })], - ['stroke', values.FN_NATIVE(() => { ctx.stroke(); })], - ])); - }), - 'MkPages:chart': values.FN_NATIVE(([id, opts]) => { - /* TODO - utils.assertString(id); - utils.assertObject(opts); - const canvas = hpml.canvases[id.value]; - const color = getComputedStyle(document.documentElement).getPropertyValue('--accent'); - Chart.defaults.color = '#555'; - const chart = new Chart(canvas, { - type: opts.value.get('type').value, - data: { - labels: opts.value.get('labels').value.map(x => x.value), - datasets: opts.value.get('datasets').value.map(x => ({ - label: x.value.has('label') ? x.value.get('label').value : '', - data: x.value.get('data').value.map(x => x.value), - pointRadius: 0, - lineTension: 0, - borderWidth: 2, - borderColor: x.value.has('color') ? x.value.get('color') : color, - backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(), - })) - }, - options: { - responsive: false, - devicePixelRatio: 1.5, - title: { - display: opts.value.has('title'), - text: opts.value.has('title') ? opts.value.get('title').value : '', - fontSize: 14, - }, - layout: { - padding: { - left: 32, - right: 32, - top: opts.value.has('title') ? 16 : 32, - bottom: 16 - } - }, - legend: { - display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true, - position: 'bottom', - labels: { - boxWidth: 16, - } - }, - tooltips: { - enabled: false, - }, - chartArea: { - backgroundColor: '#fff' - }, - ...(opts.value.get('type').value === 'radar' ? { - scale: { - ticks: { - display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false, - min: opts.value.has('min') ? opts.value.get('min').value : undefined, - max: opts.value.has('max') ? opts.value.get('max').value : undefined, - maxTicksLimit: 8, - }, - pointLabels: { - fontSize: 12 - } - } - } : { - scales: { - yAxes: [{ - ticks: { - display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true, - min: opts.value.has('min') ? opts.value.get('min').value : undefined, - max: opts.value.has('max') ? opts.value.get('max').value : undefined, - } - }] - } - }) - } - }); - */ - }), - }; -} - -export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = { - if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'ti ti-share' }, - for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'ti ti-recycle' }, - not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' }, - or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' }, - and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' }, - add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-plus' }, - subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-minus' }, - multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-x' }, - divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' }, - mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' }, - round: { in: ['number'], out: 'number', category: 'operation', icon: 'ti ti-calculator' }, - eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal' }, - notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal-not' }, - gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-greater' }, - lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-lower' }, - gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-greater' }, - ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-lower' }, - strLen: { in: ['string'], out: 'number', category: 'text', icon: 'ti ti-quote' }, - strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'ti ti-quote' }, - strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' }, - strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'ti ti-quote' }, - join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' }, - stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'ti ti-arrows-right-left' }, - numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'ti ti-arrows-right-left' }, - splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'ti ti-arrows-right-left' }, - pick: { in: [null, 'number'], out: null, category: 'list', icon: 'ti ti-indent-increase' }, - listLen: { in: [null], out: 'number', category: 'list', icon: 'ti ti-indent-increase' }, - rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' }, - dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' }, - seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' }, - random: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' }, - dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' }, - seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' }, - randomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' }, - dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' }, - seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'ti ti-dice' }, - DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'ti ti-dice' }, // dailyRandomPickWithProbabilityMapping -}; - -export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) { - const date = new Date(); - const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; - - // SHOULD be fine to ignore since it's intended + function shape isn't defined - // eslint-disable-next-line @typescript-eslint/ban-types - const funcs: Record<string, Function> = { - not: (a: boolean) => !a, - or: (a: boolean, b: boolean) => a || b, - and: (a: boolean, b: boolean) => a && b, - eq: (a: any, b: any) => a === b, - notEq: (a: any, b: any) => a !== b, - gt: (a: number, b: number) => a > b, - lt: (a: number, b: number) => a < b, - gtEq: (a: number, b: number) => a >= b, - ltEq: (a: number, b: number) => a <= b, - if: (bool: boolean, a: any, b: any) => bool ? a : b, - for: (times: number, fn: Fn) => { - const result: any[] = []; - for (let i = 0; i < times; i++) { - result.push(fn.exec({ - [fn.slots[0]]: i + 1, - })); - } - return result; - }, - add: (a: number, b: number) => a + b, - subtract: (a: number, b: number) => a - b, - multiply: (a: number, b: number) => a * b, - divide: (a: number, b: number) => a / b, - mod: (a: number, b: number) => a % b, - round: (a: number) => Math.round(a), - strLen: (a: string) => a.length, - strPick: (a: string, b: number) => a[b - 1], - strReplace: (a: string, b: string, c: string) => a.split(b).join(c), - strReverse: (a: string) => a.split('').reverse().join(''), - join: (texts: string[], separator: string) => texts.join(separator || ''), - stringToNumber: (a: string) => parseInt(a), - numberToString: (a: number) => a.toString(), - splitStrByLine: (a: string) => a.split('\n'), - pick: (list: any[], i: number) => list[i - 1], - listLen: (list: any[]) => list.length, - random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * 100) < probability, - rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * (max - min + 1)), - randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * list.length)], - dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${expr.id}`)() * 100) < probability, - dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${expr.id}`)() * (max - min + 1)), - dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${expr.id}`)() * list.length)], - seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability, - seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)), - seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)], - DRPWPM: (list: string[]) => { - const xs: any[] = []; - let totalFactor = 0; - for (const x of list) { - const parts = x.split(' '); - const factor = parseInt(parts.pop()!, 10); - const text = parts.join(' '); - totalFactor += factor; - xs.push({ factor, text }); - } - const r = seedrandom(`${day}:${expr.id}`)() * totalFactor; - let stackedFactor = 0; - for (const x of xs) { - if (r >= stackedFactor && r <= stackedFactor + x.factor) { - return x.text; - } else { - stackedFactor += x.factor; - } - } - return xs[0].text; - }, - }; - - return funcs; -} diff --git a/packages/frontend/src/scripts/hpml/type-checker.ts b/packages/frontend/src/scripts/hpml/type-checker.ts deleted file mode 100644 index ea8133f297619d2ca684fd0606e0100f66e8379d..0000000000000000000000000000000000000000 --- a/packages/frontend/src/scripts/hpml/type-checker.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { isLiteralValue } from './expr'; -import { funcDefs } from './lib'; -import { envVarsDef } from '.'; -import type { Type, PageVar } from '.'; -import type { Expr, Variable } from './expr'; - -type TypeError = { - arg: number; - expect: Type; - actual: Type; -}; - -/** - * Hpml type checker - */ -export class HpmlTypeChecker { - public variables: Variable[]; - public pageVars: PageVar[]; - - constructor(variables: HpmlTypeChecker['variables'] = [], pageVars: HpmlTypeChecker['pageVars'] = []) { - this.variables = variables; - this.pageVars = pageVars; - } - - public typeCheck(v: Expr): TypeError | null { - if (isLiteralValue(v)) return null; - - const def = funcDefs[v.type || '']; - if (def == null) { - throw new Error('Unknown type: ' + v.type); - } - - const generic: Type[] = []; - - for (let i = 0; i < def.in.length; i++) { - const arg = def.in[i]; - const type = this.infer(v.args[i]); - if (type === null) continue; - - if (typeof arg === 'number') { - if (generic[arg] === undefined) { - generic[arg] = type; - } else if (type !== generic[arg]) { - return { - arg: i, - expect: generic[arg], - actual: type, - }; - } - } else if (type !== arg) { - return { - arg: i, - expect: arg, - actual: type, - }; - } - } - - return null; - } - - public getExpectedType(v: Expr, slot: number): Type { - const def = funcDefs[v.type ?? '']; - if (def == null) { - throw new Error('Unknown type: ' + v.type); - } - - const generic: Type[] = []; - - for (let i = 0; i < def.in.length; i++) { - const arg = def.in[i]; - const type = this.infer(v.args[i]); - if (type === null) continue; - - if (typeof arg === 'number') { - if (generic[arg] === undefined) { - generic[arg] = type; - } - } - } - - if (typeof def.in[slot] === 'number') { - return generic[def.in[slot]] ?? null; - } else { - return def.in[slot]; - } - } - - public infer(v: Expr): Type { - if (v.type === null) return null; - if (v.type === 'text') return 'string'; - if (v.type === 'multiLineText') return 'string'; - if (v.type === 'textList') return 'stringArray'; - if (v.type === 'number') return 'number'; - if (v.type === 'ref') { - const variable = this.variables.find(va => va.name === v.value); - if (variable) { - return this.infer(variable); - } - - const pageVar = this.pageVars.find(va => va.name === v.value); - if (pageVar) { - return pageVar.type; - } - - const envVar = envVarsDef[v.value ?? '']; - if (envVar !== undefined) { - return envVar; - } - - return null; - } - if (v.type === 'aiScriptVar') return null; - if (v.type === 'fn') return null; // todo - if (v.type.startsWith('fn:')) return null; // todo - - const generic: Type[] = []; - - const def = funcDefs[v.type]; - - for (let i = 0; i < def.in.length; i++) { - const arg = def.in[i]; - if (typeof arg === 'number') { - const type = this.infer(v.args[i]); - - if (generic[arg] === undefined) { - generic[arg] = type; - } else { - if (type !== generic[arg]) { - generic[arg] = null; - } - } - } - } - - if (typeof def.out === 'number') { - return generic[def.out]; - } else { - return def.out; - } - } - - public getVarByName(name: string): Variable { - const v = this.variables.find(x => x.name === name); - if (v !== undefined) { - return v; - } else { - throw new Error(`No such variable '${name}'`); - } - } - - public getVarsByType(type: Type): Variable[] { - if (type == null) return this.variables; - return this.variables.filter(x => (this.infer(x) === null) || (this.infer(x) === type)); - } - - public getEnvVarsByType(type: Type): string[] { - if (type == null) return Object.keys(envVarsDef); - return Object.entries(envVarsDef).filter(([k, v]) => v === null || type === v).map(([k, v]) => k); - } - - public getPageVarsByType(type: Type): string[] { - if (type == null) return this.pageVars.map(v => v.name); - return this.pageVars.filter(v => type === v.type).map(v => v.name); - } - - public isUsedName(name: string) { - if (this.variables.some(v => v.name === name)) { - return true; - } - - if (this.pageVars.some(v => v.name === name)) { - return true; - } - - if (envVarsDef[name]) { - return true; - } - - return false; - } -} diff --git a/packages/frontend/src/scripts/idle-render.ts b/packages/frontend/src/scripts/idle-render.ts new file mode 100644 index 0000000000000000000000000000000000000000..ccce8b02bfc47afb3ab75670a3fbe984f1d6cfb6 --- /dev/null +++ b/packages/frontend/src/scripts/idle-render.ts @@ -0,0 +1,38 @@ +class IdlingRenderScheduler { + #renderers: Set<FrameRequestCallback>; + #rafId: number; + #ricId: number; + + constructor() { + this.#renderers = new Set(); + this.#rafId = 0; + this.#ricId = requestIdleCallback((deadline) => this.#schedule(deadline)); + } + + #schedule(deadline: IdleDeadline): void { + if (deadline.timeRemaining()) { + this.#rafId = requestAnimationFrame((time) => { + for (const renderer of this.#renderers) { + renderer(time); + } + }); + } + this.#ricId = requestIdleCallback((arg) => this.#schedule(arg)); + } + + add(renderer: FrameRequestCallback): void { + this.#renderers.add(renderer); + } + + delete(renderer: FrameRequestCallback): void { + this.#renderers.delete(renderer); + } + + dispose(): void { + this.#renderers.clear(); + cancelAnimationFrame(this.#rafId); + cancelIdleCallback(this.#ricId); + } +} + +export const defaultIdlingRenderScheduler = new IdlingRenderScheduler(); diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index fe9f0a2447ae22626a81b72c4f9fd18b50d1beb2..44a58d6c7d61f36fa1e8093206e298130374ddc5 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -1,7 +1,7 @@ import { ref } from 'vue'; import { DriveFile } from 'misskey-js/built/entities'; import * as os from '@/os'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { uploadFile } from '@/scripts/upload'; @@ -51,7 +51,7 @@ export function chooseFileFromUrl(): Promise<DriveFile> { const marker = Math.random().toString(); // TODO: UUIDã¨ã‹ä½¿ã† - const connection = stream.useChannel('main'); + const connection = useStream().useChannel('main'); connection.on('urlUploadFinished', urlResponse => { if (urlResponse.marker === marker) { res(urlResponse.file); diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index 28284c7bcf31d185ddf11e089d4bfdbc11333543..f2e825356511360b1d1f74fcbbb1fbfee20c5f7f 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -60,7 +60,7 @@ export function applyTheme(theme: Theme, persist = true) { document.documentElement.classList.remove('_themeChanging_'); }, 1000); - const colorSchema = theme.base === 'dark' ? 'dark' : 'light'; + const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; // Deep copy const _theme = deepClone(theme); @@ -83,11 +83,11 @@ export function applyTheme(theme: Theme, persist = true) { document.documentElement.style.setProperty(`--${k}`, v.toString()); } - document.documentElement.style.setProperty('color-schema', colorSchema); + document.documentElement.style.setProperty('color-scheme', colorScheme); if (persist) { miLocalStorage.setItem('theme', JSON.stringify(props)); - miLocalStorage.setItem('colorSchema', colorSchema); + miLocalStorage.setItem('colorScheme', colorScheme); } // 色計算ãªã©å†åº¦è¡Œãˆã‚‹ã‚ˆã†ã«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆå…¨ä½“ã«é€šçŸ¥ diff --git a/packages/frontend/src/scripts/time.ts b/packages/frontend/src/scripts/time.ts index 34e8b6b17c56ef714a7ec50d29a863f498c1fe3d..b21978b186893c8adef27aabf771916801f8a7df 100644 --- a/packages/frontend/src/scripts/time.ts +++ b/packages/frontend/src/scripts/time.ts @@ -5,15 +5,16 @@ const dateTimeIntervals = { }; export function dateUTC(time: number[]): Date { - const d = time.length === 2 ? Date.UTC(time[0], time[1]) - : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) - : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) - : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) - : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) - : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) - : null; - - if (!d) throw 'wrong number of arguments'; + const d = + time.length === 2 ? Date.UTC(time[0], time[1]) + : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) + : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) + : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) + : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) + : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) + : null; + + if (!d) throw new Error('wrong number of arguments'); return new Date(d); } diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts index ffe33cccc09c1def1789ea04efd98b8494ac6c54..22a01e066a347e92cce8e3e5eb7ed7e0e008bf4f 100644 --- a/packages/frontend/src/scripts/use-note-capture.ts +++ b/packages/frontend/src/scripts/use-note-capture.ts @@ -1,6 +1,6 @@ import { onUnmounted, Ref } from 'vue'; import * as misskey from 'misskey-js'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { $i } from '@/account'; export function useNoteCapture(props: { @@ -9,7 +9,7 @@ export function useNoteCapture(props: { isDeletedRef: Ref<boolean>; }) { const note = props.note; - const connection = $i ? stream : null; + const connection = $i ? useStream() : null; function onStreamNoteUpdated(noteData): void { const { type, id, body } = noteData; diff --git a/packages/frontend/src/scripts/worker-multi-dispatch.ts b/packages/frontend/src/scripts/worker-multi-dispatch.ts new file mode 100644 index 0000000000000000000000000000000000000000..1847a8ccff10e57430df72355723526f4fa912e1 --- /dev/null +++ b/packages/frontend/src/scripts/worker-multi-dispatch.ts @@ -0,0 +1,75 @@ +function defaultUseWorkerNumber(prev: number, totalWorkers: number) { + return prev + 1; +} + +export class WorkerMultiDispatch<POST = any, RETURN = any> { + private symbol = Symbol('WorkerMultiDispatch'); + private workers: Worker[] = []; + private terminated = false; + private prevWorkerNumber = 0; + private getUseWorkerNumber = defaultUseWorkerNumber; + private finalizationRegistry: FinalizationRegistry<symbol>; + + constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) { + this.getUseWorkerNumber = getUseWorkerNumber; + for (let i = 0; i < concurrency; i++) { + this.workers.push(workerConstructor()); + } + + this.finalizationRegistry = new FinalizationRegistry(() => { + this.terminate(); + }); + this.finalizationRegistry.register(this, this.symbol); + + if (_DEV_) console.log('WorkerMultiDispatch: Created', this); + } + + public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = this.getUseWorkerNumber) { + let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length); + workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length; + if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber); + this.prevWorkerNumber = workerNumber; + + // ä¸æ¯›ã ãŒunionã‚’overloadã«çªã£è¾¼ã‚ãªã„ + // https://stackoverflow.com/questions/66507585/overload-signatures-union-types-and-no-overload-matches-this-call-error + // https://github.com/microsoft/TypeScript/issues/14107 + if (Array.isArray(options)) { + this.workers[workerNumber].postMessage(message, options); + } else { + this.workers[workerNumber].postMessage(message, options); + } + return workerNumber; + } + + public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) { + this.workers.forEach(worker => { + worker.addEventListener('message', callback, options); + }); + } + + public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) { + this.workers.forEach(worker => { + worker.removeEventListener('message', callback, options); + }); + } + + public terminate() { + this.terminated = true; + if (_DEV_) console.log('WorkerMultiDispatch: Terminating', this); + this.workers.forEach(worker => { + worker.terminate(); + }); + this.workers = []; + this.finalizationRegistry.unregister(this); + } + + public isTerminated() { + return this.terminated; + } + public getWorkers() { + return this.workers; + } + public getSymbol() { + return this.symbol; + } +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 245bcbefe11f40277bd9afae24ebb84be74481f2..6ba05c36abbd53c592bfebd09d152d5923117b48 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -92,7 +92,7 @@ export const defaultStore = markRaw(new Storage('base', { }, reactionAcceptance: { where: 'account', - default: null as 'likeOnly' | 'likeOnlyForRemote' | null, + default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null, }, mutedWords: { where: 'account', @@ -102,6 +102,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'account', default: [] as string[], }, + showTimelineReplies: { + where: 'account', + default: false, + }, menu: { where: 'deviceAccount', @@ -314,6 +318,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, + devMode: { + where: 'device', + default: false, + }, mediaListWithOneImageAppearance: { where: 'device', default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3', @@ -328,7 +336,11 @@ export const defaultStore = markRaw(new Storage('base', { }, enableCondensedLineForAcct: { where: 'device', - default: true, + default: false, + }, + additionalUnicodeEmojiIndexes: { + where: 'device', + default: {} as Record<string, Record<string, string[]>>, }, })); diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts index dea3459b862334a79a0664cb565f74be943ff113..9cae58a26a42d8241e8f2f5c19284a31cf3e633c 100644 --- a/packages/frontend/src/stream.ts +++ b/packages/frontend/src/stream.ts @@ -3,6 +3,14 @@ import { markRaw } from 'vue'; import { $i } from '@/account'; import { url } from '@/config'; -export const stream = markRaw(new Misskey.Stream(url, $i ? { - token: $i.token, -} : null)); +let stream: Misskey.Stream | null = null; + +export function useStream(): Misskey.Stream { + if (stream) return stream; + + stream = markRaw(new Misskey.Stream(url, $i ? { + token: $i.token, + } : null)); + + return stream; +} diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index 20254d335ea491d5aba6866430d75f0c7a5e4476..b376e4c42dd09ae2241aa0120d4d5659c4913bfd 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -22,11 +22,7 @@ } html { - touch-action: manipulation; background-color: var(--bg); - background-attachment: fixed; - background-size: cover; - background-position: center; color: var(--fg); accent-color: var(--accent); overflow: auto; @@ -38,7 +34,7 @@ html { tab-size: 2; &, * { - scrollbar-color: var(--scrollbarHandle) inherit; + scrollbar-color: var(--scrollbarHandle) transparent; scrollbar-width: thin; &::-webkit-scrollbar { @@ -87,6 +83,7 @@ html._themeChanging_ { } html, body { + touch-action: manipulation; margin: 0; padding: 0; scroll-behavior: smooth; @@ -483,3 +480,140 @@ hr { transform: scaleX(1.00) scaleY(1.00) ; } } + +// MFM ----------------------------- + +._mfm_blur_ { + filter: blur(6px); + transition: filter 0.3s; + + &:hover { + filter: blur(0px); + } +} + +.mfm-x2 { + --mfm-zoom-size: 200%; +} + +.mfm-x3 { + --mfm-zoom-size: 400%; +} + +.mfm-x4 { + --mfm-zoom-size: 600%; +} + +.mfm-x2, .mfm-x3, .mfm-x4 { + font-size: var(--mfm-zoom-size); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* only half effective */ + font-size: calc(var(--mfm-zoom-size) / 2 + 50%); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* disabled */ + font-size: 100%; + } + } +} + +@keyframes mfm-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@keyframes mfm-spinX { + 0% { transform: perspective(128px) rotateX(0deg); } + 100% { transform: perspective(128px) rotateX(360deg); } +} + +@keyframes mfm-spinY { + 0% { transform: perspective(128px) rotateY(0deg); } + 100% { transform: perspective(128px) rotateY(360deg); } +} + +@keyframes mfm-jump { + 0% { transform: translateY(0); } + 25% { transform: translateY(-16px); } + 50% { transform: translateY(0); } + 75% { transform: translateY(-8px); } + 100% { transform: translateY(0); } +} + +@keyframes mfm-bounce { + 0% { transform: translateY(0) scale(1, 1); } + 25% { transform: translateY(-16px) scale(1, 1); } + 50% { transform: translateY(0) scale(1, 1); } + 75% { transform: translateY(0) scale(1.5, 0.75); } + 100% { transform: translateY(0) scale(1, 1); } +} + +// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`; +// let css = ''; +// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } +@keyframes mfm-twitch { + 0% { transform: translate(7px, -2px) } + 5% { transform: translate(-3px, 1px) } + 10% { transform: translate(-7px, -1px) } + 15% { transform: translate(0px, -1px) } + 20% { transform: translate(-8px, 6px) } + 25% { transform: translate(-4px, -3px) } + 30% { transform: translate(-4px, -6px) } + 35% { transform: translate(-8px, -8px) } + 40% { transform: translate(4px, 6px) } + 45% { transform: translate(-3px, 1px) } + 50% { transform: translate(2px, -10px) } + 55% { transform: translate(-7px, 0px) } + 60% { transform: translate(-2px, 4px) } + 65% { transform: translate(3px, -8px) } + 70% { transform: translate(6px, 7px) } + 75% { transform: translate(-7px, -2px) } + 80% { transform: translate(-7px, -8px) } + 85% { transform: translate(9px, 3px) } + 90% { transform: translate(-3px, -2px) } + 95% { transform: translate(-10px, 2px) } + 100% { transform: translate(-2px, -6px) } +} + +// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`; +// let css = ''; +// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } +@keyframes mfm-shake { + 0% { transform: translate(-3px, -1px) rotate(-8deg) } + 5% { transform: translate(0px, -1px) rotate(-10deg) } + 10% { transform: translate(1px, -3px) rotate(0deg) } + 15% { transform: translate(1px, 1px) rotate(11deg) } + 20% { transform: translate(-2px, 1px) rotate(1deg) } + 25% { transform: translate(-1px, -2px) rotate(-2deg) } + 30% { transform: translate(-1px, 2px) rotate(-3deg) } + 35% { transform: translate(2px, 1px) rotate(6deg) } + 40% { transform: translate(-2px, -3px) rotate(-9deg) } + 45% { transform: translate(0px, -1px) rotate(-12deg) } + 50% { transform: translate(1px, 2px) rotate(10deg) } + 55% { transform: translate(0px, -3px) rotate(8deg) } + 60% { transform: translate(1px, -1px) rotate(8deg) } + 65% { transform: translate(0px, -1px) rotate(-7deg) } + 70% { transform: translate(-1px, -3px) rotate(6deg) } + 75% { transform: translate(0px, -2px) rotate(4deg) } + 80% { transform: translate(-2px, -1px) rotate(3deg) } + 85% { transform: translate(1px, -3px) rotate(-10deg) } + 90% { transform: translate(1px, 0px) rotate(3deg) } + 95% { transform: translate(-2px, 0px) rotate(-3deg) } + 100% { transform: translate(2px, 1px) rotate(2deg) } +} + +@keyframes mfm-rubberBand { + from { transform: scale3d(1, 1, 1); } + 30% { transform: scale3d(1.25, 0.75, 1); } + 40% { transform: scale3d(0.75, 1.25, 1); } + 50% { transform: scale3d(1.15, 0.85, 1); } + 65% { transform: scale3d(0.95, 1.05, 1); } + 75% { transform: scale3d(1.05, 0.95, 1); } + to { transform: scale3d(1, 1, 1); } +} + +@keyframes mfm-rainbow { + 0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } + 100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } +} diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5 index a23d25e866b2bf1e7eaad63e596a82f5d658cfc4..5ef6adb085e53dbac4178fcab63898c2ecfd9868 100644 --- a/packages/frontend/src/themes/_dark.json5 +++ b/packages/frontend/src/themes/_dark.json5 @@ -21,6 +21,7 @@ fgTransparent: ':alpha<0.5<@fg', fgHighlighted: ':lighten<3<@fg', fgOnAccent: '#fff', + fgOnWhite: '#333', divider: 'rgba(255, 255, 255, 0.1)', indicator: '@accent', panel: ':lighten<3<@bg', @@ -77,7 +78,7 @@ codeString: '#ffb675', codeNumber: '#cfff9e', codeBoolean: '#c59eff', - deckDivider: '#000', + deckBg: '#000', htmlThemeColor: '@bg', X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5 index 713756221aaa1f6afeb95878d52a5027e91b0e40..32f3c7490917f59df4783c8c6397e5d438fb365f 100644 --- a/packages/frontend/src/themes/_light.json5 +++ b/packages/frontend/src/themes/_light.json5 @@ -21,6 +21,7 @@ fgTransparent: ':alpha<0.5<@fg', fgHighlighted: ':darken<3<@fg', fgOnAccent: '#fff', + fgOnWhite: '#333', divider: 'rgba(0, 0, 0, 0.1)', indicator: '@accent', panel: ':lighten<3<@bg', @@ -77,7 +78,7 @@ codeString: '#b98710', codeNumber: '#0fbbbb', codeBoolean: '#62b70c', - deckDivider: ':darken<3<@bg', + deckBg: ':darken<3<@bg', htmlThemeColor: '@bg', X2: ':darken<2<@panel', X3: 'rgba(0, 0, 0, 0.05)', diff --git a/packages/frontend/src/themes/d-astro.json5 b/packages/frontend/src/themes/d-astro.json5 index c6a927ec3ae7e49d4286774f050be97a09285242..09a9ead1a2e3398fc367e2dc615ece71c80cdf80 100644 --- a/packages/frontend/src/themes/d-astro.json5 +++ b/packages/frontend/src/themes/d-astro.json5 @@ -53,6 +53,7 @@ panelHeaderBg: ':lighten<3<@panel', panelHeaderFg: '@fg', htmlThemeColor: '@bg', + fgOnWhite: '@accent', panelHighlight: ':lighten<3<@panel', listItemHoverBg: 'rgba(255, 255, 255, 0.03)', scrollbarHandle: 'rgba(255, 255, 255, 0.2)', diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend/src/themes/d-botanical.json5 index 33cf7aa8174502b1226b9645d036bb56b2db6fca..62208d23781e4dd02bcaf7f4f8990f59d24e312c 100644 --- a/packages/frontend/src/themes/d-botanical.json5 +++ b/packages/frontend/src/themes/d-botanical.json5 @@ -11,6 +11,7 @@ bg: 'rgb(37, 38, 36)', fg: 'rgb(216, 212, 199)', fgHighlighted: '#fff', + fgOnWhite: '@accent', divider: 'rgba(255, 255, 255, 0.14)', panel: 'rgb(47, 47, 44)', panelHeaderDivider: 'rgba(0, 0, 0, 0)', diff --git a/packages/frontend/src/themes/d-cherry.json5 b/packages/frontend/src/themes/d-cherry.json5 index a7e1ad1c809a90a589607f4cef5fe77d418dc4bf..f9638124c2eb1544b9d0ebde86506ac54f5ee6c5 100644 --- a/packages/frontend/src/themes/d-cherry.json5 +++ b/packages/frontend/src/themes/d-cherry.json5 @@ -10,6 +10,7 @@ accent: 'rgb(255, 89, 117)', bg: 'rgb(28, 28, 37)', fg: 'rgb(236, 239, 244)', + fgOnWhite: '@accent', panel: 'rgb(35, 35, 47)', renote: '@accent', link: '@accent', diff --git a/packages/frontend/src/themes/d-dark.json5 b/packages/frontend/src/themes/d-dark.json5 index 63144e88ea26f36cd9b551acda87b6e2ef292df6..ae4f7d53f5dbe9638d3e805b4a823ebd4bae09b0 100644 --- a/packages/frontend/src/themes/d-dark.json5 +++ b/packages/frontend/src/themes/d-dark.json5 @@ -11,6 +11,7 @@ bg: '#232323', fg: 'rgb(199, 209, 216)', fgHighlighted: '#fff', + fgOnWhite: '@accent', divider: 'rgba(255, 255, 255, 0.14)', panel: '#2d2d2d', panelHeaderDivider: 'rgba(0, 0, 0, 0)', diff --git a/packages/frontend/src/themes/d-future.json5 b/packages/frontend/src/themes/d-future.json5 index 0962a124110a4f655b7e1a312ea0ddd87e91f72a..f2c1f3eb86dd0db52f999603e22f150beda4f913 100644 --- a/packages/frontend/src/themes/d-future.json5 +++ b/packages/frontend/src/themes/d-future.json5 @@ -12,6 +12,7 @@ fg: '#D5D5D6', fgHighlighted: '#fff', fgOnAccent: '#000', + fgOnWhite: '@accent', divider: 'rgba(255, 255, 255, 0.1)', panel: '#18181c', panelHeaderDivider: 'rgba(0, 0, 0, 0)', diff --git a/packages/frontend/src/themes/d-green-lime.json5 b/packages/frontend/src/themes/d-green-lime.json5 index 9522f534a41b41fc4ff8f8db6009dc1dcef32726..ca4e688fdb1ad3541dd744d914b973f85b734251 100644 --- a/packages/frontend/src/themes/d-green-lime.json5 +++ b/packages/frontend/src/themes/d-green-lime.json5 @@ -12,6 +12,7 @@ fg: '#dee7e4', fgHighlighted: '#fff', fgOnAccent: '#192320', + fgOnWhite: '@accent', divider: '#e7fffb24', panel: '#192320', panelHeaderDivider: 'rgba(0, 0, 0, 0)', diff --git a/packages/frontend/src/themes/d-green-orange.json5 b/packages/frontend/src/themes/d-green-orange.json5 index e542782c6651ac5870c7e7b6d9c91f5eb88a726e..c2539816e254ebfae74336ea10dcf1afbb4488e5 100644 --- a/packages/frontend/src/themes/d-green-orange.json5 +++ b/packages/frontend/src/themes/d-green-orange.json5 @@ -12,6 +12,7 @@ fg: '#dee7e4', fgHighlighted: '#fff', fgOnAccent: '#192320', + fgOnWhite: '@accent', divider: '#e7fffb24', panel: '#192320', panelHeaderDivider: 'rgba(0, 0, 0, 0)', diff --git a/packages/frontend/src/themes/d-ice.json5 b/packages/frontend/src/themes/d-ice.json5 index 179b060dcf62b15d61367c8dfc0f2462632ab53a..b4abc0cacbe390712b87d3c7f3ea13418dbe4c38 100644 --- a/packages/frontend/src/themes/d-ice.json5 +++ b/packages/frontend/src/themes/d-ice.json5 @@ -8,6 +8,7 @@ props: { accent: '#47BFE8', + fgOnWhite: '@accent', bg: '#212526', }, } diff --git a/packages/frontend/src/themes/d-persimmon.json5 b/packages/frontend/src/themes/d-persimmon.json5 index e36265ff10ceaf2af5060bf094e1f81b0b1cafa1..0ab6523dd7cb46ca5b22c558846d89ec0c4069ee 100644 --- a/packages/frontend/src/themes/d-persimmon.json5 +++ b/packages/frontend/src/themes/d-persimmon.json5 @@ -11,6 +11,7 @@ bg: 'rgb(31, 33, 31)', fg: '#cdd8c7', fgHighlighted: '#fff', + fgOnWhite: '@accent', divider: 'rgba(255, 255, 255, 0.14)', panel: 'rgb(41, 43, 41)', infoFg: '@fg', diff --git a/packages/frontend/src/themes/d-u0.json5 b/packages/frontend/src/themes/d-u0.json5 index b270f809ac1718851f99ab0718448eb15ca13c12..ed776746a88a7238259386d38d3cfd6f9d644ed8 100644 --- a/packages/frontend/src/themes/d-u0.json5 +++ b/packages/frontend/src/themes/d-u0.json5 @@ -55,6 +55,7 @@ codeNumber: '#cfff9e', codeString: '#ffb675', fgOnAccent: '#fff', + fgOnWhite: '@accent', infoWarnBg: '#42321c', infoWarnFg: '#ffbd3e', navHoverFg: ':lighten<17<@fg', @@ -83,6 +84,6 @@ fgTransparentWeak: ':alpha<0.75<@fg', panelHeaderDivider: 'rgba(0, 0, 0, 0)', scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)', - deckDivider: '#142022', + deckBg: '#142022', }, } diff --git a/packages/frontend/src/themes/l-apricot.json5 b/packages/frontend/src/themes/l-apricot.json5 index 1ed552557585358df555d9df7159ca157fb590a3..fe1f9f892753f2ed021115dfe6acdf2fb2ab4e57 100644 --- a/packages/frontend/src/themes/l-apricot.json5 +++ b/packages/frontend/src/themes/l-apricot.json5 @@ -10,6 +10,7 @@ accent: 'rgb(234, 154, 82)', bg: '#e6e5e2', fg: 'rgb(149, 143, 139)', + fgOnWhite: '@accent', panel: '#EEECE8', renote: '@accent', link: '@accent', diff --git a/packages/frontend/src/themes/l-botanical.json5 b/packages/frontend/src/themes/l-botanical.json5 index 2ea9a7d6c9776a464f3d07d804bcc76d3c179ca0..5c9892789641d8d03f260bd9f6d3c112aaa47b7d 100644 --- a/packages/frontend/src/themes/l-botanical.json5 +++ b/packages/frontend/src/themes/l-botanical.json5 @@ -11,6 +11,7 @@ bg: 'e2deda', fg: '#3d3d3d', fgHighlighted: '#6bc9a0', + fgOnWhite: '@accent', divider: '#cfcfcf', panel: '@X14', panelHeaderBg: '@panel', diff --git a/packages/frontend/src/themes/l-cherry.json5 b/packages/frontend/src/themes/l-cherry.json5 index 5ad240241ed93402f63dec0f96450db90ee94f81..1189a28fe62cb5011c1898b766ecf336b39a23b3 100644 --- a/packages/frontend/src/themes/l-cherry.json5 +++ b/packages/frontend/src/themes/l-cherry.json5 @@ -10,6 +10,7 @@ accent: 'rgb(219, 96, 114)', bg: 'rgb(254, 248, 249)', fg: 'rgb(152, 13, 26)', + fgOnWhite: '@accent', panel: 'rgb(255, 255, 255)', renote: '@accent', link: 'rgb(156, 187, 5)', diff --git a/packages/frontend/src/themes/l-coffee.json5 b/packages/frontend/src/themes/l-coffee.json5 index fbcd4fa9efb3e373ee42fcbe58a03c160cc3eb17..b64cc735836baa0e038044dc65752461615b3800 100644 --- a/packages/frontend/src/themes/l-coffee.json5 +++ b/packages/frontend/src/themes/l-coffee.json5 @@ -10,6 +10,7 @@ accent: '#9f8989', bg: '#f5f3f3', fg: '#7f6666', + fgOnWhite: '@accent', panel: '#fff', divider: 'rgba(87, 68, 68, 0.1)', renote: 'rgb(160, 172, 125)', diff --git a/packages/frontend/src/themes/l-light.json5 b/packages/frontend/src/themes/l-light.json5 index 248355c9459d32588dad2cba8e829ec503bd78be..63c2e6d278b9afaa8559d53273b7965fb73da7d3 100644 --- a/packages/frontend/src/themes/l-light.json5 +++ b/packages/frontend/src/themes/l-light.json5 @@ -10,6 +10,7 @@ props: { bg: '#f9f9f9', fg: '#676767', + fgOnWhite: '@accent', divider: '#e8e8e8', header: ':alpha<0.7<@panel', navBg: '#fff', diff --git a/packages/frontend/src/themes/l-rainy.json5 b/packages/frontend/src/themes/l-rainy.json5 index 283dd74c6cfe6ef3938e68acbace9c194b7227bd..e7d1d5af009516d544dccef2cbcdfbfbb0f01c2f 100644 --- a/packages/frontend/src/themes/l-rainy.json5 +++ b/packages/frontend/src/themes/l-rainy.json5 @@ -10,6 +10,7 @@ accent: '#5db0da', bg: 'rgb(246 248 249)', fg: '#636b71', + fgOnWhite: '@accent', panel: '#fff', divider: 'rgb(230 233 234)', panelHeaderDivider: '@divider', diff --git a/packages/frontend/src/themes/l-sushi.json5 b/packages/frontend/src/themes/l-sushi.json5 index 5846927d6535e23693bae3cbe5aa52636e2e8e7b..e787d63734b5e25f7ef9df2d2450bc626bebbd86 100644 --- a/packages/frontend/src/themes/l-sushi.json5 +++ b/packages/frontend/src/themes/l-sushi.json5 @@ -10,6 +10,7 @@ accent: '#e36749', bg: '#f0eee9', fg: '#5f5f5f', + fgOnWhite: '@accent', renote: '@accent', link: '@accent', mention: '@accent', diff --git a/packages/frontend/src/themes/l-u0.json5 b/packages/frontend/src/themes/l-u0.json5 index 03b114ba39bc1f2e077bc3cbfc850d281614a96e..b77b15e3f0ad1c9d3fbb64d6976fd9d7a35acf51 100644 --- a/packages/frontend/src/themes/l-u0.json5 +++ b/packages/frontend/src/themes/l-u0.json5 @@ -55,6 +55,7 @@ codeNumber: '#cfff9e', codeString: '#ffb675', fgOnAccent: '#fff', + fgOnWhite: '@accent', infoWarnBg: '#42321c', infoWarnFg: '#ffbd3e', navHoverFg: ':lighten<17<@fg', diff --git a/packages/frontend/src/themes/l-vivid.json5 b/packages/frontend/src/themes/l-vivid.json5 index b3c08f38ae14d09640e31c667ac44c4b40fa8801..822ef948dd8c912b1119f178d693d6f16c4929e9 100644 --- a/packages/frontend/src/themes/l-vivid.json5 +++ b/packages/frontend/src/themes/l-vivid.json5 @@ -52,6 +52,7 @@ driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':darken<3<@fg', fgTransparent: ':alpha<0.5<@fg', + fgOnWhite: '@accent', panelHeaderBg: ':lighten<3<@panel', panelHeaderFg: '@fg', htmlThemeColor: '@bg', diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index 71a4285e9db12c95c580258c0ad4bb3849b6981b..3b970eefbec75cc99a582f0b91cfad0f24440e87 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -10,12 +10,20 @@ <XUpload v-if="uploads.length > 0"/> <TransitionGroup - tag="div" :class="[$style.notifications, $style[`notificationsPosition-${defaultStore.state.notificationPosition}`], $style[`notificationsStackAxis-${defaultStore.state.notificationStackAxis}`]]" - :move-class="defaultStore.state.animation ? $style.transition_notification_move : ''" - :enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''" + tag="div" + :class="[$style.notifications, { + [$style.notificationsPosition_leftTop]: defaultStore.state.notificationPosition === 'leftTop', + [$style.notificationsPosition_leftBottom]: defaultStore.state.notificationPosition === 'leftBottom', + [$style.notificationsPosition_rightTop]: defaultStore.state.notificationPosition === 'rightTop', + [$style.notificationsPosition_rightBottom]: defaultStore.state.notificationPosition === 'rightBottom', + [$style.notificationsStackAxis_vertical]: defaultStore.state.notificationStackAxis === 'vertical', + [$style.notificationsStackAxis_horizontal]: defaultStore.state.notificationStackAxis === 'horizontal', + }]" + :moveClass="defaultStore.state.animation ? $style.transition_notification_move : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_notification_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''" > <div v-for="notification in notifications" :key="notification.id" :class="$style.notification"> <XNotification :notification="notification"/> @@ -40,7 +48,7 @@ import { popups, pendingApiRequestsCount } from '@/os'; import { uploads } from '@/scripts/upload'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; @@ -55,7 +63,7 @@ function onNotification(notification) { if ($i.mutingNotificationTypes.includes(notification.type)) return; if (document.visibilityState === 'visible') { - stream.send('readNotification'); + useStream().send('readNotification'); notifications.unshift(notification); window.setTimeout(() => { @@ -71,7 +79,7 @@ function onNotification(notification) { } if ($i) { - const connection = stream.useChannel('main', null, 'UI'); + const connection = useStream().useChannel('main', null, 'UI'); connection.on('notification', onNotification); //#region Listen message from SW @@ -103,31 +111,31 @@ if ($i) { pointer-events: none; display: flex; - &.notificationsPosition-leftTop { + &.notificationsPosition_leftTop { top: var(--margin); left: 0; } - &.notificationsPosition-rightTop { + &.notificationsPosition_rightTop { top: var(--margin); right: 0; } - &.notificationsPosition-leftBottom { + &.notificationsPosition_leftBottom { bottom: calc(var(--minBottomSpacing) + var(--margin)); left: 0; } - &.notificationsPosition-rightBottom { + &.notificationsPosition_rightBottom { bottom: calc(var(--minBottomSpacing) + var(--margin)); right: 0; } - &.notificationsStackAxis-vertical { + &.notificationsStackAxis_vertical { width: 250px; - &.notificationsPosition-leftTop, - &.notificationsPosition-rightTop { + &.notificationsPosition_leftTop, + &.notificationsPosition_rightTop { flex-direction: column; .notification { @@ -137,8 +145,8 @@ if ($i) { } } - &.notificationsPosition-leftBottom, - &.notificationsPosition-rightBottom { + &.notificationsPosition_leftBottom, + &.notificationsPosition_rightBottom { flex-direction: column-reverse; .notification { @@ -149,11 +157,11 @@ if ($i) { } } - &.notificationsStackAxis-horizontal { + &.notificationsStackAxis_horizontal { width: 100%; - &.notificationsPosition-leftTop, - &.notificationsPosition-leftBottom { + &.notificationsPosition_leftTop, + &.notificationsPosition_leftBottom { flex-direction: row; .notification { @@ -163,8 +171,8 @@ if ($i) { } } - &.notificationsPosition-rightTop, - &.notificationsPosition-rightBottom { + &.notificationsPosition_rightTop, + &.notificationsPosition_rightBottom { flex-direction: row-reverse; .notification { diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index 7a94a0c3eedfa570afca95353c4a9e1d574d20dc..365486a0aea6d6287f991e235df0343a996fb155 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -1,43 +1,41 @@ <template> -<div class="kmwsukvl"> - <div class="body"> - <div class="top"> - <div class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div> - <button v-click-anime class="item _button instance" @click="openInstanceMenu"> - <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> - </button> - </div> - <div class="middle"> - <MkA v-click-anime class="item index" active-class="active" to="/" exact> - <i class="icon ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span> - </MkA> - <template v-for="item in menu"> - <div v-if="item === '-'" class="divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> - <i class="icon ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span> - <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon _indicatorCircle"></i></span> - </component> - </template> - <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin"> - <i class="icon ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span> - </MkA> - <button v-click-anime class="item _button" @click="more"> - <i class="icon ti ti-grid-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span> - <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon _indicatorCircle"></i></span> - </button> - <MkA v-click-anime class="item" active-class="active" to="/settings"> - <i class="icon ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span> - </MkA> - </div> - <div class="bottom"> - <button class="item _button post" data-cy-open-post-form @click="os.post"> - <i class="icon ti ti-pencil ti-fw"></i><span class="text">{{ i18n.ts.note }}</span> - </button> - <button v-click-anime class="item _button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><MkAcct class="text _nowrap" :user="$i"/> - </button> - </div> +<div :class="$style.root"> + <div :class="$style.top"> + <div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div> + <button class="_button" :class="$style.instance" @click="openInstanceMenu"> + <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/> + </button> + </div> + <div :class="$style.middle"> + <MkA :class="$style.item" :activeClass="$style.active" to="/" exact> + <i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span> + </MkA> + <template v-for="item in menu"> + <div v-if="item === '-'" :class="$style.divider"></div> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span> + <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span> + </component> + </template> + <div :class="$style.divider"></div> + <MkA v-if="$i.isAdmin || $i.isModerator" :class="$style.item" :activeClass="$style.active" to="/admin"> + <i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span> + </MkA> + <button :class="$style.item" class="_button" @click="more"> + <i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span> + <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span> + </button> + <MkA :class="$style.item" :activeClass="$style.active" to="/settings"> + <i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span> + </MkA> + </div> + <div :class="$style.bottom"> + <button class="_button" :class="$style.post" data-cy-open-post-form @click="os.post"> + <i :class="$style.postIcon" class="ti ti-pencil ti-fw"></i><span style="position: relative;">{{ i18n.ts.note }}</span> + </button> + <button class="_button" :class="$style.account" @click="openAccountMenu"> + <MkAvatar :user="$i" :class="$style.avatar"/><MkAcct :class="$style.acct" class="_nowrap" :user="$i"/> + </button> </div> </div> </template> @@ -73,192 +71,186 @@ function more() { } </script> -<style lang="scss" scoped> -.kmwsukvl { - > .body { - display: flex; - flex-direction: column; - - > .top { - position: sticky; - top: 0; - z-index: 1; - padding: 20px 0; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); +<style lang="scss" module> +.root { + display: flex; + flex-direction: column; +} - > .banner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-size: cover; - background-position: center center; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - } +.top { + position: sticky; + top: 0; + z-index: 1; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); +} - > .instance { - position: relative; - display: block; - text-align: center; - width: 100%; +.banner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); +} - > .icon { - display: inline-block; - width: 38px; - aspect-ratio: 1; - } - } - } +.instance { + position: relative; + display: block; + text-align: center; + width: 100%; +} - > .bottom { - position: sticky; - bottom: 0; - padding: 20px 0; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); +.instanceIcon { + display: inline-block; + width: 38px; + aspect-ratio: 1; +} - > .post { - position: relative; - display: block; - width: 100%; - height: 40px; - color: var(--fgOnAccent); - font-weight: bold; - text-align: left; +.bottom { + position: sticky; + bottom: 0; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); +} - &:before { - content: ""; - display: block; - width: calc(100% - 38px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); - } +.post { + position: relative; + display: block; + width: 100%; + height: 40px; + color: var(--fgOnAccent); + font-weight: bold; + text-align: left; - &:hover, &.active { - &:before { - background: var(--accentLighten); - } - } + &:before { + content: ""; + display: block; + width: calc(100% - 38px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + } - > .icon { - position: relative; - margin-left: 30px; - margin-right: 8px; - width: 32px; - } + &:hover, &.active { + &:before { + background: var(--accentLighten); + } + } +} - > .text { - position: relative; - } - } +.postIcon { + position: relative; + margin-left: 30px; + margin-right: 8px; + width: 32px; +} - > .account { - position: relative; - display: flex; - align-items: center; - padding-left: 30px; - width: 100%; - text-align: left; - box-sizing: border-box; - margin-top: 16px; +.account { + position: relative; + display: flex; + align-items: center; + padding-left: 30px; + width: 100%; + text-align: left; + box-sizing: border-box; + margin-top: 16px; +} - > .avatar { - display: block; - flex-shrink: 0; - position: relative; - width: 32px; - aspect-ratio: 1; - margin-right: 8px; - } +.avatar { + display: block; + flex-shrink: 0; + position: relative; + width: 32px; + aspect-ratio: 1; + margin-right: 8px; +} - > .text { - display: block; - flex-shrink: 1; - padding-right: 8px; - } - } - } +.acct { + display: block; + flex-shrink: 1; + padding-right: 8px; +} - > .middle { - flex: 1; +.middle { + flex: 1; +} - > .divider { - margin: 16px 16px; - border-top: solid 0.5px var(--divider); - } +.divider { + margin: 16px 16px; + border-top: solid 0.5px var(--divider); +} - > .item { - position: relative; - display: block; - padding-left: 24px; - line-height: 2.85rem; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 100%; - text-align: left; - box-sizing: border-box; - color: var(--navFg); +.item { + position: relative; + display: block; + padding-left: 24px; + line-height: 2.85rem; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + color: var(--navFg); - > .icon { - position: relative; - width: 32px; - margin-right: 8px; - } + &:hover { + text-decoration: none; + color: var(--navHoverFg); + } - > .indicator { - position: absolute; - top: 0; - left: 20px; - color: var(--navIndicator); - font-size: 8px; - animation: blink 1s infinite; - } + &.active { + color: var(--navActive); + } - > .text { - position: relative; - font-size: 0.9em; - } + &:hover, &.active { + &:before { + content: ""; + display: block; + width: calc(100% - 24px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: var(--accentedBg); + } + } +} - &:hover { - text-decoration: none; - color: var(--navHoverFg); - } +.itemIcon { + position: relative; + width: 32px; + margin-right: 8px; +} - &.active { - color: var(--navActive); - } +.itemIndicator { + position: absolute; + top: 0; + left: 20px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; +} - &:hover, &.active { - &:before { - content: ""; - display: block; - width: calc(100% - 24px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: var(--accentedBg); - } - } - } - } - } +.itemText { + position: relative; + font-size: 0.9em; } </style> diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 3b4b16142262fb1feca175cd27284486c349b043..a184f1d2f0076796dd6f3197dadaa2946cabfdb2 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -1,51 +1,50 @@ <template> -<div class="mvcprjjd" :class="{ iconOnly }"> - <div class="body"> - <div class="top"> - <div class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div> - <button v-click-anime v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu"> - <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> +<div :class="[$style.root, { [$style.iconOnly]: iconOnly }]"> + <div :class="$style.body"> + <div :class="$style.top"> + <div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div> + <button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu"> + <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/> </button> </div> - <div class="middle"> - <MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact> - <i class="icon ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span> + <div :class="$style.middle"> + <MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact> + <i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span> </MkA> <template v-for="item in menu"> - <div v-if="item === '-'" class="divider"></div> + <div v-if="item === '-'" :class="$style.divider"></div> <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" - v-click-anime v-tooltip.noDelay.right="navbarItemDef[item].title" - class="item _button" - :class="[item, { active: navbarItemDef[item].active }]" - active-class="active" + class="_button" + :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" + :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}" > - <i class="icon ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span> - <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon _indicatorCircle"></i></span> + <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span> + <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span> </component> </template> - <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.noDelay.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin"> - <i class="icon ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span> + <div :class="$style.divider"></div> + <MkA v-if="$i.isAdmin || $i.isModerator" v-tooltip.noDelay.right="i18n.ts.controlPanel" :class="$style.item" :activeClass="$style.active" to="/admin"> + <i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span> </MkA> - <button v-click-anime class="item _button" @click="more"> - <i class="icon ti ti-grid-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span> - <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon _indicatorCircle"></i></span> + <button class="_button" :class="$style.item" @click="more"> + <i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span> + <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span> </button> - <MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.settings" class="item" active-class="active" to="/settings"> - <i class="icon ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span> + <MkA v-tooltip.noDelay.right="i18n.ts.settings" :class="$style.item" :activeClass="$style.active" to="/settings"> + <i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span> </MkA> </div> - <div class="bottom"> - <button v-tooltip.noDelay.right="i18n.ts.note" class="item _button post" data-cy-open-post-form @click="os.post"> - <i class="icon ti ti-pencil ti-fw"></i><span class="text">{{ i18n.ts.note }}</span> + <div :class="$style.bottom"> + <button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="os.post"> + <i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span> </button> - <button v-click-anime v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="item _button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><MkAcct class="text _nowrap" :user="$i"/> + <button v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu"> + <MkAvatar :user="$i" :class="$style.avatar"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/> </button> </div> </div> @@ -99,374 +98,376 @@ function more(ev: MouseEvent) { } </script> -<style lang="scss" scoped> -.mvcprjjd { - $nav-width: 250px; - $nav-icon-only-width: 80px; +<style lang="scss" module> +.root { + --nav-width: 250px; + --nav-icon-only-width: 72px; - flex: 0 0 $nav-width; - width: $nav-width; + flex: 0 0 var(--nav-width); + width: var(--nav-width); box-sizing: border-box; +} + +.body { + position: fixed; + top: 0; + left: 0; + z-index: 1001; + width: var(--nav-icon-only-width); + height: 100dvh; + box-sizing: border-box; + overflow: auto; + overflow-x: clip; + overscroll-behavior: contain; + background: var(--navBg); + contain: strict; + display: flex; + flex-direction: column; +} + +.root:not(.iconOnly) { + .body { + width: var(--nav-width); + } - > .body { - position: fixed; + .top { + position: sticky; + top: 0; + z-index: 1; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + } + + .banner { + position: absolute; top: 0; left: 0; - z-index: 1001; - width: $nav-icon-only-width; - height: 100dvh; - box-sizing: border-box; - overflow: auto; - overflow-x: clip; - background: var(--navBg); - contain: strict; - display: flex; - flex-direction: column; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); } - &:not(.iconOnly) { - > .body { - width: $nav-width; + .instance { + position: relative; + display: block; + text-align: center; + width: 100%; + } - > .top { - position: sticky; - top: 0; - z-index: 1; - padding: 20px 0; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - - > .banner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-size: cover; - background-position: center center; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - } - - > .instance { - position: relative; - display: block; - text-align: center; - width: 100%; - - > .icon { - display: inline-block; - width: 38px; - aspect-ratio: 1; - } - } + .instanceIcon { + display: inline-block; + width: 38px; + aspect-ratio: 1; + } + + .bottom { + position: sticky; + bottom: 0; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + } + + .post { + position: relative; + display: block; + width: 100%; + height: 40px; + color: var(--fgOnAccent); + font-weight: bold; + text-align: left; + + &:before { + content: ""; + display: block; + width: calc(100% - 38px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + } + + &:hover, &.active { + &:before { + background: var(--accentLighten); } + } + } + + .postIcon { + position: relative; + margin-left: 30px; + margin-right: 8px; + width: 32px; + } - > .bottom { - position: sticky; + .postText { + position: relative; + } + + .account { + position: relative; + display: flex; + align-items: center; + padding-left: 30px; + width: 100%; + text-align: left; + box-sizing: border-box; + margin-top: 16px; + } + + .avatar { + display: block; + flex-shrink: 0; + position: relative; + width: 32px; + aspect-ratio: 1; + margin-right: 8px; + } + + .acct { + display: block; + flex-shrink: 1; + padding-right: 8px; + } + + .middle { + flex: 1; + } + + .divider { + margin: 16px 16px; + border-top: solid 0.5px var(--divider); + } + + .item { + position: relative; + display: block; + padding-left: 30px; + line-height: 2.85rem; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + color: var(--navFg); + + &:hover { + text-decoration: none; + color: var(--navHoverFg); + } + + &.active { + color: var(--navActive); + } + + &:hover, &.active { + color: var(--accent); + + &:before { + content: ""; + display: block; + width: calc(100% - 34px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; bottom: 0; - padding: 20px 0; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - - > .post { - position: relative; - display: block; - width: 100%; - height: 40px; - color: var(--fgOnAccent); - font-weight: bold; - text-align: left; - - &:before { - content: ""; - display: block; - width: calc(100% - 38px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); - } - - &:hover, &.active { - &:before { - background: var(--accentLighten); - } - } - - > .icon { - position: relative; - margin-left: 30px; - margin-right: 8px; - width: 32px; - } - - > .text { - position: relative; - } - } - - > .account { - position: relative; - display: flex; - align-items: center; - padding-left: 30px; - width: 100%; - text-align: left; - box-sizing: border-box; - margin-top: 16px; - - > .avatar { - display: block; - flex-shrink: 0; - position: relative; - width: 32px; - aspect-ratio: 1; - margin-right: 8px; - } - - > .text { - display: block; - flex-shrink: 1; - padding-right: 8px; - } - } + border-radius: 999px; + background: var(--accentedBg); } + } + } + + .itemIcon { + position: relative; + width: 32px; + margin-right: 8px; + } + + .itemIndicator { + position: absolute; + top: 0; + left: 20px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } + + .itemText { + position: relative; + font-size: 0.9em; + } +} + +.root.iconOnly { + flex: 0 0 var(--nav-icon-only-width); + width: var(--nav-icon-only-width); + + .body { + width: var(--nav-icon-only-width); + } + + .top { + position: sticky; + top: 0; + z-index: 1; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + } + + .instance { + display: block; + text-align: center; + width: 100%; + } + + .instanceIcon { + display: inline-block; + width: 30px; + aspect-ratio: 1; + } + + .bottom { + position: sticky; + bottom: 0; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + } - > .middle { - flex: 1; - - > .divider { - margin: 16px 16px; - border-top: solid 0.5px var(--divider); - } - - > .item { - position: relative; - display: block; - padding-left: 30px; - line-height: 2.85rem; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 100%; - text-align: left; - box-sizing: border-box; - color: var(--navFg); - - > .icon { - position: relative; - width: 32px; - margin-right: 8px; - } - - > .indicator { - position: absolute; - top: 0; - left: 20px; - color: var(--navIndicator); - font-size: 8px; - animation: blink 1s infinite; - } - - > .text { - position: relative; - font-size: 0.9em; - } - - &:hover { - text-decoration: none; - color: var(--navHoverFg); - } - - &.active { - color: var(--navActive); - } - - &:hover, &.active { - color: var(--accent); - - &:before { - content: ""; - display: block; - width: calc(100% - 34px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: var(--accentedBg); - } - } - } + .post { + display: block; + position: relative; + width: 100%; + height: 52px; + margin-bottom: 16px; + text-align: center; + + &:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 52px; + aspect-ratio: 1/1; + border-radius: 100%; + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + } + + &:hover, &.active { + &:before { + background: var(--accentLighten); } } } - &.iconOnly { - flex: 0 0 $nav-icon-only-width; - width: $nav-icon-only-width; + .postIcon { + position: relative; + color: var(--fgOnAccent); + } - > .body { - width: $nav-icon-only-width; + .postText { + display: none; + } - > .top { - position: sticky; - top: 0; - z-index: 1; - padding: 20px 0; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - - > .instance { - display: block; - text-align: center; - width: 100%; - - > .icon { - display: inline-block; - width: 30px; - aspect-ratio: 1; - } - } - } + .account { + display: block; + text-align: center; + width: 100%; + } + + .avatar { + display: inline-block; + width: 38px; + aspect-ratio: 1; + } + + .acct { + display: none; + } - > .bottom { - position: sticky; + .middle { + flex: 1; + } + + .divider { + margin: 8px auto; + width: calc(100% - 32px); + border-top: solid 0.5px var(--divider); + } + + .item { + display: block; + position: relative; + padding: 18px 0; + width: 100%; + text-align: center; + + &:hover, &.active { + text-decoration: none; + color: var(--accent); + + &:before { + content: ""; + display: block; + height: 100%; + aspect-ratio: 1; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; bottom: 0; - padding: 20px 0; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - - > .post { - display: block; - position: relative; - width: 100%; - height: 52px; - margin-bottom: 16px; - text-align: center; - - &:before { - content: ""; - display: block; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: 52px; - aspect-ratio: 1/1; - border-radius: 100%; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); - } - - &:hover, &.active { - &:before { - background: var(--accentLighten); - } - } - - > .icon { - position: relative; - color: var(--fgOnAccent); - } - - > .text { - display: none; - } - } - - > .account { - display: block; - text-align: center; - width: 100%; - - > .avatar { - display: inline-block; - width: 38px; - aspect-ratio: 1; - } - - > .text { - display: none; - } - } + border-radius: 999px; + background: var(--accentedBg); } - > .middle { - flex: 1; - - > .divider { - margin: 8px auto; - width: calc(100% - 32px); - border-top: solid 0.5px var(--divider); - } - - > .item { - display: block; - position: relative; - padding: 18px 0; - width: 100%; - text-align: center; - - > .icon { - display: block; - margin: 0 auto; - opacity: 0.7; - } - - > .text { - display: none; - } - - > .indicator { - position: absolute; - top: 6px; - left: 24px; - color: var(--navIndicator); - font-size: 8px; - animation: blink 1s infinite; - } - - &:hover, &.active { - text-decoration: none; - color: var(--accent); - - &:before { - content: ""; - display: block; - height: 100%; - aspect-ratio: 1; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: var(--accentedBg); - } - - > .icon, > .text { - opacity: 1; - } - } - } + > .icon, + > .text { + opacity: 1; } } } + + .itemIcon { + display: block; + margin: 0 auto; + opacity: 0.7; + } + + .itemText { + display: none; + } + + .itemIndicator { + position: absolute; + top: 6px; + left: 24px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } } </style> diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue index fe95460ba4b894be41b61065a3c67e544d1c753d..6f2e4bc9a76ea91e1cb7870d165a383867f5e81a 100644 --- a/packages/frontend/src/ui/_common_/statusbar-federation.vue +++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue @@ -1,14 +1,20 @@ <template> -<span v-if="!fetching" class="nmidsaqw"> +<span v-if="!fetching" :class="$style.root"> <template v-if="display === 'marquee'"> - <Transition name="change" mode="default"> + <Transition + :enterActiveClass="$style.transition_change_enterActive" + :leaveActiveClass="$style.transition_change_leaveActive" + :enterFromClass="$style.transition_change_enterFrom" + :leaveToClass="$style.transition_change_leaveTo" + mode="default" + > <MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse"> - <span v-for="instance in instances" :key="instance.id" class="item" :class="{ colored }" :style="{ background: colored ? instance.themeColor : null }"> - <img class="icon" :src="getInstanceIcon(instance)" alt=""/> - <MkA :to="`/instance-info/${instance.host}`" class="host _monospace"> + <span v-for="instance in instances" :key="instance.id" :class="[$style.item, { [$style.colored]: colored }]" :style="{ background: colored ? instance.themeColor : null }"> + <img :class="$style.icon" :src="getInstanceIcon(instance)" alt=""/> + <MkA :to="`/instance-info/${instance.host}`" :class="$style.host" class="_monospace"> {{ instance.host }} </MkA> - <span class="divider"></span> + <span></span> </span> </MarqueeText> </Transition> @@ -61,46 +67,47 @@ function getInstanceIcon(instance): string { } </script> -<style lang="scss" scoped> -.change-enter-active, .change-leave-active { +<style lang="scss" module> +.transition_change_enterActive, +.transition_change_leaveActive { position: absolute; top: 0; transition: all 1s ease; } -.change-enter-from { - opacity: 0; +.transition_change_enterFrom { + opacity: 0; transform: translateY(-100%); } -.change-leave-to { - opacity: 0; +.transition_change_leaveTo { + opacity: 0; transform: translateY(100%); } -.nmidsaqw { +.root { display: inline-block; position: relative; +} - ::v-deep(.item) { - display: inline-block; - vertical-align: bottom; - margin-right: 5em; +.item { + display: inline-block; + vertical-align: bottom; + margin-right: 5em; - > .icon { - display: inline-block; - height: var(--height); - aspect-ratio: 1; - vertical-align: bottom; - margin-right: 1em; - } + &.colored { + padding-right: 1em; + color: #fff; + } +} - > .host { - vertical-align: bottom; - } +.icon { + display: inline-block; + height: var(--height); + aspect-ratio: 1; + vertical-align: bottom; + margin-right: 1em; +} - &.colored { - padding-right: 1em; - color: #fff; - } - } +.host { + vertical-align: bottom; } </style> diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue index 44b6b278eac309855d069477652415521f005232..82473b609fe50e379ac5f92b6e60268837884b56 100644 --- a/packages/frontend/src/ui/_common_/statusbar-rss.vue +++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue @@ -1,10 +1,16 @@ <template> -<span v-if="!fetching" class="xbhtxfms"> +<span v-if="!fetching" :class="$style.root"> <template v-if="display === 'marquee'"> - <Transition name="change" mode="default"> + <Transition + :enterActiveClass="$style.transition_change_enterActive" + :leaveActiveClass="$style.transition_change_leaveActive" + :enterFromClass="$style.transition_change_enterFrom" + :leaveToClass="$style.transition_change_leaveTo" + mode="default" + > <MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse"> - <span v-for="item in items" class="item"> - <a class="link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span class="divider"></span> + <span v-for="item in items" :class="$style.item"> + <a :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span :class="$style.divider"></span> </span> </MarqueeText> </Transition> @@ -54,39 +60,40 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), { }); </script> -<style lang="scss" scoped> -.change-enter-active, .change-leave-active { +<style lang="scss" module> +.transition_change_enterActive, +.transition_change_leaveActive { position: absolute; top: 0; transition: all 1s ease; } -.change-enter-from { - opacity: 0; +.transition_change_enterFrom { + opacity: 0; transform: translateY(-100%); } -.change-leave-to { - opacity: 0; +.transition_change_leaveTo { + opacity: 0; transform: translateY(100%); } -.xbhtxfms { +.root { display: inline-block; position: relative; +} - ::v-deep(.item) { - display: inline-flex; - align-items: center; - vertical-align: bottom; - margin: 0; +.item { + display: inline-flex; + align-items: center; + vertical-align: bottom; + margin: 0; +} - > .divider { - display: inline-block; - width: 0.5px; - height: var(--height); - margin: 0 3em; - background: currentColor; - opacity: 0.3; - } - } +.divider { + display: inline-block; + width: 0.5px; + height: var(--height); + margin: 0 3em; + background: currentColor; + opacity: 0.3; } </style> diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue index 16df69d968de38d13e4833edadfa1a7549efe458..9ac744943d32b92ba9a9a19199411c4784a0aecb 100644 --- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue +++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue @@ -1,14 +1,20 @@ <template> -<span v-if="!fetching" class="osdsvwzy"> +<span v-if="!fetching" :class="$style.root"> <template v-if="display === 'marquee'"> - <Transition name="change" mode="default"> + <Transition + :enterActiveClass="$style.transition_change_enterActive" + :leaveActiveClass="$style.transition_change_leaveActive" + :enterFromClass="$style.transition_change_enterFrom" + :leaveToClass="$style.transition_change_leaveTo" + mode="default" + > <MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse"> - <span v-for="note in notes" :key="note.id" class="item"> - <img class="avatar" :src="note.user.avatarUrl" decoding="async"/> - <MkA class="text" :to="notePage(note)"> - <Mfm class="text" :text="getNoteSummary(note)" :plain="true" :nowrap="true"/> + <span v-for="note in notes" :key="note.id" :class="$style.item"> + <img :class="$style.avatar" :src="note.user.avatarUrl" decoding="async"/> + <MkA :class="$style.text" :to="notePage(note)"> + <Mfm :text="getNoteSummary(note)" :plain="true" :nowrap="true"/> </MkA> - <span class="divider"></span> + <span :class="$style.divider"></span> </span> </MarqueeText> </Transition> @@ -60,54 +66,53 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), { }); </script> -<style lang="scss" scoped> -.change-enter-active, .change-leave-active { +<style lang="scss" module> +.transition_change_enterActive, +.transition_change_leaveActive { position: absolute; top: 0; transition: all 1s ease; } -.change-enter-from { - opacity: 0; +.transition_change_enterFrom { + opacity: 0; transform: translateY(-100%); } -.change-leave-to { - opacity: 0; +.transition_change_leaveTo { + opacity: 0; transform: translateY(100%); } -.osdsvwzy { +.root { display: inline-block; position: relative; +} - ::v-deep(.item) { - display: inline-flex; - align-items: center; - vertical-align: bottom; - margin: 0; +.item { + display: inline-flex; + align-items: center; + vertical-align: bottom; + margin: 0; +} - > .avatar { - display: inline-block; - height: var(--height); - aspect-ratio: 1; - vertical-align: bottom; - margin-right: 8px; - } +.avatar { + display: inline-block; + height: var(--height); + aspect-ratio: 1; + vertical-align: bottom; + margin-right: 8px; +} - > .text { - > .text { - display: inline-block; - vertical-align: bottom; - } - } +.text { + display: inline-block; + vertical-align: bottom; +} - > .divider { - display: inline-block; - width: 0.5px; - height: 16px; - margin: 0 3em; - background: currentColor; - opacity: 0; - } - } +.divider { + display: inline-block; + width: 0.5px; + height: 16px; + margin: 0 3em; + background: currentColor; + opacity: 0; } </style> diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue index f84695c15f8562ed804b752487476a47e1ac9dd5..3533972cdf94cc1bd0d25fbec2175146587efae7 100644 --- a/packages/frontend/src/ui/_common_/statusbars.vue +++ b/packages/frontend/src/ui/_common_/statusbars.vue @@ -1,18 +1,17 @@ <template> -<div class="dlrsnxqu"> +<div :class="$style.root"> <div - v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" class="item" :class="[{ black: x.black }, { - verySmall: x.size === 'verySmall', - small: x.size === 'small', - medium: x.size === 'medium', - large: x.size === 'large', - veryLarge: x.size === 'veryLarge', + v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" :class="[$style.item, { [$style.black]: x.black, + [$style.verySmall]: x.size === 'verySmall', + [$style.small]: x.size === 'small', + [$style.large]: x.size === 'large', + [$style.veryLarge]: x.size === 'veryLarge', }]" > - <span class="name">{{ x.name }}</span> - <XRss v-if="x.type === 'rss'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/> - <XFederation v-else-if="x.type === 'federation'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/> - <XUserList v-else-if="x.type === 'userList'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :user-list-id="x.props.userListId"/> + <span :class="$style.name">{{ x.name }}</span> + <XRss v-if="x.type === 'rss'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/> + <XFederation v-else-if="x.type === 'federation'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/> + <XUserList v-else-if="x.type === 'userList'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :userListId="x.props.userListId"/> </div> </div> </template> @@ -25,67 +24,67 @@ const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vu const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue')); </script> -<style lang="scss" scoped> -.dlrsnxqu { +<style lang="scss" module> +.root { font-size: 15px; background: var(--panel); +} - > .item { - --height: 24px; - --nameMargin: 10px; - font-size: 0.85em; - - &.verySmall { - --nameMargin: 7px; - --height: 16px; - font-size: 0.75em; - } +.item { + --height: 24px; + --nameMargin: 10px; + font-size: 0.85em; - &.small { - --nameMargin: 8px; - --height: 20px; - font-size: 0.8em; - } + &.verySmall { + --nameMargin: 7px; + --height: 16px; + font-size: 0.75em; + } - &.large { - --nameMargin: 12px; - --height: 26px; - font-size: 0.875em; - } + &.small { + --nameMargin: 8px; + --height: 20px; + font-size: 0.8em; + } - &.veryLarge { - --nameMargin: 14px; - --height: 30px; - font-size: 0.9em; - } + &.large { + --nameMargin: 12px; + --height: 26px; + font-size: 0.875em; + } - display: flex; - vertical-align: bottom; - width: 100%; - line-height: var(--height); - height: var(--height); - overflow: clip; - contain: strict; + &.veryLarge { + --nameMargin: 14px; + --height: 30px; + font-size: 0.9em; + } - > .name { - padding: 0 var(--nameMargin); - font-weight: bold; - color: var(--accent); + display: flex; + vertical-align: bottom; + width: 100%; + line-height: var(--height); + height: var(--height); + overflow: clip; + contain: strict; - &:empty { - display: none; - } - } + &.black { + background: #000; + color: #fff; + } +} - > .body { - min-width: 0; - flex: 1; - } +.name { + padding: 0 var(--nameMargin); + font-weight: bold; + color: var(--accent); - &.black { - background: #000; - color: #fff; - } + &:empty { + display: none; } } + +.body { + min-width: 0; + flex: 1; +} </style> diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue index 2a856e2a4542d2f9494b279605fc8e1be94538ad..74c475fc7de614631501d2df0b506c2b03256b5d 100644 --- a/packages/frontend/src/ui/_common_/stream-indicator.vue +++ b/packages/frontend/src/ui/_common_/stream-indicator.vue @@ -2,15 +2,15 @@ <div v-if="hasDisconnected && defaultStore.state.serverDisconnectedBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetDisconnected"> <div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.disconnectedFromServer }}</div> <div :class="$style.command" class="_buttons"> - <MkButton :class="$style.commandButton" small primary @click="reload">{{ i18n.ts.reload }}</MkButton> - <MkButton :class="$style.commandButton" small>{{ i18n.ts.doNothing }}</MkButton> + <MkButton small primary @click="reload">{{ i18n.ts.reload }}</MkButton> + <MkButton small>{{ i18n.ts.doNothing }}</MkButton> </div> </div> </template> <script lang="ts" setup> import { onUnmounted } from 'vue'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; @@ -32,10 +32,10 @@ function reload() { location.reload(); } -stream.on('_disconnected_', onDisconnected); +useStream().on('_disconnected_', onDisconnected); onUnmounted(() => { - stream.off('_disconnected_', onDisconnected); + useStream().off('_disconnected_', onDisconnected); }); </script> @@ -54,7 +54,4 @@ onUnmounted(() => { .command { margin-top: 8px; } - -.commandButton { -} </style> diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue index daea775552528478098a7b2a6ca16a8836999c7d..747d4edcb4495fe1dd5d310b76a3cc6ad0456aac 100644 --- a/packages/frontend/src/ui/classic.header.vue +++ b/packages/frontend/src/ui/classic.header.vue @@ -5,18 +5,18 @@ <button v-click-anime class="item _button instance" @click="openInstanceMenu"> <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/> </button> - <MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" active-class="active" to="/" exact> + <MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact> <i class="ti ti-home ti-fw"></i> </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <i class="ti-fw" :class="navbarItemDef[item].icon"></i> <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="_indicatorCircle"></i></span> </component> </template> <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" active-class="active" to="/admin" :behavior="settingsWindowed ? 'modalWindow' : null"> + <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null"> <i class="ti ti-dashboard ti-fw"></i> </MkA> <button v-click-anime class="item _button" @click="more"> @@ -25,7 +25,7 @@ </button> </div> <div class="right"> - <MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null"> + <MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null"> <i class="ti ti-settings ti-fw"></i> </MkA> <button v-click-anime class="item _button account" @click="openAccountMenu"> diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue index 73db14c65e5929e9c75860a1cc9c8786860875a2..cb264fc3ba70e7c83ccc64c3cf7264ffdff5f338 100644 --- a/packages/frontend/src/ui/classic.sidebar.vue +++ b/packages/frontend/src/ui/classic.sidebar.vue @@ -9,25 +9,25 @@ </MkButton> </div> <div class="divider"></div> - <MkA v-click-anime class="item index" active-class="active" to="/" exact> + <MkA v-click-anime class="item index" activeClass="active" to="/" exact> <i class="ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span> </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <i class="ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span> <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="_indicatorCircle"></i></span> </component> </template> <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin" :behavior="settingsWindowed ? 'modalWindow' : null"> + <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null"> <i class="ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span> </MkA> <button v-click-anime class="item _button" @click="more"> <i class="ti ti-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span> <span v-if="otherNavItemIndicated" class="indicator"><i class="_indicatorCircle"></i></span> </button> - <MkA v-click-anime class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null"> + <MkA v-click-anime class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null"> <i class="ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span> </MkA> <div class="divider"></div> diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index 792c1ccc5e44b668ad55e8a9b3bdf31bf56ad70c..d50f2b0454b8358ec577dbbd868e144cd00d4d7c 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -7,17 +7,17 @@ <XSidebar/> </div> <div v-else ref="widgetsLeft" class="widgets left"> - <XWidgets place="left" :margin-top="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/> + <XWidgets place="left" :marginTop="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/> </div> - <main class="main" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu"> + <main class="main" @contextmenu.stop="onContextmenu"> <div class="content" style="container-type: inline-size;"> <RouterView/> </div> </main> <div v-if="isDesktop" ref="widgetsRight" class="widgets right"> - <XWidgets :place="showMenuOnTop ? 'right' : null" :margin-top="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/> + <XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/> </div> </div> diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index 33e752513b9dd6d1c6aab11b2b207c85e4b71789..c82873177334c738439c3086abc7d5583206489c 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -4,27 +4,23 @@ <div :class="$style.main"> <XStatusBars/> - <div ref="columnsEl" :class="[$style.columns, deckStore.reactiveState.columnAlign.value, { [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu"> - <template v-for="ids in layout"> - <!-- sectionを利用ã—ã¦ã„ã‚‹ã®ã¯ã€deck.vueå´ã§columnã«å¯¾ã—ã¦first-of-typeを効ã‹ã›ã‚‹ãŸã‚ --> - <section - v-if="ids.length > 1" - :class="$style.folder" - :style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }" - > - <DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/> - </section> - <DeckColumnCore - v-else - :ref="ids[0]" - :key="ids[0]" + <div ref="columnsEl" :class="[$style.sections, { [$style.center]: deckStore.reactiveState.columnAlign.value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu"> + <!-- sectionを利用ã—ã¦ã„ã‚‹ã®ã¯ã€deck.vueå´ã§columnã«å¯¾ã—ã¦first-of-typeを効ã‹ã›ã‚‹ãŸã‚ --> + <section + v-for="ids in layout" + :class="$style.section" + :style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }" + > + <component + :is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn" + v-for="id in ids" + :ref="id" + :key="id" :class="$style.column" - :column="columns.find(c => c.id === ids[0])" - :is-stacked="false" - :style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }" - @parent-focus="moveFocus(ids[0], $event)" + :column="columns.find(c => c.id === id)" + :isStacked="ids.length > 1" /> - </template> + </section> <div v-if="layout.length === 0" class="_panel" :class="$style.onboarding"> <div>{{ i18n.ts._deck.introduction }}</div> <MkButton primary style="margin: 1em auto;" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton> @@ -53,10 +49,10 @@ </div> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''" > <div v-if="drawerMenuShowing" @@ -68,10 +64,10 @@ </Transition> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''" > <div v-if="drawerMenuShowing" :class="$style.menu"> <XDrawerMenu/> @@ -87,7 +83,6 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import XCommon from './_common_/common.vue'; import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store'; -import DeckColumnCore from '@/ui/deck/column-core.vue'; import XSidebar from '@/ui/_common_/navbar.vue'; import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import MkButton from '@/components/MkButton.vue'; @@ -100,8 +95,31 @@ import { mainRouter } from '@/router'; import { unisonReload } from '@/scripts/unison-reload'; import { deviceKind } from '@/scripts/device-kind'; import { defaultStore } from '@/store'; +import XMainColumn from '@/ui/deck/main-column.vue'; +import XTlColumn from '@/ui/deck/tl-column.vue'; +import XAntennaColumn from '@/ui/deck/antenna-column.vue'; +import XListColumn from '@/ui/deck/list-column.vue'; +import XChannelColumn from '@/ui/deck/channel-column.vue'; +import XNotificationsColumn from '@/ui/deck/notifications-column.vue'; +import XWidgetsColumn from '@/ui/deck/widgets-column.vue'; +import XMentionsColumn from '@/ui/deck/mentions-column.vue'; +import XDirectColumn from '@/ui/deck/direct-column.vue'; +import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); +const columnComponents = { + main: XMainColumn, + widgets: XWidgetsColumn, + notifications: XNotificationsColumn, + tl: XTlColumn, + list: XListColumn, + channel: XChannelColumn, + antenna: XAntennaColumn, + mentions: XMentionsColumn, + direct: XDirectColumn, + roleTimeline: XRoleTimelineColumn, +}; + mainRouter.navHook = (path, flag): boolean => { if (flag === 'forcePage') return false; const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main'); @@ -187,11 +205,8 @@ window.addEventListener('wheel', (ev) => { columnsEl.scrollLeft += ev.deltaY; } }); -loadDeck(); -function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { - // TODO?? -} +loadDeck(); function changeProfile(ev: MouseEvent) { const items = ref([{ @@ -267,7 +282,7 @@ async function deleteProfile() { --margin: var(--marginHalf); - --deckDividerThickness: 5px; + --columnGap: 6px; display: flex; height: 100dvh; @@ -286,19 +301,21 @@ async function deleteProfile() { flex-direction: column; } -.columns { +.sections { flex: 1; display: flex; overflow-x: auto; overflow-y: clip; + overscroll-behavior: contain; + background: var(--deckBg); &.center { - > .column:first-of-type { - margin-left: auto; + > .section:first-of-type { + margin-left: auto !important; } - > .column:last-of-type { - margin-right: auto; + > .section:last-of-type { + margin-right: auto !important; } } @@ -307,23 +324,17 @@ async function deleteProfile() { } } -.column { - scroll-snap-align: start; - flex-shrink: 0; - border-right: solid var(--deckDividerThickness) var(--deckDivider); - - &:first-of-type { - border-left: solid var(--deckDividerThickness) var(--deckDivider); - } -} - -.folder { - composes: column; +.section { display: flex; flex-direction: column; + scroll-snap-align: start; + flex-shrink: 0; + padding-top: var(--columnGap); + padding-bottom: var(--columnGap); + padding-left: var(--columnGap); - > *:not(:last-of-type) { - border-bottom: solid var(--deckDividerThickness) var(--deckDivider); + > .column:not(:last-of-type) { + margin-bottom: var(--columnGap); } } diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index 76a8b6e760b98e42da80b1b513ad06361b73be4d..d21a9cc5809cc3fe0e8a78f7a1593232f3056567 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -1,10 +1,10 @@ <template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked"> <template #header> <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> + <MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId"/> </XColumn> </template> @@ -21,11 +21,6 @@ const props = defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); onMounted(() => { diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 9605d1b22ee6e029efb5d0f9c2302402210d1e87..8b05ecc0bbfb2d8887760daa724465277832f7d9 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked"> <template #header> <i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> @@ -8,30 +8,25 @@ <div style="padding: 8px; text-align: center;"> <MkButton primary gradate rounded inline @click="post"><i class="ti ti-pencil"></i></MkButton> </div> - <MkTimeline ref="timeline" src="channel" :channel="column.channelId" @after="() => emit('loaded')"/> + <MkTimeline ref="timeline" src="channel" :channel="column.channelId"/> </template> </XColumn> </template> <script lang="ts" setup> +import * as misskey from 'misskey-js'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store'; import MkTimeline from '@/components/MkTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; -import * as misskey from 'misskey-js'; const props = defineProps<{ column: Column; isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); let channel = $shallowRef<misskey.entities.Channel>(); diff --git a/packages/frontend/src/ui/deck/column-core.vue b/packages/frontend/src/ui/deck/column-core.vue deleted file mode 100644 index 8e7addf3596aceb7935482ca4e6aa8ce9242520c..0000000000000000000000000000000000000000 --- a/packages/frontend/src/ui/deck/column-core.vue +++ /dev/null @@ -1,38 +0,0 @@ -<template> -<!-- TODO: リファクタã®ä½™åœ°ãŒã‚ã‚Šãㆠ--> -<div v-if="!column">ãŸã¶ã‚“見ãˆã¡ã‚ƒã„ã‘ãªã„ã‚„ã¤</div> -<XMainColumn v-else-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XChannelColumn v-else-if="column.type === 'channel'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -<XRoleTimelineColumn v-else-if="column.type === 'roleTimeline'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import XMainColumn from './main-column.vue'; -import XTlColumn from './tl-column.vue'; -import XAntennaColumn from './antenna-column.vue'; -import XListColumn from './list-column.vue'; -import XChannelColumn from './channel-column.vue'; -import XNotificationsColumn from './notifications-column.vue'; -import XWidgetsColumn from './widgets-column.vue'; -import XMentionsColumn from './mentions-column.vue'; -import XDirectColumn from './direct-column.vue'; -import XRoleTimelineColumn from './role-timeline-column.vue'; -import { Column } from './deck-store'; - -defineProps<{ - column?: Column; - isStacked: boolean; -}>(); - -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); -</script> diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 402bbe035260e6a2e6fded26c9300310a6e5167f..c376eb2b471d382f374b8cb54c0c0d2044f6f1e2 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -1,8 +1,6 @@ <template> -<!-- sectionを利用ã—ã¦ã„ã‚‹ã®ã¯ã€deck.vueå´ã§columnã«å¯¾ã—ã¦first-of-typeを効ã‹ã›ã‚‹ãŸã‚ --> -<section - v-hotkey="keymap" - :class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.isStacked]: isStacked, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]" +<div + :class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]" @dragover.prevent.stop="onDragover" @dragleave="onDragleave" @drop.prevent.stop="onDrop" @@ -15,17 +13,26 @@ @dragend="onDragend" @contextmenu.prevent.stop="onContextmenu" > + <svg viewBox="0 0 256 128" :class="$style.tabShape"> + <g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)"> + <path d="M149.512,4.707L108.507,4.707C116.252,4.719 118.758,14.958 118.758,14.958C118.758,14.958 121.381,25.283 129.009,25.209L149.512,25.209L149.512,4.707Z" style="fill:var(--deckBg);"/> + </g> + </svg> + <div :class="$style.color"></div> <button v-if="isStacked && !isMainColumn" :class="$style.toggleActive" class="_button" @click="toggleActive"> <template v-if="active"><i class="ti ti-chevron-up"></i></template> <template v-else><i class="ti ti-chevron-down"></i></template> </button> <span :class="$style.title"><slot name="header"></slot></span> + <svg viewBox="0 0 16 16" version="1.1" :class="$style.grabber"> + <path fill="currentColor" d="M10 13a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm0-4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm-4 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm5-9a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path> + </svg> <button v-tooltip="i18n.ts.settings" :class="$style.menu" class="_button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button> </header> - <div v-show="active" ref="body" :class="$style.body"> + <div v-if="active" ref="body" :class="$style.body"> <slot></slot> </div> -</section> +</div> </template> <script lang="ts" setup> @@ -49,12 +56,7 @@ const props = withDefaults(defineProps<{ naked: false, }); -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; - (ev: 'change-active-state', v: boolean): void; -}>(); - -let body = $shallowRef<HTMLDivElement>(); +let body = $shallowRef<HTMLDivElement | null>(); let dragging = $ref(false); watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd')); @@ -64,14 +66,6 @@ let dropready = $ref(false); const isMainColumn = $computed(() => props.column.type === 'main'); const active = $computed(() => props.column.active !== false); -watch($$(active), v => emit('change-active-state', v)); - -const keymap = $computed(() => ({ - 'shift+up': () => emit('parent-focus', 'up'), - 'shift+down': () => emit('parent-focus', 'down'), - 'shift+left': () => emit('parent-focus', 'left'), - 'shift+right': () => emit('parent-focus', 'right'), -})); onMounted(() => { os.deckGlobalEvents.on('column.dragStart', onOtherDragStart); @@ -190,10 +184,12 @@ function onContextmenu(ev: MouseEvent) { } function goTop() { - body.scrollTo({ - top: 0, - behavior: 'smooth', - }); + if (body) { + body.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } } function onDragstart(ev) { @@ -248,6 +244,7 @@ function onDrop(ev) { height: 100%; overflow: clip; contain: strict; + border-radius: 10px; &.draghover { &:after { @@ -287,6 +284,7 @@ function onDrop(ev) { &:not(.active) { flex-basis: var(--deckColumnHeaderHeight); min-height: var(--deckColumnHeaderHeight); + border-bottom-right-radius: 0; } &.naked { @@ -299,10 +297,28 @@ function onDrop(ev) { box-shadow: none; color: var(--fg); } + + > .body { + background: transparent !important; + + &::-webkit-scrollbar-track { + background: transparent; + } + scrollbar-color: var(--scrollbarHandle) transparent; + } } &.paged { background: var(--bg) !important; + + > .body { + background: var(--bg) !important; + + &::-webkit-scrollbar-track { + background: inherit; + } + scrollbar-color: var(--scrollbarHandle) transparent; + } } } @@ -312,7 +328,7 @@ function onDrop(ev) { z-index: 2; line-height: var(--deckColumnHeaderHeight); height: var(--deckColumnHeaderHeight); - padding: 0 16px; + padding: 0 16px 0 30px; font-size: 0.9em; color: var(--panelHeaderFg); background: var(--panelHeaderBg); @@ -321,6 +337,24 @@ function onDrop(ev) { user-select: none; } +.color { + position: absolute; + top: 12px; + left: 12px; + width: 3px; + height: calc(100% - 24px); + background: var(--accent); + border-radius: 999px; +} + +.tabShape { + position: absolute; + top: 0; + right: -8px; + width: auto; + height: calc(100% - 6px); +} + .title { display: inline-block; align-items: center; @@ -335,34 +369,39 @@ function onDrop(ev) { z-index: 1; width: var(--deckColumnHeaderHeight); line-height: var(--deckColumnHeaderHeight); - color: var(--faceTextButton); - - &:hover { - color: var(--faceTextButtonHover); - } - - &:active { - color: var(--faceTextButtonActive); - } } .toggleActive { margin-left: -16px; } -.menu { +.grabber { margin-left: auto; + margin-right: 10px; + padding: 8px 8px; + box-sizing: border-box; + height: var(--deckColumnHeaderHeight); + cursor: move; + user-select: none; + opacity: 0.5; +} + +.menu { margin-right: -16px; } .body { height: calc(100% - var(--deckColumnHeaderHeight)); overflow-y: auto; - overflow-x: hidden; // Safari does not supports clip overflow-x: clip; - -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; box-sizing: border-box; container-type: size; background-color: var(--bg); + + &::-webkit-scrollbar-track { + background: var(--panel); + } + scrollbar-color: var(--scrollbarHandle) var(--panel); } </style> diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue index 15b76c4d92bcf2f4f5bd7e9632c5f02959d0f6bb..dc3f58e6a4a65a877d51c1254d204258aa63bfc4 100644 --- a/packages/frontend/src/ui/deck/direct-column.vue +++ b/packages/frontend/src/ui/deck/direct-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :column="column" :isStacked="isStacked"> <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name }}</template> <MkNotes :pagination="pagination"/> @@ -17,10 +17,6 @@ defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - const pagination = { endpoint: 'notes/mentions' as const, limit: 10, diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index 352c1d246a4a3e54b0de58f1d7efef8da41528a6..f36dc6151c56b43fa8de8fdc6e6baa4fc2c13e7e 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -1,10 +1,10 @@ <template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked"> <template #header> <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> + <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId"/> </XColumn> </template> @@ -21,11 +21,6 @@ const props = defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); if (props.column.listId == null) { diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue index f3826a8d31eb09b23cf70a6e482b2ff1409b8ed3..169fac70a2145154bf3f291c33068b907ed7ff0b 100644 --- a/packages/frontend/src/ui/deck/main-column.vue +++ b/packages/frontend/src/ui/deck/main-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :isStacked="isStacked"> <template #header> <template v-if="pageMetadata?.value"> <i :class="pageMetadata?.value.icon"></i> @@ -25,10 +25,6 @@ defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); provide('router', mainRouter); diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue index 852d7a8f7e2f1f932585cfcf4a30374f700ac8c0..98cf898749150c5fc6fdd00fa777f0417261b2d6 100644 --- a/packages/frontend/src/ui/deck/mentions-column.vue +++ b/packages/frontend/src/ui/deck/mentions-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :column="column" :isStacked="isStacked"> <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name }}</template> <MkNotes :pagination="pagination"/> @@ -17,10 +17,6 @@ defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - const pagination = { endpoint: 'notes/mentions' as const, limit: 10, diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue index 9d133035fe5a3a73ba10b3b28e8d035731f27b8d..8cf6ec1f65823bf266dc81c933260331b67edf28 100644 --- a/packages/frontend/src/ui/deck/notifications-column.vue +++ b/packages/frontend/src/ui/deck/notifications-column.vue @@ -1,8 +1,8 @@ <template> -<XColumn :column="column" :is-stacked="isStacked" :menu="menu" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :column="column" :isStacked="isStacked" :menu="menu"> <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotifications :include-types="column.includingTypes"/> + <XNotifications :includeTypes="column.includingTypes"/> </XColumn> </template> @@ -19,10 +19,6 @@ const props = defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - function func() { os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), { includingTypes: props.column.includingTypes, diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue index 5783b3f071522cd5ae3b034b85a7fe0d7c4b0434..a0b7f1c67595ee01882e5b5c0c3c88926ccbaa2f 100644 --- a/packages/frontend/src/ui/deck/role-timeline-column.vue +++ b/packages/frontend/src/ui/deck/role-timeline-column.vue @@ -1,10 +1,10 @@ <template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked"> <template #header> <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId" @after="() => emit('loaded')"/> + <MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId"/> </XColumn> </template> @@ -21,11 +21,6 @@ const props = defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); onMounted(() => { @@ -35,7 +30,7 @@ onMounted(() => { }); async function setRole() { - const roles = await os.api('roles/list'); + const roles = (await os.api('roles/list')).filter(x => x.isExplorable); const { canceled, result: role } = await os.select({ title: i18n.ts.role, items: roles.map(x => ({ diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index c23943d4db8ae39e4e624d3048ec3fe7eaae1482..4844ad11ff181ffdfd410b0a17321b14f77ba3e5 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :menu="menu" :column="column" :isStacked="isStacked"> <template #header> <i v-if="column.tl === 'home'" class="ti ti-home"></i> <i v-else-if="column.tl === 'local'" class="ti ti-planet"></i> @@ -15,7 +15,7 @@ </p> <p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p> </div> - <MkTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')"/> + <MkTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl"/> </XColumn> </template> @@ -34,11 +34,6 @@ const props = defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'loaded'): void; - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let disabled = $ref(false); const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable)); diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue index 3b5b72799174bb5edc8a650d726b56a2db3f1d19..da14e54f749bdeb0b6a5dffeebc889602e6a27d4 100644 --- a/packages/frontend/src/ui/deck/widgets-column.vue +++ b/packages/frontend/src/ui/deck/widgets-column.vue @@ -1,10 +1,10 @@ <template> -<XColumn :menu="menu" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> +<XColumn :menu="menu" :naked="true" :column="column" :isStacked="isStacked"> <template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name }}</template> <div :class="$style.root"> <div v-if="!(column.widgets && column.widgets.length > 0) && !edit" :class="$style.intro">{{ i18n.ts._deck.widgetsIntroduction }}</div> - <XWidgets :edit="edit" :widgets="column.widgets ?? []" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> + <XWidgets :edit="edit" :widgets="column.widgets ?? []" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="edit = false"/> </div> </XColumn> </template> @@ -21,10 +21,6 @@ const props = defineProps<{ isStacked: boolean; }>(); -const emit = defineEmits<{ - (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; -}>(); - let edit = $ref(false); function addWidget(widget) { diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue new file mode 100644 index 0000000000000000000000000000000000000000..e656f00bb209fbf70b0e4eac4261c659c2f9baa7 --- /dev/null +++ b/packages/frontend/src/ui/minimum.vue @@ -0,0 +1,34 @@ +<template> +<div :class="$style.root" style="container-type: inline-size;"> + <RouterView/> + + <XCommon/> +</div> +</template> + +<script lang="ts" setup> +import { provide, ComputedRef } from 'vue'; +import XCommon from './_common_/common.vue'; +import { mainRouter } from '@/router'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; +import { instanceName } from '@/config'; + +let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); + +provide('router', mainRouter); +provideMetadataReceiver((info) => { + pageMetadata = info; + if (pageMetadata.value) { + document.title = `${pageMetadata.value.title} | ${instanceName}`; + } +}); + +document.documentElement.style.overflowY = 'scroll'; +</script> + +<style lang="scss" module> +.root { + min-height: 100dvh; + box-sizing: border-box; +} +</style> diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 27d0c26ac4a1147ab338f54a8fd91e4a33fd9c1b..c0da59a57d9d9be90e80e8419f6565fe89e15127 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -1,19 +1,15 @@ <template> -<div :class="[$style.root, { [$style.withWallpaper]: wallpaper }]"> +<div :class="$style.root"> <XSidebar v-if="!isMobile" :class="$style.sidebar"/> - <MkStickyContainer :class="$style.contents"> + <MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;" @contextmenu.stop="onContextmenu"> <template #header><XStatusBars :class="$style.statusbars"/></template> - <main style="min-width: 0;" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu"> - <div :class="$style.content" style="container-type: inline-size;"> - <RouterView/> - </div> - <div :class="$style.spacer"></div> - </main> + <RouterView/> + <div :class="$style.spacer"></div> </MkStickyContainer> - <div v-if="isDesktop" ref="widgetsEl" :class="$style.widgets"> - <XWidgets :margin-top="'var(--margin)'" @mounted="attachSticky"/> + <div v-if="isDesktop" :class="$style.widgets"> + <XWidgets/> </div> <button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button> @@ -27,10 +23,10 @@ </div> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''" > <div v-if="drawerMenuShowing" @@ -42,10 +38,10 @@ </Transition> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''" > <div v-if="drawerMenuShowing" :class="$style.menuDrawer"> <XDrawerMenu/> @@ -53,10 +49,10 @@ </Transition> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''" > <div v-if="widgetsShowing" @@ -68,10 +64,10 @@ </Transition> <Transition - :enter-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''" - :leave-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''" - :enter-from-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''" - :leave-to-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''" + :enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''" + :leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''" + :enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''" + :leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''" > <div v-if="widgetsShowing" :class="$style.widgetsDrawer"> <button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button> @@ -84,10 +80,10 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, inject, Ref } from 'vue'; +import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, shallowRef, Ref } from 'vue'; import XCommon from './_common_/common.vue'; +import type MkStickyContainer from '@/components/global/MkStickyContainer.vue'; import { instanceName } from '@/config'; -import { StickySidebar } from '@/scripts/sticky-sidebar'; import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; @@ -99,6 +95,7 @@ import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { deviceKind } from '@/scripts/device-kind'; import { miLocalStorage } from '@/local-storage'; import { CURRENT_STICKY_BOTTOM } from '@/const'; + const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); @@ -114,9 +111,9 @@ window.addEventListener('resize', () => { }); let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); -const widgetsEl = $shallowRef<HTMLElement>(); const widgetsShowing = $ref(false); const navFooter = $shallowRef<HTMLElement>(); +const contents = shallowRef<InstanceType<typeof MkStickyContainer>>(); provide('router', mainRouter); provideMetadataReceiver((info) => { @@ -140,8 +137,6 @@ mainRouter.on('change', () => { drawerMenuShowing.value = false; }); -document.documentElement.style.overflowY = 'scroll'; - if (window.innerWidth > 1024) { const tempUI = miLocalStorage.getItem('ui_temp'); if (tempUI) { @@ -197,19 +192,13 @@ const onContextmenu = (ev) => { }], ev); }; -const attachSticky = (el) => { - const sticky = new StickySidebar(widgetsEl); - window.addEventListener('scroll', () => { - sticky.calc(window.scrollY); - }, { passive: true }); -}; - function top() { - window.scroll({ top: 0, behavior: 'smooth' }); + contents.value.rootEl.scrollTo({ + top: 0, + behavior: 'smooth', + }); } -const wallpaper = miLocalStorage.getItem('wallpaper') != null; - let navFooterHeight = $ref(0); provide<Ref<number>>(CURRENT_STICKY_BOTTOM, $$(navFooterHeight)); @@ -275,28 +264,33 @@ $widgets-hide-threshold: 1090px; } .root { - min-height: 100dvh; + height: 100dvh; + overflow: clip; + contain: strict; box-sizing: border-box; display: flex; } -.withWallpaper { - background: var(--wallpaperOverlay); - //backdrop-filter: var(--blur, blur(4px)); -} - .sidebar { border-right: solid 0.5px var(--divider); } .contents { - width: 100%; + flex: 1; + height: 100%; min-width: 0; + overflow: auto; + overflow-y: scroll; + overscroll-behavior: contain; background: var(--bg); } .widgets { - padding: 0 var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)); + width: 350px; + height: 100%; + box-sizing: border-box; + overflow: auto; + padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)); border-left: solid 0.5px var(--divider); background: var(--bg); @@ -328,6 +322,7 @@ $widgets-hide-threshold: 1090px; top: 0; right: 0; z-index: 1001; + width: 310px; height: 100dvh; padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important; box-sizing: border-box; diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue index 3e0c38bb831bd624cd14402b676346c467cc4b45..ec5e8bb03fbcc28eed09cb6088c19937e8ed0a2a 100644 --- a/packages/frontend/src/ui/universal.widgets.vue +++ b/packages/frontend/src/ui/universal.widgets.vue @@ -1,6 +1,6 @@ <template> -<div :class="$style.root" :style="{ paddingTop: marginTop }"> - <XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> +<div> + <XWidgets :edit="editMode" :widgets="widgets" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="editMode = false"/> <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button> <button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button> @@ -11,7 +11,7 @@ let editMode = $ref(false); </script> <script lang="ts" setup> -import { onMounted } from 'vue'; +import { } from 'vue'; import XWidgets from '@/components/MkWidgets.vue'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; @@ -21,28 +21,16 @@ const props = withDefaults(defineProps<{ // left = place: leftã ã‘を表示 // right = rightã¨nullを表示 place?: 'left' | null | 'right'; - marginTop?: string; }>(), { place: null, - marginTop: '0', }); -const emit = defineEmits<{ - (ev: 'mounted', el?: Element): void; -}>(); - -let rootEl = $shallowRef<HTMLDivElement>(); - const widgets = $computed(() => { if (props.place === null) return defaultStore.reactiveState.widgets.value; if (props.place === 'left') return defaultStore.reactiveState.widgets.value.filter(w => w.place === 'left'); return defaultStore.reactiveState.widgets.value.filter(w => w.place !== 'left'); }); -onMounted(() => { - emit('mounted', rootEl); -}); - function addWidget(widget) { defaultStore.set('widgets', [{ ...widget, @@ -82,16 +70,6 @@ function updateWidgets(thisWidgets) { </script> <style lang="scss" module> -.root { - position: sticky; - height: min-content; - box-sizing: border-box; -} - -.widgets { - width: 300px; -} - .edit { width: 100%; } diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index 623abbda39dbdea2a11f5990ace54fc6689a2e14..d6de145edb00356d19bd53bce901053c4d55fc7f 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -12,10 +12,10 @@ <div class="main"> <div v-if="!root" class="header"> <div v-if="narrow === false" class="wide"> - <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA> - <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA> - <MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA> - <MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA> + <MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA> + <MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA> + <MkA to="/explore" class="link" activeClass="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA> + <MkA to="/channels" class="link" activeClass="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA> </div> <div v-else-if="narrow === true" class="narrow"> <button class="menu _button" @click="showMenu = true"> @@ -44,15 +44,15 @@ <Transition :name="'tray'"> <div v-if="showMenu" class="menu"> - <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA> - <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA> - <MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA> - <MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA> - <MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA> + <MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA> + <MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA> + <MkA to="/explore" class="link" activeClass="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA> + <MkA to="/announcements" class="link" activeClass="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA> + <MkA to="/channels" class="link" activeClass="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA> <div class="divider"></div> - <MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA> - <MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA> - <MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA> + <MkA to="/pages" class="link" activeClass="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA> + <MkA to="/play" class="link" activeClass="active"><i class="ti ti-player-play icon"></i>Play</MkA> + <MkA to="/gallery" class="link" activeClass="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA> <div class="action"> <button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button> <button class="_button" @click="signin()">{{ i18n.ts.login }}</button> diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index 628390e3f7bd2750c9d144a63bcdc243595c8aa8..d516a5df758e5143e89e5c98f846083558c9d517 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -1,9 +1,17 @@ <template> -<div class="mk-app" style="container-type: inline-size;"> +<div :class="showBottom ? $style.rootWithBottom : $style.root" style="container-type: inline-size;"> <RouterView/> <XCommon/> </div> + +<!-- + デッã‚UIãŒè¨å®šã•ã‚Œã¦ã„ã‚‹å ´åˆã¯ãƒ‡ãƒƒã‚UIã«æˆ»ã‚Œã‚‹ã‚ˆã†ã«ã™ã‚‹ (ãŸã ã—?zenãŒæ˜Žç¤ºã•ã‚ŒãŸå ´åˆã¯è¡¨ç¤ºã—ãªã„) + See https://github.com/misskey-dev/misskey/issues/10905 +--> +<div v-if="showBottom" :class="$style.bottom"> + <button v-tooltip="i18n.ts.goToMisskey" :class="['_button', '_shadow', $style.button]" @click="goToMisskey"><i class="ti ti-home"></i></button> +</div> </template> <script lang="ts" setup> @@ -11,10 +19,13 @@ import { provide, ComputedRef } from 'vue'; import XCommon from './_common_/common.vue'; import { mainRouter } from '@/router'; import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; -import { instanceName } from '@/config'; +import { instanceName, ui } from '@/config'; +import { i18n } from '@/i18n'; let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); +const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck'; + provide('router', mainRouter); provideMetadataReceiver((info) => { pageMetadata = info; @@ -23,12 +34,41 @@ provideMetadataReceiver((info) => { } }); +function goToMisskey() { + window.location.href = '/'; +} + document.documentElement.style.overflowY = 'scroll'; </script> -<style lang="scss" scoped> -.mk-app { +<style lang="scss" module> +.root { min-height: 100dvh; box-sizing: border-box; } + +.rootWithBottom { + min-height: calc(100dvh - (60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px))); + box-sizing: border-box; +} + +.bottom { + height: calc(60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px)); + width: 100%; + margin-top: auto; +} + +.button { + position: fixed !important; + padding: 0; + aspect-ratio: 1; + width: 100%; + max-width: 60px; + margin: auto; + border-radius: 100%; + background: var(--panel); + color: var(--fg); + right: var(--margin); + bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px)); +} </style> diff --git a/packages/frontend/src/unicode-emoji-indexes/en-US.json b/packages/frontend/src/unicode-emoji-indexes/en-US.json new file mode 100644 index 0000000000000000000000000000000000000000..c5544418db326acfe2d91ca74c495a4661652aec --- /dev/null +++ b/packages/frontend/src/unicode-emoji-indexes/en-US.json @@ -0,0 +1,1784 @@ +{ + "😀": ["face", "smile", "happy", "joy", ": D", "grin"], + "😬": ["face", "grimace", "teeth"], + "ðŸ˜": ["face", "happy", "smile", "joy", "kawaii"], + "😂": ["face", "cry", "tears", "weep", "happy", "happytears", "haha"], + "🤣": ["face", "rolling", "floor", "laughing", "lol", "haha"], + "🥳": ["face", "celebration", "woohoo"], + "😃": ["face", "happy", "joy", "haha", ": D", ": )", "smile", "funny"], + "😄": ["face", "happy", "joy", "funny", "haha", "laugh", "like", ": D", ": )"], + "😅": ["face", "hot", "happy", "laugh", "sweat", "smile", "relief"], + "🥲": ["face"], + "😆": ["happy", "joy", "lol", "satisfied", "haha", "face", "glad", "XD", "laugh"], + "😇": ["face", "angel", "heaven", "halo"], + "😉": ["face", "happy", "mischievous", "secret", ";)", "smile", "eye"], + "😊": ["face", "smile", "happy", "flushed", "crush", "embarrassed", "shy", "joy"], + "🙂": ["face", "smile"], + "🙃": ["face", "flipped", "silly", "smile"], + "☺ï¸": ["face", "blush", "massage", "happiness"], + "😋": ["happy", "joy", "tongue", "smile", "face", "silly", "yummy", "nom", "delicious", "savouring"], + "😌": ["face", "relaxed", "phew", "massage", "happiness"], + "ðŸ˜": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "heart"], + "🥰": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "hearts", "adore"], + "😘": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"], + "😗": ["love", "like", "face", "3", "valentines", "infatuation", "kiss"], + "😙": ["face", "affection", "valentines", "infatuation", "kiss"], + "😚": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"], + "😜": ["face", "prank", "childish", "playful", "mischievous", "smile", "wink", "tongue"], + "🤪": ["face", "goofy", "crazy"], + "🤨": ["face", "distrust", "scepticism", "disapproval", "disbelief", "surprise"], + "ðŸ§": ["face", "stuffy", "wealthy"], + "ðŸ˜": ["face", "prank", "playful", "mischievous", "smile", "tongue"], + "😛": ["face", "prank", "childish", "playful", "mischievous", "smile", "tongue"], + "🤑": ["face", "rich", "dollar", "money"], + "🤓": ["face", "nerdy", "geek", "dork"], + "🥸": ["face", "nose", "glasses", "incognito"], + "😎": ["face", "cool", "smile", "summer", "beach", "sunglass"], + "🤩": ["face", "smile", "starry", "eyes", "grinning"], + "🤡": ["face"], + "🤠": ["face", "cowgirl", "hat"], + "🤗": ["face", "smile", "hug"], + "ðŸ˜": ["face", "smile", "mean", "prank", "smug", "sarcasm"], + "😶": ["face", "hellokitty"], + "ðŸ˜": ["indifference", "meh", ": |", "neutral"], + "😑": ["face", "indifferent", "-_-", "meh", "deadpan"], + "😒": ["indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye"], + "🙄": ["face", "eyeroll", "frustrated"], + "🤔": ["face", "hmmm", "think", "consider"], + "🤥": ["face", "lie", "pinocchio"], + "ðŸ¤": ["face", "whoops", "shock", "surprise"], + "🤫": ["face", "quiet", "shhh"], + "🤬": ["face", "swearing", "cursing", "cussing", "profanity", "expletive"], + "🤯": ["face", "shocked", "mind", "blown"], + "😳": ["face", "blush", "shy", "flattered"], + "😞": ["face", "sad", "upset", "depressed", ": ("], + "😟": ["face", "concern", "nervous", ": ("], + "😠": ["mad", "face", "annoyed", "frustrated"], + "😡": ["angry", "mad", "hate", "despise"], + "😔": ["face", "sad", "depressed", "upset"], + "😕": ["face", "indifference", "huh", "weird", "hmmm", ": /"], + "ðŸ™": ["face", "frowning", "disappointed", "sad", "upset"], + "☹": ["face", "sad", "upset", "frown"], + "😣": ["face", "sick", "no", "upset", "oops"], + "😖": ["face", "confused", "sick", "unwell", "oops", ": S"], + "😫": ["sick", "whine", "upset", "frustrated"], + "😩": ["face", "tired", "sleepy", "sad", "frustrated", "upset"], + "🥺": ["face", "begging", "mercy"], + "😤": ["face", "gas", "phew", "proud", "pride"], + "😮": ["face", "surprise", "impressed", "wow", "whoa", ": O"], + "😱": ["face", "munch", "scared", "omg"], + "😨": ["face", "scared", "terrified", "nervous", "oops", "huh"], + "😰": ["face", "nervous", "sweat"], + "😯": ["face", "woo", "shh"], + "😦": ["face", "aw", "what"], + "😧": ["face", "stunned", "nervous"], + "😢": ["face", "tears", "sad", "depressed", "upset", ": '("], + "😥": ["face", "phew", "sweat", "nervous"], + "🤤": ["face"], + "😪": ["face", "tired", "rest", "nap"], + "😓": ["face", "hot", "sad", "tired", "exercise"], + "🥵": ["face", "feverish", "heat", "red", "sweating"], + "🥶": ["face", "blue", "freezing", "frozen", "frostbite", "icicles"], + "ðŸ˜": ["face", "cry", "tears", "sad", "upset", "depressed"], + "😵": ["spent", "unconscious", "xox", "dizzy"], + "😲": ["face", "xox", "surprised", "poisoned"], + "ðŸ¤": ["face", "sealed", "zipper", "secret"], + "🤢": ["face", "vomit", "gross", "green", "sick", "throw up", "ill"], + "🤧": ["face", "gesundheit", "sneeze", "sick", "allergy"], + "🤮": ["face", "sick"], + "😷": ["face", "sick", "ill", "disease"], + "🤒": ["sick", "temperature", "thermometer", "cold", "fever"], + "🤕": ["injured", "clumsy", "bandage", "hurt"], + "🥴": ["face", "dizzy", "intoxicated", "tipsy", "wavy"], + "🥱": ["face", "tired", "yawning"], + "😴": ["face", "tired", "sleepy", "night", "zzz"], + "💤": ["sleepy", "tired", "dream"], + "😶â€ðŸŒ«ï¸": [], + "😮â€ðŸ’¨": [], + "😵â€ðŸ’«": [], + "🫠": ["disappear", "dissolve", "liquid", "melt", "toketa"], + "🫢": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"], + "🫣": ["captivated", "peep", "stare", "chunibyo"], + "🫡": ["ok", "salute", "sunny", "troops", "yes", "raja"], + "🫥": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"], + "🫤": ["disappointed", "meh", "skeptical", "unsure"], + "🥹": ["angry", "cry", "proud", "resist", "sad"], + "💩": ["hankey", "shitface", "fail", "turd", "shit"], + "😈": ["devil", "horns"], + "👿": ["devil", "angry", "horns"], + "👹": ["monster", "red", "mask", "halloween", "scary", "creepy", "devil", "demon", "japanese", "ogre"], + "👺": ["red", "evil", "mask", "monster", "scary", "creepy", "japanese", "goblin"], + "💀": ["dead", "skeleton", "creepy", "death"], + "👻": ["halloween", "spooky", "scary"], + "👽": ["UFO", "paul", "weird", "outer_space"], + "🤖": ["computer", "machine", "bot"], + "😺": ["animal", "cats", "happy", "smile"], + "😸": ["animal", "cats", "smile"], + "😹": ["animal", "cats", "haha", "happy", "tears"], + "😻": ["animal", "love", "like", "affection", "cats", "valentines", "heart"], + "😼": ["animal", "cats", "smirk"], + "😽": ["animal", "cats", "kiss"], + "🙀": ["animal", "cats", "munch", "scared", "scream"], + "😿": ["animal", "tears", "weep", "sad", "cats", "upset", "cry"], + "😾": ["animal", "cats"], + "🤲": ["hands", "gesture", "cupped", "prayer"], + "🙌": ["gesture", "hooray", "yea", "celebration", "hands"], + "ðŸ‘": ["hands", "praise", "applause", "congrats", "yay"], + "👋": ["hands", "gesture", "goodbye", "solong", "farewell", "hello", "hi", "palm"], + "🤙": ["hands", "gesture"], + "ðŸ‘": ["thumbsup", "yes", "awesome", "good", "agree", "accept", "cool", "hand", "like"], + "👎": ["thumbsdown", "no", "dislike", "hand"], + "👊": ["angry", "violence", "fist", "hit", "attack", "hand"], + "✊": ["fingers", "hand", "grasp"], + "🤛": ["hand", "fistbump"], + "🤜": ["hand", "fistbump"], + "✌": ["fingers", "ohyeah", "hand", "peace", "victory", "two"], + "👌": ["fingers", "limbs", "perfect", "ok", "okay"], + "✋": ["fingers", "stop", "highfive", "palm", "ban"], + "🤚": ["fingers", "raised", "backhand"], + "ðŸ‘": ["fingers", "butterfly", "hands", "open"], + "💪": ["arm", "flex", "hand", "summer", "strong", "biceps"], + "🦾": ["flex", "hand", "strong", "biceps"], + "ðŸ™": ["please", "hope", "wish", "namaste", "highfive"], + "🦶": ["kick", "stomp"], + "🦵": ["kick", "limb"], + "🦿": ["kick", "limb"], + "ðŸ¤": ["agreement", "shake"], + "â˜": ["hand", "fingers", "direction", "up"], + "👆": ["fingers", "hand", "direction", "up"], + "👇": ["fingers", "hand", "direction", "down"], + "👈": ["direction", "fingers", "hand", "left"], + "👉": ["fingers", "hand", "direction", "right"], + "🖕": ["hand", "fingers", "rude", "middle", "flipping"], + "ðŸ–": ["hand", "fingers", "palm"], + "🤟": ["hand", "fingers", "gesture"], + "🤘": ["hand", "fingers", "evil_eye", "sign_of_horns", "rock_on"], + "🤞": ["good", "lucky"], + "🖖": ["hand", "fingers", "spock", "star trek"], + "âœ": ["lower_left_ballpoint_pen", "stationery", "write", "compose"], + "🫰": [], + "🫱": [], + "🫲": [], + "🫳": [], + "🫴": [], + "🫵": [], + "🫶": ["moemoekyun"], + "ðŸ¤": ["hand", "fingers"], + "🤌": ["hand", "fingers"], + "🤳": ["camera", "phone"], + "💅": ["beauty", "manicure", "finger", "fashion", "nail"], + "👄": ["mouth", "kiss"], + "🫦": [], + "🦷": ["teeth", "dentist"], + "👅": ["mouth", "playful"], + "👂": ["face", "hear", "sound", "listen"], + "🦻": ["face", "hear", "sound", "listen"], + "👃": ["smell", "sniff"], + "ðŸ‘": ["face", "look", "see", "watch", "stare"], + "👀": ["look", "watch", "stalk", "peek", "see"], + "🧠": ["smart", "intelligent"], + "🫀": [], + "ðŸ«": [], + "👤": ["user", "person", "human"], + "👥": ["user", "person", "human", "group", "team"], + "🗣": ["user", "person", "human", "sing", "say", "talk"], + "👶": ["child", "boy", "girl", "toddler"], + "🧒": ["gender-neutral", "young"], + "👦": ["man", "male", "guy", "teenager"], + "👧": ["female", "woman", "teenager"], + "🧑": ["gender-neutral", "person"], + "👨": ["mustache", "father", "dad", "guy", "classy", "sir", "moustache"], + "👩": ["female", "girls", "lady"], + "🧑â€ðŸ¦±": ["curly", "afro", "braids", "ringlets"], + "👩â€ðŸ¦±": ["woman", "female", "girl", "curly", "afro", "braids", "ringlets"], + "👨â€ðŸ¦±": ["man", "male", "boy", "guy", "curly", "afro", "braids", "ringlets"], + "🧑â€ðŸ¦°": ["redhead"], + "👩â€ðŸ¦°": ["woman", "female", "girl", "ginger", "redhead"], + "👨â€ðŸ¦°": ["man", "male", "boy", "guy", "ginger", "redhead"], + "👱â€â™€ï¸": ["woman", "female", "girl", "blonde", "person"], + "👱": ["man", "male", "boy", "blonde", "guy", "person"], + "🧑â€ðŸ¦³": ["gray", "old", "white"], + "👩â€ðŸ¦³": ["woman", "female", "girl", "gray", "old", "white"], + "👨â€ðŸ¦³": ["man", "male", "boy", "guy", "gray", "old", "white"], + "🧑â€ðŸ¦²": ["bald", "chemotherapy", "hairless", "shaven"], + "👩â€ðŸ¦²": ["woman", "female", "girl", "bald", "chemotherapy", "hairless", "shaven"], + "👨â€ðŸ¦²": ["man", "male", "boy", "guy", "bald", "chemotherapy", "hairless", "shaven"], + "🧔": ["person", "bewhiskered"], + "🧓": ["human", "elder", "senior", "gender-neutral"], + "👴": ["human", "male", "men", "old", "elder", "senior"], + "👵": ["human", "female", "women", "lady", "old", "elder", "senior"], + "👲": ["male", "boy", "chinese"], + "🧕": ["female", "hijab", "mantilla", "tichel"], + "👳â€â™€ï¸": ["female", "indian", "hinduism", "arabs", "woman"], + "👳": ["male", "indian", "hinduism", "arabs"], + "👮â€â™€ï¸": ["woman", "police", "law", "legal", "enforcement", "arrest", "911", "female"], + "👮": ["man", "police", "law", "legal", "enforcement", "arrest", "911"], + "👷â€â™€ï¸": ["female", "human", "wip", "build", "construction", "worker", "labor", "woman"], + "👷": ["male", "human", "wip", "guy", "build", "construction", "worker", "labor"], + "💂â€â™€ï¸": ["uk", "gb", "british", "female", "royal", "woman"], + "💂": ["uk", "gb", "british", "male", "guy", "royal"], + "🕵ï¸â€â™€ï¸": ["human", "spy", "detective", "female", "woman"], + "🕵": ["human", "spy", "detective"], + "🧑â€âš•ï¸": ["doctor", "nurse", "therapist", "healthcare", "human"], + "👩â€âš•ï¸": ["doctor", "nurse", "therapist", "healthcare", "woman", "human"], + "👨â€âš•ï¸": ["doctor", "nurse", "therapist", "healthcare", "man", "human"], + "🧑â€ðŸŒ¾": ["rancher", "gardener", "human"], + "👩â€ðŸŒ¾": ["rancher", "gardener", "woman", "human"], + "👨â€ðŸŒ¾": ["rancher", "gardener", "man", "human"], + "🧑â€ðŸ³": ["chef", "human"], + "👩â€ðŸ³": ["chef", "woman", "human"], + "👨â€ðŸ³": ["chef", "man", "human"], + "🧑â€ðŸŽ“": ["graduate", "human"], + "👩â€ðŸŽ“": ["graduate", "woman", "human"], + "👨â€ðŸŽ“": ["graduate", "man", "human"], + "🧑â€ðŸŽ¤": ["rockstar", "entertainer", "human"], + "👩â€ðŸŽ¤": ["rockstar", "entertainer", "woman", "human"], + "👨â€ðŸŽ¤": ["rockstar", "entertainer", "man", "human"], + "🧑â€ðŸ«": ["instructor", "professor", "human"], + "👩â€ðŸ«": ["instructor", "professor", "woman", "human"], + "👨â€ðŸ«": ["instructor", "professor", "man", "human"], + "🧑â€ðŸ": ["assembly", "industrial", "human"], + "👩â€ðŸ": ["assembly", "industrial", "woman", "human"], + "👨â€ðŸ": ["assembly", "industrial", "man", "human"], + "🧑â€ðŸ’»": ["coder", "developer", "engineer", "programmer", "software", "human", "laptop", "computer"], + "👩â€ðŸ’»": ["coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer"], + "👨â€ðŸ’»": ["coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer"], + "🧑â€ðŸ’¼": ["business", "manager", "human"], + "👩â€ðŸ’¼": ["business", "manager", "woman", "human"], + "👨â€ðŸ’¼": ["business", "manager", "man", "human"], + "🧑â€ðŸ”§": ["plumber", "human", "wrench"], + "👩â€ðŸ”§": ["plumber", "woman", "human", "wrench"], + "👨â€ðŸ”§": ["plumber", "man", "human", "wrench"], + "🧑â€ðŸ”¬": ["biologist", "chemist", "engineer", "physicist", "human"], + "👩â€ðŸ”¬": ["biologist", "chemist", "engineer", "physicist", "woman", "human"], + "👨â€ðŸ”¬": ["biologist", "chemist", "engineer", "physicist", "man", "human"], + "🧑â€ðŸŽ¨": ["painter", "human"], + "👩â€ðŸŽ¨": ["painter", "woman", "human"], + "👨â€ðŸŽ¨": ["painter", "man", "human"], + "🧑â€ðŸš’": ["fireman", "human"], + "👩â€ðŸš’": ["fireman", "woman", "human"], + "👨â€ðŸš’": ["fireman", "man", "human"], + "🧑â€âœˆï¸": ["aviator", "plane", "human"], + "👩â€âœˆï¸": ["aviator", "plane", "woman", "human"], + "👨â€âœˆï¸": ["aviator", "plane", "man", "human"], + "🧑â€ðŸš€": ["space", "rocket", "human"], + "👩â€ðŸš€": ["space", "rocket", "woman", "human"], + "👨â€ðŸš€": ["space", "rocket", "man", "human"], + "🧑â€âš–ï¸": ["justice", "court", "human"], + "👩â€âš–ï¸": ["justice", "court", "woman", "human"], + "👨â€âš–ï¸": ["justice", "court", "man", "human"], + "🦸â€â™€ï¸": ["woman", "female", "good", "heroine", "superpowers"], + "🦸â€â™‚ï¸": ["man", "male", "good", "hero", "superpowers"], + "🦹â€â™€ï¸": ["woman", "female", "evil", "bad", "criminal", "heroine", "superpowers"], + "🦹â€â™‚ï¸": ["man", "male", "evil", "bad", "criminal", "hero", "superpowers"], + "🤶": ["woman", "female", "xmas", "mother christmas"], + "🧑â€ðŸŽ„": ["xmas", "christmas"], + "🎅": ["festival", "man", "male", "xmas", "father christmas"], + "🥷": [], + "🧙â€â™€ï¸": ["woman", "female", "mage", "witch"], + "🧙â€â™‚ï¸": ["man", "male", "mage", "sorcerer"], + "ðŸ§â€â™€ï¸": ["woman", "female"], + "ðŸ§â€â™‚ï¸": ["man", "male"], + "🧛â€â™€ï¸": ["woman", "female"], + "🧛â€â™‚ï¸": ["man", "male", "dracula"], + "🧟â€â™€ï¸": ["woman", "female", "undead", "walking dead"], + "🧟â€â™‚ï¸": ["man", "male", "dracula", "undead", "walking dead"], + "🧞â€â™€ï¸": ["woman", "female"], + "🧞â€â™‚ï¸": ["man", "male"], + "🧜â€â™€ï¸": ["woman", "female", "merwoman", "ariel"], + "🧜â€â™‚ï¸": ["man", "male", "triton"], + "🧚â€â™€ï¸": ["woman", "female"], + "🧚â€â™‚ï¸": ["man", "male"], + "👼": ["heaven", "wings", "halo"], + "🧌": [], + "🤰": ["baby"], + "🫃": [], + "🫄": [], + "🫅": [], + "🤱": ["nursing", "baby"], + "👩â€ðŸ¼": [], + "👨â€ðŸ¼": [], + "🧑â€ðŸ¼": [], + "👸": ["girl", "woman", "female", "blond", "crown", "royal", "queen"], + "🤴": ["boy", "man", "male", "crown", "royal", "king"], + "👰": ["couple", "marriage", "wedding", "woman", "bride"], + "👰": ["couple", "marriage", "wedding", "woman", "bride"], + "🤵": ["couple", "marriage", "wedding", "groom"], + "🤵": ["couple", "marriage", "wedding", "groom"], + "ðŸƒâ€â™€ï¸": ["woman", "walking", "exercise", "race", "running", "female"], + "ðŸƒ": ["man", "walking", "exercise", "race", "running"], + "🚶â€â™€ï¸": ["human", "feet", "steps", "woman", "female"], + "🚶": ["human", "feet", "steps"], + "💃": ["female", "girl", "woman", "fun"], + "🕺": ["male", "boy", "fun", "dancer"], + "👯": ["female", "bunny", "women", "girls"], + "👯â€â™‚ï¸": ["male", "bunny", "men", "boys"], + "👫": ["pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage"], + "🧑â€ðŸ¤â€ðŸ§‘": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "human"], + "👬": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "man", "human"], + "ðŸ‘": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "female", "human"], + "🫂": [], + "🙇â€â™€ï¸": ["woman", "female", "girl"], + "🙇": ["man", "male", "boy"], + "🤦â€â™‚ï¸": ["man", "male", "boy", "disbelief"], + "🤦â€â™€ï¸": ["woman", "female", "girl", "disbelief"], + "🤷": ["woman", "female", "girl", "confused", "indifferent", "doubt"], + "🤷â€â™‚ï¸": ["man", "male", "boy", "confused", "indifferent", "doubt"], + "ðŸ’": ["female", "girl", "woman", "human", "information"], + "ðŸ’â€â™‚ï¸": ["male", "boy", "man", "human", "information"], + "🙅": ["female", "girl", "woman", "nope"], + "🙅â€â™‚ï¸": ["male", "boy", "man", "nope"], + "🙆": ["women", "girl", "female", "pink", "human", "woman"], + "🙆â€â™‚ï¸": ["men", "boy", "male", "blue", "human", "man"], + "🙋": ["female", "girl", "woman"], + "🙋â€â™‚ï¸": ["male", "boy", "man"], + "🙎": ["female", "girl", "woman"], + "🙎â€â™‚ï¸": ["male", "boy", "man"], + "ðŸ™": ["female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy"], + "ðŸ™â€â™‚ï¸": ["male", "boy", "man", "sad", "depressed", "discouraged", "unhappy"], + "💇": ["female", "girl", "woman"], + "💇â€â™‚ï¸": ["male", "boy", "man"], + "💆": ["female", "girl", "woman", "head"], + "💆â€â™‚ï¸": ["male", "boy", "man", "head"], + "🧖â€â™€ï¸": ["female", "woman", "spa", "steamroom", "sauna"], + "🧖â€â™‚ï¸": ["male", "man", "spa", "steamroom", "sauna"], + "ðŸ§â€â™€ï¸": ["woman", "female"], + "ðŸ§â€â™‚ï¸": ["man", "male"], + "ðŸ§â€â™€ï¸": ["woman", "female"], + "ðŸ§â€â™‚ï¸": ["man", "male"], + "🧎â€â™€ï¸": ["woman", "female"], + "🧎â€â™‚ï¸": ["man", "male"], + "🧑â€ðŸ¦¯": ["accessibility", "blind"], + "👩â€ðŸ¦¯": ["woman", "female", "accessibility", "blind"], + "👨â€ðŸ¦¯": ["man", "male", "accessibility", "blind"], + "🧑â€ðŸ¦¼": ["accessibility"], + "👩â€ðŸ¦¼": ["woman", "female", "accessibility"], + "👨â€ðŸ¦¼": ["man", "male", "accessibility"], + "🧑â€ðŸ¦½": ["accessibility"], + "👩â€ðŸ¦½": ["woman", "female", "accessibility"], + "👨â€ðŸ¦½": ["man", "male", "accessibility"], + "💑": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"], + "👩â€â¤ï¸â€ðŸ‘©": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"], + "👨â€â¤ï¸â€ðŸ‘¨": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"], + "ðŸ’": ["pair", "valentines", "love", "like", "dating", "marriage"], + "👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©": ["pair", "valentines", "love", "like", "dating", "marriage"], + "👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨": ["pair", "valentines", "love", "like", "dating", "marriage"], + "👪": ["home", "parents", "child", "mom", "dad", "father", "mother", "people", "human"], + "👨â€ðŸ‘©â€ðŸ‘§": ["home", "parents", "people", "human", "child"], + "👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§": ["home", "parents", "people", "human", "children"], + "👩â€ðŸ‘©â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👩â€ðŸ‘©â€ðŸ‘§": ["home", "parents", "people", "human", "children"], + "👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘¨â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘¨â€ðŸ‘§": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦": ["home", "parents", "people", "human", "children"], + "👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§": ["home", "parents", "people", "human", "children"], + "👩â€ðŸ‘¦": ["home", "parent", "people", "human", "child"], + "👩â€ðŸ‘§": ["home", "parent", "people", "human", "child"], + "👩â€ðŸ‘§â€ðŸ‘¦": ["home", "parent", "people", "human", "children"], + "👩â€ðŸ‘¦â€ðŸ‘¦": ["home", "parent", "people", "human", "children"], + "👩â€ðŸ‘§â€ðŸ‘§": ["home", "parent", "people", "human", "children"], + "👨â€ðŸ‘¦": ["home", "parent", "people", "human", "child"], + "👨â€ðŸ‘§": ["home", "parent", "people", "human", "child"], + "👨â€ðŸ‘§â€ðŸ‘¦": ["home", "parent", "people", "human", "children"], + "👨â€ðŸ‘¦â€ðŸ‘¦": ["home", "parent", "people", "human", "children"], + "👨â€ðŸ‘§â€ðŸ‘§": ["home", "parent", "people", "human", "children"], + "🧶": ["ball", "crochet", "knit"], + "🧵": ["needle", "sewing", "spool", "string"], + "🧥": ["jacket"], + "🥼": ["doctor", "experiment", "scientist", "chemist"], + "👚": ["fashion", "shopping_bags", "female"], + "👕": ["fashion", "cloth", "casual", "shirt", "tee"], + "👖": ["fashion", "shopping"], + "👔": ["shirt", "suitup", "formal", "fashion", "cloth", "business"], + "👗": ["clothes", "fashion", "shopping"], + "👙": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"], + "🩱": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"], + "👘": ["dress", "fashion", "women", "female", "japanese"], + "🥻": ["dress", "fashion", "women", "female"], + "🩲": ["dress", "fashion"], + "🩳": ["dress", "fashion"], + "💄": ["female", "girl", "fashion", "woman"], + "💋": ["face", "lips", "love", "like", "affection", "valentines"], + "👣": ["feet", "tracking", "walking", "beach"], + "🥿": ["ballet", "slip-on", "slipper"], + "👠": ["fashion", "shoes", "female", "pumps", "stiletto"], + "👡": ["shoes", "fashion", "flip flops"], + "👢": ["shoes", "fashion"], + "👞": ["fashion", "male"], + "👟": ["shoes", "sports", "sneakers"], + "🩴": [], + "🩰": ["shoes", "sports"], + "🧦": ["stockings", "clothes"], + "🧤": ["hands", "winter", "clothes"], + "🧣": ["neck", "winter", "clothes"], + "👒": ["fashion", "accessories", "female", "lady", "spring"], + "🎩": ["magic", "gentleman", "classy", "circus"], + "🧢": ["cap", "baseball"], + "⛑": ["construction", "build"], + "🪖": [], + "🎓": ["school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education"], + "👑": ["king", "kod", "leader", "royalty", "lord"], + "🎒": ["student", "education", "bag", "backpack"], + "🧳": ["packing", "travel"], + "ðŸ‘": ["bag", "accessories", "shopping"], + "👛": ["fashion", "accessories", "money", "sales", "shopping"], + "👜": ["fashion", "accessory", "accessories", "shopping"], + "💼": ["business", "documents", "work", "law", "legal", "job", "career"], + "👓": ["fashion", "accessories", "eyesight", "nerdy", "dork", "geek"], + "🕶": ["face", "cool", "accessories"], + "🥽": ["eyes", "protection", "safety"], + "ðŸ’": ["wedding", "propose", "marriage", "valentines", "diamond", "fashion", "jewelry", "gem", "engagement"], + "🌂": ["weather", "rain", "drizzle"], + "ðŸ¶": ["animal", "friend", "nature", "woof", "puppy", "pet", "faithful"], + "ðŸ±": ["animal", "meow", "nature", "pet", "kitten"], + "ðŸˆâ€â¬›": ["animal", "meow", "nature", "pet", "kitten"], + "ðŸ": ["animal", "nature", "cheese_wedge", "rodent"], + "ðŸ¹": ["animal", "nature"], + "ðŸ°": ["animal", "nature", "pet", "spring", "magic", "bunny"], + "🦊": ["animal", "nature", "face"], + "ðŸ»": ["animal", "nature", "wild"], + "ðŸ¼": ["animal", "nature", "panda"], + "ðŸ¨": ["animal", "nature"], + "ðŸ¯": ["animal", "cat", "danger", "wild", "nature", "roar"], + "ðŸ¦": ["animal", "nature"], + "ðŸ®": ["beef", "ox", "animal", "nature", "moo", "milk"], + "ðŸ·": ["animal", "oink", "nature"], + "ðŸ½": ["animal", "oink"], + "ðŸ¸": ["animal", "nature", "croak", "toad"], + "🦑": ["animal", "nature", "ocean", "sea"], + "ðŸ™": ["animal", "creature", "ocean", "sea", "nature", "beach"], + "ðŸ¦": ["animal", "ocean", "nature", "seafood"], + "ðŸµ": ["animal", "nature", "circus"], + "ðŸ¦": ["animal", "nature", "circus"], + "🙈": ["monkey", "animal", "nature", "haha"], + "🙉": ["animal", "monkey", "nature"], + "🙊": ["monkey", "animal", "nature", "omg"], + "ðŸ’": ["animal", "nature", "banana", "circus"], + "ðŸ”": ["animal", "cluck", "nature", "bird"], + "ðŸ§": ["animal", "nature"], + "ðŸ¦": ["animal", "nature", "fly", "tweet", "spring"], + "ðŸ¤": ["animal", "chicken", "bird"], + "ðŸ£": ["animal", "chicken", "egg", "born", "baby", "bird"], + "ðŸ¥": ["animal", "chicken", "baby", "bird"], + "🦆": ["animal", "nature", "bird", "mallard"], + "🦅": ["animal", "nature", "bird"], + "🦉": ["animal", "nature", "bird", "hoot"], + "🦇": ["animal", "nature", "blind", "vampire"], + "ðŸº": ["animal", "nature", "wild"], + "ðŸ—": ["animal", "nature"], + "ðŸ´": ["animal", "brown", "nature"], + "🦄": ["animal", "nature", "mystical"], + "ðŸ": ["animal", "insect", "nature", "bug", "spring", "honey"], + "ðŸ›": ["animal", "insect", "nature", "worm"], + "🦋": ["animal", "insect", "nature", "caterpillar"], + "ðŸŒ": ["slow", "animal", "shell"], + "ðŸž": ["animal", "insect", "nature", "ladybug"], + "ðŸœ": ["animal", "insect", "nature", "bug"], + "🦗": ["animal", "cricket", "chirp"], + "🕷": ["animal", "arachnid"], + "🪲": ["animal"], + "🪳": ["animal"], + "🪰": ["animal"], + "🪱": ["animal"], + "🦂": ["animal", "arachnid"], + "🦀": ["animal", "crustacean"], + "ðŸ": ["animal", "evil", "nature", "hiss", "python"], + "🦎": ["animal", "nature", "reptile"], + "🦖": ["animal", "nature", "dinosaur", "tyrannosaurus", "extinct"], + "🦕": ["animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct"], + "ðŸ¢": ["animal", "slow", "nature", "tortoise"], + "ðŸ ": ["animal", "swim", "ocean", "beach", "nemo"], + "ðŸŸ": ["animal", "food", "nature"], + "ðŸ¡": ["animal", "nature", "food", "sea", "ocean"], + "ðŸ¬": ["animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach"], + "🦈": ["animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach"], + "ðŸ³": ["animal", "nature", "sea", "ocean"], + "ðŸ‹": ["animal", "nature", "sea", "ocean"], + "ðŸŠ": ["animal", "nature", "reptile", "lizard", "alligator"], + "ðŸ†": ["animal", "nature"], + "🦓": ["animal", "nature", "stripes", "safari"], + "ðŸ…": ["animal", "nature", "roar"], + "ðŸƒ": ["animal", "nature", "ox", "cow"], + "ðŸ‚": ["animal", "cow", "beef"], + "ðŸ„": ["beef", "ox", "animal", "nature", "moo", "milk"], + "🦌": ["animal", "nature", "horns", "venison"], + "ðŸª": ["animal", "hot", "desert", "hump"], + "ðŸ«": ["animal", "nature", "hot", "desert", "hump"], + "🦒": ["animal", "nature", "spots", "safari"], + "ðŸ˜": ["animal", "nature", "nose", "th", "circus"], + "ðŸ¦": ["animal", "nature", "horn"], + "ðŸ": ["animal", "nature"], + "ðŸ": ["animal", "sheep", "nature"], + "ðŸ‘": ["animal", "nature", "wool", "shipit"], + "ðŸŽ": ["animal", "gamble", "luck"], + "ðŸ–": ["animal", "nature"], + "ðŸ€": ["animal", "mouse", "rodent"], + "ðŸ": ["animal", "nature", "rodent"], + "ðŸ“": ["animal", "nature", "chicken"], + "🦃": ["animal", "bird"], + "🕊": ["animal", "bird"], + "ðŸ•": ["animal", "nature", "friend", "doge", "pet", "faithful"], + "ðŸ©": ["dog", "animal", "101", "nature", "pet"], + "ðŸˆ": ["animal", "meow", "pet", "cats"], + "ðŸ‡": ["animal", "nature", "pet", "magic", "spring"], + "ðŸ¿": ["animal", "nature", "rodent", "squirrel"], + "🦔": ["animal", "nature", "spiny"], + "ðŸ¦": ["animal", "nature"], + "🦙": ["animal", "nature", "alpaca"], + "🦛": ["animal", "nature"], + "🦘": ["animal", "nature", "australia", "joey", "hop", "marsupial"], + "🦡": ["animal", "nature", "honey"], + "🦢": ["animal", "nature", "bird"], + "🦚": ["animal", "nature", "peahen", "bird"], + "🦜": ["animal", "nature", "bird", "pirate", "talk"], + "🦞": ["animal", "nature", "bisque", "claws", "seafood"], + "🦠": ["amoeba", "bacteria", "germs"], + "🦟": ["animal", "nature", "insect", "malaria"], + "🦬": ["animal", "nature"], + "🦣": ["animal", "nature"], + "🦫": ["animal", "nature"], + "ðŸ»â€â„ï¸": ["animal", "nature"], + "🦤": ["animal", "nature"], + "🪶": ["animal", "nature"], + "ðŸ¦": ["animal", "nature"], + "ðŸ¾": ["animal", "tracking", "footprints", "dog", "cat", "pet", "feet"], + "ðŸ‰": ["animal", "myth", "nature", "chinese", "green"], + "ðŸ²": ["animal", "myth", "nature", "chinese", "green"], + "🦧": ["animal", "nature"], + "🦮": ["animal", "nature"], + "ðŸ•â€ðŸ¦º": ["animal", "nature"], + "🦥": ["animal", "nature"], + "🦦": ["animal", "nature"], + "🦨": ["animal", "nature"], + "🦩": ["animal", "nature"], + "🌵": ["vegetable", "plant", "nature"], + "🎄": ["festival", "vacation", "december", "xmas", "celebration"], + "🌲": ["plant", "nature"], + "🌳": ["plant", "nature"], + "🌴": ["plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical"], + "🌱": ["plant", "nature", "grass", "lawn", "spring"], + "🌿": ["vegetable", "plant", "medicine", "weed", "grass", "lawn"], + "☘": ["vegetable", "plant", "nature", "irish", "clover"], + "ðŸ€": ["vegetable", "plant", "nature", "lucky", "irish"], + "ðŸŽ": ["plant", "nature", "vegetable", "panda", "pine_decoration"], + "🎋": ["plant", "nature", "branch", "summer"], + "ðŸƒ": ["nature", "plant", "tree", "vegetable", "grass", "lawn", "spring"], + "ðŸ‚": ["nature", "plant", "vegetable", "leaves"], + "ðŸ": ["nature", "plant", "vegetable", "ca", "fall"], + "🌾": ["nature", "plant"], + "🌺": ["plant", "vegetable", "flowers", "beach"], + "🌻": ["nature", "plant", "fall"], + "🌹": ["flowers", "valentines", "love", "spring"], + "🥀": ["plant", "nature", "flower"], + "🌷": ["flowers", "plant", "nature", "summer", "spring"], + "🌼": ["nature", "flowers", "yellow"], + "🌸": ["nature", "plant", "spring", "flower"], + "ðŸ’": ["flowers", "nature", "spring"], + "ðŸ„": ["plant", "vegetable"], + "🪴": ["plant"], + "🌰": ["food", "squirrel"], + "🎃": ["halloween", "light", "pumpkin", "creepy", "fall"], + "ðŸš": ["nature", "sea", "beach"], + "🕸": ["animal", "insect", "arachnid", "silk"], + "🌎": ["globe", "world", "USA", "international"], + "ðŸŒ": ["globe", "world", "international"], + "ðŸŒ": ["globe", "world", "east", "international"], + "ðŸª": ["saturn"], + "🌕": ["nature", "yellow", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌖": ["nature", "twilight", "planet", "space", "night", "evening", "sleep", "waxing_gibbous_moon"], + "🌗": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌘": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌑": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌒": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌓": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌔": ["nature", "night", "sky", "gray", "twilight", "planet", "space", "evening", "sleep"], + "🌚": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "ðŸŒ": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌛": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌜": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"], + "🌞": ["nature", "morning", "sky"], + "🌙": ["night", "sleep", "sky", "evening", "magic"], + "â": ["night", "yellow"], + "🌟": ["night", "sparkle", "awesome", "good", "magic"], + "💫": ["star", "sparkle", "shoot", "magic"], + "✨": ["stars", "shine", "shiny", "cool", "awesome", "good", "magic"], + "☄": ["space"], + "☀ï¸": ["weather", "nature", "brightness", "summer", "beach", "spring"], + "🌤": ["weather"], + "â›…": ["weather", "nature", "cloudy", "morning", "fall", "spring"], + "🌥": ["weather"], + "🌦": ["weather"], + "â˜ï¸": ["weather", "sky"], + "🌧": ["weather"], + "⛈": ["weather", "lightning"], + "🌩": ["weather", "thunder"], + "âš¡": ["thunder", "weather", "lightning bolt", "fast"], + "🔥": ["hot", "cook", "flame"], + "💥": ["bomb", "explode", "explosion", "collision", "blown"], + "â„ï¸": ["winter", "season", "cold", "weather", "christmas", "xmas"], + "🌨": ["weather"], + "⛄": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen", "without_snow"], + "☃": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen"], + "🌬": ["gust", "air"], + "💨": ["wind", "air", "fast", "shoo", "fart", "smoke", "puff"], + "🌪": ["weather", "cyclone", "twister"], + "🌫": ["weather"], + "☂": ["weather", "spring"], + "☔": ["rainy", "weather", "spring"], + "💧": ["water", "drip", "faucet", "spring"], + "💦": ["water", "drip", "oops"], + "🌊": ["sea", "water", "wave", "nature", "tsunami", "disaster"], + "🪷": [], + "🪸": [], + "🪹": [], + "🪺": [], + "ðŸ": ["fruit", "nature"], + "ðŸŽ": ["fruit", "mac", "school"], + "ðŸ": ["fruit", "nature", "food"], + "ðŸŠ": ["food", "fruit", "nature", "orange"], + "ðŸ‹": ["fruit", "nature"], + "ðŸŒ": ["fruit", "food", "monkey"], + "ðŸ‰": ["fruit", "food", "picnic", "summer"], + "ðŸ‡": ["fruit", "food", "wine"], + "ðŸ“": ["fruit", "food", "nature"], + "ðŸˆ": ["fruit", "nature", "food"], + "ðŸ’": ["food", "fruit"], + "ðŸ‘": ["fruit", "nature", "food"], + "ðŸ": ["fruit", "nature", "food"], + "🥥": ["fruit", "nature", "food", "palm"], + "ðŸ¥": ["fruit", "food"], + "ðŸ¥": ["fruit", "food", "tropical"], + "🥑": ["fruit", "food"], + "🥦": ["fruit", "food", "vegetable"], + "ðŸ…": ["fruit", "vegetable", "nature", "food"], + "ðŸ†": ["vegetable", "nature", "food", "aubergine"], + "🥒": ["fruit", "food", "pickle"], + "ðŸ«": ["fruit", "food"], + "🫒": ["fruit", "food"], + "🫑": ["fruit", "food"], + "🥕": ["vegetable", "food", "orange"], + "🌶": ["food", "spicy", "chilli", "chili"], + "🥔": ["food", "tuber", "vegatable", "starch"], + "🌽": ["food", "vegetable", "plant"], + "🥬": ["food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce"], + "ðŸ ": ["food", "nature"], + "🥜": ["food", "nut"], + "🧄": ["food"], + "🧅": ["food"], + "ðŸ¯": ["bees", "sweet", "kitchen"], + "ðŸ¥": ["food", "bread", "french"], + "ðŸž": ["food", "wheat", "breakfast", "toast"], + "🥖": ["food", "bread", "french"], + "🥯": ["food", "bread", "bakery", "schmear"], + "🥨": ["food", "bread", "twisted"], + "🧀": ["food", "chadder"], + "🥚": ["food", "chicken", "breakfast"], + "🥓": ["food", "breakfast", "pork", "pig", "meat"], + "🥩": ["food", "cow", "meat", "cut", "chop", "lambchop", "porkchop"], + "🥞": ["food", "breakfast", "flapjacks", "hotcakes"], + "ðŸ—": ["food", "meat", "drumstick", "bird", "chicken", "turkey"], + "ðŸ–": ["good", "food", "drumstick"], + "🦴": ["skeleton"], + "ðŸ¤": ["food", "animal", "appetizer", "summer"], + "ðŸ³": ["food", "breakfast", "kitchen", "egg"], + "ðŸ”": ["meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king"], + "ðŸŸ": ["chips", "snack", "fast food"], + "🥙": ["food", "flatbread", "stuffed", "gyro"], + "ðŸŒ": ["food", "frankfurter"], + "ðŸ•": ["food", "party"], + "🥪": ["food", "lunch", "bread"], + "🥫": ["food", "soup"], + "ðŸ": ["food", "italian", "noodle"], + "🌮": ["food", "mexican"], + "🌯": ["food", "mexican"], + "🥗": ["food", "healthy", "lettuce"], + "🥘": ["food", "cooking", "casserole", "paella"], + "ðŸœ": ["food", "japanese", "noodle", "chopsticks"], + "ðŸ²": ["food", "meat", "soup"], + "ðŸ¥": ["food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen"], + "🥠": ["food", "prophecy"], + "ðŸ£": ["food", "fish", "japanese", "rice"], + "ðŸ±": ["food", "japanese", "box"], + "ðŸ›": ["food", "spicy", "hot", "indian"], + "ðŸ™": ["food", "japanese"], + "ðŸš": ["food", "china", "asian"], + "ðŸ˜": ["food", "japanese"], + "ðŸ¢": ["food", "japanese"], + "ðŸ¡": ["food", "dessert", "sweet", "japanese", "barbecue", "meat"], + "ðŸ§": ["hot", "dessert", "summer"], + "ðŸ¨": ["food", "hot", "dessert"], + "ðŸ¦": ["food", "hot", "dessert", "summer"], + "🥧": ["food", "dessert", "pastry"], + "ðŸ°": ["food", "dessert"], + "ðŸ§": ["food", "dessert", "bakery", "sweet"], + "🥮": ["food", "autumn"], + "🎂": ["food", "dessert", "cake"], + "ðŸ®": ["dessert", "food"], + "ðŸ¬": ["snack", "dessert", "sweet", "lolly"], + "ðŸ": ["food", "snack", "candy", "sweet"], + "ðŸ«": ["food", "snack", "dessert", "sweet"], + "ðŸ¿": ["food", "movie theater", "films", "snack"], + "🥟": ["food", "empanada", "pierogi", "potsticker"], + "ðŸ©": ["food", "dessert", "snack", "sweet", "donut"], + "ðŸª": ["food", "snack", "oreo", "chocolate", "sweet", "dessert"], + "🧇": ["food"], + "🧆": ["food"], + "🧈": ["food"], + "🦪": ["food"], + "🫓": ["food"], + "🫔": ["food"], + "🫕": ["food"], + "🥛": ["beverage", "drink", "cow"], + "ðŸº": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"], + "ðŸ»": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"], + "🥂": ["beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast"], + "ðŸ·": ["drink", "beverage", "drunk", "alcohol", "booze"], + "🥃": ["drink", "beverage", "drunk", "alcohol", "liquor", "booze", "bourbon", "scotch", "whisky", "glass", "shot"], + "ðŸ¸": ["drink", "drunk", "alcohol", "beverage", "booze", "mojito"], + "ðŸ¹": ["beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito"], + "ðŸ¾": ["drink", "wine", "bottle", "celebration"], + "ðŸ¶": ["wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze"], + "ðŸµ": ["drink", "bowl", "breakfast", "green", "british"], + "🥤": ["drink", "soda"], + "☕": ["beverage", "caffeine", "latte", "espresso"], + "🫖": [], + "🧋": ["tapioca"], + "ðŸ¼": ["food", "container", "milk"], + "🧃": ["food", "drink"], + "🧉": ["food", "drink"], + "🧊": ["food"], + "🧂": ["condiment", "shaker"], + "🥄": ["cutlery", "kitchen", "tableware"], + "ðŸ´": ["cutlery", "kitchen"], + "ðŸ½": ["food", "eat", "meal", "lunch", "dinner", "restaurant"], + "🥣": ["food", "breakfast", "cereal", "oatmeal", "porridge"], + "🥡": ["food", "leftovers"], + "🥢": ["food"], + "🫗": [], + "🫘": [], + "🫙": [], + "âš½": ["sports", "football"], + "ðŸ€": ["sports", "balls", "NBA"], + "ðŸˆ": ["sports", "balls", "NFL"], + "âš¾": ["sports", "balls"], + "🥎": ["sports", "balls"], + "🎾": ["sports", "balls", "green"], + "ðŸ": ["sports", "balls"], + "ðŸ‰": ["sports", "team"], + "ðŸ¥": ["sports", "frisbee", "ultimate"], + "🎱": ["pool", "hobby", "game", "luck", "magic"], + "⛳": ["sports", "business", "flag", "hole", "summer"], + "ðŸŒï¸â€â™€ï¸": ["sports", "business", "woman", "female"], + "ðŸŒ": ["sports", "business"], + "ðŸ“": ["sports", "pingpong"], + "ðŸ¸": ["sports"], + "🥅": ["sports"], + "ðŸ’": ["sports"], + "ðŸ‘": ["sports"], + "ðŸ¥": ["sports", "ball", "stick"], + "ðŸ": ["sports"], + "🎿": ["sports", "winter", "cold", "snow"], + "â›·": ["sports", "winter", "snow"], + "ðŸ‚": ["sports", "winter"], + "🤺": ["sports", "fencing", "sword"], + "🤼â€â™€ï¸": ["sports", "wrestlers"], + "🤼â€â™‚ï¸": ["sports", "wrestlers"], + "🤸â€â™€ï¸": ["gymnastics"], + "🤸â€â™‚ï¸": ["gymnastics"], + "🤾â€â™€ï¸": ["sports"], + "🤾â€â™‚ï¸": ["sports"], + "⛸": ["sports"], + "🥌": ["sports"], + "🛹": ["board"], + "🛷": ["sleigh", "luge", "toboggan"], + "ðŸ¹": ["sports"], + "🎣": ["food", "hobby", "summer"], + "🥊": ["sports", "fighting"], + "🥋": ["judo", "karate", "taekwondo"], + "🚣â€â™€ï¸": ["sports", "hobby", "water", "ship", "woman", "female"], + "🚣": ["sports", "hobby", "water", "ship"], + "🧗â€â™€ï¸": ["sports", "hobby", "woman", "female", "rock"], + "🧗â€â™‚ï¸": ["sports", "hobby", "man", "male", "rock"], + "ðŸŠâ€â™€ï¸": ["sports", "exercise", "human", "athlete", "water", "summer", "woman", "female"], + "ðŸŠ": ["sports", "exercise", "human", "athlete", "water", "summer"], + "🤽â€â™€ï¸": ["sports", "pool"], + "🤽â€â™‚ï¸": ["sports", "pool"], + "🧘â€â™€ï¸": ["woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness"], + "🧘â€â™‚ï¸": ["man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness"], + "ðŸ„â€â™€ï¸": ["sports", "ocean", "sea", "summer", "beach", "woman", "female"], + "ðŸ„": ["sports", "ocean", "sea", "summer", "beach"], + "🛀": ["clean", "shower", "bathroom"], + "⛹ï¸â€â™€ï¸": ["sports", "human", "woman", "female"], + "⛹": ["sports", "human"], + "ðŸ‹ï¸â€â™€ï¸": ["sports", "training", "exercise", "woman", "female"], + "ðŸ‹": ["sports", "training", "exercise"], + "🚴â€â™€ï¸": ["sports", "bike", "exercise", "hipster", "woman", "female"], + "🚴": ["sports", "bike", "exercise", "hipster"], + "🚵â€â™€ï¸": ["transportation", "sports", "human", "race", "bike", "woman", "female"], + "🚵": ["transportation", "sports", "human", "race", "bike"], + "ðŸ‡": ["animal", "betting", "competition", "gambling", "luck"], + "🤿": ["sports"], + "🪀": ["sports"], + "ðŸª": ["sports"], + "🦺": ["sports"], + "🪡": [], + "🪢": [], + "🕴": ["suit", "business", "levitate", "hover", "jump"], + "ðŸ†": ["win", "award", "contest", "place", "ftw", "ceremony"], + "🎽": ["play", "pageant"], + "ðŸ…": ["award", "winning"], + "🎖": ["award", "winning", "army"], + "🥇": ["award", "winning", "first"], + "🥈": ["award", "second"], + "🥉": ["award", "third"], + "🎗": ["sports", "cause", "support", "awareness"], + "ðŸµ": ["flower", "decoration", "military"], + "🎫": ["event", "concert", "pass"], + "🎟": ["sports", "concert", "entrance"], + "ðŸŽ": ["acting", "theater", "drama"], + "🎨": ["design", "paint", "draw", "colors"], + "🎪": ["festival", "carnival", "party"], + "🤹â€â™€ï¸": ["juggle", "balance", "skill", "multitask"], + "🤹â€â™‚ï¸": ["juggle", "balance", "skill", "multitask"], + "🎤": ["sound", "music", "PA", "sing", "talkshow"], + "🎧": ["music", "score", "gadgets"], + "🎼": ["treble", "clef", "compose"], + "🎹": ["piano", "instrument", "compose"], + "ðŸ¥": ["music", "instrument", "drumsticks", "snare"], + "🎷": ["music", "instrument", "jazz", "blues"], + "🎺": ["music", "brass"], + "🎸": ["music", "instrument"], + "🎻": ["music", "instrument", "orchestra", "symphony"], + "🪕": ["music", "instrument"], + "🪗": ["music", "instrument"], + "🪘": ["music", "instrument"], + "🎬": ["movie", "film", "record"], + "🎮": ["play", "console", "PS4", "controller"], + "👾": ["game", "arcade", "play"], + "🎯": ["game", "play", "bar", "target", "bullseye"], + "🎲": ["dice", "random", "tabletop", "play", "luck"], + "♟ï¸": ["expendable"], + "🎰": ["bet", "gamble", "vegas", "fruit machine", "luck", "casino"], + "🧩": ["interlocking", "puzzle", "piece"], + "🎳": ["sports", "fun", "play"], + "🪄": [], + "🪅": [], + "🪆": [], + "🪬": [], + "🪩": [], + "🚗": ["red", "transportation", "vehicle"], + "🚕": ["uber", "vehicle", "cars", "transportation"], + "🚙": ["transportation", "vehicle"], + "🚌": ["car", "vehicle", "transportation"], + "🚎": ["bart", "transportation", "vehicle"], + "ðŸŽ": ["sports", "race", "fast", "formula", "f1"], + "🚓": ["vehicle", "cars", "transportation", "law", "legal", "enforcement"], + "🚑": ["health", "911", "hospital"], + "🚒": ["transportation", "cars", "vehicle"], + "ðŸš": ["vehicle", "car", "transportation"], + "🚚": ["cars", "transportation"], + "🚛": ["vehicle", "cars", "transportation", "express"], + "🚜": ["vehicle", "car", "farming", "agriculture"], + "🛴": ["vehicle", "kick", "razor"], + "ðŸ": ["race", "sports", "fast"], + "🚲": ["sports", "bicycle", "exercise", "hipster"], + "🛵": ["vehicle", "vespa", "sasha"], + "🦽": ["vehicle"], + "🦼": ["vehicle"], + "🛺": ["vehicle"], + "🪂": ["vehicle"], + "🚨": ["police", "ambulance", "911", "emergency", "alert", "error", "pinged", "law", "legal"], + "🚔": ["vehicle", "law", "legal", "enforcement", "911"], + "ðŸš": ["vehicle", "transportation"], + "🚘": ["car", "vehicle", "transportation"], + "🚖": ["vehicle", "cars", "uber"], + "🚡": ["transportation", "vehicle", "ski"], + "🚠": ["transportation", "vehicle", "ski"], + "🚟": ["vehicle", "transportation"], + "🚃": ["transportation", "vehicle", "train"], + "🚋": ["transportation", "vehicle", "carriage", "public", "travel"], + "ðŸš": ["transportation", "vehicle"], + "🚄": ["transportation", "vehicle"], + "🚅": ["transportation", "vehicle", "speed", "fast", "public", "travel"], + "🚈": ["transportation", "vehicle"], + "🚞": ["transportation", "vehicle"], + "🚂": ["transportation", "vehicle", "train"], + "🚆": ["transportation", "vehicle"], + "🚇": ["transportation", "blue-square", "mrt", "underground", "tube"], + "🚊": ["transportation", "vehicle"], + "🚉": ["transportation", "vehicle", "public"], + "🛸": ["transportation", "vehicle", "ufo"], + "ðŸš": ["transportation", "vehicle", "fly"], + "🛩": ["flight", "transportation", "fly", "vehicle"], + "✈ï¸": ["vehicle", "transportation", "flight", "fly"], + "🛫": ["airport", "flight", "landing"], + "🛬": ["airport", "flight", "boarding"], + "⛵": ["ship", "summer", "transportation", "water", "sailing"], + "🛥": ["ship"], + "🚤": ["ship", "transportation", "vehicle", "summer"], + "â›´": ["boat", "ship", "yacht"], + "🛳": ["yacht", "cruise", "ferry"], + "🚀": ["launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly"], + "🛰": ["communication", "gps", "orbit", "spaceflight", "NASA", "ISS"], + "🛻": ["car"], + "🛼": [], + "💺": ["sit", "airplane", "transport", "bus", "flight", "fly"], + "🛶": ["boat", "paddle", "water", "ship"], + "âš“": ["ship", "ferry", "sea", "boat"], + "🚧": ["wip", "progress", "caution", "warning"], + "⛽": ["gas station", "petroleum"], + "ðŸš": ["transportation", "wait"], + "🚦": ["transportation", "driving"], + "🚥": ["transportation", "signal"], + "ðŸ": ["contest", "finishline", "race", "gokart"], + "🚢": ["transportation", "titanic", "deploy"], + "🎡": ["photo", "carnival", "londoneye"], + "🎢": ["carnival", "playground", "photo", "fun"], + "🎠": ["photo", "carnival"], + "ðŸ—": ["wip", "working", "progress"], + "ðŸŒ": ["photo", "mountain"], + "ðŸ": ["building", "industry", "pollution", "smoke"], + "⛲": ["photo", "summer", "water", "fresh"], + "🎑": ["photo", "japan", "asia", "tsukimi"], + "â›°": ["photo", "nature", "environment"], + "ðŸ”": ["photo", "nature", "environment", "winter", "cold"], + "🗻": ["photo", "mountain", "nature", "japanese"], + "🌋": ["photo", "nature", "disaster"], + "🗾": ["nation", "country", "japanese", "asia"], + "ðŸ•": ["photo", "outdoors", "tent"], + "⛺": ["photo", "camping", "outdoors"], + "ðŸž": ["photo", "environment", "nature"], + "🛣": ["road", "cupertino", "interstate", "highway"], + "🛤": ["train", "transportation"], + "🌅": ["morning", "view", "vacation", "photo"], + "🌄": ["view", "vacation", "photo"], + "ðŸœ": ["photo", "warm", "saharah"], + "ðŸ–": ["weather", "summer", "sunny", "sand", "mojito"], + "ðŸ": ["photo", "tropical", "mojito"], + "🌇": ["photo", "good morning", "dawn"], + "🌆": ["photo", "evening", "sky", "buildings"], + "ðŸ™": ["photo", "night life", "urban"], + "🌃": ["evening", "city", "downtown"], + "🌉": ["photo", "sanfrancisco"], + "🌌": ["photo", "space", "stars"], + "🌠": ["night", "photo"], + "🎇": ["stars", "night", "shine"], + "🎆": ["photo", "festival", "carnival", "congratulations"], + "🌈": ["nature", "happy", "unicorn_face", "photo", "sky", "spring"], + "ðŸ˜": ["buildings", "photo"], + "ðŸ°": ["building", "royalty", "history"], + "ðŸ¯": ["photo", "building"], + "🗼": ["photo", "japanese"], + "": ["photo", "japanese"], + "ðŸŸ": ["photo", "place", "sports", "concert", "venue"], + "🗽": ["american", "newyork"], + "ðŸ ": ["building", "home"], + "ðŸ¡": ["home", "plant", "nature"], + "ðŸš": ["abandon", "evict", "broken", "building"], + "ðŸ¢": ["building", "bureau", "work"], + "ðŸ¬": ["building", "shopping", "mall"], + "ðŸ£": ["building", "envelope", "communication"], + "ðŸ¤": ["building", "email"], + "ðŸ¥": ["building", "health", "surgery", "doctor"], + "ðŸ¦": ["building", "money", "sales", "cash", "business", "enterprise"], + "ðŸ¨": ["building", "accomodation", "checkin"], + "ðŸª": ["building", "shopping", "groceries"], + "ðŸ«": ["building", "student", "education", "learn", "teach"], + "ðŸ©": ["like", "affection", "dating"], + "💒": ["love", "like", "affection", "couple", "marriage", "bride", "groom"], + "ðŸ›": ["art", "culture", "history"], + "⛪": ["building", "religion", "christ"], + "🕌": ["islam", "worship", "minaret"], + "ðŸ•": ["judaism", "worship", "temple", "jewish"], + "🕋": ["mecca", "mosque", "islam"], + "⛩": ["temple", "japan", "kyoto"], + "🛕": ["temple"], + "🪨": [], + "🪵": [], + "🛖": [], + "ðŸ›": [], + "🛞": [], + "🛟": [], + "⌚": ["time", "accessories"], + "📱": ["technology", "apple", "gadgets", "dial"], + "📲": ["iphone", "incoming"], + "💻": ["technology", "laptop", "screen", "display", "monitor"], + "⌨": ["technology", "computer", "type", "input", "text"], + "🖥": ["technology", "computing", "screen"], + "🖨": ["paper", "ink"], + "🖱": ["click"], + "🖲": ["technology", "trackpad"], + "🕹": ["game", "play"], + "🗜": ["tool"], + "💽": ["technology", "record", "data", "disk", "90s"], + "💾": ["oldschool", "technology", "save", "90s", "80s"], + "💿": ["technology", "dvd", "disk", "disc", "90s"], + "📀": ["cd", "disk", "disc"], + "📼": ["record", "video", "oldschool", "90s", "80s"], + "📷": ["gadgets", "photography"], + "📸": ["photography", "gadgets"], + "📹": ["film", "record"], + "🎥": ["film", "record"], + "📽": ["video", "tape", "record", "movie"], + "🎞": ["movie"], + "📞": ["technology", "communication", "dial"], + "☎ï¸": ["technology", "communication", "dial", "telephone"], + "📟": ["bbcall", "oldschool", "90s"], + "📠": ["communication", "technology"], + "📺": ["technology", "program", "oldschool", "show", "television"], + "📻": ["communication", "music", "podcast", "program"], + "🎙": ["sing", "recording", "artist", "talkshow"], + "🎚": ["scale"], + "🎛": ["dial"], + "ðŸ§": ["magnetic", "navigation", "orienteering"], + "â±": ["time", "deadline"], + "â²": ["alarm"], + "â°": ["time", "wake"], + "🕰": ["time"], + "â³": ["oldschool", "time", "countdown"], + "⌛": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"], + "📡": ["communication", "future", "radio", "space"], + "🔋": ["power", "energy", "sustain"], + "🪫": [], + "🔌": ["charger", "power"], + "💡": ["light", "electricity", "idea"], + "🔦": ["dark", "camping", "sight", "night"], + "🕯": ["fire", "wax"], + "🧯": ["quench"], + "🗑": ["bin", "trash", "rubbish", "garbage", "toss"], + "🛢": ["barrell"], + "💸": ["dollar", "bills", "payment", "sale"], + "💵": ["money", "sales", "bill", "currency"], + "💴": ["money", "sales", "japanese", "dollar", "currency"], + "💶": ["money", "sales", "dollar", "currency"], + "💷": ["british", "sterling", "money", "sales", "bills", "uk", "england", "currency"], + "💰": ["dollar", "payment", "coins", "sale"], + "🪙": ["dollar", "payment", "coins", "sale"], + "💳": ["money", "sales", "dollar", "bill", "payment", "shopping"], + "🪫": [], + "💎": ["blue", "ruby", "diamond", "jewelry"], + "âš–": ["law", "fairness", "weight"], + "🧰": ["tools", "diy", "fix", "maintainer", "mechanic"], + "🔧": ["tools", "diy", "ikea", "fix", "maintainer"], + "🔨": ["tools", "build", "create"], + "âš’": ["tools", "build", "create"], + "🛠": ["tools", "build", "create"], + "â›": ["tools", "dig"], + "🪓": ["tools"], + "🦯": ["tools"], + "🔩": ["handy", "tools", "fix"], + "âš™": ["cog"], + "🪃": ["tool"], + "🪚": ["tool"], + "🪛": ["tool"], + "ðŸª": ["tool"], + "🪜": ["tool"], + "🧱": ["bricks"], + "⛓": ["lock", "arrest"], + "🧲": ["attraction", "magnetic"], + "🔫": ["violence", "weapon", "pistol", "revolver"], + "💣": ["boom", "explode", "explosion", "terrorism"], + "🧨": ["dynamite", "boom", "explode", "explosion", "explosive"], + "🔪": ["knife", "blade", "cutlery", "kitchen", "weapon"], + "🗡": ["weapon"], + "âš”": ["weapon"], + "🛡": ["protection", "security"], + "🚬": ["kills", "tobacco", "cigarette", "joint", "smoke"], + "☠": ["poison", "danger", "deadly", "scary", "death", "pirate", "evil"], + "âš°": ["vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box"], + "âš±": ["dead", "die", "death", "rip", "ashes"], + "ðŸº": ["vase", "jar"], + "🔮": ["disco", "party", "magic", "circus", "fortune_teller"], + "📿": ["dhikr", "religious"], + "🧿": ["bead", "charm"], + "💈": ["hair", "salon", "style"], + "âš—": ["distilling", "science", "experiment", "chemistry"], + "ðŸ”": ["stars", "space", "zoom", "science", "astronomy"], + "🔬": ["laboratory", "experiment", "zoomin", "science", "study"], + "🕳": ["embarrassing"], + "💊": ["health", "medicine", "doctor", "pharmacy", "drug"], + "💉": ["health", "hospital", "drugs", "blood", "medicine", "needle", "doctor", "nurse"], + "🩸": ["health", "hospital", "medicine", "needle", "doctor", "nurse"], + "🩹": ["health", "hospital", "medicine", "needle", "doctor", "nurse"], + "🩺": ["health", "hospital", "medicine", "needle", "doctor", "nurse"], + "🪒": ["health"], + "🩻": [], + "🩼": [], + "🧬": ["biologist", "genetics", "life"], + "🧫": ["bacteria", "biology", "culture", "lab"], + "🧪": ["chemistry", "experiment", "lab", "science"], + "🌡": ["weather", "temperature", "hot", "cold"], + "🧹": ["cleaning", "sweeping", "witch"], + "🧺": ["laundry"], + "🧻": ["roll"], + "ðŸ·": ["sale", "tag"], + "🔖": ["favorite", "label", "save"], + "🚽": ["restroom", "wc", "washroom", "bathroom", "potty"], + "🚿": ["clean", "water", "bathroom"], + "ðŸ›": ["clean", "shower", "bathroom"], + "🧼": ["bar", "bathing", "cleaning", "lather"], + "🧽": ["absorbing", "cleaning", "porous"], + "🧴": ["moisturizer", "sunscreen"], + "🔑": ["lock", "door", "password"], + "ðŸ—": ["lock", "door", "password"], + "🛋": ["read", "chill"], + "🪔": ["light", "oil"], + "🛌": ["bed", "rest"], + "ðŸ›": ["sleep", "rest"], + "🚪": ["house", "entry", "exit"], + "🪑": ["house", "desk"], + "🛎": ["service"], + "🧸": ["plush", "stuffed"], + "🖼": ["photography"], + "🗺": ["location", "direction"], + "🛗": ["household"], + "🪞": ["household"], + "🪟": ["household"], + "🪠": ["household"], + "🪤": ["household"], + "🪣": ["household"], + "🪥": ["household"], + "🫧": [], + "â›±": ["weather", "summer"], + "🗿": ["rock", "easter island", "moai"], + "ðŸ›": ["mall", "buy", "purchase"], + "🛒": ["trolley"], + "🎈": ["party", "celebration", "birthday", "circus"], + "ðŸŽ": ["fish", "japanese", "koinobori", "carp", "banner"], + "🎀": ["decoration", "pink", "girl", "bowtie"], + "ðŸŽ": ["present", "birthday", "christmas", "xmas"], + "🎊": ["festival", "party", "birthday", "circus"], + "🎉": ["party", "congratulations", "birthday", "magic", "circus", "celebration"], + "🎎": ["japanese", "toy", "kimono"], + "ðŸŽ": ["nature", "ding", "spring", "bell"], + "🎌": ["japanese", "nation", "country", "border"], + "ðŸ®": ["light", "paper", "halloween", "spooky"], + "🧧": ["gift"], + "✉ï¸": ["letter", "postal", "inbox", "communication"], + "📩": ["email", "communication"], + "📨": ["email", "inbox"], + "📧": ["communication", "inbox"], + "💌": ["email", "like", "affection", "envelope", "valentines"], + "📮": ["email", "letter", "envelope"], + "📪": ["email", "communication", "inbox"], + "📫": ["email", "inbox", "communication"], + "📬": ["email", "inbox", "communication"], + "ðŸ“": ["email", "inbox"], + "📦": ["mail", "gift", "cardboard", "box", "moving"], + "📯": ["instrument", "music"], + "📥": ["email", "documents"], + "📤": ["inbox", "email"], + "📜": ["documents", "ancient", "history", "paper"], + "📃": ["documents", "office", "paper"], + "📑": ["favorite", "save", "order", "tidy"], + "🧾": ["accounting", "expenses"], + "📊": ["graph", "presentation", "stats"], + "📈": ["graph", "presentation", "stats", "recovery", "business", "economics", "money", "sales", "good", "success"], + "📉": ["graph", "presentation", "stats", "recession", "business", "economics", "money", "sales", "bad", "failure"], + "📄": ["documents", "office", "paper", "information"], + "📅": ["calendar", "schedule"], + "📆": ["schedule", "date", "planning"], + "🗓": ["date", "schedule", "planning"], + "📇": ["business", "stationery"], + "🗃": ["business", "stationery"], + "🗳": ["election", "vote"], + "🗄": ["filing", "organizing"], + "📋": ["stationery", "documents"], + "🗒": ["memo", "stationery"], + "ðŸ“": ["documents", "business", "office"], + "📂": ["documents", "load"], + "🗂": ["organizing", "business", "stationery"], + "🗞": ["press", "headline"], + "📰": ["press", "headline"], + "📓": ["stationery", "record", "notes", "paper", "study"], + "📕": ["read", "library", "knowledge", "textbook", "learn"], + "📗": ["read", "library", "knowledge", "study"], + "📘": ["read", "library", "knowledge", "learn", "study"], + "📙": ["read", "library", "knowledge", "textbook", "study"], + "📔": ["classroom", "notes", "record", "paper", "study"], + "📒": ["notes", "paper"], + "📚": ["literature", "library", "study"], + "📖": ["book", "read", "library", "knowledge", "literature", "learn", "study"], + "🧷": ["diaper"], + "🔗": ["rings", "url"], + "📎": ["documents", "stationery"], + "🖇": ["documents", "stationery"], + "✂ï¸": ["stationery", "cut"], + "ðŸ“": ["stationery", "math", "architect", "sketch"], + "ðŸ“": ["stationery", "calculate", "length", "math", "school", "drawing", "architect", "sketch"], + "🧮": ["calculation"], + "📌": ["stationery", "mark", "here"], + "ðŸ“": ["stationery", "location", "map", "here"], + "🚩": ["mark", "milestone", "place"], + "ðŸ³": ["losing", "loser", "lost", "surrender", "give up", "fail"], + "ðŸ´": ["pirate"], + "ðŸ³ï¸â€ðŸŒˆ": ["flag", "rainbow", "pride", "gay", "lgbt", "glbt", "queer", "homosexual", "lesbian", "bisexual", "transgender"], + "ðŸ³ï¸â€âš§ï¸": ["flag", "transgender"], + "ðŸ”": ["security", "privacy"], + "🔒": ["security", "password", "padlock"], + "🔓": ["privacy", "security"], + "ðŸ”": ["security", "secret"], + "🖊": ["stationery", "writing", "write"], + "🖋": ["stationery", "writing", "write"], + "✒ï¸": ["pen", "stationery", "writing", "write"], + "ðŸ“": ["write", "documents", "stationery", "pencil", "paper", "writing", "legal", "exam", "quiz", "test", "study", "compose"], + "âœï¸": ["stationery", "write", "paper", "writing", "school", "study"], + "ðŸ–": ["drawing", "creativity"], + "🖌": ["drawing", "creativity", "art"], + "ðŸ”": ["search", "zoom", "find", "detective"], + "🔎": ["search", "zoom", "find", "detective"], + "🪦": [], + "🪧": [], + "💯": ["score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred"], + "🔢": ["numbers", "blue-square"], + "â¤ï¸": ["love", "like", "affection", "valentines"], + "🧡": ["love", "like", "affection", "valentines"], + "💛": ["love", "like", "affection", "valentines"], + "💚": ["love", "like", "affection", "valentines"], + "💙": ["love", "like", "affection", "valentines"], + "💜": ["love", "like", "affection", "valentines"], + "🤎": ["love", "like", "affection", "valentines"], + "🖤": ["love", "like", "affection", "valentines"], + "ðŸ¤": ["love", "like", "affection", "valentines"], + "💔": ["sad", "sorry", "break", "heart", "heartbreak"], + "â£": ["decoration", "love"], + "💕": ["love", "like", "affection", "valentines", "heart"], + "💞": ["love", "like", "affection", "valentines"], + "💓": ["love", "like", "affection", "valentines", "pink", "heart"], + "💗": ["like", "love", "affection", "valentines", "pink"], + "💖": ["love", "like", "affection", "valentines"], + "💘": ["love", "like", "heart", "affection", "valentines"], + "ðŸ’": ["love", "valentines"], + "💟": ["purple-square", "love", "like"], + "â¤ï¸â€ðŸ”¥": [], + "â¤ï¸â€ðŸ©¹": [], + "☮": ["hippie"], + "âœ": ["christianity"], + "☪": ["islam"], + "🕉": ["hinduism", "buddhism", "sikhism", "jainism"], + "☸": ["hinduism", "buddhism", "sikhism", "jainism"], + "✡": ["judaism"], + "🔯": ["purple-square", "religion", "jewish", "hexagram"], + "🕎": ["hanukkah", "candles", "jewish"], + "☯": ["balance"], + "☦": ["suppedaneum", "religion"], + "ðŸ›": ["religion", "church", "temple", "prayer"], + "⛎": ["sign", "purple-square", "constellation", "astrology"], + "♈": ["sign", "purple-square", "zodiac", "astrology"], + "♉": ["purple-square", "sign", "zodiac", "astrology"], + "♊": ["sign", "zodiac", "purple-square", "astrology"], + "♋": ["sign", "zodiac", "purple-square", "astrology"], + "♌": ["sign", "purple-square", "zodiac", "astrology"], + "â™": ["sign", "zodiac", "purple-square", "astrology"], + "♎": ["sign", "purple-square", "zodiac", "astrology"], + "â™": ["sign", "zodiac", "purple-square", "astrology", "scorpio"], + "â™": ["sign", "zodiac", "purple-square", "astrology"], + "♑": ["sign", "zodiac", "purple-square", "astrology"], + "â™’": ["sign", "purple-square", "zodiac", "astrology"], + "♓": ["purple-square", "sign", "zodiac", "astrology"], + "🆔": ["purple-square", "words"], + "âš›": ["science", "physics", "chemistry"], + "⚧ï¸": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"], + "🈳": ["kanji", "japanese", "chinese", "empty", "sky", "blue-square", "aki"], + "🈹": ["cut", "divide", "chinese", "kanji", "pink-square", "waribiki"], + "☢": ["nuclear", "danger"], + "☣": ["danger"], + "📴": ["mute", "orange-square", "silence", "quiet"], + "📳": ["orange-square", "phone"], + "🈶": ["orange-square", "chinese", "have", "kanji", "ari"], + "🈚": ["nothing", "chinese", "kanji", "japanese", "orange-square", "nashi"], + "🈸": ["chinese", "japanese", "kanji", "orange-square", "moushikomi"], + "🈺": ["japanese", "opening hours", "orange-square", "eigyo"], + "🈷ï¸": ["chinese", "month", "moon", "japanese", "orange-square", "kanji", "tsuki", "tsukigime", "getsugaku"], + "✴ï¸": ["orange-square", "shape", "polygon"], + "🆚": ["words", "orange-square"], + "🉑": ["ok", "good", "chinese", "kanji", "agree", "yes", "orange-circle"], + "💮": ["japanese", "spring"], + "ðŸ‰": ["chinese", "kanji", "obtain", "get", "circle"], + "㊙ï¸": ["privacy", "chinese", "sshh", "kanji", "red-circle"], + "㊗ï¸": ["chinese", "kanji", "japanese", "red-circle"], + "🈴": ["japanese", "chinese", "join", "kanji", "red-square", "goukaku", "pass"], + "🈵": ["full", "chinese", "japanese", "red-square", "kanji", "man"], + "🈲": ["kanji", "japanese", "chinese", "forbidden", "limit", "restricted", "red-square", "kinshi"], + "🅰ï¸": ["red-square", "alphabet", "letter"], + "🅱ï¸": ["red-square", "alphabet", "letter"], + "🆎": ["red-square", "alphabet"], + "🆑": ["alphabet", "words", "red-square"], + "🅾ï¸": ["alphabet", "red-square", "letter"], + "🆘": ["help", "red-square", "words", "emergency", "911"], + "â›”": ["limit", "security", "privacy", "bad", "denied", "stop", "circle"], + "📛": ["fire", "forbid"], + "🚫": ["forbid", "stop", "limit", "denied", "disallow", "circle"], + "âŒ": ["no", "delete", "remove", "cancel", "red"], + "â•": ["circle", "round"], + "🛑": ["stop"], + "💢": ["angry", "mad"], + "♨ï¸": ["bath", "warm", "relax"], + "🚷": ["rules", "crossing", "walking", "circle"], + "🚯": ["trash", "bin", "garbage", "circle"], + "🚳": ["cyclist", "prohibited", "circle"], + "🚱": ["drink", "faucet", "tap", "circle"], + "🔞": ["18", "drink", "pub", "night", "minor", "circle"], + "📵": ["iphone", "mute", "circle"], + "â—": ["heavy_exclamation_mark", "danger", "surprise", "punctuation", "wow", "warning"], + "â•": ["surprise", "punctuation", "gray", "wow", "warning"], + "â“": ["doubt", "confused"], + "â”": ["doubts", "gray", "huh", "confused"], + "‼ï¸": ["exclamation", "surprise"], + "â‰ï¸": ["wat", "punctuation", "surprise"], + "🔅": ["sun", "afternoon", "warm", "summer"], + "🔆": ["sun", "light"], + "🔱": ["weapon", "spear"], + "âšœ": ["decorative", "scout"], + "〽ï¸": ["graph", "presentation", "stats", "business", "economics", "bad"], + "âš ï¸": ["exclamation", "wip", "alert", "error", "problem", "issue"], + "🚸": ["school", "warning", "danger", "sign", "driving", "yellow-diamond"], + "🔰": ["badge", "shield"], + "â™»ï¸": ["arrow", "environment", "garbage", "trash"], + "🈯": ["chinese", "point", "green-square", "kanji", "reserved", "shiteiseki"], + "💹": ["green-square", "graph", "presentation", "stats"], + "â‡ï¸": ["stars", "green-square", "awesome", "good", "fireworks"], + "✳ï¸": ["star", "sparkle", "green-square"], + "âŽ": ["x", "green-square", "no", "deny"], + "✅": ["green-square", "ok", "agree", "vote", "election", "answer", "tick"], + "💠": ["jewel", "blue", "gem", "crystal", "fancy"], + "🌀": ["weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon"], + "âž¿": ["tape", "cassette"], + "ðŸŒ": ["earth", "international", "world", "internet", "interweb", "i18n"], + "â“‚ï¸": ["alphabet", "blue-circle", "letter"], + "ðŸ§": ["money", "sales", "cash", "blue-square", "payment", "bank"], + "🈂ï¸": ["japanese", "blue-square", "katakana"], + "🛂": ["custom", "blue-square"], + "🛃": ["passport", "border", "blue-square"], + "🛄": ["blue-square", "airport", "transport"], + "🛅": ["blue-square", "travel"], + "♿": ["blue-square", "disabled", "a11y", "accessibility"], + "ðŸš": ["cigarette", "blue-square", "smell", "smoke"], + "🚾": ["toilet", "restroom", "blue-square"], + "🅿ï¸": ["cars", "blue-square", "alphabet", "letter"], + "🚰": ["blue-square", "liquid", "restroom", "cleaning", "faucet"], + "🚹": ["toilet", "restroom", "wc", "blue-square", "gender", "male"], + "🚺": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"], + "🚼": ["orange-square", "child"], + "🚻": ["blue-square", "toilet", "refresh", "wc", "gender"], + "🚮": ["blue-square", "sign", "human", "info"], + "🎦": ["blue-square", "record", "film", "movie", "curtain", "stage", "theater"], + "📶": ["blue-square", "reception", "phone", "internet", "connection", "wifi", "bluetooth", "bars"], + "ðŸˆ": ["blue-square", "here", "katakana", "japanese", "destination"], + "🆖": ["blue-square", "words", "shape", "icon"], + "🆗": ["good", "agree", "yes", "blue-square"], + "🆙": ["blue-square", "above", "high"], + "🆒": ["words", "blue-square"], + "🆕": ["blue-square", "words", "start"], + "🆓": ["blue-square", "words"], + "0ï¸âƒ£": ["0", "numbers", "blue-square", "null"], + "1ï¸âƒ£": ["blue-square", "numbers", "1"], + "2ï¸âƒ£": ["numbers", "2", "prime", "blue-square"], + "3ï¸âƒ£": ["3", "numbers", "prime", "blue-square"], + "4ï¸âƒ£": ["4", "numbers", "blue-square"], + "5ï¸âƒ£": ["5", "numbers", "blue-square", "prime"], + "6ï¸âƒ£": ["6", "numbers", "blue-square"], + "7ï¸âƒ£": ["7", "numbers", "blue-square", "prime"], + "8ï¸âƒ£": ["8", "blue-square", "numbers"], + "9ï¸âƒ£": ["blue-square", "numbers", "9"], + "🔟": ["numbers", "10", "blue-square"], + "*⃣": ["star", "keycap"], + "âï¸": ["blue-square"], + "â–¶ï¸": ["blue-square", "right", "direction", "play"], + "â¸": ["pause", "blue-square"], + "â": ["forward", "next", "blue-square"], + "â¹": ["blue-square"], + "âº": ["blue-square"], + "â¯": ["blue-square", "play", "pause"], + "â®": ["backward"], + "â©": ["blue-square", "play", "speed", "continue"], + "âª": ["play", "blue-square"], + "🔀": ["blue-square", "shuffle", "music", "random"], + "ðŸ”": ["loop", "record"], + "🔂": ["blue-square", "loop"], + "â—€ï¸": ["blue-square", "left", "direction"], + "🔼": ["blue-square", "triangle", "direction", "point", "forward", "top"], + "🔽": ["blue-square", "direction", "bottom"], + "â«": ["blue-square", "direction", "top"], + "â¬": ["blue-square", "direction", "bottom"], + "âž¡ï¸": ["blue-square", "next"], + "⬅ï¸": ["blue-square", "previous", "back"], + "⬆ï¸": ["blue-square", "continue", "top", "direction"], + "⬇ï¸": ["blue-square", "direction", "bottom"], + "↗ï¸": ["blue-square", "point", "direction", "diagonal", "northeast"], + "↘ï¸": ["blue-square", "direction", "diagonal", "southeast"], + "↙ï¸": ["blue-square", "direction", "diagonal", "southwest"], + "↖ï¸": ["blue-square", "point", "direction", "diagonal", "northwest"], + "↕ï¸": ["blue-square", "direction", "way", "vertical"], + "↔ï¸": ["shape", "direction", "horizontal", "sideways"], + "🔄": ["blue-square", "sync", "cycle"], + "↪ï¸": ["blue-square", "return", "rotate", "direction"], + "↩ï¸": ["back", "return", "blue-square", "undo", "enter"], + "⤴ï¸": ["blue-square", "direction", "top"], + "⤵ï¸": ["blue-square", "direction", "bottom"], + "#ï¸âƒ£": ["symbol", "blue-square", "twitter"], + "ℹï¸": ["blue-square", "alphabet", "letter"], + "🔤": ["blue-square", "alphabet"], + "🔡": ["blue-square", "alphabet"], + "🔠": ["alphabet", "words", "blue-square"], + "🔣": ["blue-square", "music", "note", "ampersand", "percent", "glyphs", "characters"], + "🎵": ["score", "tone", "sound"], + "🎶": ["music", "score"], + "〰ï¸": ["draw", "line", "moustache", "mustache", "squiggle", "scribble"], + "âž°": ["scribble", "draw", "shape", "squiggle"], + "✔ï¸": ["ok", "nike", "answer", "yes", "tick"], + "🔃": ["sync", "cycle", "round", "repeat"], + "âž•": ["math", "calculation", "addition", "more", "increase"], + "âž–": ["math", "calculation", "subtract", "less"], + "âž—": ["divide", "math", "calculation"], + "✖ï¸": ["math", "calculation"], + "🟰": [], + "♾": ["forever"], + "💲": ["money", "sales", "payment", "currency", "buck"], + "💱": ["money", "sales", "dollar", "travel"], + "©ï¸": ["ip", "license", "circle", "law", "legal"], + "®ï¸": ["alphabet", "circle"], + "â„¢ï¸": ["trademark", "brand", "law", "legal"], + "🔚": ["words", "arrow"], + "🔙": ["arrow", "words", "return"], + "🔛": ["arrow", "words"], + "ðŸ”": ["words", "blue-square"], + "🔜": ["arrow", "words"], + "☑ï¸": ["ok", "agree", "confirm", "black-square", "vote", "election", "yes", "tick"], + "🔘": ["input", "old", "music", "circle"], + "âš«": ["shape", "button", "round"], + "⚪": ["shape", "round"], + "🔴": ["shape", "error", "danger"], + "🟠": ["shape"], + "🟡": ["shape"], + "🟢": ["shape"], + "🔵": ["shape", "icon", "button"], + "🟣": ["shape"], + "🟤": ["shape"], + "🔸": ["shape", "jewel", "gem"], + "🔹": ["shape", "jewel", "gem"], + "🔶": ["shape", "jewel", "gem"], + "🔷": ["shape", "jewel", "gem"], + "🔺": ["shape", "direction", "up", "top"], + "â–ªï¸": ["shape", "icon"], + "â–«ï¸": ["shape", "icon"], + "⬛": ["shape", "icon", "button"], + "⬜": ["shape", "icon", "stone", "button"], + "🟥": ["shape"], + "🟧": ["shape"], + "🟨": ["shape"], + "🟩": ["shape"], + "🟦": ["shape"], + "🟪": ["shape"], + "🟫": ["shape"], + "🔻": ["shape", "direction", "bottom"], + "â—¼ï¸": ["shape", "button", "icon"], + "â—»ï¸": ["shape", "stone", "icon"], + "â—¾": ["icon", "shape", "button"], + "â—½": ["shape", "stone", "icon", "button"], + "🔲": ["shape", "input", "frame"], + "🔳": ["shape", "input"], + "🔈": ["sound", "volume", "silence", "broadcast"], + "🔉": ["volume", "speaker", "broadcast"], + "🔊": ["volume", "noise", "noisy", "speaker", "broadcast"], + "🔇": ["sound", "volume", "silence", "quiet"], + "📣": ["sound", "speaker", "volume"], + "📢": ["volume", "sound"], + "🔔": ["sound", "notification", "christmas", "xmas", "chime"], + "🔕": ["sound", "volume", "mute", "quiet", "silent"], + "ðŸƒ": ["poker", "cards", "game", "play", "magic"], + "🀄": ["game", "play", "chinese", "kanji"], + "â™ ï¸": ["poker", "cards", "suits", "magic"], + "♣ï¸": ["poker", "cards", "magic", "suits"], + "♥ï¸": ["poker", "cards", "magic", "suits"], + "♦ï¸": ["poker", "cards", "magic", "suits"], + "🎴": ["game", "sunset", "red"], + "ðŸ’": ["bubble", "cloud", "speech", "thinking", "dream"], + "🗯": ["caption", "speech", "thinking", "mad"], + "💬": ["bubble", "words", "message", "talk", "chatting"], + "🗨": ["words", "message", "talk", "chatting"], + "ðŸ•": ["time", "late", "early", "schedule"], + "🕑": ["time", "late", "early", "schedule"], + "🕒": ["time", "late", "early", "schedule"], + "🕓": ["time", "late", "early", "schedule"], + "🕔": ["time", "late", "early", "schedule"], + "🕕": ["time", "late", "early", "schedule", "dawn", "dusk"], + "🕖": ["time", "late", "early", "schedule"], + "🕗": ["time", "late", "early", "schedule"], + "🕘": ["time", "late", "early", "schedule"], + "🕙": ["time", "late", "early", "schedule"], + "🕚": ["time", "late", "early", "schedule"], + "🕛": ["time", "noon", "midnight", "midday", "late", "early", "schedule"], + "🕜": ["time", "late", "early", "schedule"], + "ðŸ•": ["time", "late", "early", "schedule"], + "🕞": ["time", "late", "early", "schedule"], + "🕟": ["time", "late", "early", "schedule"], + "🕠": ["time", "late", "early", "schedule"], + "🕡": ["time", "late", "early", "schedule"], + "🕢": ["time", "late", "early", "schedule"], + "🕣": ["time", "late", "early", "schedule"], + "🕤": ["time", "late", "early", "schedule"], + "🕥": ["time", "late", "early", "schedule"], + "🕦": ["time", "late", "early", "schedule"], + "🕧": ["time", "late", "early", "schedule"], + "🇦🇫": ["af", "flag", "nation", "country", "banner"], + "🇦🇽": ["Ã…land", "islands", "flag", "nation", "country", "banner"], + "🇦🇱": ["al", "flag", "nation", "country", "banner"], + "🇩🇿": ["dz", "flag", "nation", "country", "banner"], + "🇦🇸": ["american", "ws", "flag", "nation", "country", "banner"], + "🇦🇩": ["ad", "flag", "nation", "country", "banner"], + "🇦🇴": ["ao", "flag", "nation", "country", "banner"], + "🇦🇮": ["ai", "flag", "nation", "country", "banner"], + "🇦🇶": ["aq", "flag", "nation", "country", "banner"], + "🇦🇬": ["antigua", "barbuda", "flag", "nation", "country", "banner"], + "🇦🇷": ["ar", "flag", "nation", "country", "banner"], + "🇦🇲": ["am", "flag", "nation", "country", "banner"], + "🇦🇼": ["aw", "flag", "nation", "country", "banner"], + "🇦🇨": ["flag", "nation", "country", "banner"], + "🇦🇺": ["au", "flag", "nation", "country", "banner"], + "🇦🇹": ["at", "flag", "nation", "country", "banner"], + "🇦🇿": ["az", "flag", "nation", "country", "banner"], + "🇧🇸": ["bs", "flag", "nation", "country", "banner"], + "🇧ðŸ‡": ["bh", "flag", "nation", "country", "banner"], + "🇧🇩": ["bd", "flag", "nation", "country", "banner"], + "🇧🇧": ["bb", "flag", "nation", "country", "banner"], + "🇧🇾": ["by", "flag", "nation", "country", "banner"], + "🇧🇪": ["be", "flag", "nation", "country", "banner"], + "🇧🇿": ["bz", "flag", "nation", "country", "banner"], + "🇧🇯": ["bj", "flag", "nation", "country", "banner"], + "🇧🇲": ["bm", "flag", "nation", "country", "banner"], + "🇧🇹": ["bt", "flag", "nation", "country", "banner"], + "🇧🇴": ["bo", "flag", "nation", "country", "banner"], + "🇧🇶": ["bonaire", "flag", "nation", "country", "banner"], + "🇧🇦": ["bosnia", "herzegovina", "flag", "nation", "country", "banner"], + "🇧🇼": ["bw", "flag", "nation", "country", "banner"], + "🇧🇷": ["br", "flag", "nation", "country", "banner"], + "🇮🇴": ["british", "indian", "ocean", "territory", "flag", "nation", "country", "banner"], + "🇻🇬": ["british", "virgin", "islands", "bvi", "flag", "nation", "country", "banner"], + "🇧🇳": ["bn", "darussalam", "flag", "nation", "country", "banner"], + "🇧🇬": ["bg", "flag", "nation", "country", "banner"], + "🇧🇫": ["burkina", "faso", "flag", "nation", "country", "banner"], + "🇧🇮": ["bi", "flag", "nation", "country", "banner"], + "🇨🇻": ["cabo", "verde", "flag", "nation", "country", "banner"], + "🇰ðŸ‡": ["kh", "flag", "nation", "country", "banner"], + "🇨🇲": ["cm", "flag", "nation", "country", "banner"], + "🇨🇦": ["ca", "flag", "nation", "country", "banner"], + "🇮🇨": ["canary", "islands", "flag", "nation", "country", "banner"], + "🇰🇾": ["cayman", "islands", "flag", "nation", "country", "banner"], + "🇨🇫": ["central", "african", "republic", "flag", "nation", "country", "banner"], + "🇹🇩": ["td", "flag", "nation", "country", "banner"], + "🇨🇱": ["flag", "nation", "country", "banner"], + "🇨🇳": ["china", "chinese", "prc", "flag", "country", "nation", "banner"], + "🇨🇽": ["christmas", "island", "flag", "nation", "country", "banner"], + "🇨🇨": ["cocos", "keeling", "islands", "flag", "nation", "country", "banner"], + "🇨🇴": ["co", "flag", "nation", "country", "banner"], + "🇰🇲": ["km", "flag", "nation", "country", "banner"], + "🇨🇬": ["congo", "flag", "nation", "country", "banner"], + "🇨🇩": ["congo", "democratic", "republic", "flag", "nation", "country", "banner"], + "🇨🇰": ["cook", "islands", "flag", "nation", "country", "banner"], + "🇨🇷": ["costa", "rica", "flag", "nation", "country", "banner"], + "ðŸ‡ðŸ‡·": ["hr", "flag", "nation", "country", "banner"], + "🇨🇺": ["cu", "flag", "nation", "country", "banner"], + "🇨🇼": ["curaçao", "flag", "nation", "country", "banner"], + "🇨🇾": ["cy", "flag", "nation", "country", "banner"], + "🇨🇿": ["cz", "flag", "nation", "country", "banner"], + "🇩🇰": ["dk", "flag", "nation", "country", "banner"], + "🇩🇯": ["dj", "flag", "nation", "country", "banner"], + "🇩🇲": ["dm", "flag", "nation", "country", "banner"], + "🇩🇴": ["dominican", "republic", "flag", "nation", "country", "banner"], + "🇪🇨": ["ec", "flag", "nation", "country", "banner"], + "🇪🇬": ["eg", "flag", "nation", "country", "banner"], + "🇸🇻": ["el", "salvador", "flag", "nation", "country", "banner"], + "🇬🇶": ["equatorial", "gn", "flag", "nation", "country", "banner"], + "🇪🇷": ["er", "flag", "nation", "country", "banner"], + "🇪🇪": ["ee", "flag", "nation", "country", "banner"], + "🇪🇹": ["et", "flag", "nation", "country", "banner"], + "🇪🇺": ["european", "union", "flag", "banner"], + "🇫🇰": ["falkland", "islands", "malvinas", "flag", "nation", "country", "banner"], + "🇫🇴": ["faroe", "islands", "flag", "nation", "country", "banner"], + "🇫🇯": ["fj", "flag", "nation", "country", "banner"], + "🇫🇮": ["fi", "flag", "nation", "country", "banner"], + "🇫🇷": ["banner", "flag", "nation", "france", "french", "country"], + "🇬🇫": ["french", "guiana", "flag", "nation", "country", "banner"], + "🇵🇫": ["french", "polynesia", "flag", "nation", "country", "banner"], + "🇹🇫": ["french", "southern", "territories", "flag", "nation", "country", "banner"], + "🇬🇦": ["ga", "flag", "nation", "country", "banner"], + "🇬🇲": ["gm", "flag", "nation", "country", "banner"], + "🇬🇪": ["ge", "flag", "nation", "country", "banner"], + "🇩🇪": ["german", "nation", "flag", "country", "banner"], + "🇬ðŸ‡": ["gh", "flag", "nation", "country", "banner"], + "🇬🇮": ["gi", "flag", "nation", "country", "banner"], + "🇬🇷": ["gr", "flag", "nation", "country", "banner"], + "🇬🇱": ["gl", "flag", "nation", "country", "banner"], + "🇬🇩": ["gd", "flag", "nation", "country", "banner"], + "🇬🇵": ["gp", "flag", "nation", "country", "banner"], + "🇬🇺": ["gu", "flag", "nation", "country", "banner"], + "🇬🇹": ["gt", "flag", "nation", "country", "banner"], + "🇬🇬": ["gg", "flag", "nation", "country", "banner"], + "🇬🇳": ["gn", "flag", "nation", "country", "banner"], + "🇬🇼": ["gw", "bissau", "flag", "nation", "country", "banner"], + "🇬🇾": ["gy", "flag", "nation", "country", "banner"], + "ðŸ‡ðŸ‡¹": ["ht", "flag", "nation", "country", "banner"], + "ðŸ‡ðŸ‡³": ["hn", "flag", "nation", "country", "banner"], + "ðŸ‡ðŸ‡°": ["hong", "kong", "flag", "nation", "country", "banner"], + "ðŸ‡ðŸ‡º": ["hu", "flag", "nation", "country", "banner"], + "🇮🇸": ["is", "flag", "nation", "country", "banner"], + "🇮🇳": ["in", "flag", "nation", "country", "banner"], + "🇮🇩": ["flag", "nation", "country", "banner"], + "🇮🇷": ["iran, ", "islamic", "republic", "flag", "nation", "country", "banner"], + "🇮🇶": ["iq", "flag", "nation", "country", "banner"], + "🇮🇪": ["ie", "flag", "nation", "country", "banner"], + "🇮🇲": ["isle", "man", "flag", "nation", "country", "banner"], + "🇮🇱": ["il", "flag", "nation", "country", "banner"], + "🇮🇹": ["italy", "flag", "nation", "country", "banner"], + "🇨🇮": ["ivory", "coast", "flag", "nation", "country", "banner"], + "🇯🇲": ["jm", "flag", "nation", "country", "banner"], + "🇯🇵": ["japanese", "nation", "flag", "country", "banner"], + "🇯🇪": ["je", "flag", "nation", "country", "banner"], + "🇯🇴": ["jo", "flag", "nation", "country", "banner"], + "🇰🇿": ["kz", "flag", "nation", "country", "banner"], + "🇰🇪": ["ke", "flag", "nation", "country", "banner"], + "🇰🇮": ["ki", "flag", "nation", "country", "banner"], + "🇽🇰": ["xk", "flag", "nation", "country", "banner"], + "🇰🇼": ["kw", "flag", "nation", "country", "banner"], + "🇰🇬": ["kg", "flag", "nation", "country", "banner"], + "🇱🇦": ["lao", "democratic", "republic", "flag", "nation", "country", "banner"], + "🇱🇻": ["lv", "flag", "nation", "country", "banner"], + "🇱🇧": ["lb", "flag", "nation", "country", "banner"], + "🇱🇸": ["ls", "flag", "nation", "country", "banner"], + "🇱🇷": ["lr", "flag", "nation", "country", "banner"], + "🇱🇾": ["ly", "flag", "nation", "country", "banner"], + "🇱🇮": ["li", "flag", "nation", "country", "banner"], + "🇱🇹": ["lt", "flag", "nation", "country", "banner"], + "🇱🇺": ["lu", "flag", "nation", "country", "banner"], + "🇲🇴": ["macao", "flag", "nation", "country", "banner"], + "🇲🇰": ["macedonia, ", "flag", "nation", "country", "banner"], + "🇲🇬": ["mg", "flag", "nation", "country", "banner"], + "🇲🇼": ["mw", "flag", "nation", "country", "banner"], + "🇲🇾": ["my", "flag", "nation", "country", "banner"], + "🇲🇻": ["mv", "flag", "nation", "country", "banner"], + "🇲🇱": ["ml", "flag", "nation", "country", "banner"], + "🇲🇹": ["mt", "flag", "nation", "country", "banner"], + "🇲ðŸ‡": ["marshall", "islands", "flag", "nation", "country", "banner"], + "🇲🇶": ["mq", "flag", "nation", "country", "banner"], + "🇲🇷": ["mr", "flag", "nation", "country", "banner"], + "🇲🇺": ["mu", "flag", "nation", "country", "banner"], + "🇾🇹": ["yt", "flag", "nation", "country", "banner"], + "🇲🇽": ["mx", "flag", "nation", "country", "banner"], + "🇫🇲": ["micronesia, ", "federated", "states", "flag", "nation", "country", "banner"], + "🇲🇩": ["moldova, ", "republic", "flag", "nation", "country", "banner"], + "🇲🇨": ["mc", "flag", "nation", "country", "banner"], + "🇲🇳": ["mn", "flag", "nation", "country", "banner"], + "🇲🇪": ["me", "flag", "nation", "country", "banner"], + "🇲🇸": ["ms", "flag", "nation", "country", "banner"], + "🇲🇦": ["ma", "flag", "nation", "country", "banner"], + "🇲🇿": ["mz", "flag", "nation", "country", "banner"], + "🇲🇲": ["mm", "flag", "nation", "country", "banner"], + "🇳🇦": ["na", "flag", "nation", "country", "banner"], + "🇳🇷": ["nr", "flag", "nation", "country", "banner"], + "🇳🇵": ["np", "flag", "nation", "country", "banner"], + "🇳🇱": ["nl", "flag", "nation", "country", "banner"], + "🇳🇨": ["new", "caledonia", "flag", "nation", "country", "banner"], + "🇳🇿": ["new", "zealand", "flag", "nation", "country", "banner"], + "🇳🇮": ["ni", "flag", "nation", "country", "banner"], + "🇳🇪": ["ne", "flag", "nation", "country", "banner"], + "🇳🇬": ["flag", "nation", "country", "banner"], + "🇳🇺": ["nu", "flag", "nation", "country", "banner"], + "🇳🇫": ["norfolk", "island", "flag", "nation", "country", "banner"], + "🇲🇵": ["northern", "mariana", "islands", "flag", "nation", "country", "banner"], + "🇰🇵": ["north", "korea", "nation", "flag", "country", "banner"], + "🇳🇴": ["no", "flag", "nation", "country", "banner"], + "🇴🇲": ["om_symbol", "flag", "nation", "country", "banner"], + "🇵🇰": ["pk", "flag", "nation", "country", "banner"], + "🇵🇼": ["pw", "flag", "nation", "country", "banner"], + "🇵🇸": ["palestine", "palestinian", "territories", "flag", "nation", "country", "banner"], + "🇵🇦": ["pa", "flag", "nation", "country", "banner"], + "🇵🇬": ["papua", "new", "guinea", "flag", "nation", "country", "banner"], + "🇵🇾": ["py", "flag", "nation", "country", "banner"], + "🇵🇪": ["pe", "flag", "nation", "country", "banner"], + "🇵ðŸ‡": ["ph", "flag", "nation", "country", "banner"], + "🇵🇳": ["pitcairn", "flag", "nation", "country", "banner"], + "🇵🇱": ["pl", "flag", "nation", "country", "banner"], + "🇵🇹": ["pt", "flag", "nation", "country", "banner"], + "🇵🇷": ["puerto", "rico", "flag", "nation", "country", "banner"], + "🇶🇦": ["qa", "flag", "nation", "country", "banner"], + "🇷🇪": ["réunion", "flag", "nation", "country", "banner"], + "🇷🇴": ["ro", "flag", "nation", "country", "banner"], + "🇷🇺": ["russian", "federation", "flag", "nation", "country", "banner"], + "🇷🇼": ["rw", "flag", "nation", "country", "banner"], + "🇧🇱": ["saint", "barthélemy", "flag", "nation", "country", "banner"], + "🇸ðŸ‡": ["saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner"], + "🇰🇳": ["saint", "kitts", "nevis", "flag", "nation", "country", "banner"], + "🇱🇨": ["saint", "lucia", "flag", "nation", "country", "banner"], + "🇵🇲": ["saint", "pierre", "miquelon", "flag", "nation", "country", "banner"], + "🇻🇨": ["saint", "vincent", "grenadines", "flag", "nation", "country", "banner"], + "🇼🇸": ["ws", "flag", "nation", "country", "banner"], + "🇸🇲": ["san", "marino", "flag", "nation", "country", "banner"], + "🇸🇹": ["sao", "tome", "principe", "flag", "nation", "country", "banner"], + "🇸🇦": ["flag", "nation", "country", "banner"], + "🇸🇳": ["sn", "flag", "nation", "country", "banner"], + "🇷🇸": ["rs", "flag", "nation", "country", "banner"], + "🇸🇨": ["sc", "flag", "nation", "country", "banner"], + "🇸🇱": ["sierra", "leone", "flag", "nation", "country", "banner"], + "🇸🇬": ["sg", "flag", "nation", "country", "banner"], + "🇸🇽": ["sint", "maarten", "dutch", "flag", "nation", "country", "banner"], + "🇸🇰": ["sk", "flag", "nation", "country", "banner"], + "🇸🇮": ["si", "flag", "nation", "country", "banner"], + "🇸🇧": ["solomon", "islands", "flag", "nation", "country", "banner"], + "🇸🇴": ["so", "flag", "nation", "country", "banner"], + "🇿🇦": ["south", "africa", "flag", "nation", "country", "banner"], + "🇬🇸": ["south", "georgia", "sandwich", "islands", "flag", "nation", "country", "banner"], + "🇰🇷": ["south", "korea", "nation", "flag", "country", "banner"], + "🇸🇸": ["south", "sd", "flag", "nation", "country", "banner"], + "🇪🇸": ["spain", "flag", "nation", "country", "banner"], + "🇱🇰": ["sri", "lanka", "flag", "nation", "country", "banner"], + "🇸🇩": ["sd", "flag", "nation", "country", "banner"], + "🇸🇷": ["sr", "flag", "nation", "country", "banner"], + "🇸🇿": ["sz", "flag", "nation", "country", "banner"], + "🇸🇪": ["se", "flag", "nation", "country", "banner"], + "🇨ðŸ‡": ["ch", "flag", "nation", "country", "banner"], + "🇸🇾": ["syrian", "arab", "republic", "flag", "nation", "country", "banner"], + "🇹🇼": ["tw", "flag", "nation", "country", "banner"], + "🇹🇯": ["tj", "flag", "nation", "country", "banner"], + "🇹🇿": ["tanzania, ", "united", "republic", "flag", "nation", "country", "banner"], + "🇹ðŸ‡": ["th", "flag", "nation", "country", "banner"], + "🇹🇱": ["timor", "leste", "flag", "nation", "country", "banner"], + "🇹🇬": ["tg", "flag", "nation", "country", "banner"], + "🇹🇰": ["tk", "flag", "nation", "country", "banner"], + "🇹🇴": ["to", "flag", "nation", "country", "banner"], + "🇹🇹": ["trinidad", "tobago", "flag", "nation", "country", "banner"], + "🇹🇦": ["flag", "nation", "country", "banner"], + "🇹🇳": ["tn", "flag", "nation", "country", "banner"], + "🇹🇷": ["turkey", "flag", "nation", "country", "banner"], + "🇹🇲": ["flag", "nation", "country", "banner"], + "🇹🇨": ["turks", "caicos", "islands", "flag", "nation", "country", "banner"], + "🇹🇻": ["flag", "nation", "country", "banner"], + "🇺🇬": ["ug", "flag", "nation", "country", "banner"], + "🇺🇦": ["ua", "flag", "nation", "country", "banner"], + "🇦🇪": ["united", "arab", "emirates", "flag", "nation", "country", "banner"], + "🇬🇧": ["united", "kingdom", "great", "britain", "northern", "ireland", "flag", "nation", "country", "banner", "british", "UK", "english", "england", "union jack"], + "ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿": ["flag", "english"], + "ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿": ["flag", "scottish"], + "ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿": ["flag", "welsh"], + "🇺🇸": ["united", "states", "america", "flag", "nation", "country", "banner"], + "🇻🇮": ["virgin", "islands", "us", "flag", "nation", "country", "banner"], + "🇺🇾": ["uy", "flag", "nation", "country", "banner"], + "🇺🇿": ["uz", "flag", "nation", "country", "banner"], + "🇻🇺": ["vu", "flag", "nation", "country", "banner"], + "🇻🇦": ["vatican", "city", "flag", "nation", "country", "banner"], + "🇻🇪": ["ve", "bolivarian", "republic", "flag", "nation", "country", "banner"], + "🇻🇳": ["viet", "nam", "flag", "nation", "country", "banner"], + "🇼🇫": ["wallis", "futuna", "flag", "nation", "country", "banner"], + "🇪ðŸ‡": ["western", "sahara", "flag", "nation", "country", "banner"], + "🇾🇪": ["ye", "flag", "nation", "country", "banner"], + "🇿🇲": ["zm", "flag", "nation", "country", "banner"], + "🇿🇼": ["zw", "flag", "nation", "country", "banner"], + "🇺🇳": ["un", "flag", "banner"], + "ðŸ´â€â˜ ï¸": ["skull", "crossbones", "flag", "banner"] +} diff --git a/packages/frontend/src/widgets/WidgetActivity.calendar.vue b/packages/frontend/src/widgets/WidgetActivity.calendar.vue index 84f6af1c134986278daa8139cde1aa4a418bcf7c..110f1d32eb04539b358cdb58d84ce5069703856b 100644 --- a/packages/frontend/src/widgets/WidgetActivity.calendar.vue +++ b/packages/frontend/src/widgets/WidgetActivity.calendar.vue @@ -1,25 +1,31 @@ <template> <svg viewBox="0 0 21 7"> - <rect v-for="record in activity" class="day" + <rect + v-for="record in activity" class="day" width="1" height="1" :x="record.x" :y="record.date.weekday" rx="1" ry="1" - fill="transparent"> + fill="transparent" + > <title>{{ record.date.year }}/{{ record.date.month + 1 }}/{{ record.date.day }}</title> </rect> - <rect v-for="record in activity" class="day" + <rect + v-for="record in activity" class="day" :width="record.v" :height="record.v" :x="record.x + ((1 - record.v) / 2)" :y="record.date.weekday + ((1 - record.v) / 2)" rx="1" ry="1" :fill="record.color" - style="pointer-events: none;"/> - <rect class="today" + style="pointer-events: none;" + /> + <rect + class="today" width="1" height="1" :x="activity[0].x" :y="activity[0].date.weekday" rx="1" ry="1" fill="none" stroke-width="0.1" - stroke="#f73520"/> + stroke="#f73520" + /> </svg> </template> diff --git a/packages/frontend/src/widgets/WidgetActivity.chart.vue b/packages/frontend/src/widgets/WidgetActivity.chart.vue index b61e419f9466a76f776924b6276e4922cd31ccb9..cc4df65dd2f1c9484f06dc790eb256812e40e0da 100644 --- a/packages/frontend/src/widgets/WidgetActivity.chart.vue +++ b/packages/frontend/src/widgets/WidgetActivity.chart.vue @@ -1,26 +1,30 @@ <template> -<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" @mousedown.prevent="onMousedown"> +<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" :class="$style.root" @mousedown.prevent="onMousedown"> <polyline :points="pointsNote" fill="none" stroke-width="1" - stroke="#41ddde"/> + stroke="#41ddde" + /> <polyline :points="pointsReply" fill="none" stroke-width="1" - stroke="#f7796c"/> + stroke="#f7796c" + /> <polyline :points="pointsRenote" fill="none" stroke-width="1" - stroke="#a1de41"/> + stroke="#a1de41" + /> <polyline :points="pointsTotal" fill="none" stroke-width="1" stroke="#555" - stroke-dasharray="2 2"/> + stroke-dasharray="2 2" + /> </svg> </template> @@ -81,8 +85,8 @@ function render() { } </script> -<style lang="scss" scoped> -svg { +<style lang="scss" module> +.root { display: block; padding: 16px; width: 100%; diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue index e7f8819abd18f0f591c6602896d9ca8dd8aa7f46..892b24f69d8fa0abee07963801b4667c230e88e3 100644 --- a/packages/frontend/src/widgets/WidgetActivity.vue +++ b/packages/frontend/src/widgets/WidgetActivity.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" data-cy-mkw-activity class="mkw-activity"> +<MkContainer :showHeader="widgetProps.showHeader" :naked="widgetProps.transparent" data-cy-mkw-activity class="mkw-activity"> <template #icon><i class="ti ti-chart-line"></i></template> <template #header>{{ i18n.ts._widgets.activity }}</template> <template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="toggleView()"><i class="ti ti-selector"></i></button></template> @@ -16,7 +16,7 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import XCalendar from './WidgetActivity.calendar.vue'; import XChart from './WidgetActivity.chart.vue'; import { GetFormResultType } from '@/scripts/form'; @@ -45,11 +45,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue index 37326ee981f3aa9b839be0bff1b0f4b4880e4ce2..797dd9c09ffff65014d18e0e3e0ca85e6825ba3f 100644 --- a/packages/frontend/src/widgets/WidgetAichan.vue +++ b/packages/frontend/src/widgets/WidgetAichan.vue @@ -1,12 +1,12 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false" data-cy-mkw-aichan class="mkw-aichan"> - <iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe> +<MkContainer :naked="widgetProps.transparent" :showHeader="false" data-cy-mkw-aichan class="mkw-aichan"> + <iframe ref="live2d" :class="$style.root" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe> </MkContainer> </template> <script lang="ts" setup> import { onMounted, onUnmounted, shallowRef } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; const name = 'ai'; @@ -20,11 +20,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -64,8 +61,8 @@ defineExpose<WidgetComponentExpose>({ }); </script> -<style lang="scss" scoped> -.dedjhjmo { +<style lang="scss" module> +.root { width: 100%; height: 350px; border: none; diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue index 947dbe5e77f573489d7c50b204a942b88d4d9531..d6c94cd56a6dcf0cddbb2d2253886153519b11a6 100644 --- a/packages/frontend/src/widgets/WidgetAiscript.vue +++ b/packages/frontend/src/widgets/WidgetAiscript.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-aiscript class="mkw-aiscript"> +<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-aiscript class="mkw-aiscript"> <template #icon><i class="ti ti-terminal-2"></i></template> <template #header>{{ i18n.ts._widgets.aiscript }}</template> @@ -16,7 +16,7 @@ <script lang="ts" setup> import { ref } from 'vue'; import { Interpreter, Parser, utils } from '@syuilo/aiscript'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; @@ -41,11 +41,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue index 455a6e6ea56444f50e5261fb3a60e8013202a56e..3b67972e400f7769b68aa9c1d3caaab4e1c520cc 100644 --- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue +++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscriptApp"> +<MkContainer :showHeader="widgetProps.showHeader" class="mkw-aiscriptApp"> <template #header>App</template> <div :class="$style.root"> <MkAsUi v-if="root" :component="root" :components="components" size="small"/> @@ -10,7 +10,7 @@ <script lang="ts" setup> import { onMounted, Ref, ref, watch } from 'vue'; import { Interpreter, Parser } from '@syuilo/aiscript'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; @@ -35,12 +35,9 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); - +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); + const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, props, diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue index 9eee9680dbf6dd1a3dcaabd7e4cc5f6aec1b286c..bcb380f849d155801cb8afca302be433cd41a1af 100644 --- a/packages/frontend/src/widgets/WidgetButton.vue +++ b/packages/frontend/src/widgets/WidgetButton.vue @@ -8,7 +8,7 @@ <script lang="ts" setup> import { Interpreter, Parser } from '@syuilo/aiscript'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; @@ -35,11 +35,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -101,8 +98,3 @@ defineExpose<WidgetComponentExpose>({ id: props.widget ? props.widget.id : null, }); </script> - -<style lang="scss" scoped> -.mkw-button { -} -</style> diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue index 58d073226369e780872f434d810a74b2e6ff86dd..447525837c020bff219a0fec6a323fca3d6472cd 100644 --- a/packages/frontend/src/widgets/WidgetCalendar.vue +++ b/packages/frontend/src/widgets/WidgetCalendar.vue @@ -34,7 +34,7 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { i18n } from '@/i18n'; import { useInterval } from '@/scripts/use-interval'; @@ -50,11 +50,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue index 981788a3c53161f2a7889f6addc9df4fc8712eac..b7be2e8c83a6a9de9ec79dbe99cb81ab22ba5c1c 100644 --- a/packages/frontend/src/widgets/WidgetClicker.vue +++ b/packages/frontend/src/widgets/WidgetClicker.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" class="mkw-clicker"> +<MkContainer :showHeader="widgetProps.showHeader" class="mkw-clicker"> <template #icon><i class="ti ti-cookie"></i></template> <template #header>Clicker</template> <MkClickerGame/> @@ -7,7 +7,7 @@ </template> <script lang="ts" setup> -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkClickerGame from '@/components/MkClickerGame.vue'; @@ -23,12 +23,9 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); - +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); + const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, props, diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue index ebd73cb9f551e998d9d578c6a2ade6c25db2823a..aee5026db48ccfe1850ea031c9afc982d120bce8 100644 --- a/packages/frontend/src/widgets/WidgetClock.vue +++ b/packages/frontend/src/widgets/WidgetClock.vue @@ -1,25 +1,31 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false" data-cy-mkw-clock class="mkw-clock"> - <div class="vubelbmv" :class="widgetProps.size"> - <div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label a abbrev">{{ tzAbbrev }}</div> +<MkContainer :naked="widgetProps.transparent" :showHeader="false" data-cy-mkw-clock> + <div + :class="[$style.root, { + [$style.small]: widgetProps.size === 'small', + [$style.medium]: widgetProps.size === 'medium', + [$style.large]: widgetProps.size === 'large', + }]" + > + <div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace" :class="[$style.label, $style.a]">{{ tzAbbrev }}</div> <MkAnalogClock - class="clock" + :class="$style.clock" :thickness="widgetProps.thickness" :offset="tzOffset" :graduations="widgetProps.graduations" - :fade-graduations="widgetProps.fadeGraduations" + :fadeGraduations="widgetProps.fadeGraduations" :twentyfour="widgetProps.twentyFour" - :s-animation="widgetProps.sAnimation" + :sAnimation="widgetProps.sAnimation" /> - <MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" class="_monospace label c time" :show-s="false" :offset="tzOffset"/> - <div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label d offset">{{ tzOffsetLabel }}</div> + <MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" :class="[$style.label, $style.c]" class="_monospace" :showS="false" :offset="tzOffset"/> + <div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace" :class="[$style.label, $style.d]">{{ tzOffsetLabel }}</div> </div> </MkContainer> </template> <script lang="ts" setup> import { } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkAnalogClock from '@/components/MkAnalogClock.vue'; @@ -114,11 +120,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -143,39 +146,10 @@ defineExpose<WidgetComponentExpose>({ }); </script> -<style lang="scss" scoped> -.vubelbmv { +<style lang="scss" module> +.root { position: relative; - > .label { - position: absolute; - opacity: 0.7; - - &.a { - top: 14px; - left: 14px; - } - - &.b { - top: 14px; - right: 14px; - } - - &.c { - bottom: 14px; - left: 14px; - } - - &.d { - bottom: 14px; - right: 14px; - } - } - - > .clock { - margin: auto; - } - &.small { padding: 12px; @@ -200,4 +174,33 @@ defineExpose<WidgetComponentExpose>({ } } } + +.label { + position: absolute; + opacity: 0.7; + + &.a { + top: 14px; + left: 14px; + } + + &.b { + top: 14px; + right: 14px; + } + + &.c { + bottom: 14px; + left: 14px; + } + + &.d { + bottom: 14px; + right: 14px; + } +} + +.clock { + margin: auto; +} </style> diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue index cdd9c3a4016ebcd2429615c2ee741258b140d496..6148177d9a144e197a22e6b1399a836949804ccb 100644 --- a/packages/frontend/src/widgets/WidgetDigitalClock.vue +++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue @@ -2,14 +2,14 @@ <div data-cy-mkw-digitalClock class="_monospace" :class="[$style.root, { _panel: !widgetProps.transparent }]" :style="{ fontSize: `${widgetProps.fontSize}em` }"> <div v-if="widgetProps.showLabel" :class="$style.label">{{ tzAbbrev }}</div> <div> - <MkDigitalClock :show-ms="widgetProps.showMs" :offset="tzOffset"/> + <MkDigitalClock :showMs="widgetProps.showMs" :offset="tzOffset"/> </div> <div v-if="widgetProps.showLabel" :class="$style.label">{{ tzOffsetLabel }}</div> </div> </template> <script lang="ts" setup> -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { timezones } from '@/scripts/timezones'; import MkDigitalClock from '@/components/MkDigitalClock.vue'; @@ -49,11 +49,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue index 2033b074e021cc0c80bfa9f2af6e6abdc59cc1fe..951c4aaa6da92ba57b6206ac2bc799f5f8474885 100644 --- a/packages/frontend/src/widgets/WidgetFederation.vue +++ b/packages/frontend/src/widgets/WidgetFederation.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" data-cy-mkw-federation class="mkw-federation"> +<MkContainer :showHeader="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" data-cy-mkw-federation class="mkw-federation"> <template #icon><i class="ti ti-whirl"></i></template> <template #header>{{ i18n.ts._widgets.federation }}</template> @@ -21,7 +21,7 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; @@ -42,11 +42,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue index b1578076559cbd8def3c84dc6606df7aa509d2e9..f8b811e6baddd02f2ec24c33449874a71047ea6e 100644 --- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue +++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-instance-cloud"> +<MkContainer :naked="widgetProps.transparent" :showHeader="false" class="mkw-instance-cloud"> <div class=""> <MkTagCloud v-if="activeInstances"> <li v-for="instance in activeInstances" :key="instance.id"> @@ -14,7 +14,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkTagCloud from '@/components/MkTagCloud.vue'; @@ -33,11 +33,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -75,7 +72,3 @@ defineExpose<WidgetComponentExpose>({ id: props.widget ? props.widget.id : null, }); </script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue index d702fd2cb02ac6e5f9bc04101c9873ae7afb03fc..c77b98f8f469a884467e01bbc835ad626aefcac5 100644 --- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue +++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue @@ -15,7 +15,7 @@ </template> <script lang="ts" setup> -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { host } from '@/config'; import { instance } from '@/instance'; @@ -27,11 +27,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index 84043cf13fb24f9932e2669215c52a1df1bab4b8..3c8ffdb55a30798e663fd73a11529ad319c3c01e 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -47,9 +47,9 @@ <script lang="ts" setup> import { onUnmounted, reactive } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import number from '@/filters/number'; import * as sound from '@/scripts/sound'; import { deepClone } from '@/scripts/clone'; @@ -69,11 +69,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -81,7 +78,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, emit, ); -const connection = stream.useChannel('queueStats'); +const connection = useStream().useChannel('queueStats'); const current = reactive({ inbox: { activeSincePrevTick: 0, diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue index 959cf776ad9f4bd0ab91c68f243a4396301dbbc0..78d27a31b94d1ae8cec921b76ff0c23cf322591b 100644 --- a/packages/frontend/src/widgets/WidgetMemo.vue +++ b/packages/frontend/src/widgets/WidgetMemo.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-memo class="mkw-memo"> +<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-memo class="mkw-memo"> <template #icon><i class="ti ti-note"></i></template> <template #header>{{ i18n.ts._widgets.memo }}</template> @@ -12,7 +12,7 @@ <script lang="ts" setup> import { ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import { defaultStore } from '@/store'; @@ -33,11 +33,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue index 661f68b278eeacf72c797e5cdcc9b7624344d574..a24aa9b2e9a985a04ace759085e20196923efea3 100644 --- a/packages/frontend/src/widgets/WidgetNotifications.vue +++ b/packages/frontend/src/widgets/WidgetNotifications.vue @@ -1,18 +1,18 @@ <template> -<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true" data-cy-mkw-notifications class="mkw-notifications"> +<MkContainer :style="`height: ${widgetProps.height}px;`" :showHeader="widgetProps.showHeader" :scrollable="true" data-cy-mkw-notifications class="mkw-notifications"> <template #icon><i class="ti ti-bell"></i></template> <template #header>{{ i18n.ts.notifications }}</template> <template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configureNotification()"><i class="ti ti-settings"></i></button></template> <div> - <XNotifications :include-types="widgetProps.includingTypes"/> + <XNotifications :includeTypes="widgetProps.includingTypes"/> </div> </MkContainer> </template> <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import XNotifications from '@/components/MkNotifications.vue'; @@ -39,12 +39,9 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); - +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); + const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, props, diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue index 44e073545d91e4e295b56496e9cff84a9da4a09c..c920c3ca5372055b0a4783db9fabd3ffdc470e97 100644 --- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue +++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue @@ -1,14 +1,16 @@ <template> -<div data-cy-mkw-onlineUsers class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }"> - <I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" text-tag="span" class="text"> - <template #n><b>{{ number(onlineUsersCount) }}</b></template> - </I18n> +<div data-cy-mkw-onlineUsers :class="[$style.root, { _panel: !widgetProps.transparent, [$style.pad]: !widgetProps.transparent }]"> + <span :class="$style.text"> + <I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" textTag="span"> + <template #n><b style="color: #41b781;">{{ number(onlineUsersCount) }}</b></template> + </I18n> + </span> </div> </template> <script lang="ts" setup> import { ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; @@ -26,11 +28,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -58,22 +57,16 @@ defineExpose<WidgetComponentExpose>({ }); </script> -<style lang="scss" scoped> -.mkw-onlineUsers { +<style lang="scss" module> +.root { text-align: center; &.pad { padding: 16px 0; } +} - > .text { - ::v-deep(b) { - color: #41b781; - } - - ::v-deep(span) { - opacity: 0.7; - } - } +.text { + color: var(--fgTransparentWeak); } </style> diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue index 716bbb42749139c475bd9996a8384c91db0276e2..5c6a8cbf83741b867e7e6adb9d39c746a7c3c336 100644 --- a/packages/frontend/src/widgets/WidgetPhotos.vue +++ b/packages/frontend/src/widgets/WidgetPhotos.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" data-cy-mkw-photos class="mkw-photos"> +<MkContainer :showHeader="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" data-cy-mkw-photos class="mkw-photos"> <template #icon><i class="ti ti-camera"></i></template> <template #header>{{ i18n.ts._widgets.photos }}</template> @@ -18,9 +18,9 @@ <script lang="ts" setup> import { onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { getStaticImageUrl } from '@/scripts/media-proxy'; import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; @@ -42,11 +42,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -54,7 +51,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, emit, ); -const connection = stream.useChannel('main'); +const connection = useStream().useChannel('main'); const images = ref([]); const fetching = ref(true); diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue index 7a96b00217415c17ade33bc8e25f35aecc1cb93e..bc63f028214fdb732800783a382d68619206bb6c 100644 --- a/packages/frontend/src/widgets/WidgetPostForm.vue +++ b/packages/frontend/src/widgets/WidgetPostForm.vue @@ -4,7 +4,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkPostForm from '@/components/MkPostForm.vue'; @@ -15,11 +15,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue index 819663a3662e36a7fddd4bcd29a435dc88765841..72e229ef8f5cf18b732ab45e2a3c72c79f8c5789 100644 --- a/packages/frontend/src/widgets/WidgetProfile.vue +++ b/packages/frontend/src/widgets/WidgetProfile.vue @@ -17,7 +17,7 @@ </template> <script lang="ts" setup> -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { $i } from '@/account'; import { userPage } from '@/filters/user'; @@ -29,11 +29,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index 18fa2e2c22b9d67127ad3419e653b291f2b9ec02..1be882c66dfac5a6573a288a1b79f65811bab0cc 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-rss class="mkw-rss"> +<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-rss class="mkw-rss"> <template #icon><i class="ti ti-rss"></i></template> <template #header>RSS</template> <template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configure"><i class="ti ti-settings"></i></button></template> @@ -19,7 +19,7 @@ <script lang="ts" setup> import { ref, watch, computed } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import { url as base } from '@/config'; @@ -49,11 +49,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index b0408f0d7f76365a00c7564d2f5a40c5067ebaaf..6b346c05981eb7e58ce0f68b6e12d97ba42f8784 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="widgetProps.showHeader" class="mkw-rss-ticker"> +<MkContainer :naked="widgetProps.transparent" :showHeader="widgetProps.showHeader" class="mkw-rss-ticker"> <template #icon><i class="ti ti-rss"></i></template> <template #header>RSS</template> <template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configure"><i class="ti ti-settings"></i></button></template> @@ -12,7 +12,7 @@ <Transition :name="$style.change" mode="default" appear> <MarqueeText :key="key" :duration="widgetProps.duration" :reverse="widgetProps.reverse"> <span v-for="item in items" :key="item.link" :class="$style.item"> - <a :class="$style.link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span :class="$style.divider"></span> + <a :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span :class="$style.divider"></span> </span> </MarqueeText> </Transition> @@ -23,7 +23,7 @@ <script lang="ts" setup> import { ref, watch, computed } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MarqueeText from '@/components/MkMarquee.vue'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; @@ -73,11 +73,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue index 915e7aaaf436131c2cef470ea8545da6bfd76f7d..d4ede57926d162f0295425eab57ada68ab9607d2 100644 --- a/packages/frontend/src/widgets/WidgetSlideshow.vue +++ b/packages/frontend/src/widgets/WidgetSlideshow.vue @@ -13,7 +13,7 @@ <script lang="ts" setup> import { onMounted, ref, shallowRef } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; @@ -35,11 +35,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index 71ee75f6cb6507aae29157db9b1f3fa859324b5c..3d497c2e237773126fc3e7647bee536571694b44 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" data-cy-mkw-timeline class="mkw-timeline"> +<MkContainer :showHeader="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" data-cy-mkw-timeline class="mkw-timeline"> <template #icon> <i v-if="widgetProps.src === 'home'" class="ti ti-home"></i> <i v-else-if="widgetProps.src === 'local'" class="ti ti-planet"></i> @@ -30,7 +30,7 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; @@ -71,11 +71,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index 01450a7ab5bc3a4a4f26ece4782c925c95490f52..36f908d5ea280323879ad60b63a284e2869f20c5 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-trends class="mkw-trends"> +<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-trends class="mkw-trends"> <template #icon><i class="ti ti-hash"></i></template> <template #header>{{ i18n.ts._widgets.trends }}</template> @@ -20,7 +20,7 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; @@ -40,11 +40,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue index 22162d2b2c920b139cb9a4632ee8082560a86f90..f1af71adda49010471a06940dcaa5f75481032ce 100644 --- a/packages/frontend/src/widgets/WidgetUnixClock.vue +++ b/packages/frontend/src/widgets/WidgetUnixClock.vue @@ -12,7 +12,7 @@ <script lang="ts" setup> import { onUnmounted, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; const name = 'unixClock'; @@ -39,11 +39,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue index b8811d2fed4202ed57fd012def800e5e6776ea12..4380fdb62fd7d663052402f8ab82f51efbb750ab 100644 --- a/packages/frontend/src/widgets/WidgetUserList.vue +++ b/packages/frontend/src/widgets/WidgetUserList.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" class="mkw-userList"> +<MkContainer :showHeader="widgetProps.showHeader" class="mkw-userList"> <template #icon><i class="ti ti-users"></i></template> <template #header>{{ list ? list.name : i18n.ts._widgets.userList }}</template> <template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configure()"><i class="ti ti-settings"></i></button></template> @@ -19,7 +19,7 @@ </template> <script lang="ts" setup> -import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import * as os from '@/os'; @@ -43,11 +43,8 @@ const widgetPropsDef = { type WidgetProps = GetFormResultType<typeof widgetPropsDef>; -// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ -//const props = defineProps<WidgetComponentProps<WidgetProps>>(); -//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); -const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); +const props = defineProps<WidgetComponentProps<WidgetProps>>(); +const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue index 357d0ab78b3757e5d11d8c068a41c12fa07ace51..e019ff540b8ecf4912c1ac19ef478ac8b32d6e1e 100644 --- a/packages/frontend/src/widgets/server-metric/index.vue +++ b/packages/frontend/src/widgets/server-metric/index.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> +<MkContainer :showHeader="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #icon><i class="ti ti-server"></i></template> <template #header>{{ i18n.ts._widgets.serverMetric }}</template> <template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="toggleView()"><i class="ti ti-selector"></i></button></template> @@ -25,7 +25,7 @@ import XDisk from './disk.vue'; import MkContainer from '@/components/MkContainer.vue'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; -import { stream } from '@/stream'; +import { useStream } from '@/stream'; import { i18n } from '@/i18n'; const name = 'serverMetric'; @@ -75,7 +75,7 @@ const toggleView = () => { save(); }; -const connection = stream.useChannel('serverStats'); +const connection = useStream().useChannel('serverStats'); onUnmounted(() => { connection.dispose(); }); diff --git a/packages/frontend/src/widgets/server-metric/pie.vue b/packages/frontend/src/widgets/server-metric/pie.vue index 868dbc0484307dea66ba11c86cfc540619d0d9a6..398815a6ae3ac8e9e34d75b68840907748ab1453 100644 --- a/packages/frontend/src/widgets/server-metric/pie.vue +++ b/packages/frontend/src/widgets/server-metric/pie.vue @@ -1,11 +1,12 @@ <template> -<svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none"> +<svg :class="$style.root" viewBox="0 0 1 1" preserveAspectRatio="none"> <circle :r="r" cx="50%" cy="50%" fill="none" stroke-width="0.1" stroke="rgba(0, 0, 0, 0.05)" + :class="$style.circle" /> <circle :r="r" @@ -16,7 +17,7 @@ stroke-width="0.1" :stroke="color" /> - <text x="50%" y="50%" dy="0.05" text-anchor="middle">{{ (value * 100).toFixed(0) }}%</text> + <text x="50%" y="50%" dy="0.05" text-anchor="middle" :class="$style.text">{{ (value * 100).toFixed(0) }}%</text> </svg> </template> @@ -33,20 +34,20 @@ const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`); const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2))); </script> -<style lang="scss" scoped> -.hsalcinq { +<style lang="scss" module> +.root { display: block; height: 100%; +} - > circle { - transform-origin: center; - transform: rotate(-90deg); - transition: stroke-dashoffset 0.5s ease; - } +.circle { + transform-origin: center; + transform: rotate(-90deg); + transition: stroke-dashoffset 0.5s ease; +} - > text { - font-size: 0.15px; - fill: currentColor; - } +.text { + font-size: 0.15px; + fill: currentColor; } </style> diff --git a/packages/frontend/src/workers/draw-blurhash.ts b/packages/frontend/src/workers/draw-blurhash.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f2168a44a30fbc711e569acc571b184c99f0b03 --- /dev/null +++ b/packages/frontend/src/workers/draw-blurhash.ts @@ -0,0 +1,15 @@ +import { render } from 'buraha'; + +onmessage = (event) => { + // console.log(event.data); + if (!('id' in event.data && typeof event.data.id === 'string')) { + return; + } + if (!('hash' in event.data && typeof event.data.hash === 'string')) { + return; + } + const work = new OffscreenCanvas(event.data.width ?? 64, event.data.height ?? 64); + render(event.data.hash, work); + const bitmap = work.transferToImageBitmap(); + postMessage({ id: event.data.id, bitmap }); +}; diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts new file mode 100644 index 0000000000000000000000000000000000000000..4769524d9c81184b73de71e14b6891d20417fbe2 --- /dev/null +++ b/packages/frontend/src/workers/test-webgl2.ts @@ -0,0 +1,7 @@ +const canvas = new OffscreenCanvas(1, 1); +const gl = canvas.getContext('webgl2'); +if (gl) { + postMessage({ result: true }); +} else { + postMessage({ result: false }); +} diff --git a/packages/frontend/src/workers/tsconfig.json b/packages/frontend/src/workers/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..8ee893046590f2c9aba8e4cbb8a51a722bdfa6b6 --- /dev/null +++ b/packages/frontend/src/workers/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["esnext", "webworker"], + } +} diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index fad0dd0177a48088b8ec1562cd8afe636a751edb..531dd0b488c516c4b9802deac8d9e2758d411856 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -6,7 +6,9 @@ import { type UserConfig, defineConfig } from 'vite'; import ReactivityTransform from '@vue-macros/reactivity-transform/vite'; import locales from '../../locales'; +import generateDTS from '../../locales/generateDTS'; import meta from '../../package.json'; +import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name'; import pluginJson5 from './vite.json5'; const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; @@ -53,6 +55,7 @@ export function getConfig(): UserConfig { reactivityTransform: true, }), ReactivityTransform(), + pluginUnwindCssModuleClassName(), pluginJson5(), ...process.env.NODE_ENV === 'production' ? [ @@ -64,6 +67,10 @@ export function getConfig(): UserConfig { }), ] : [], + { + name: 'locale:generateDTS', + buildStart: generateDTS, + }, ], resolve: { @@ -117,13 +124,15 @@ export function getConfig(): UserConfig { manifest: 'manifest.json', rollupOptions: { input: { - app: './src/init.ts', + app: './src/_boot_.ts', }, output: { manualChunks: { vue: ['vue'], photoswipe: ['photoswipe', 'photoswipe/lightbox', 'photoswipe/style.css'], }, + chunkFileNames: process.env.NODE_ENV === 'production' ? '[hash:8].js' : '[name]-[hash:8].js', + assetFileNames: process.env.NODE_ENV === 'production' ? '[hash:8][extname]' : '[name]-[hash:8][extname]', }, }, cssCodeSplit: true, @@ -139,6 +148,10 @@ export function getConfig(): UserConfig { }, }, + worker: { + format: 'es', + }, + test: { environment: 'happy-dom', deps: { diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js index 7c979a93dcd3b4988fdffb78540e942fe24dd4ff..a53ad178943dcc6b90b17a71454c626b7db499ed 100644 --- a/packages/shared/.eslintrc.js +++ b/packages/shared/.eslintrc.js @@ -49,7 +49,7 @@ module.exports = { 'no-multi-spaces': ['error'], 'no-var': ['error'], 'prefer-arrow-callback': ['error'], - 'no-throw-literal': ['warn'], + 'no-throw-literal': ['error'], 'no-param-reassign': ['warn'], 'no-constant-condition': ['warn'], 'no-empty-pattern': ['warn'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74a7e89857cb9c0e2313e7fed132363677b3400b..f48fac3f479cd362866b09b72ee01b5d7eae2885 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,8 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false overrides: chokidar: 3.5.3 @@ -30,8 +34,8 @@ importers: specifier: 4.1.0 version: 4.1.0 typescript: - specifier: 5.0.4 - version: 5.0.4 + specifier: 5.1.3 + version: 5.1.3 optionalDependencies: '@tensorflow/tfjs-core': specifier: 4.4.0 @@ -44,20 +48,20 @@ importers: specifier: 2.0.1 version: 2.0.1 '@typescript-eslint/eslint-plugin': - specifier: 5.59.5 - version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4) + specifier: 5.59.8 + version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3) '@typescript-eslint/parser': - specifier: 5.59.5 - version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) + specifier: 5.59.8 + version: 5.59.8(eslint@8.41.0)(typescript@5.1.3) cross-env: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 12.12.0 - version: 12.12.0 + specifier: 12.13.0 + version: 12.13.0 eslint: - specifier: 8.40.0 - version: 8.40.0 + specifier: 8.41.0 + version: 8.41.0 start-server-and-test: specifier: 2.0.0 version: 2.0.0 @@ -74,14 +78,14 @@ importers: specifier: 3.321.1 version: 3.321.1 '@bull-board/api': - specifier: 5.1.2 - version: 5.1.2(@bull-board/ui@5.1.2) + specifier: 5.2.0 + version: 5.2.0(@bull-board/ui@5.2.0) '@bull-board/fastify': - specifier: 5.1.2 - version: 5.1.2 + specifier: 5.2.0 + version: 5.2.0 '@bull-board/ui': - specifier: 5.1.2 - version: 5.1.2 + specifier: 5.2.0 + version: 5.2.0 '@discordapp/twemoji': specifier: 14.1.2 version: 14.1.2 @@ -92,41 +96,41 @@ importers: specifier: 8.3.0 version: 8.3.0 '@fastify/cors': - specifier: 8.2.1 - version: 8.2.1 + specifier: 8.3.0 + version: 8.3.0 '@fastify/http-proxy': specifier: 9.1.0 - version: 9.1.0 + version: 9.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) '@fastify/multipart': specifier: 7.6.0 version: 7.6.0 '@fastify/static': - specifier: 6.10.1 - version: 6.10.1 + specifier: 6.10.2 + version: 6.10.2 '@fastify/view': specifier: 7.4.1 version: 7.4.1 '@nestjs/common': - specifier: 9.4.0 - version: 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1) + specifier: 9.4.2 + version: 9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': - specifier: 9.4.0 - version: 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + specifier: 9.4.2 + version: 9.4.2(@nestjs/common@9.4.2)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/testing': - specifier: 9.4.0 - version: 9.4.0(@nestjs/common@9.4.0)(@nestjs/core@9.4.0) + specifier: 9.4.2 + version: 9.4.2(@nestjs/common@9.4.2)(@nestjs/core@9.4.2) '@peertube/http-signature': specifier: 1.7.0 version: 1.7.0 '@sinonjs/fake-timers': - specifier: 10.0.2 - version: 10.0.2 + specifier: 10.2.0 + version: 10.2.0 '@swc/cli': specifier: 0.1.62 - version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3) + version: 0.1.62(@swc/core@1.3.61)(chokidar@3.5.3) '@swc/core': - specifier: 1.3.56 - version: 1.3.56 + specifier: 1.3.61 + version: 1.3.61 accepts: specifier: 1.3.8 version: 1.3.8 @@ -145,15 +149,15 @@ importers: blurhash: specifier: 2.0.5 version: 2.0.5 - bull: - specifier: 4.10.4 - version: 4.10.4 + bullmq: + specifier: 3.15.0 + version: 3.15.0 cacheable-lookup: specifier: 6.1.0 version: 6.1.0 cbor: - specifier: 8.1.0 - version: 8.1.0 + specifier: 9.0.0 + version: 9.0.0 chalk: specifier: 5.2.0 version: 5.2.0 @@ -200,8 +204,8 @@ importers: specifier: 12.6.0 version: 12.6.0 happy-dom: - specifier: 9.16.0 - version: 9.16.0 + specifier: 9.20.3 + version: 9.20.3 hpagent: specifier: 1.2.0 version: 1.2.0 @@ -218,20 +222,20 @@ importers: specifier: 4.1.0 version: 4.1.0 jsdom: - specifier: 21.1.1 - version: 21.1.1 + specifier: 22.1.0 + version: 22.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) json5: specifier: 2.2.3 version: 2.2.3 jsonld: - specifier: 8.1.1 - version: 8.1.1 + specifier: 8.2.0 + version: 8.2.0 jsrsasign: specifier: 10.8.6 version: 10.8.6 meilisearch: - specifier: 0.32.3 - version: 0.32.3 + specifier: 0.32.5 + version: 0.32.5 mfm-js: specifier: 0.23.3 version: 0.23.3 @@ -251,8 +255,8 @@ importers: specifier: 3.3.1 version: 3.3.1 nodemailer: - specifier: 6.9.2 - version: 6.9.2 + specifier: 6.9.3 + version: 6.9.3 nsfwjs: specifier: 2.4.2 version: 2.4.2(@tensorflow/tfjs@4.4.0) @@ -269,8 +273,8 @@ importers: specifier: 7.1.2 version: 7.1.2 pg: - specifier: 8.10.0 - version: 8.10.0 + specifier: 8.11.0 + version: 8.11.0 private-ip: specifier: 3.0.0 version: 3.0.0 @@ -299,8 +303,8 @@ importers: specifier: 3.4.1 version: 3.4.1 re2: - specifier: 1.18.0 - version: 1.18.0 + specifier: 1.19.0 + version: 1.19.0 redis-lock: specifier: 0.1.4 version: 0.1.4 @@ -329,8 +333,8 @@ importers: specifier: 3.0.5 version: 3.0.5 semver: - specifier: 7.5.0 - version: 7.5.0 + specifier: 7.5.1 + version: 7.5.1 sharp: specifier: 0.32.1 version: 0.32.1 @@ -338,8 +342,8 @@ importers: specifier: github:misskey-dev/sharp-read-bmp version: github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01 slacc: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 strict-event-emitter-types: specifier: 2.0.0 version: 2.0.0 @@ -350,8 +354,8 @@ importers: specifier: github:misskey-dev/summaly version: github.com/misskey-dev/summaly/77dd5654bb82280b38c1f50e51a771c33f3df503 systeminformation: - specifier: 5.17.12 - version: 5.17.12 + specifier: 5.17.16 + version: 5.17.16 tinycolor2: specifier: 1.6.0 version: 1.6.0 @@ -369,16 +373,16 @@ importers: version: 14.0.0 typeorm: specifier: 0.3.16 - version: 0.3.16(ioredis@5.3.2)(pg@8.10.0) + version: 0.3.16(ioredis@5.3.2)(pg@8.11.0) typescript: - specifier: 5.0.4 - version: 5.0.4 + specifier: 5.1.3 + version: 5.1.3 ulid: specifier: 2.3.0 version: 2.3.0 unzipper: - specifier: 0.10.11 - version: 0.10.11 + specifier: 0.10.14 + version: 0.10.14 uuid: specifier: 9.0.0 version: 9.0.0 @@ -388,12 +392,9 @@ importers: web-push: specifier: 3.6.1 version: 3.6.1 - websocket: - specifier: 1.0.34 - version: 1.0.34 ws: specifier: 8.13.0 - version: 8.13.0 + version: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) xev: specifier: 3.0.2 version: 3.0.2 @@ -437,46 +438,55 @@ importers: '@tensorflow/tfjs-node': specifier: 4.4.0 version: 4.4.0(seedrandom@3.0.5) + bufferutil: + specifier: ^4.0.7 + version: 4.0.7 slacc-android-arm-eabi: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-android-arm64: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-darwin-arm64: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-darwin-universal: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-darwin-x64: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 + slacc-freebsd-x64: + specifier: 0.0.9 + version: 0.0.9 slacc-linux-arm-gnueabihf: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-linux-arm64-gnu: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-linux-arm64-musl: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-linux-x64-gnu: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-win32-arm64-msvc: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 slacc-win32-x64-msvc: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.9 + version: 0.0.9 + utf-8-validate: + specifier: ^6.0.3 + version: 6.0.3 devDependencies: '@jest/globals': specifier: 29.5.0 version: 29.5.0 '@swc/jest': specifier: 0.2.26 - version: 0.2.26(@swc/core@1.3.56) + version: 0.2.26(@swc/core@1.3.61) '@types/accepts': specifier: 1.3.5 version: 1.3.5 @@ -486,9 +496,6 @@ importers: '@types/bcryptjs': specifier: 2.4.2 version: 2.4.2 - '@types/bull': - specifier: 4.10.0 - version: 4.10.0 '@types/cbor': specifier: 6.0.0 version: 6.0.0 @@ -505,8 +512,8 @@ importers: specifier: 2.1.21 version: 2.1.21 '@types/jest': - specifier: 29.5.1 - version: 29.5.1 + specifier: 29.5.2 + version: 29.5.2 '@types/js-yaml': specifier: 4.0.5 version: 4.0.5 @@ -523,20 +530,20 @@ importers: specifier: 2.1.1 version: 2.1.1 '@types/node': - specifier: 20.1.3 - version: 20.1.3 + specifier: 20.2.5 + version: 20.2.5 '@types/node-fetch': specifier: 3.0.3 version: 3.0.3 '@types/nodemailer': - specifier: 6.4.7 - version: 6.4.7 + specifier: 6.4.8 + version: 6.4.8 '@types/oauth': specifier: 0.9.1 version: 0.9.1 '@types/pg': - specifier: 8.6.6 - version: 8.6.6 + specifier: 8.10.1 + version: 8.10.1 '@types/pug': specifier: 2.0.6 version: 2.0.6 @@ -577,8 +584,8 @@ importers: specifier: 0.2.3 version: 0.2.3 '@types/unzipper': - specifier: 0.10.5 - version: 0.10.5 + specifier: 0.10.6 + version: 0.10.6 '@types/uuid': specifier: 9.0.1 version: 9.0.1 @@ -595,11 +602,11 @@ importers: specifier: 8.5.4 version: 8.5.4 '@typescript-eslint/eslint-plugin': - specifier: 5.59.5 - version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4) + specifier: 5.59.8 + version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3) '@typescript-eslint/parser': - specifier: 5.59.5 - version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) + specifier: 5.59.8 + version: 5.59.8(eslint@8.41.0)(typescript@5.1.3) aws-sdk-client-mock: specifier: 2.1.1 version: 2.1.1 @@ -607,17 +614,17 @@ importers: specifier: 7.0.3 version: 7.0.3 eslint: - specifier: 8.40.0 - version: 8.40.0 + specifier: 8.41.0 + version: 8.41.0 eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0) + version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0) execa: specifier: 6.1.0 version: 6.1.0 jest: specifier: 29.5.0 - version: 29.5.0(@types/node@20.1.3) + version: 29.5.0(@types/node@20.2.5) jest-mock: specifier: 29.5.0 version: 29.5.0 @@ -629,43 +636,46 @@ importers: version: 14.1.2 '@rollup/plugin-alias': specifier: 5.0.0 - version: 5.0.0(rollup@3.21.6) + version: 5.0.0(rollup@3.23.0) '@rollup/plugin-json': specifier: 6.0.0 - version: 6.0.0(rollup@3.21.6) + version: 6.0.0(rollup@3.23.0) '@rollup/plugin-replace': specifier: 5.0.2 - version: 5.0.2(rollup@3.21.6) + version: 5.0.2(rollup@3.23.0) '@rollup/pluginutils': specifier: 5.0.2 - version: 5.0.2(rollup@3.21.6) + version: 5.0.2(rollup@3.23.0) '@syuilo/aiscript': - specifier: 0.13.2 - version: 0.13.2 + specifier: 0.13.3 + version: 0.13.3 '@tabler/icons-webfont': - specifier: 2.17.0 - version: 2.17.0 + specifier: 2.21.0 + version: 2.21.0 '@vitejs/plugin-vue': - specifier: 4.2.2 - version: 4.2.2(vite@4.3.5)(vue@3.3.1) + specifier: 4.2.3 + version: 4.2.3(vite@4.3.9)(vue@3.3.4) '@vue-macros/reactivity-transform': - specifier: 0.3.6 - version: 0.3.6(rollup@3.21.6)(vue@3.3.1) + specifier: 0.3.9 + version: 0.3.9(rollup@3.23.0)(vue@3.3.4) '@vue/compiler-sfc': - specifier: 3.3.1 - version: 3.3.1 + specifier: 3.3.4 + version: 3.3.4 + astring: + specifier: 1.8.6 + version: 1.8.6 autosize: - specifier: 5.0.2 - version: 5.0.2 - blurhash: - specifier: 2.0.5 - version: 2.0.5 + specifier: 6.0.1 + version: 6.0.1 broadcast-channel: - specifier: 4.20.2 - version: 4.20.2 + specifier: 5.1.0 + version: 5.1.0 browser-image-resizer: specifier: github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3 version: github.com/misskey-dev/browser-image-resizer/0227e860621e55cbed0aabe6dc601096a7748c4a + buraha: + specifier: github:misskey-dev/buraha + version: github.com/misskey-dev/buraha/92b20c1ab15c5cb5a224cf3b1ecd4f6baca12b7c canvas-confetti: specifier: 1.6.0 version: 1.6.0 @@ -685,8 +695,8 @@ importers: specifier: 2.0.1 version: 2.0.1(chart.js@4.3.0) chromatic: - specifier: 6.17.4 - version: 6.17.4 + specifier: 6.18.0 + version: 6.18.0 compare-versions: specifier: 5.0.3 version: 5.0.3 @@ -699,6 +709,9 @@ importers: escape-regexp: specifier: 0.0.1 version: 0.0.1 + estree-walker: + specifier: ^3.0.3 + version: 3.0.3 eventemitter3: specifier: 5.0.1 version: 5.0.1 @@ -742,8 +755,8 @@ importers: specifier: 1.0.0 version: 1.0.0 rollup: - specifier: 3.21.6 - version: 3.21.6 + specifier: 3.23.0 + version: 3.23.0 s-age: specifier: 1.1.2 version: 1.1.2 @@ -766,8 +779,8 @@ importers: specifier: 3.1.0 version: 3.1.0 three: - specifier: 0.151.3 - version: 0.151.3 + specifier: 0.153.0 + version: 0.153.0 throttle-debounce: specifier: 5.0.0 version: 5.0.0 @@ -784,8 +797,8 @@ importers: specifier: 14.0.0 version: 14.0.0 typescript: - specifier: 5.0.4 - version: 5.0.4 + specifier: 5.1.3 + version: 5.1.3 uuid: specifier: 9.0.0 version: 9.0.0 @@ -793,81 +806,78 @@ importers: specifier: 1.8.0 version: 1.8.0 vite: - specifier: 4.3.5 - version: 4.3.5(@types/node@20.1.3)(sass@1.62.1) + specifier: 4.3.9 + version: 4.3.9(@types/node@20.2.5)(sass@1.62.1) vue: - specifier: 3.3.1 - version: 3.3.1 - vue-plyr: - specifier: 7.0.0 - version: 7.0.0 + specifier: 3.3.4 + version: 3.3.4 vue-prism-editor: specifier: 2.0.0-alpha.2 - version: 2.0.0-alpha.2(vue@3.3.1) + version: 2.0.0-alpha.2(vue@3.3.4) vuedraggable: specifier: next - version: 4.1.0(vue@3.3.1) + version: 4.1.0(vue@3.3.4) devDependencies: '@storybook/addon-actions': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-essentials': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-interactions': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-links': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-storysource': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/addons': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/blocks': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': - specifier: 7.0.10 - version: 7.0.10 + specifier: 7.0.18 + version: 7.0.18 '@storybook/jest': specifier: 0.1.0 version: 0.1.0 '@storybook/manager-api': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/preview-api': - specifier: 7.0.10 - version: 7.0.10 + specifier: 7.0.18 + version: 7.0.18 '@storybook/react': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3) '@storybook/react-vite': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9) '@storybook/testing-library': specifier: 0.1.0 version: 0.1.0 '@storybook/theming': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0) '@storybook/types': - specifier: 7.0.10 - version: 7.0.10 + specifier: 7.0.18 + version: 7.0.18 '@storybook/vue3': - specifier: 7.0.10 - version: 7.0.10(vue@3.3.1) + specifier: 7.0.18 + version: 7.0.18(vue@3.3.4) '@storybook/vue3-vite': - specifier: 7.0.10 - version: 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5)(vue@3.3.1) + specifier: 7.0.18 + version: 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9)(vue@3.3.4) '@testing-library/jest-dom': specifier: 5.16.5 version: 5.16.5 '@testing-library/vue': specifier: 7.0.0 - version: 7.0.0(@vue/compiler-sfc@3.3.1)(vue@3.3.1) + version: 7.0.0(@vue/compiler-sfc@3.3.4)(vue@3.3.4) '@types/escape-regexp': specifier: 0.0.1 version: 0.0.1 @@ -881,14 +891,14 @@ importers: specifier: 2.0.2 version: 2.0.2 '@types/matter-js': - specifier: 0.18.3 - version: 0.18.3 + specifier: 0.18.5 + version: 0.18.5 '@types/micromatch': specifier: 4.0.2 version: 4.0.2 '@types/node': - specifier: 20.1.3 - version: 20.1.3 + specifier: 20.2.5 + version: 20.2.5 '@types/punycode': specifier: 2.1.0 version: 2.1.0 @@ -899,8 +909,8 @@ importers: specifier: 3.0.5 version: 3.0.5 '@types/testing-library__jest-dom': - specifier: ^5.14.5 - version: 5.14.5 + specifier: ^5.14.6 + version: 5.14.6 '@types/throttle-debounce': specifier: 5.0.0 version: 5.0.0 @@ -917,20 +927,20 @@ importers: specifier: 8.5.4 version: 8.5.4 '@typescript-eslint/eslint-plugin': - specifier: 5.59.5 - version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4) + specifier: 5.59.8 + version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3) '@typescript-eslint/parser': - specifier: 5.59.5 - version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) + specifier: 5.59.8 + version: 5.59.8(eslint@8.41.0)(typescript@5.1.3) '@vitest/coverage-c8': - specifier: 0.31.0 - version: 0.31.0(vitest@0.31.0) + specifier: 0.31.4 + version: 0.31.4(vitest@0.31.4) '@vue/runtime-core': - specifier: 3.3.1 - version: 3.3.1 - astring: - specifier: 1.8.4 - version: 1.8.4 + specifier: 3.3.4 + version: 3.3.4 + acorn: + specifier: ^8.8.2 + version: 8.8.2 chokidar-cli: specifier: 3.0.0 version: 3.0.0 @@ -938,29 +948,29 @@ importers: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 12.12.0 - version: 12.12.0 + specifier: 12.13.0 + version: 12.13.0 eslint: - specifier: 8.40.0 - version: 8.40.0 + specifier: 8.41.0 + version: 8.41.0 eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0) + version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0) eslint-plugin-vue: - specifier: 9.12.0 - version: 9.12.0(eslint@8.40.0) + specifier: 9.14.1 + version: 9.14.1(eslint@8.41.0) fast-glob: specifier: 3.2.12 version: 3.2.12 happy-dom: - specifier: 9.16.0 - version: 9.16.0 + specifier: 9.20.3 + version: 9.20.3 micromatch: specifier: 3.1.10 version: 3.1.10 msw: specifier: 1.2.1 - version: 1.2.1(typescript@5.0.4) + version: 1.2.1(typescript@5.1.3) msw-storybook-addon: specifier: 1.8.0 version: 1.8.0(msw@1.2.1) @@ -977,11 +987,11 @@ importers: specifier: 2.0.0 version: 2.0.0 storybook: - specifier: 7.0.10 - version: 7.0.10 + specifier: 7.0.18 + version: 7.0.18 storybook-addon-misskey-theme: specifier: github:misskey-dev/storybook-addon-misskey-theme - version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.10)(@storybook/components@7.0.10)(@storybook/core-events@7.0.10)(@storybook/manager-api@7.0.10)(@storybook/preview-api@7.0.10)(@storybook/theming@7.0.10)(@storybook/types@7.0.10)(react-dom@18.2.0)(react@18.2.0) + version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.18)(@storybook/components@7.0.18)(@storybook/core-events@7.0.18)(@storybook/manager-api@7.0.18)(@storybook/preview-api@7.0.18)(@storybook/theming@7.0.18)(@storybook/types@7.0.18)(react-dom@18.2.0)(react@18.2.0) summaly: specifier: github:misskey-dev/summaly version: github.com/misskey-dev/summaly/77dd5654bb82280b38c1f50e51a771c33f3df503 @@ -989,23 +999,23 @@ importers: specifier: 1.0.2 version: 1.0.2 vitest: - specifier: 0.31.0 - version: 0.31.0(happy-dom@9.16.0)(sass@1.62.1) + specifier: 0.31.4 + version: 0.31.4(happy-dom@9.20.3)(sass@1.62.1) vitest-fetch-mock: specifier: 0.2.2 - version: 0.2.2(vitest@0.31.0) + version: 0.2.2(vitest@0.31.4) vue-eslint-parser: - specifier: 9.2.1 - version: 9.2.1(eslint@8.40.0) + specifier: 9.3.0 + version: 9.3.0(eslint@8.41.0) vue-tsc: - specifier: 1.6.4 - version: 1.6.4(typescript@5.0.4) + specifier: 1.6.5 + version: 1.6.5(typescript@5.1.3) packages/misskey-js: dependencies: '@swc/cli': specifier: 0.1.62 - version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3) + version: 0.1.62(@swc/core@1.3.61)(chokidar@3.5.3) '@swc/core': specifier: 1.3.56 version: 1.3.56 @@ -1090,11 +1100,11 @@ packages: resolution: {integrity: sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==} dev: true - /@ampproject/remapping@2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.17 dev: true @@ -1185,13 +1195,13 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/chunked-blob-reader@3.310.0: resolution: {integrity: sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/client-s3@3.321.1: @@ -1292,7 +1302,7 @@ packages: '@aws-sdk/util-user-agent-browser': 3.310.0 '@aws-sdk/util-user-agent-node': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1332,7 +1342,7 @@ packages: '@aws-sdk/util-user-agent-browser': 3.310.0 '@aws-sdk/util-user-agent-node': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1376,7 +1386,7 @@ packages: '@aws-sdk/util-user-agent-node': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 fast-xml-parser: 4.1.2 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1388,7 +1398,7 @@ packages: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-config-provider': 3.310.0 '@aws-sdk/util-middleware': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/credential-provider-env@3.310.0: @@ -1397,7 +1407,7 @@ packages: dependencies: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/credential-provider-imds@3.310.0: @@ -1408,7 +1418,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/url-parser': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/credential-provider-ini@3.321.1: @@ -1423,7 +1433,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/shared-ini-file-loader': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1441,7 +1451,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/shared-ini-file-loader': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1453,7 +1463,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/shared-ini-file-loader': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/credential-provider-sso@3.321.1: @@ -1465,7 +1475,7 @@ packages: '@aws-sdk/shared-ini-file-loader': 3.310.0 '@aws-sdk/token-providers': 3.321.1 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1476,7 +1486,7 @@ packages: dependencies: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/eventstream-codec@3.310.0: @@ -1485,7 +1495,7 @@ packages: '@aws-crypto/crc32': 3.0.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-hex-encoding': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/eventstream-serde-browser@3.310.0: @@ -1494,7 +1504,7 @@ packages: dependencies: '@aws-sdk/eventstream-serde-universal': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/eventstream-serde-config-resolver@3.310.0: @@ -1502,7 +1512,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/eventstream-serde-node@3.310.0: @@ -1511,7 +1521,7 @@ packages: dependencies: '@aws-sdk/eventstream-serde-universal': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/eventstream-serde-universal@3.310.0: @@ -1520,7 +1530,7 @@ packages: dependencies: '@aws-sdk/eventstream-codec': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/fetch-http-handler@3.310.0: @@ -1530,7 +1540,7 @@ packages: '@aws-sdk/querystring-builder': 3.310.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-base64': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/hash-blob-browser@3.310.0: @@ -1538,7 +1548,7 @@ packages: dependencies: '@aws-sdk/chunked-blob-reader': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/hash-node@3.310.0: @@ -1548,7 +1558,7 @@ packages: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-buffer-from': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/hash-stream-node@3.310.0: @@ -1557,21 +1567,21 @@ packages: dependencies: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/invalid-dependency@3.310.0: resolution: {integrity: sha512-1s5RG5rSPXoa/aZ/Kqr5U/7lqpx+Ry81GprQ2bxWqJvWQIJ0IRUwo5pk8XFxbKVr/2a+4lZT/c3OGoBOM1yRRA==} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/is-array-buffer@3.310.0: resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/lib-storage@3.321.1(@aws-sdk/abort-controller@3.310.0)(@aws-sdk/client-s3@3.321.1): @@ -1596,7 +1606,7 @@ packages: dependencies: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-bucket-endpoint@3.310.0: @@ -1607,7 +1617,7 @@ packages: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-arn-parser': 3.310.0 '@aws-sdk/util-config-provider': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-content-length@3.310.0: @@ -1616,7 +1626,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-endpoint@3.310.0: @@ -1627,7 +1637,7 @@ packages: '@aws-sdk/types': 3.310.0 '@aws-sdk/url-parser': 3.310.0 '@aws-sdk/util-middleware': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-expect-continue@3.310.0: @@ -1636,7 +1646,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-flexible-checksums@3.310.0: @@ -1649,7 +1659,7 @@ packages: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-host-header@3.310.0: @@ -1658,7 +1668,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-location-constraint@3.310.0: @@ -1666,7 +1676,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-logger@3.310.0: @@ -1674,7 +1684,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-recursion-detection@3.310.0: @@ -1683,7 +1693,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-retry@3.310.0: @@ -1695,7 +1705,7 @@ packages: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-middleware': 3.310.0 '@aws-sdk/util-retry': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 uuid: 8.3.2 dev: false @@ -1706,7 +1716,7 @@ packages: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-arn-parser': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-sdk-sts@3.310.0: @@ -1715,7 +1725,7 @@ packages: dependencies: '@aws-sdk/middleware-signing': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-serde@3.310.0: @@ -1723,7 +1733,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-signing@3.310.0: @@ -1735,7 +1745,7 @@ packages: '@aws-sdk/signature-v4': 3.310.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-middleware': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-ssec@3.310.0: @@ -1743,14 +1753,14 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-stack@3.310.0: resolution: {integrity: sha512-010O1PD+UAcZVKRvqEusE1KJqN96wwrf6QsqbRM0ywsKQ21NDweaHvEDlds2VHpgmofxkRLRu/IDrlPkKRQrRg==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/middleware-user-agent@3.319.0: @@ -1760,7 +1770,7 @@ packages: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-endpoints': 3.319.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/node-config-provider@3.310.0: @@ -1770,7 +1780,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/shared-ini-file-loader': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/node-http-handler@3.321.1: @@ -1789,7 +1799,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/protocol-http@3.310.0: @@ -1797,7 +1807,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/querystring-builder@3.310.0: @@ -1806,7 +1816,7 @@ packages: dependencies: '@aws-sdk/types': 3.310.0 '@aws-sdk/util-uri-escape': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/querystring-parser@3.310.0: @@ -1814,7 +1824,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/service-error-classification@3.310.0: @@ -1827,7 +1837,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/signature-v4-multi-region@3.310.0: @@ -1842,7 +1852,7 @@ packages: '@aws-sdk/protocol-http': 3.310.0 '@aws-sdk/signature-v4': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/signature-v4@3.310.0: @@ -1855,7 +1865,7 @@ packages: '@aws-sdk/util-middleware': 3.310.0 '@aws-sdk/util-uri-escape': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/smithy-client@3.316.0: @@ -1864,7 +1874,7 @@ packages: dependencies: '@aws-sdk/middleware-stack': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/token-providers@3.321.1: @@ -1875,7 +1885,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/shared-ini-file-loader': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - aws-crt dev: false @@ -1884,7 +1894,7 @@ packages: resolution: {integrity: sha512-j8eamQJ7YcIhw7fneUfs8LYl3t01k4uHi4ZDmNRgtbmbmTTG3FZc2MotStZnp3nZB6vLiPF1o5aoJxWVvkzS6A==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/url-parser@3.310.0: @@ -1892,14 +1902,14 @@ packages: dependencies: '@aws-sdk/querystring-parser': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-arn-parser@3.310.0: resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-base64@3.310.0: @@ -1907,20 +1917,20 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/util-buffer-from': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-body-length-browser@3.310.0: resolution: {integrity: sha512-sxsC3lPBGfpHtNTUoGXMQXLwjmR0zVpx0rSvzTPAuoVILVsp5AU/w5FphNPxD5OVIjNbZv9KsKTuvNTiZjDp9g==} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-body-length-node@3.310.0: resolution: {integrity: sha512-2tqGXdyKhyA6w4zz7UPoS8Ip+7sayOg9BwHNidiGm2ikbDxm1YrCfYXvCBdwaJxa4hJfRVz+aL9e+d3GqPI9pQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-buffer-from@3.310.0: @@ -1928,14 +1938,14 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/is-array-buffer': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-config-provider@3.310.0: resolution: {integrity: sha512-xIBaYo8dwiojCw8vnUcIL4Z5tyfb1v3yjqyJKJWV/dqKUFOOS0U591plmXbM+M/QkXyML3ypon1f8+BoaDExrg==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-defaults-mode-browser@3.316.0: @@ -1945,7 +1955,7 @@ packages: '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/types': 3.310.0 bowser: 2.11.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-defaults-mode-node@3.316.0: @@ -1957,7 +1967,7 @@ packages: '@aws-sdk/node-config-provider': 3.310.0 '@aws-sdk/property-provider': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-endpoints@3.319.0: @@ -1965,28 +1975,28 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-hex-encoding@3.310.0: resolution: {integrity: sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-locate-window@3.208.0: resolution: {integrity: sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-middleware@3.310.0: resolution: {integrity: sha512-FTSUKL/eRb9X6uEZClrTe27QFXUNNp7fxYrPndZwk1hlaOP5ix+MIHBcI7pIiiY/JPfOUmPyZOu+HetlFXjWog==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-retry@3.310.0: @@ -1994,7 +2004,7 @@ packages: engines: {node: '>= 14.0.0'} dependencies: '@aws-sdk/service-error-classification': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-stream-browser@3.310.0: @@ -2005,7 +2015,7 @@ packages: '@aws-sdk/util-base64': 3.310.0 '@aws-sdk/util-hex-encoding': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-stream-node@3.321.1: @@ -2015,14 +2025,14 @@ packages: '@aws-sdk/node-http-handler': 3.321.1 '@aws-sdk/types': 3.310.0 '@aws-sdk/util-buffer-from': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-uri-escape@3.310.0: resolution: {integrity: sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-user-agent-browser@3.310.0: @@ -2030,7 +2040,7 @@ packages: dependencies: '@aws-sdk/types': 3.310.0 bowser: 2.11.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-user-agent-node@3.310.0: @@ -2044,13 +2054,13 @@ packages: dependencies: '@aws-sdk/node-config-provider': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-utf8@3.310.0: @@ -2058,7 +2068,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/util-buffer-from': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/util-waiter@3.310.0: @@ -2067,25 +2077,24 @@ packages: dependencies: '@aws-sdk/abort-controller': 3.310.0 '@aws-sdk/types': 3.310.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: false /@aws-sdk/xml-builder@3.310.0: resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false - /@babel/code-frame@7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + /@babel/code-frame@7.21.4: + resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 - dev: true - /@babel/compat-data@7.21.4: - resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==} + /@babel/compat-data@7.22.3: + resolution: {integrity: sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==} engines: {node: '>=6.9.0'} dev: true @@ -2093,16 +2102,39 @@ packages: resolution: {integrity: sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==} engines: {node: '>=6.9.0'} dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.21.3 - '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) - '@babel/helper-module-transforms': 7.21.2 - '@babel/helpers': 7.21.0 - '@babel/parser': 7.21.8 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.5 + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.22.3 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3) + '@babel/helper-module-transforms': 7.22.1 + '@babel/helpers': 7.22.3 + '@babel/parser': 7.22.4 + '@babel/template': 7.21.9 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 + convert-source-map: 1.9.0 + debug: 4.3.4(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/core@7.22.1: + resolution: {integrity: sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.22.3 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) + '@babel/helper-module-transforms': 7.22.1 + '@babel/helpers': 7.22.3 + '@babel/parser': 7.22.4 + '@babel/template': 7.21.9 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 convert-source-map: 1.9.0 debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 @@ -2116,17 +2148,25 @@ packages: resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + jsesc: 2.5.2 + + /@babel/generator@7.22.3: + resolution: {integrity: sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.4 '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 - dev: true /@babel/helper-annotate-as-pure@7.18.6: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9: @@ -2134,16 +2174,16 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true - /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.3): - resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==} + /@babel/helper-compilation-targets@7.22.1(@babel/core@7.21.3): + resolution: {integrity: sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.21.4 + '@babel/compat-data': 7.22.3 '@babel/core': 7.21.3 '@babel/helper-validator-option': 7.21.0 browserslist: 4.21.5 @@ -2151,6 +2191,20 @@ packages: semver: 6.3.0 dev: true + /@babel/helper-compilation-targets@7.22.1(@babel/core@7.22.1): + resolution: {integrity: sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.22.3 + '@babel/core': 7.22.1 + '@babel/helper-validator-option': 7.21.0 + browserslist: 4.21.5 + lru-cache: 5.1.1 + semver: 6.3.0 + dev: true + /@babel/helper-create-class-features-plugin@7.21.0(@babel/core@7.21.3): resolution: {integrity: sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==} engines: {node: '>=6.9.0'} @@ -2159,7 +2213,26 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-member-expression-to-functions': 7.21.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/helper-split-export-declaration': 7.18.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-create-class-features-plugin@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.22.1 '@babel/helper-function-name': 7.21.0 '@babel/helper-member-expression-to-functions': 7.21.0 '@babel/helper-optimise-call-expression': 7.18.6 @@ -2181,13 +2254,24 @@ packages: regexpu-core: 5.3.2 dev: true + /@babel/helper-create-regexp-features-plugin@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-annotate-as-pure': 7.18.6 + regexpu-core: 5.3.2 + dev: true + /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.21.3): resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} peerDependencies: '@babel/core': ^7.4.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3) '@babel/helper-plugin-utils': 7.20.2 debug: 4.3.4(supports-color@8.1.1) lodash.debounce: 4.0.8 @@ -2197,59 +2281,72 @@ packages: - supports-color dev: true - /@babel/helper-environment-visitor@7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} - engines: {node: '>=6.9.0'} + /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.22.1): + resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + debug: 4.3.4(supports-color@8.1.1) + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color dev: true + /@babel/helper-environment-visitor@7.22.1: + resolution: {integrity: sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==} + engines: {node: '>=6.9.0'} + /@babel/helper-explode-assignable-expression@7.18.6: resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true /@babel/helper-function-name@7.21.0: resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.21.5 - dev: true + '@babel/template': 7.21.9 + '@babel/types': 7.22.4 /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 - dev: true + '@babel/types': 7.22.4 /@babel/helper-member-expression-to-functions@7.21.0: resolution: {integrity: sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true - /@babel/helper-module-imports@7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + /@babel/helper-module-imports@7.21.4: + resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true - /@babel/helper-module-transforms@7.21.2: - resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} + /@babel/helper-module-transforms@7.22.1: + resolution: {integrity: sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.20.2 + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-module-imports': 7.21.4 + '@babel/helper-simple-access': 7.21.5 '@babel/helper-split-export-declaration': 7.18.6 '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.5 + '@babel/template': 7.21.9 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 transitivePeerDependencies: - supports-color dev: true @@ -2258,7 +2355,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true /@babel/helper-plugin-utils@7.20.2: @@ -2274,9 +2371,24 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.22.1 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-remap-async-to-generator@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-wrap-function': 7.20.5 + '@babel/types': 7.22.4 transitivePeerDependencies: - supports-color dev: true @@ -2285,40 +2397,35 @@ packages: resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.22.1 '@babel/helper-member-expression-to-functions': 7.21.0 '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.5 + '@babel/template': 7.21.9 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 transitivePeerDependencies: - supports-color dev: true - /@babel/helper-simple-access@7.20.2: - resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} + /@babel/helper-simple-access@7.21.5: + resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 dev: true /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 - dev: true - - /@babel/helper-string-parser@7.19.4: - resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} - engines: {node: '>=6.9.0'} + '@babel/types': 7.22.4 /@babel/helper-string-parser@7.21.5: resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} @@ -2338,20 +2445,20 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-function-name': 7.21.0 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.5 + '@babel/template': 7.21.9 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 transitivePeerDependencies: - supports-color dev: true - /@babel/helpers@7.21.0: - resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} + /@babel/helpers@7.22.3: + resolution: {integrity: sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.5 + '@babel/template': 7.21.9 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 transitivePeerDependencies: - supports-color dev: true @@ -2363,21 +2470,27 @@ packages: '@babel/helper-validator-identifier': 7.19.1 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true - /@babel/parser@7.21.4: - resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==} + /@babel/parser@7.21.8: + resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.22.4 - /@babel/parser@7.21.8: - resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} + /@babel/parser@7.21.9: + resolution: {integrity: sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.22.4 + + /@babel/parser@7.22.4: + resolution: {integrity: sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.22.4 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -2389,6 +2502,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==} engines: {node: '>=6.9.0'} @@ -2401,6 +2524,18 @@ packages: '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3) dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} engines: {node: '>=6.9.0'} @@ -2408,7 +2543,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.3) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.3) @@ -2416,6 +2551,21 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.1) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} @@ -2429,6 +2579,19 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.21.3): resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==} engines: {node: '>=6.9.0'} @@ -2443,6 +2606,20 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.1) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} engines: {node: '>=6.9.0'} @@ -2454,6 +2631,17 @@ packages: '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.21.3): resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} engines: {node: '>=6.9.0'} @@ -2465,6 +2653,17 @@ packages: '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} engines: {node: '>=6.9.0'} @@ -2476,6 +2675,17 @@ packages: '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} engines: {node: '>=6.9.0'} @@ -2487,6 +2697,17 @@ packages: '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} @@ -2498,6 +2719,17 @@ packages: '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} engines: {node: '>=6.9.0'} @@ -2509,20 +2741,45 @@ packages: '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.21.4 + '@babel/compat-data': 7.22.3 '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3) '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.3) '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.3 + '@babel/core': 7.22.1 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} engines: {node: '>=6.9.0'} @@ -2534,6 +2791,17 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.21.3): resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} engines: {node: '>=6.9.0'} @@ -2546,6 +2814,18 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.3) dev: true + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1) + dev: true + /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} @@ -2559,6 +2839,19 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.0(@babel/core@7.21.3): resolution: {integrity: sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==} engines: {node: '>=6.9.0'} @@ -2574,6 +2867,21 @@ packages: - supports-color dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.1) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} engines: {node: '>=4'} @@ -2585,6 +2893,17 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.3): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -2594,6 +2913,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.1): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: @@ -2603,6 +2931,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.3): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: @@ -2612,6 +2949,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.1): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.21.3): resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} @@ -2622,6 +2968,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.22.1): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: @@ -2631,6 +2987,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} peerDependencies: @@ -2640,13 +3005,22 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.21.3): + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.22.1): resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -2660,6 +3034,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-import-assertions@7.20.0(@babel/core@7.22.1): + resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.3): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -2669,6 +3053,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.1): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: @@ -2678,13 +3071,22 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.21.3): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.22.1): resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -2697,6 +3099,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.1): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: @@ -2706,6 +3117,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.3): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: @@ -2715,6 +3135,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.1): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: @@ -2724,6 +3153,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: @@ -2733,6 +3171,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.3): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: @@ -2742,6 +3189,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.1): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.21.3): resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} @@ -2752,6 +3208,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.22.1): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.3): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -2762,13 +3228,23 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.21.3): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.1): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.22.1): resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -2782,6 +3258,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-arrow-functions@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-async-to-generator@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==} engines: {node: '>=6.9.0'} @@ -2789,87 +3275,270 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.3) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-async-to-generator@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-module-imports': 7.21.4 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.22.1) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.21.3): + resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-block-scoping@7.21.0(@babel/core@7.21.3): + resolution: {integrity: sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-block-scoping@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-classes@7.21.0(@babel/core@7.21.3): + resolution: {integrity: sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3) + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-split-export-declaration': 7.18.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-classes@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-split-export-declaration': 7.18.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.21.3): + resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/template': 7.21.9 + dev: true + + /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/template': 7.21.9 + dev: true + + /@babel/plugin-transform-destructuring@7.21.3(@babel/core@7.21.3): + resolution: {integrity: sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-destructuring@7.21.3(@babel/core@7.22.1): + resolution: {integrity: sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.21.3): + resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.21.3) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.21.3): + resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.21.3): + resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.3 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.3) - transitivePeerDependencies: - - supports-color dev: true - /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.21.3): - resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} + /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-block-scoping@7.21.0(@babel/core@7.21.3): - resolution: {integrity: sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==} + /@babel/plugin-transform-flow-strip-types@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.22.1) dev: true - /@babel/plugin-transform-classes@7.21.0(@babel/core@7.21.3): - resolution: {integrity: sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==} + /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.21.3): + resolution: {integrity: sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-replace-supers': 7.20.7 - '@babel/helper-split-export-declaration': 7.18.6 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color dev: true - /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.21.3): - resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==} + /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.22.1): + resolution: {integrity: sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 - '@babel/template': 7.20.7 dev: true - /@babel/plugin-transform-destructuring@7.21.3(@babel/core@7.21.3): - resolution: {integrity: sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==} + /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.21.3): + resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3) + '@babel/helper-function-name': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.21.3): - resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 - '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.21.3) + '@babel/core': 7.22.1 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) + '@babel/helper-function-name': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.21.3): - resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + /@babel/plugin-transform-literals@7.18.9(@babel/core@7.21.3): + resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2878,106 +3547,114 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.21.3): - resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} + /@babel/plugin-transform-literals@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-flow-strip-types@7.21.0(@babel/core@7.21.3): - resolution: {integrity: sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==} + /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.21.3): + resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.3) dev: true - /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.21.3): - resolution: {integrity: sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==} + /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.21.3): - resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} + /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.21.3): + resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) - '@babel/helper-function-name': 7.21.0 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color dev: true - /@babel/plugin-transform-literals@7.18.9(@babel/core@7.21.3): - resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} + /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.22.1): + resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color dev: true - /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.21.3): - resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} + /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.21.3): + resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-simple-access': 7.21.5 + transitivePeerDependencies: + - supports-color dev: true - /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.21.3): - resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==} + /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.22.1): + resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 - '@babel/helper-module-transforms': 7.21.2 + '@babel/core': 7.22.1 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-simple-access': 7.21.5 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.21.3): - resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==} + /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.21.3): + resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-module-transforms': 7.21.2 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-simple-access': 7.20.2 + '@babel/helper-validator-identifier': 7.19.1 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.21.3): + /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.22.1): resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-module-transforms': 7.21.2 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-validator-identifier': 7.19.1 transitivePeerDependencies: @@ -2991,7 +3668,20 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-module-transforms': 7.21.2 + '@babel/helper-module-transforms': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-module-transforms': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 transitivePeerDependencies: - supports-color @@ -3008,6 +3698,17 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.20.5(@babel/core@7.22.1): + resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} engines: {node: '>=6.9.0'} @@ -3018,6 +3719,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} engines: {node: '>=6.9.0'} @@ -3031,6 +3742,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-parameters@7.21.3(@babel/core@7.21.3): resolution: {integrity: sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==} engines: {node: '>=6.9.0'} @@ -3041,6 +3765,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-parameters@7.21.3(@babel/core@7.22.1): + resolution: {integrity: sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} engines: {node: '>=6.9.0'} @@ -3051,38 +3785,48 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.3): + /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.22.1): resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.3): + /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.22.1): resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.21.3): + /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.22.1): resolution: {integrity: sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3) - '@babel/types': 7.21.5 + '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.22.1) + '@babel/types': 7.22.4 dev: true /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.21.3): @@ -3096,6 +3840,17 @@ packages: regenerator-transform: 0.15.1 dev: true + /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.22.1): + resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + regenerator-transform: 0.15.1 + dev: true + /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} engines: {node: '>=6.9.0'} @@ -3106,6 +3861,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} engines: {node: '>=6.9.0'} @@ -3116,6 +3881,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-spread@7.20.7(@babel/core@7.21.3): resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} engines: {node: '>=6.9.0'} @@ -3127,6 +3902,17 @@ packages: '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 dev: true + /@babel/plugin-transform-spread@7.20.7(@babel/core@7.22.1): + resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + dev: true + /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} engines: {node: '>=6.9.0'} @@ -3137,6 +3923,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.21.3): resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} engines: {node: '>=6.9.0'} @@ -3147,6 +3943,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.21.3): resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} engines: {node: '>=6.9.0'} @@ -3157,17 +3963,27 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.21.3): + /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.22.1): + resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.22.1): resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.21.3) + '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1) '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.21.3) + '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.22.1) transitivePeerDependencies: - supports-color dev: true @@ -3182,6 +3998,16 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-unicode-escapes@7.18.10(@babel/core@7.22.1): + resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} engines: {node: '>=6.9.0'} @@ -3193,15 +4019,26 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true + /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.22.1): + resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + dev: true + /@babel/preset-env@7.21.4(@babel/core@7.21.3): resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.21.4 + '@babel/compat-data': 7.22.3 '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3) '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-validator-option': 7.21.0 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.21.3) @@ -3269,7 +4106,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.21.3) '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.21.3) '@babel/preset-modules': 0.1.5(@babel/core@7.21.3) - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3) babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.3) babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.3) @@ -3279,16 +4116,102 @@ packages: - supports-color dev: true - /@babel/preset-flow@7.18.6(@babel/core@7.21.3): + /@babel/preset-env@7.21.4(@babel/core@7.22.1): + resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.3 + '@babel/core': 7.22.1 + '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.21.0 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-private-property-in-object': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.1) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.1) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.1) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.22.1) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.1) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.1) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.1) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.1) + '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-transform-async-to-generator': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-block-scoping': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-transform-classes': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-transform-destructuring': 7.21.3(@babel/core@7.22.1) + '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-for-of': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-modules-amd': 7.20.11(@babel/core@7.22.1) + '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.22.1) + '@babel/plugin-transform-modules-systemjs': 7.20.11(@babel/core@7.22.1) + '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5(@babel/core@7.22.1) + '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.22.1) + '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-regenerator': 7.20.5(@babel/core@7.22.1) + '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.22.1) + '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.22.1) + '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.22.1) + '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.22.1) + '@babel/preset-modules': 0.1.5(@babel/core@7.22.1) + '@babel/types': 7.22.4 + babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.22.1) + babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.22.1) + babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.22.1) + core-js-compat: 3.29.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-flow@7.18.6(@babel/core@7.22.1): resolution: {integrity: sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-validator-option': 7.21.0 - '@babel/plugin-transform-flow-strip-types': 7.21.0(@babel/core@7.21.3) + '@babel/plugin-transform-flow-strip-types': 7.21.0(@babel/core@7.22.1) dev: true /@babel/preset-modules@0.1.5(@babel/core@7.21.3): @@ -3300,31 +4223,44 @@ packages: '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.3) '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.3) - '@babel/types': 7.21.5 + '@babel/types': 7.22.4 + esutils: 2.0.3 + dev: true + + /@babel/preset-modules@0.1.5(@babel/core@7.22.1): + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.22.1) + '@babel/types': 7.22.4 esutils: 2.0.3 dev: true - /@babel/preset-typescript@7.21.0(@babel/core@7.21.3): + /@babel/preset-typescript@7.21.0(@babel/core@7.22.1): resolution: {integrity: sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-validator-option': 7.21.0 - '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.21.3) + '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.22.1) transitivePeerDependencies: - supports-color dev: true - /@babel/register@7.21.0(@babel/core@7.21.3): + /@babel/register@7.21.0(@babel/core@7.22.1): resolution: {integrity: sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -3341,6 +4277,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 + dev: true /@babel/runtime@7.21.0: resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} @@ -3348,43 +4285,59 @@ packages: dependencies: regenerator-runtime: 0.13.11 - /@babel/template@7.20.7: - resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} + /@babel/template@7.21.9: + resolution: {integrity: sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 - dev: true + '@babel/code-frame': 7.21.4 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 + + /@babel/traverse@7.21.3: + resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.22.3 + '@babel/helper-environment-visitor': 7.22.1 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 + debug: 4.3.4(supports-color@8.1.1) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color - /@babel/traverse@7.21.3: - resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==} + /@babel/traverse@7.22.4: + resolution: {integrity: sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.21.3 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.22.3 + '@babel/helper-environment-visitor': 7.22.1 '@babel/helper-function-name': 7.21.0 '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types@7.21.4: - resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} + /@babel/types@7.21.5: + resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.19.4 + '@babel/helper-string-parser': 7.21.5 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - /@babel/types@7.21.5: - resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} + /@babel/types@7.22.4: + resolution: {integrity: sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.21.5 @@ -3399,29 +4352,29 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@bull-board/api@5.1.2(@bull-board/ui@5.1.2): - resolution: {integrity: sha512-NLV88eDnOMd0XuYUNBcaYL6UdxbGYY91kP+P5Jled6CGcYmXLXv/mzAopxmgctqmP07cjC2EXN/4Oc7dyalDqA==} + /@bull-board/api@5.2.0(@bull-board/ui@5.2.0): + resolution: {integrity: sha512-1HGF2EF/4zI3+Cj414nQzwFprLXOJTlVdqXUf5UEBS4HtYafWv93mGIwkrD8S4Bpz4VSvM87adF6tQPJ7Ewt+w==} peerDependencies: - '@bull-board/ui': 5.1.2 + '@bull-board/ui': 5.2.0 dependencies: - '@bull-board/ui': 5.1.2 + '@bull-board/ui': 5.2.0 redis-info: 3.1.0 dev: false - /@bull-board/fastify@5.1.2: - resolution: {integrity: sha512-qiURhcqMfER5hp4RtgepMDbPj5H4ZKNOgK+7RIG3bd3d0tBoLjXzooXFryxzd6w130pXU9/crUMtcMP+Ulaj6g==} + /@bull-board/fastify@5.2.0: + resolution: {integrity: sha512-tvvgCAxFoiogqmAhxUiAOV/rXBVXlmg7JO3jkePA778O/YSiE7nrwwjiiLbLNuIYLZfdoYnRK4bIDmLeg1nK2A==} dependencies: - '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2) - '@bull-board/ui': 5.1.2 - '@fastify/static': 6.10.1 + '@bull-board/api': 5.2.0(@bull-board/ui@5.2.0) + '@bull-board/ui': 5.2.0 + '@fastify/static': 6.10.2 '@fastify/view': 7.4.1 ejs: 3.1.8 dev: false - /@bull-board/ui@5.1.2: - resolution: {integrity: sha512-DXXbKA4NLo5D19Vssrg4pPFaFjXVzjFN0ht4GVuoJQejy7t/RVrWzZCjdyVuSiOFTlG3SyB39zW5a95Q5EXUTg==} + /@bull-board/ui@5.2.0: + resolution: {integrity: sha512-f2sgs7AjOVch7tFhbmlVCkhZjJWboxwNxWEfAsIUd1WidUC+Ef5J02tpQvu7apzRtu5zcn8IiJtI5HFO6oKaCA==} dependencies: - '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2) + '@bull-board/api': 5.2.0(@bull-board/ui@5.2.0) dev: false /@canvas/image-data@1.0.0: @@ -3560,13 +4513,13 @@ packages: - supports-color dev: true - /@digitalbazaar/http-client@3.2.0: - resolution: {integrity: sha512-NhYXcWE/JDE7AnJikNX7q0S6zNuUPA2NuIoRdUpmvHlarjmRqyr6hIO3Awu2FxlUzbdiI1uzuWrZyB9mD1tTvw==} + /@digitalbazaar/http-client@3.4.1: + resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==} engines: {node: '>=14.0'} dependencies: - ky: 0.30.0 - ky-universal: 0.10.1(ky@0.30.0) - undici: 5.16.0 + ky: 0.33.3 + ky-universal: 0.11.0(ky@0.33.3) + undici: 5.22.1 transitivePeerDependencies: - web-streams-polyfill dev: false @@ -3778,6 +4731,16 @@ packages: eslint-visitor-keys: 3.4.1 dev: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.41.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.41.0 + eslint-visitor-keys: 3.4.1 + dev: true + /@eslint-community/regexpp@4.5.0: resolution: {integrity: sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -3805,6 +4768,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@eslint/js@8.41.0: + resolution: {integrity: sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@fal-works/esbuild-plugin-global-externals@2.1.2: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true @@ -3843,8 +4811,8 @@ packages: fastify-plugin: 4.5.0 dev: false - /@fastify/cors@8.2.1: - resolution: {integrity: sha512-2H2MrDD3ea7g707g1CNNLWb9/tYbmw7HS+MK2SDcgjxwzbOFR93JortelTIO8DBFsZqFtEpKNxiZfSyrGgYcbw==} + /@fastify/cors@8.3.0: + resolution: {integrity: sha512-oj9xkka2Tg0MrwuKhsSUumcAkfp2YCnKxmFEusi01pjk1YrdDsuSYTHXEelWNW+ilSy/ApZq0c2SvhKrLX0H1g==} dependencies: fastify-plugin: 4.5.0 mnemonist: 0.39.5 @@ -3864,12 +4832,12 @@ packages: fast-json-stringify: 5.7.0 dev: false - /@fastify/http-proxy@9.1.0: + /@fastify/http-proxy@9.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): resolution: {integrity: sha512-vgHCTDKOqLB437zQJiLWFFnsrYfFZ6Lfwu/xXQoKqRUKIPDt+xG6LBRtf8s5MNqfFVoTE7kw1U/0qdRGDsMp4Q==} dependencies: '@fastify/reply-from': 9.0.1 fastify-plugin: 4.5.0 - ws: 8.13.0 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3910,8 +4878,8 @@ packages: mime: 3.0.0 dev: false - /@fastify/static@6.10.1: - resolution: {integrity: sha512-DNnG+5QenQcTQw37qk0/191STThnN6SbU+2XMpWtpYR3gQUfUvMax14jTT/jqNINNbCkQJaKMnPtpFPKo4/68g==} + /@fastify/static@6.10.2: + resolution: {integrity: sha512-UoaMvIHSBLCZBYOVZwFRYqX2ufUhd7FFMYGDeSf0Z+D8jhYtwljjmuQGuanUP8kS4y/ZEV1a8mfLha3zNwsnnQ==} dependencies: '@fastify/accept-negotiator': 1.0.0 '@fastify/send': 2.0.1 @@ -3965,6 +4933,7 @@ packages: /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -3987,7 +4956,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 chalk: 4.1.2 jest-message-util: 29.5.0 jest-util: 29.5.0 @@ -4008,14 +4977,14 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.1 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.5.0 - jest-config: 29.5.0(@types/node@20.1.3) + jest-config: 29.5.0(@types/node@20.2.5) jest-haste-map: 29.5.0 jest-message-util: 29.5.0 jest-regex-util: 29.4.3 @@ -4049,7 +5018,7 @@ packages: dependencies: '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 jest-mock: 29.5.0 dev: true @@ -4075,8 +5044,8 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@sinonjs/fake-timers': 10.0.2 - '@types/node': 20.1.3 + '@sinonjs/fake-timers': 10.2.0 + '@types/node': 20.2.5 jest-message-util: 29.5.0 jest-mock: 29.5.0 jest-util: 29.5.0 @@ -4109,7 +5078,7 @@ packages: '@jest/transform': 29.5.0 '@jest/types': 29.5.0 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 20.1.3 + '@types/node': 20.2.5 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -4178,7 +5147,7 @@ packages: resolution: {integrity: sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 '@jest/types': 29.5.0 '@jridgewell/trace-mapping': 0.3.17 babel-plugin-istanbul: 6.1.1 @@ -4203,7 +5172,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -4215,12 +5184,12 @@ packages: '@jest/schemas': 29.4.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/yargs': 17.0.19 chalk: 4.1.2 dev: true - /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.4)(vite@4.3.5): + /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.1.3)(vite@4.3.9): resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==} peerDependencies: typescript: '>= 4.3.x' @@ -4232,17 +5201,9 @@ packages: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 - react-docgen-typescript: 2.2.2(typescript@5.0.4) - typescript: 5.0.4 - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) - dev: true - - /@jridgewell/gen-mapping@0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 + react-docgen-typescript: 2.2.2(typescript@5.1.3) + typescript: 5.1.3 + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) dev: true /@jridgewell/gen-mapping@0.3.2: @@ -4306,7 +5267,7 @@ packages: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.5.0 + semver: 7.5.1 tar: 6.1.13 transitivePeerDependencies: - encoding @@ -4381,46 +5342,52 @@ packages: os-filter-obj: 2.0.0 dev: false - /@msgpackr-extract/msgpackr-extract-darwin-arm64@2.2.0: - resolution: {integrity: sha512-Z9LFPzfoJi4mflGWV+rv7o7ZbMU5oAU9VmzCgL240KnqDW65Y2HFCT3MW06/ITJSnbVLacmcEJA8phywK7JinQ==} + /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2: + resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==} cpu: [arm64] os: [darwin] requiresBuild: true + dev: false optional: true - /@msgpackr-extract/msgpackr-extract-darwin-x64@2.2.0: - resolution: {integrity: sha512-vq0tT8sjZsy4JdSqmadWVw6f66UXqUCabLmUVHZwUFzMgtgoIIQjT4VVRHKvlof3P/dMCkbMJ5hB1oJ9OWHaaw==} + /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2: + resolution: {integrity: sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==} cpu: [x64] os: [darwin] requiresBuild: true + dev: false optional: true - /@msgpackr-extract/msgpackr-extract-linux-arm64@2.2.0: - resolution: {integrity: sha512-hlxxLdRmPyq16QCutUtP8Tm6RDWcyaLsRssaHROatgnkOxdleMTgetf9JsdncL8vLh7FVy/RN9i3XR5dnb9cRA==} + /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2: + resolution: {integrity: sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==} cpu: [arm64] os: [linux] requiresBuild: true + dev: false optional: true - /@msgpackr-extract/msgpackr-extract-linux-arm@2.2.0: - resolution: {integrity: sha512-SaJ3Qq4lX9Syd2xEo9u3qPxi/OB+5JO/ngJKK97XDpa1C587H9EWYO6KD8995DAjSinWvdHKRrCOXVUC5fvGOg==} + /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2: + resolution: {integrity: sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==} cpu: [arm] os: [linux] requiresBuild: true + dev: false optional: true - /@msgpackr-extract/msgpackr-extract-linux-x64@2.2.0: - resolution: {integrity: sha512-94y5PJrSOqUNcFKmOl7z319FelCLAE0rz/jPCWS+UtdMZvpa4jrQd+cJPQCLp2Fes1yAW/YUQj/Di6YVT3c3Iw==} + /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2: + resolution: {integrity: sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==} cpu: [x64] os: [linux] requiresBuild: true + dev: false optional: true - /@msgpackr-extract/msgpackr-extract-win32-x64@2.2.0: - resolution: {integrity: sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA==} + /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2: + resolution: {integrity: sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==} cpu: [x64] os: [win32] requiresBuild: true + dev: false optional: true /@mswjs/cookies@0.2.2: @@ -4455,8 +5422,8 @@ packages: tar-fs: 2.1.1 dev: true - /@nestjs/common@9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1): - resolution: {integrity: sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==} + /@nestjs/common@9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1): + resolution: {integrity: sha512-sea+qZnbD5x3YWZDVQT/wbVJ2NiABaM1tyZTLuW9hpkcM2KFA96xKtK3VaCxyz49zoXIgSOefsyK7HuUMCe27Q==} peerDependencies: cache-manager: <=5 class-transformer: '*' @@ -4474,12 +5441,12 @@ packages: iterare: 1.2.1 reflect-metadata: 0.1.13 rxjs: 7.8.1 - tslib: 2.5.0 + tslib: 2.5.2 uid: 2.0.2 dev: false - /@nestjs/core@9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1): - resolution: {integrity: sha512-yTLryCgFD0462wPe4HIzhyTcDgibt8Stfwb5YzcX7Ma0NM4m8uBIpcPG109KBubp8ZmV85e5mw4rl20qLQQVsQ==} + /@nestjs/core@9.4.2(@nestjs/common@9.4.2)(reflect-metadata@0.1.13)(rxjs@7.8.1): + resolution: {integrity: sha512-S5K9GTpjBqEJtu5VxRsVaaGEBZ1bkY+Ht4+2hqZSKsI+rzcEB5hcvR+5KiMsMY1VGYvlZ99lxYz72p4h8B0mKw==} requiresBuild: true peerDependencies: '@nestjs/common': ^9.0.0 @@ -4496,21 +5463,21 @@ packages: '@nestjs/websockets': optional: true dependencies: - '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 path-to-regexp: 3.2.0 reflect-metadata: 0.1.13 rxjs: 7.8.1 - tslib: 2.5.0 + tslib: 2.5.2 uid: 2.0.2 transitivePeerDependencies: - encoding dev: false - /@nestjs/testing@9.4.0(@nestjs/common@9.4.0)(@nestjs/core@9.4.0): - resolution: {integrity: sha512-xZWp363P4otcebg++gSjUcdCfTK0RorORzyFq3aLaSAQOlq8kxfFDRIKzEATR4aOUfqTMMsAA8lhnMJWf35N6A==} + /@nestjs/testing@9.4.2(@nestjs/common@9.4.2)(@nestjs/core@9.4.2): + resolution: {integrity: sha512-4WZPJz85zLVZkhmWYq+Unr43MixISelg/TyuX1YFZYOeukIN+O6fRtAAPIKLqRQsiY0rE/h8FAEbYGWhNrRfSA==} peerDependencies: '@nestjs/common': ^9.0.0 '@nestjs/core': ^9.0.0 @@ -4522,9 +5489,9 @@ packages: '@nestjs/platform-express': optional: true dependencies: - '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/core': 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) - tslib: 2.5.0 + '@nestjs/common': 9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 9.4.2(@nestjs/common@9.4.2)(reflect-metadata@0.1.13)(rxjs@7.8.1) + tslib: 2.5.2 dev: false /@nodelib/fs.scandir@2.1.5: @@ -4550,12 +5517,13 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: '@gar/promisify': 1.1.3 - semver: 7.5.0 + semver: 7.5.1 dev: false /@npmcli/move-file@2.0.1: resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs dependencies: mkdirp: 1.0.4 rimraf: 3.0.2 @@ -4653,7 +5621,7 @@ packages: '@redis/client': 1.4.2 dev: true - /@rollup/plugin-alias@5.0.0(rollup@3.21.6): + /@rollup/plugin-alias@5.0.0(rollup@3.23.0): resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -4662,11 +5630,11 @@ packages: rollup: optional: true dependencies: - rollup: 3.21.6 + rollup: 3.23.0 slash: 4.0.0 dev: false - /@rollup/plugin-json@6.0.0(rollup@3.21.6): + /@rollup/plugin-json@6.0.0(rollup@3.23.0): resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==} engines: {node: '>=14.0.0'} peerDependencies: @@ -4675,11 +5643,11 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.6) - rollup: 3.21.6 + '@rollup/pluginutils': 5.0.2(rollup@3.23.0) + rollup: 3.23.0 dev: false - /@rollup/plugin-replace@5.0.2(rollup@3.21.6): + /@rollup/plugin-replace@5.0.2(rollup@3.23.0): resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -4688,9 +5656,9 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.6) + '@rollup/pluginutils': 5.0.2(rollup@3.23.0) magic-string: 0.27.0 - rollup: 3.21.6 + rollup: 3.23.0 dev: false /@rollup/pluginutils@4.2.1: @@ -4701,7 +5669,7 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/pluginutils@5.0.2(rollup@3.21.6): + /@rollup/pluginutils@5.0.2(rollup@3.23.0): resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -4713,7 +5681,7 @@ packages: '@types/estree': 1.0.1 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 3.21.6 + rollup: 3.23.0 dev: false /@rushstack/node-core-library@3.58.0(@types/node@18.16.3): @@ -4791,11 +5759,17 @@ packages: resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} dependencies: type-detect: 4.0.8 + dev: true - /@sinonjs/fake-timers@10.0.2: - resolution: {integrity: sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==} + /@sinonjs/commons@3.0.0: + resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} dependencies: - '@sinonjs/commons': 2.0.0 + type-detect: 4.0.8 + + /@sinonjs/fake-timers@10.2.0: + resolution: {integrity: sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==} + dependencies: + '@sinonjs/commons': 3.0.0 /@sinonjs/fake-timers@9.1.2: resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} @@ -4836,8 +5810,8 @@ packages: lodash.values: 4.3.0 object-hash: 3.0.0 packageurl-js: 1.0.1 - semver: 7.5.0 - tslib: 2.5.0 + semver: 7.5.1 + tslib: 2.5.2 dev: false /@snyk/graphlib@2.1.9-patch.3: @@ -4864,8 +5838,8 @@ packages: resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} dev: false - /@storybook/addon-actions@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-U8c7n918/mOjXnc1Iu/sglbK+ryC4xoyjWE5SG/68h0+sHb1rioNq7leAi24mCP6jNwNI5Q7TWtuvflOGxQDKQ==} + /@storybook/addon-actions@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3M5AU/ZD79YP88vKlFezIJbIoG/II7wCixUBTmwiC3BeQZDuVsqPNl8eiP6MGT70xwyx7a993lSM5f5N5W93vg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -4875,14 +5849,14 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 dequal: 2.0.3 lodash: 4.17.21 polished: 4.2.2 @@ -4895,8 +5869,8 @@ packages: uuid: 9.0.0 dev: true - /@storybook/addon-backgrounds@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-QtOxXO9hKtwBjjdLXWYKp4HpcpNOrLfc71dn78XbMKyCkQRlYtVe8GNk/++70UQtFfKCEJIB0hTHrPmSjDJE5A==} + /@storybook/addon-backgrounds@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cPQy1Ot7Urf4hQz+xnF1YKrqSyR0DRwozBmF+sGzceACWmueFl0CifYZC8RSmaiIyVh0RyWPxZ9F/eT67NX2lA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -4906,22 +5880,22 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 dev: true - /@storybook/addon-controls@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-j5UiPH8ZJx0ieUoIeV3iENlsIRDuQCeg3gTlLD668sebx8KHOCSJygh0Zvg1sTUUGSIbenhWaPlqfaW6ShKFWQ==} + /@storybook/addon-controls@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mD6DE52CCMKugXk2Uab0QxwgfE76kFJroxASmnePnXUNWfP9EZJpJXYE3cyyBbmZuxa46VHDGGEGXQWRl4+Eog==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -4931,15 +5905,15 @@ packages: react-dom: optional: true dependencies: - '@storybook/blocks': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-common': 7.0.10 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/node-logger': 7.0.10 - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/blocks': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-common': 7.0.18 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/node-logger': 7.0.18 + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 lodash: 4.17.21 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -4948,29 +5922,29 @@ packages: - supports-color dev: true - /@storybook/addon-docs@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-1tUsJ+fuBqk4oTOBLabyPQeQYiRKs9I6+soY7dG8jN15Bxe/Ey2giNpqUkA3xAIuqS75ydRVKmsfQvILu2nLjg==} + /@storybook/addon-docs@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-oq+ZN5809gIRdTZQIpeK1F8BJtL1/VWo9rWvl6ymVOL/Xzdgd7AOfKf9Y99X35RcxAGysRIHLGJjF4bgLoY1Aw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/core': 7.21.3 - '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.3) + '@babel/core': 7.22.1 + '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.22.1) '@jest/transform': 29.5.0 '@mdx-js/react': 2.3.0(react@18.2.0) - '@storybook/blocks': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/csf-plugin': 7.0.10 - '@storybook/csf-tools': 7.0.10 + '@storybook/blocks': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/csf-plugin': 7.0.18 + '@storybook/csf-tools': 7.0.18 '@storybook/global': 5.0.0 '@storybook/mdx2-csf': 1.0.0 - '@storybook/node-logger': 7.0.10 - '@storybook/postinstall': 7.0.10 - '@storybook/preview-api': 7.0.10 - '@storybook/react-dom-shim': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/node-logger': 7.0.18 + '@storybook/postinstall': 7.0.18 + '@storybook/preview-api': 7.0.18 + '@storybook/react-dom-shim': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 fs-extra: 11.1.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -4981,25 +5955,25 @@ packages: - supports-color dev: true - /@storybook/addon-essentials@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-nOeUtNbfLXOlgGqqqlsYC9gcYSrAxABBo8jHYiZg3xaEB9+cnKjCKK8VxrqJiR002AG5JZvi+uHeAauM94fkkQ==} + /@storybook/addon-essentials@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-0XXu7xhtRefA1WxxorKk6BWeeB+7gQ+r2+bG1zQEfBgDYPR06YbPw4H79IZ8JiR97aJRsZBK5UUhOZMDrc5zcQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/addon-actions': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-backgrounds': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-controls': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-docs': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-highlight': 7.0.10 - '@storybook/addon-measure': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-outline': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-toolbars': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-viewport': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-common': 7.0.10 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/node-logger': 7.0.10 - '@storybook/preview-api': 7.0.10 + '@storybook/addon-actions': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-backgrounds': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-controls': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-docs': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-highlight': 7.0.18 + '@storybook/addon-measure': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-outline': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-toolbars': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-viewport': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-common': 7.0.18 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/node-logger': 7.0.18 + '@storybook/preview-api': 7.0.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 @@ -5007,16 +5981,16 @@ packages: - supports-color dev: true - /@storybook/addon-highlight@7.0.10: - resolution: {integrity: sha512-TohDxElSu7JrSvhLRZAwtNk/7Ot626wvlODwklocE4kbtn1fulFoUlRta7NImBGX554LITDFRy0m4R1rRQ9OfQ==} + /@storybook/addon-highlight@7.0.18: + resolution: {integrity: sha512-a3nfUhbu6whoDclIZSV/fzLj132tNNjV05ENTpuN3JpLoMd3+obDUWzeQUs9TetK4RBRN3ewM7sIMEI4oBpgmg==} dependencies: - '@storybook/core-events': 7.0.10 + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.10 + '@storybook/preview-api': 7.0.18 dev: true - /@storybook/addon-interactions@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-7hdFgoetQblwysYwRlmC5fbMVDb6lIM6le1pVEmRci6X44Gr2Xe5w2s6h5bTp4tMpNS1CFKjru9kF/TqfK46wA==} + /@storybook/addon-interactions@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-V3OD5lSj6Te6Kzc//2k2S79dLPk6Zu1pAbqWAN4RrdXyKj6YCiZ666GmVdiaG+24Qp5UuMeAkd1D05osJlOteA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5026,16 +6000,16 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-common': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-common': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/instrumenter': 7.0.10 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/instrumenter': 7.0.18 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 jest-mock: 27.5.1 polished: 4.2.2 react: 18.2.0 @@ -5045,8 +6019,8 @@ packages: - supports-color dev: true - /@storybook/addon-links@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Odhe0eICqW9X2yyIjtOVb23cKXJ2WRxPHBm5oYf6hBBoXXK7EJicwyQSJLxJyHK7r1PeAnFxSGlNrO3w7JULjg==} + /@storybook/addon-links@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-xEwflt7bp9FRoZVeqPGb6d3s2Gh+/jaSmnyIxMxrBy2oovKIqu9ptolqz1AhjFOXfaLs9c2RAmJUuFZJtETLxA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5056,22 +6030,22 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/router': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/router': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 dev: true - /@storybook/addon-measure@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-70BQT8PM6r3qjXDgXuN5mx9CBq9dYTdEgR1tlZ8FbMi8B8tB1oZJD0o6tfGM3r8WjdI0sTwX70ic5pv9Ma/MiA==} + /@storybook/addon-measure@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-iu8vQpGOA+CFYbWR6QNshj20o33OQ/xcTbp5P4U6xGYDUliUBbwJ2KLxcKlmIeBanBrBdz0jPFtHwY4dM1ZdKw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5081,19 +6055,19 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/types': 7.0.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/addon-outline@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Aakoc+II7orfgUDmjgMbnSp5HZS/47z0NeRAfh+FP4fxL0lFd9vmaeIXWYo1DjJqdEFfvlSLd8aS9Ltb+souMw==} + /@storybook/addon-outline@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3vNWO7ezo6GIvidbz8JxFrKtfVEoTQN7tnZx+wpqmCF8ihBORewkpeMUnvgb9ZKjD0X7gE8eQvvG8KKWcyHDBQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5103,20 +6077,20 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/types': 7.0.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 dev: true - /@storybook/addon-storysource@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-5anwqnBOcHDI/EB3F2q3Vs/JN+vCBRr8UVqnKS8NqN3BrpJ4q7jUeQ2cA0Q2/aAmdHJn9FLh/Cgx7aTO+6iC2w==} + /@storybook/addon-storysource@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ejOO9d9Aa63DCXCoXtCsOJLefdbrsvSAEV9wU2HfT+EOIS1dq/SV+ZtIMAvdAf4whB42K+pEzB5hLE2+zCK9PQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5126,13 +6100,13 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/router': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/source-loader': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/router': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/source-loader': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) estraverse: 5.3.0 prop-types: 15.8.1 react: 18.2.0 @@ -5140,8 +6114,8 @@ packages: react-syntax-highlighter: 15.5.0(react@18.2.0) dev: true - /@storybook/addon-toolbars@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-U4a45CDw4SZzrgboYVMgxyiD7Ejud1kSz2lyS+J3fGTZGXq2+tmJS/2oNrLJlSH7v8629lVUbKnFxsP0HbfShg==} + /@storybook/addon-toolbars@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mwhq962o0WloHAeFjJ6BXO2nzdTo5KE2fqawPpqcB2lwXP6tvaA2tDWwgntjPCHejqWTS+ZTdO4/1xrMhWYt/g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5151,17 +6125,17 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/addon-viewport@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Ck9sdCg3T8ChXoxYL5IEi+ZUOwdH6Je5XeK4kRVq+Ar+Ytm5CFTGJCCZjI6biroTnuJCUefaV2K5NUZoHkZI+A==} + /@storybook/addon-viewport@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aVVLBsWXfGDX3z1pc93LWWdG5RUoJbGL/JJPMZGwXdwWpP8V3OBl8D8bgPymyg+MgwhSRZZDDGgnJaVGGwZ6bQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5171,49 +6145,49 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) memoizerific: 1.11.3 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/addons@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-RRtozbB0JovZdLgTgC03kOjNh/5sAN77VHZFC5aK/Y9Hz2A0C6V4w/SqTt0382skSllcGMcrHjB1k06BlxlZ8A==} + /@storybook/addons@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-+j9ItxWoVzarbllaV4WRaJpDM3P2aC5O6F3cPn4YkG/unb6HOs11WLAqFbzZnLYZNAFvWS8PYEAtqs1BxG66YQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/types': 7.0.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/blocks@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-OqXuN1x2TjXgrOqGSaD0Vz8iCqmLjiPkrQpWMD7bToFpHH0dpmcrzzRhLhxgJLN2CAzyr98IYIkUgXX9Da1neA==} + /@storybook/blocks@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-HLsuzmUdVIeFXEP5v5vyjnEePRNYjzltwTjCKQhHAlt8/aQZmREiIMOfoMoAa1Rd+On8Ib2DUd2cN10VS18H8A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.0.10 - '@storybook/client-logger': 7.0.10 - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 + '@storybook/channels': 7.0.18 + '@storybook/client-logger': 7.0.18 + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 '@storybook/csf': 0.1.0 - '@storybook/docs-tools': 7.0.10 + '@storybook/docs-tools': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 '@types/lodash': 4.14.191 color-convert: 2.0.1 dequal: 2.0.3 @@ -5231,13 +6205,13 @@ packages: - supports-color dev: true - /@storybook/builder-manager@7.0.10: - resolution: {integrity: sha512-izCVE4JEbDVN5DPkX/Ym1PifAJKlheBvXKmGXGklnJQ2l+TEuvesPbOmVFNuu7ptJAFw4JO5n2KAo9+a5FRwiw==} + /@storybook/builder-manager@7.0.18: + resolution: {integrity: sha512-yFMm3xuYkyg2hS1uz3CkvyvLzK7qJsDPVEh7lew8GiJK1Xx8cc+FnAOlRTjWNxvhfiT296wAMCTPWv7LeoSgqQ==} dependencies: '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@storybook/core-common': 7.0.10 - '@storybook/manager': 7.0.10 - '@storybook/node-logger': 7.0.10 + '@storybook/core-common': 7.0.18 + '@storybook/manager': 7.0.18 + '@storybook/node-logger': 7.0.18 '@types/ejs': 3.1.2 '@types/find-cache-dir': 3.2.1 '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.18) @@ -5254,8 +6228,8 @@ packages: - supports-color dev: true - /@storybook/builder-vite@7.0.10(typescript@5.0.4)(vite@4.3.5): - resolution: {integrity: sha512-tKY2QnHni10TE3+Sy2wfR7h4FuribR849VBpDI/LcwtRkCgoOBfMCdEnAKMWyU6qAlY+9KDSOQq9SDTu3WZGOg==} + /@storybook/builder-vite@7.0.18(typescript@5.1.3)(vite@4.3.9): + resolution: {integrity: sha512-Qze6/PwUJq+z776dBoG5uinAEVZyPalZIaU/VOWpTrN8L9FQbL0+HDrZU2E/BMNW+ZfnSjF3V2USLyiutsC1Tw==} peerDependencies: '@preact/preset-vite': '*' typescript: '>= 4.3.x' @@ -5269,16 +6243,16 @@ packages: vite-plugin-glimmerx: optional: true dependencies: - '@storybook/channel-postmessage': 7.0.10 - '@storybook/channel-websocket': 7.0.10 - '@storybook/client-logger': 7.0.10 - '@storybook/core-common': 7.0.10 - '@storybook/csf-plugin': 7.0.10 + '@storybook/channel-postmessage': 7.0.18 + '@storybook/channel-websocket': 7.0.18 + '@storybook/client-logger': 7.0.18 + '@storybook/core-common': 7.0.18 + '@storybook/csf-plugin': 7.0.18 '@storybook/mdx2-csf': 1.0.0 - '@storybook/node-logger': 7.0.10 - '@storybook/preview': 7.0.10 - '@storybook/preview-api': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/node-logger': 7.0.18 + '@storybook/preview': 7.0.18 + '@storybook/preview-api': 7.0.18 + '@storybook/types': 7.0.18 browser-assert: 1.2.1 es-module-lexer: 0.9.3 express: 4.18.2 @@ -5288,19 +6262,19 @@ packages: magic-string: 0.27.0 remark-external-links: 8.0.0 remark-slug: 6.1.0 - rollup: 3.21.6 - typescript: 5.0.4 - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) + rollup: 3.23.0 + typescript: 5.1.3 + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) transitivePeerDependencies: - supports-color dev: true - /@storybook/channel-postmessage@7.0.10: - resolution: {integrity: sha512-Y5ZSp9WYH3HznQ2rrGN78Y5fYM16Bfvyn3iKy5QD38gsQk1gTrra1osIZ0X+lk3sep14D4oW4QMW3DCSrn0Big==} + /@storybook/channel-postmessage@7.0.18: + resolution: {integrity: sha512-rpwBH5ANdPnugS6+7xG9qHSoS+aPSEnBxDKsONWFubfMTTXQuFkf/793rBbxGkoINdqh8kSdKOM2rIty6e9cmQ==} dependencies: - '@storybook/channels': 7.0.10 - '@storybook/client-logger': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/channels': 7.0.18 + '@storybook/client-logger': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 qs: 6.11.1 telejson: 7.0.4 @@ -5328,18 +6302,17 @@ packages: telejson: 7.0.4 dev: true - /@storybook/channel-websocket@7.0.10: - resolution: {integrity: sha512-WXueykS71YxEqKlsIbbmmA6QSChEePffzqs7QASUpHhi21IDqmtq2HhAqYWlQptyqSWYdv6wxrOqwe6z4zakLA==} + /@storybook/channel-websocket@7.0.18: + resolution: {integrity: sha512-QYsZIfe23NN4i+oIdPKHaYBehk3a/HYk57a+M2oR3Frmv8IOqc/e31uH+xx5NxnjHrTJj7Y80ZJw6EKB682S6w==} dependencies: - '@storybook/channels': 7.0.10 - '@storybook/client-logger': 7.0.10 + '@storybook/channels': 7.0.18 + '@storybook/client-logger': 7.0.18 '@storybook/global': 5.0.0 telejson: 7.0.4 dev: true - /@storybook/channels@7.0.10: - resolution: {integrity: sha512-hdPaGV3W7s6MkVcg33S5hmkVgqXC16AA7H0ID9MROiU1lQzolKhSqCs2iVfGPQfmWzEJeqWQoEXU7dmCclRM0w==} - dev: true + /@storybook/channels@7.0.18: + resolution: {integrity: sha512-rkA7ea0M3+dWS+71iHJdiZ5R2QuIdiVg0CgyLJHDagc1qej7pEVNhMWtppeq+X5Pwp9nkz8ZTQ7aCjTf6th0/A==} /@storybook/channels@7.0.2: resolution: {integrity: sha512-qkI8mFy9c8mxN2f01etayKhCaauL6RAsxRzbX1/pKj6UqhHWqqUbtHwymrv4hG5qDYjV1e9pd7ae5eNF8Kui0g==} @@ -5349,20 +6322,20 @@ packages: resolution: {integrity: sha512-+34cVmrXZ3lb1s5tDK+OWd5HLtEPSUMas0VKFJ0k9LBpFlVl9aiCZBJRvSYmWL7beauUfa+HSmJgjlD6228ChQ==} dev: true - /@storybook/cli@7.0.10: - resolution: {integrity: sha512-FhtE6Yrk7MMa9AgLb3MTmqiQ3IlWHjjrj7Vcj2QM6BcP342xSe7C1d+V6+tYX/oPOEB3Upz+PKNrju1iHxurQQ==} + /@storybook/cli@7.0.18: + resolution: {integrity: sha512-9n4J4thiCUsGSXiRc6ZysqYUaCMCrpu0/qgC+5ngfFRuMmZgUV0y5+0fmaOhT2XjsonTTgucizO82i7+ottCVg==} hasBin: true dependencies: - '@babel/core': 7.21.3 - '@babel/preset-env': 7.21.4(@babel/core@7.21.3) + '@babel/core': 7.22.1 + '@babel/preset-env': 7.21.4(@babel/core@7.22.1) '@ndelangen/get-tarball': 3.0.7 - '@storybook/codemod': 7.0.10 - '@storybook/core-common': 7.0.10 - '@storybook/core-server': 7.0.10 - '@storybook/csf-tools': 7.0.10 - '@storybook/node-logger': 7.0.10 - '@storybook/telemetry': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/codemod': 7.0.18 + '@storybook/core-common': 7.0.18 + '@storybook/core-server': 7.0.18 + '@storybook/csf-tools': 7.0.18 + '@storybook/node-logger': 7.0.18 + '@storybook/telemetry': 7.0.18 + '@storybook/types': 7.0.18 '@types/semver': 7.5.0 boxen: 5.1.2 chalk: 4.1.2 @@ -5380,11 +6353,12 @@ packages: globby: 11.1.0 jscodeshift: 0.14.0(@babel/preset-env@7.21.4) leven: 3.1.0 + ora: 5.4.1 prettier: 2.8.8 prompts: 2.4.2 puppeteer-core: 2.1.1 read-pkg-up: 7.0.1 - semver: 7.5.0 + semver: 7.5.1 shelljs: 0.8.5 simple-update-notifier: 1.1.0 strip-json-comments: 3.1.1 @@ -5398,8 +6372,8 @@ packages: - utf-8-validate dev: true - /@storybook/client-logger@7.0.10: - resolution: {integrity: sha512-hb8tO+w28ErzjEw69ERMtZT81Xyg835FQjH6Y42ejoGcBA9Z0W6RZmx4RgkcIUOlYXkU6lSnNVne9gXodV4/Hw==} + /@storybook/client-logger@7.0.18: + resolution: {integrity: sha512-uKgFdVedYoRDZBVrE1IBdWNHDFln1IxWEeI+7ZiNSQwREG9swHpU5Fa8DceclM/oLjJRuzG1jFzv+XZY8894+Q==} dependencies: '@storybook/global': 5.0.0 dev: true @@ -5416,16 +6390,16 @@ packages: '@storybook/global': 5.0.0 dev: true - /@storybook/codemod@7.0.10: - resolution: {integrity: sha512-BnPknLV3wnaSk0azjFBAWLVfwgUHtFvVk9I6y1idIaQhc0nnegKoa0jTxWigthftZK/Pv9yG3gxG7o7O4KcChQ==} + /@storybook/codemod@7.0.18: + resolution: {integrity: sha512-+9XFns29e8FpPLsqA8ZCQ3mNnIIKD3QnqGYkbkCVKi/G1fomvVQsIfsnkrYv5SobTbz29B4aNWxAaeSnO7/OGg==} dependencies: '@babel/core': 7.21.3 '@babel/preset-env': 7.21.4(@babel/core@7.21.3) '@babel/types': 7.21.5 '@storybook/csf': 0.1.0 - '@storybook/csf-tools': 7.0.10 - '@storybook/node-logger': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/csf-tools': 7.0.18 + '@storybook/node-logger': 7.0.18 + '@storybook/types': 7.0.18 cross-spawn: 7.0.3 globby: 11.1.0 jscodeshift: 0.14.0(@babel/preset-env@7.21.4) @@ -5436,17 +6410,17 @@ packages: - supports-color dev: true - /@storybook/components@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-jdGiVP+a3XqoGpKkDFGt4g2cgb23aLfMS/RhnuhT7FK6hGh7WFfuuqx4QqQHx4VZCdXIWVIzszaCdGCs7AsW2w==} + /@storybook/components@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Jn1CbF9UAKt8BVaZtuhmthpcZ02VMaCFXR0ISfDXCpiMKnylmpP0+WfXcoKLzz6yS+EW8EW5S9+Qq8xgQY8H7A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/client-logger': 7.0.10 + '@storybook/client-logger': 7.0.18 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -5454,18 +6428,18 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/core-client@7.0.10: - resolution: {integrity: sha512-sN/TKB7QHWP6josdjyNtoqDXihROPtgvzo5+akfW6+S7hhfsQ4BJd09nkBqEX9E7z81blmFFDUOU3a8bQbPdKQ==} + /@storybook/core-client@7.0.18: + resolution: {integrity: sha512-ueExRZx6fd9LRssgdhDJ0bL4Ir2RrbXzJz/kjIT2KgYY3l7jkhe0dpT3bOgGKjQt0f7XMFU24t/r7aDLGMB+2Q==} dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/preview-api': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/preview-api': 7.0.18 dev: true - /@storybook/core-common@7.0.10: - resolution: {integrity: sha512-AAYXixukGlpMy8XoSM8cTfcyQ6ijBq5q50xNTj/ssTbGnGSk6POgtoJZf6g8XtS0OxsFXBSxuBuMBBBbKtoztw==} + /@storybook/core-common@7.0.18: + resolution: {integrity: sha512-HZAB1NIK/Yv0x9poyzqYcue2tx39+MAF1mbHgGy+JJZRerO2fRShgo8f8VPH9ChbFCoJ7isL5wNhgGdg9kp2kA==} dependencies: - '@storybook/node-logger': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/node-logger': 7.0.18 + '@storybook/types': 7.0.18 '@types/node': 16.18.16 '@types/pretty-hrtime': 1.0.1 chalk: 4.1.2 @@ -5487,8 +6461,8 @@ packages: - supports-color dev: true - /@storybook/core-events@7.0.10: - resolution: {integrity: sha512-OyBqhxVQOdI78Vgv6nKwXOdIVNChyfktpdxQZP1rz9MpO6MrqMaGAUL7k8xQMQAVx0VY+dAMYZB3bnyN2IC8FA==} + /@storybook/core-events@7.0.18: + resolution: {integrity: sha512-7gxHBQDezdKOeq/u1LL80Bwjfcwsv7XOS3yWQElcgqp+gLaYB6OwwgtkCB2yV6a6l4nep9IdPWE8G3TxIzn9xw==} dev: true /@storybook/core-events@7.0.2: @@ -5499,23 +6473,23 @@ packages: resolution: {integrity: sha512-kGrtjlYtjd4iTVk+Phb4CymZaVkB+MGscKAgcO8gfgJ/Q/gq8HQLVZSIzeoCDcDSHOGlBzbg2WVtdHIHhCKlOQ==} dev: true - /@storybook/core-server@7.0.10: - resolution: {integrity: sha512-KFCc3turPed8tiC5IUKTV7oObVmFckMP1XqO7zec2g2NlGQsN83DRso+BA1wpV/bb8AD1NJDU6LJnyN3KKdi1Q==} + /@storybook/core-server@7.0.18: + resolution: {integrity: sha512-zGSGYSoCaSXM28OYKW7zsmpo8VU1icubXLRgdF21fbMhFN1WVS+bPA5+gSkAMf8acq5RNM8uSKskh7E2YDVEqA==} dependencies: '@aw-web-design/x-default-browser': 1.4.88 '@discoveryjs/json-ext': 0.5.7 - '@storybook/builder-manager': 7.0.10 - '@storybook/core-common': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/builder-manager': 7.0.18 + '@storybook/core-common': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/csf': 0.1.0 - '@storybook/csf-tools': 7.0.10 + '@storybook/csf-tools': 7.0.18 '@storybook/docs-mdx': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager': 7.0.10 - '@storybook/node-logger': 7.0.10 - '@storybook/preview-api': 7.0.10 - '@storybook/telemetry': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/manager': 7.0.18 + '@storybook/node-logger': 7.0.18 + '@storybook/preview-api': 7.0.18 + '@storybook/telemetry': 7.0.18 + '@storybook/types': 7.0.18 '@types/detect-port': 1.3.2 '@types/node': 16.18.16 '@types/node-fetch': 2.6.2 @@ -5532,18 +6506,18 @@ packages: globby: 11.1.0 ip: 2.0.0 lodash: 4.17.21 - node-fetch: 2.6.7 + node-fetch: 2.6.11 open: 8.4.2 pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.5.0 + semver: 7.5.1 serve-favicon: 2.5.0 telejson: 7.0.4 ts-dedent: 2.2.0 util-deprecate: 1.0.2 watchpack: 2.4.0 - ws: 8.13.0 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - encoding @@ -5551,48 +6525,46 @@ packages: - utf-8-validate dev: true - /@storybook/csf-plugin@7.0.10: - resolution: {integrity: sha512-uUty5rLs6O32tJaXIne2/42UxFL3eaRCDgtAoAkGxbUPa/FLYpO0rYtqF2OG9MagwXU7+As5RlLkDLeYAvUzlQ==} + /@storybook/csf-plugin@7.0.18: + resolution: {integrity: sha512-Cr/Qr4/H4JIYgbbmDjQIYuqjp6nOaZga73R3KZcuClk27B90sI2ADegMYvORgbFgSkwweNQjgak6hLoOyogAhw==} dependencies: - '@storybook/csf-tools': 7.0.10 + '@storybook/csf-tools': 7.0.18 unplugin: 0.10.2 transitivePeerDependencies: - supports-color dev: true - /@storybook/csf-tools@7.0.10: - resolution: {integrity: sha512-sl/995jq03HD7/Q9cb54h0glgt7JLGTkfikSlB35NGMEkgEXEswDmpQHA/TbzUYylIxuAwTKghwMqL3IwSSHwA==} + /@storybook/csf-tools@7.0.18: + resolution: {integrity: sha512-0IJ2qdrxleTl67FUzsEvGcy96CY0OKyERE33tAsLNbvWcabdJKpLHP+rJwbsCw4z6IlS+kkmEffeFf5qRPTwkQ==} dependencies: '@babel/generator': 7.21.3 - '@babel/parser': 7.21.8 + '@babel/parser': 7.21.9 '@babel/traverse': 7.21.3 '@babel/types': 7.21.5 '@storybook/csf': 0.1.0 - '@storybook/types': 7.0.10 + '@storybook/types': 7.0.18 fs-extra: 11.1.0 recast: 0.23.1 ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color - dev: true /@storybook/csf@0.1.0: resolution: {integrity: sha512-uk+jMXCZ8t38jSTHk2o5btI+aV2Ksbvl6DoOv3r6VaCM1KZqeuMwtwywIQdflkA8/6q/dKT8z8L+g8hC4GC3VQ==} dependencies: type-fest: 2.19.0 - dev: true /@storybook/docs-mdx@0.1.0: resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} dev: true - /@storybook/docs-tools@7.0.10: - resolution: {integrity: sha512-w3m7+LlQGI50i07XjiOzIfoap8rnmsrs8hXGUTodbs9vvLt8HBdUaapOGnYr/1BzA0YQJ7Nz2z1nTirQEphmsQ==} + /@storybook/docs-tools@7.0.18: + resolution: {integrity: sha512-H95dW2DquGQ75ZVrFjvznPdCxT0eW6esDnemzLJB61KitcYZrWRavfrZzFtUcpzIa84OgY5pllFYt636v11LHQ==} dependencies: - '@babel/core': 7.21.3 - '@storybook/core-common': 7.0.10 - '@storybook/preview-api': 7.0.10 - '@storybook/types': 7.0.10 + '@babel/core': 7.22.1 + '@storybook/core-common': 7.0.18 + '@storybook/preview-api': 7.0.18 + '@storybook/types': 7.0.18 '@types/doctrine': 0.0.3 doctrine: 3.0.0 lodash: 4.17.21 @@ -5603,21 +6575,21 @@ packages: /@storybook/expect@27.5.2-0: resolution: {integrity: sha512-cP99mhWN/JeCp7VSIiymvj5tmuMY050iFohvp8Zq+kewKsBSZ6/qpTJAGCCZk6pneTcp4S0Fm5BSqyxzbyJ3gw==} dependencies: - '@types/jest': 29.5.1 + '@types/jest': 29.5.2 dev: true /@storybook/global@5.0.0: resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} dev: true - /@storybook/instrumenter@7.0.10: - resolution: {integrity: sha512-Z+kIidnxaq3tneUnIKB2d0DCqb4lwUdOS/AC43LNvd9C6BWYgj89cIPdLDTNhOWa0ZiEju7wTS+K/3uMvcHZ4w==} + /@storybook/instrumenter@7.0.18: + resolution: {integrity: sha512-fyQxeuVC0H+w3oyTuByE95xnAQ+l/WhUBVkHV2X+PWjg9vg9Y9JmrbNWynlvz5HLFlsY3qAWJh+ciVRVSvY5Jw==} dependencies: - '@storybook/channels': 7.0.10 - '@storybook/client-logger': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/channels': 7.0.18 + '@storybook/client-logger': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.10 + '@storybook/preview-api': 7.0.18 dev: true /@storybook/instrumenter@7.0.2: @@ -5649,41 +6621,41 @@ packages: jest-mock: 27.5.1 dev: true - /@storybook/manager-api@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Dik73GKUX9QCFOvukTXjZoZX0G6n/LrRMkwLggb28E9m8iFt2ivWvF9MVvyRoDffR9VP5t53+nV5fqxqpXWoQw==} + /@storybook/manager-api@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-anQkm09twL96YkKGXHa+LI0+yMaY6Jxs1lRaetHdMlIqN4VHBHhizHaMgtGfH6xCTuO3WdrKTN7cZii5RH7PBQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.0.10 - '@storybook/client-logger': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/channels': 7.0.18 + '@storybook/client-logger': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/router': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/router': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - semver: 7.5.0 + semver: 7.5.1 store2: 2.14.2 telejson: 7.0.4 ts-dedent: 2.2.0 dev: true - /@storybook/manager@7.0.10: - resolution: {integrity: sha512-cFMOOXmcRx1tN50TqC2huOsF91fAvNM82wTDnAbT2FtA+ZHFHNyE1PgWgiKDDepzOpKaG+FfT4bJcQAaAfYOBg==} + /@storybook/manager@7.0.18: + resolution: {integrity: sha512-hasb8XDmkT9lyX2cwb3Xg0ngcNQ1QCNHKurl2YJtXowb1CvawGKokhnVUTso15NCnurolDyw/Wqka1sagfm+Mg==} dev: true /@storybook/mdx2-csf@1.0.0: resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==} dev: true - /@storybook/node-logger@7.0.10: - resolution: {integrity: sha512-btCCreucTApi7EP84jbfqlFQZDD4Kz9lFLftalZA7nskDZW6i8reNNykTU2Y22TQvlbpqs5kL1N1cEsbG3vepw==} + /@storybook/node-logger@7.0.18: + resolution: {integrity: sha512-cIeKEBvELtoVP/5UeQ01GJWZ7wM69/9Q+R5uOtNQBlwWFcCD6AVFWMRqq7ObMvdJG/okhXSF+sDetb+BF3zvdw==} dependencies: '@types/npmlog': 4.1.4 chalk: 4.1.2 @@ -5691,20 +6663,20 @@ packages: pretty-hrtime: 1.0.3 dev: true - /@storybook/postinstall@7.0.10: - resolution: {integrity: sha512-SVPKGuuvfn1MceLWzYHGbpP77+waLKXglAH4Gkdoa2mKdk3XO45Zn8OhwwNzHuP698boMNaGaB/utBLBpkXMMg==} + /@storybook/postinstall@7.0.18: + resolution: {integrity: sha512-ObIwAK2UiYhXN/7UifISQgBoH5jnyxh6T8kvCw83YhC78SDOPNgIGjToJECizJ7iubtqAWtCfCT5TrGEpyLGbg==} dev: true - /@storybook/preview-api@7.0.10: - resolution: {integrity: sha512-URj2YJKbs8hc6JZQ3aA+MmjB4hTSzGZAVFVs3kLUEuaQPDbU1RT5GKxedwF5zlMnkZQPNoaUtopN3z7aF+SKFQ==} + /@storybook/preview-api@7.0.18: + resolution: {integrity: sha512-xxtC0gPGMn/DbwvS4ZuJaBwfFNsjUCf0yLYHFrNe6fxncbvcLZ550RuyUwYuIRfsiKrlgfa3QmmCa4JM/JesHQ==} dependencies: - '@storybook/channel-postmessage': 7.0.10 - '@storybook/channels': 7.0.10 - '@storybook/client-logger': 7.0.10 - '@storybook/core-events': 7.0.10 + '@storybook/channel-postmessage': 7.0.18 + '@storybook/channels': 7.0.18 + '@storybook/client-logger': 7.0.18 + '@storybook/core-events': 7.0.18 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/types': 7.0.10 + '@storybook/types': 7.0.18 '@types/qs': 6.9.7 dequal: 2.0.3 lodash: 4.17.21 @@ -5755,12 +6727,12 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/preview@7.0.10: - resolution: {integrity: sha512-IQX8v7OpKeo2Oqeyxo6/uSRys+dJ7zms12jViJWGzx9fg6IchV/iNtf4TBrF3Z2JBNKovk03kICAMHTpZuz9Qg==} + /@storybook/preview@7.0.18: + resolution: {integrity: sha512-L53p2eo8G12U6tp7hD3mk5tdWFXLvdEyV9e7a1x9bw1LfH15K/bp8lO6U/W1kkpse7+rqWBqoTjJC1Ktm5Sxog==} dev: true - /@storybook/react-dom-shim@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-NLuE2Be/BGmXHufwLp1Gje+IsTb0HWvwzHlci2U430WgwGU8fsTPNgALMrwCpqN9o1KnrRGpysQEoyIYStQBdg==} + /@storybook/react-dom-shim@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-O1FRypR8q1katjbznnxI+NtALd2gaWa7KnTwbIDf+ddZltXHMZ8xMiEGEtAMrfXlIuqIr9UvmLRfKZC/ysuA+g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5769,25 +6741,25 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/react-vite@7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5): - resolution: {integrity: sha512-ZEwRpMEJAQtMruG/XGha52XHkb3extXudWT5SoXOcfiRy9eK7Y3oJwHR8KHNH3LE+LrRh7c+D53k7eMudRtsNA==} + /@storybook/react-vite@7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9): + resolution: {integrity: sha512-rxJwp/b0dPazn15xLIeRgwrdZGWmoqoLhU7Mm+AXKToXvbe77i2bjHhkFbz34dpKFtD0i/ajcZSpmsxpxfB0HA==} engines: {node: '>=16'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.4)(vite@4.3.5) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.1.3)(vite@4.3.9) '@rollup/pluginutils': 4.2.1 - '@storybook/builder-vite': 7.0.10(typescript@5.0.4)(vite@4.3.5) - '@storybook/react': 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4) - '@vitejs/plugin-react': 3.1.0(vite@4.3.5) + '@storybook/builder-vite': 7.0.18(typescript@5.1.3)(vite@4.3.9) + '@storybook/react': 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3) + '@vitejs/plugin-react': 3.1.0(vite@4.3.9) ast-types: 0.14.2 magic-string: 0.27.0 react: 18.2.0 react-docgen: 6.0.0-alpha.3 react-dom: 18.2.0(react@18.2.0) - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) transitivePeerDependencies: - '@preact/preset-vite' - supports-color @@ -5795,8 +6767,8 @@ packages: - vite-plugin-glimmerx dev: true - /@storybook/react@7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4): - resolution: {integrity: sha512-/DDUGFz0bk5c/HCfSr7fL74rQc+3s317TDDKY6ZrgUzdIkze4D/TlAbWV78XV/ceeFNi1fLAUzGjFzuDwmVkJw==} + /@storybook/react@7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3): + resolution: {integrity: sha512-lumUbHYeuL3qa4SZR9K2YC4UIt1hwW19GuI/6f2HEV5gR9QHHSJHg9HD9pjcxv4fQaiG81ACZ0Sg6lyUkcJvuQ==} engines: {node: '>=16.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5806,13 +6778,13 @@ packages: typescript: optional: true dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/core-client': 7.0.10 - '@storybook/docs-tools': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/core-client': 7.0.18 + '@storybook/docs-tools': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.10 - '@storybook/react-dom-shim': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/preview-api': 7.0.18 + '@storybook/react-dom-shim': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 '@types/node': 16.18.16 @@ -5828,33 +6800,33 @@ packages: react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0) ts-dedent: 2.2.0 type-fest: 2.19.0 - typescript: 5.0.4 + typescript: 5.1.3 util-deprecate: 1.0.2 transitivePeerDependencies: - supports-color dev: true - /@storybook/router@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Vq3nuyrGsvbPYhsaVu0TwtzX8Yb5TZYg7v5gY/uk1brSIk7Mvw64E8WF4TKNhPcWnlxNrfP9S96IZgT9iuuCpw==} + /@storybook/router@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Mue4s/BnKgdYcsiW9yuvW3qL9k3AgYn5HIhnkBExAteyiUGdAca4IJFhArmGgFktgeLc4ecBQ7sgaCljApnbgg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/client-logger': 7.0.10 + '@storybook/client-logger': 7.0.18 memoizerific: 1.11.3 qs: 6.11.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/source-loader@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-DtdYllq0piU6vgoVjsuPsWaGlhSOJgJr/kRovu5zltaZzdEOyQZ7e0zQmA4Py0h9jnGbg2fQG9zccofY3jUdJw==} + /@storybook/source-loader@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-n910+/rNJ3tCRUx3JJm/5ehjp5CK2WZg+KPRtG5a4AeVhQBdxsxw2D2pDYBWY1aFhJ+S4AZJOLIk9cdOMneA9g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@storybook/csf': 0.1.0 - '@storybook/types': 7.0.10 + '@storybook/types': 7.0.18 estraverse: 5.3.0 lodash: 4.17.21 prettier: 2.8.8 @@ -5862,11 +6834,11 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/telemetry@7.0.10: - resolution: {integrity: sha512-0xlMECcSU2UnmpDTxKE/+pKpcW88fhxEZxh54yoA6NPpq6SGUN1r5ybUMffJCZ0JgaQ8HOc3Vxd13T3VXAMLXA==} + /@storybook/telemetry@7.0.18: + resolution: {integrity: sha512-JP5Z7lGU+oKjNmz2cZW5J7EerwyWBBPOU+NvvooZsymIx02ZvJ4ClmFtolJnBM7m4KoAy50JxV5NQWi+q8PicQ==} dependencies: - '@storybook/client-logger': 7.0.10 - '@storybook/core-common': 7.0.10 + '@storybook/client-logger': 7.0.18 + '@storybook/core-common': 7.0.18 chalk: 4.1.2 detect-package-manager: 2.0.1 fetch-retry: 5.0.4 @@ -5889,28 +6861,27 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/theming@7.0.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-kKxIMElOUAyIAJOlhU6NS6/F6KpZLWvfGnUYC5V4f5Rsu+lKnbWI/TJ1rCIooz2wZBQ6dv+fjA3sOh5K+LRh2w==} + /@storybook/theming@7.0.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-P1gMKa/mKQHIMq0sxBIwTzAcF6v/6hrc62YmkuV62vXu+8zNV2YWbRwywqm3Q6faZEadmb/bL9+z8whaKhCL/g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) - '@storybook/client-logger': 7.0.10 + '@storybook/client-logger': 7.0.18 '@storybook/global': 5.0.0 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/types@7.0.10: - resolution: {integrity: sha512-mFktvN8PjjDFJSjck4spikmjtr0AwfOhcEtIf4UCmUD5JHgGppkQmvO6483nGcprSFcWOvD2uYGs8Wp32wG/MQ==} + /@storybook/types@7.0.18: + resolution: {integrity: sha512-qPop2CbvmX42/BX29YT9jIzW2TlMcMjAE+KCpcKLBiD1oT5DJ1fhMzpe6RW9HkMegkBxjWx54iamN4oHM/pwcQ==} dependencies: - '@storybook/channels': 7.0.10 + '@storybook/channels': 7.0.18 '@types/babel__core': 7.20.0 '@types/express': 4.17.17 file-system-cache: 2.0.2 - dev: true /@storybook/types@7.0.2: resolution: {integrity: sha512-0OCt/kAexa8MCcljxA+yZxGMn0n2U2Ync0KxotItqNbKBKVkaLQUls0+IXTWSCpC/QJvNZ049jxUHHanNi/96w==} @@ -5930,23 +6901,23 @@ packages: file-system-cache: 2.0.2 dev: true - /@storybook/vue3-vite@7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5)(vue@3.3.1): - resolution: {integrity: sha512-BbA6uLlNFIpSBW9UAJ9e96yCGGoMho0pogEbkzoRLdw/0OoqDqnRMue78CwW5eiIWXYjNZb3UwAyh9VgYqKk5g==} + /@storybook/vue3-vite@7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9)(vue@3.3.4): + resolution: {integrity: sha512-dwkwBQRDUSvf44Z4ZDftusP6obuczPkApxALxsTczkbpOxK/13SXArlrKgyUaFrcqto9i2e8HbAYb7y1ymO3ig==} engines: {node: ^14.18 || >=16} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 dependencies: - '@storybook/builder-vite': 7.0.10(typescript@5.0.4)(vite@4.3.5) - '@storybook/core-server': 7.0.10 - '@storybook/vue3': 7.0.10(vue@3.3.1) - '@vitejs/plugin-vue': 4.2.2(vite@4.3.5)(vue@3.3.1) + '@storybook/builder-vite': 7.0.18(typescript@5.1.3)(vite@4.3.9) + '@storybook/core-server': 7.0.18 + '@storybook/vue3': 7.0.18(vue@3.3.4) + '@vitejs/plugin-vue': 4.2.3(vite@4.3.9)(vue@3.3.4) magic-string: 0.27.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) - vue-docgen-api: 4.64.1(vue@3.3.1) + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) + vue-docgen-api: 4.64.1(vue@3.3.4) transitivePeerDependencies: - '@preact/preset-vite' - bufferutil @@ -5958,25 +6929,26 @@ packages: - vue dev: true - /@storybook/vue3@7.0.10(vue@3.3.1): - resolution: {integrity: sha512-B4DW/lR9Am06RJM3TGrIgIYzurG6tsgUX9EQ6rQRDFd4EWw1bskcG8MrNwFBBiDBnXe1frL4AdDidF47CFStNg==} + /@storybook/vue3@7.0.18(vue@3.3.4): + resolution: {integrity: sha512-++oC4Ee74ln9jPJSUnA6RWLxk5PNBGSP7lu71bA0b98MYsQ4GKliNEQf8lZmelSQy6nWoVHO0iyOhsKey7K3Ow==} engines: {node: '>=16.0.0'} peerDependencies: vue: ^3.0.0 dependencies: - '@storybook/core-client': 7.0.10 - '@storybook/docs-tools': 7.0.10 + '@storybook/core-client': 7.0.18 + '@storybook/docs-tools': 7.0.18 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.10 - '@storybook/types': 7.0.10 + '@storybook/preview-api': 7.0.18 + '@storybook/types': 7.0.18 ts-dedent: 2.2.0 type-fest: 2.19.0 - vue: 3.3.1 + vue: 3.3.4 + vue-component-type-helpers: 1.6.5 transitivePeerDependencies: - supports-color dev: true - /@swc/cli@0.1.62(@swc/core@1.3.56)(chokidar@3.5.3): + /@swc/cli@0.1.62(@swc/core@1.3.61)(chokidar@3.5.3): resolution: {integrity: sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==} engines: {node: '>= 12.13'} hasBin: true @@ -5988,11 +6960,11 @@ packages: optional: true dependencies: '@mole-inc/bin-wrapper': 8.0.1 - '@swc/core': 1.3.56 + '@swc/core': 1.3.61 chokidar: 3.5.3 commander: 7.2.0 fast-glob: 3.2.12 - semver: 7.5.0 + semver: 7.5.1 slash: 3.0.0 source-map: 0.7.4 dev: false @@ -6016,6 +6988,14 @@ packages: requiresBuild: true optional: true + /@swc/core-darwin-arm64@1.3.61: + resolution: {integrity: sha512-Ra1CZIYYyIp/Y64VcKyaLjIPUwT83JmGduvHu8vhUZOvWV4dWL4s5DrcxQVaQJjjb7Z2N/IUYYS55US1TGnxZw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + /@swc/core-darwin-x64@1.3.56: resolution: {integrity: sha512-VH5saqYFasdRXJy6RAT+MXm0+IjkMZvOkohJwUei+oA65cKJofQwrJ1jZro8yOJFYvUSI3jgNRGsdBkmo/4hMw==} engines: {node: '>=10'} @@ -6024,6 +7004,14 @@ packages: requiresBuild: true optional: true + /@swc/core-darwin-x64@1.3.61: + resolution: {integrity: sha512-LUia75UByUFkYH1Ddw7IE0X9usNVGJ7aL6+cgOTju7P0dsU0f8h/OGc/GDfp1E4qnKxDCJE+GwDRLoi4SjIxpg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + /@swc/core-linux-arm-gnueabihf@1.3.56: resolution: {integrity: sha512-LWwPo6NnJkH01+ukqvkoNIOpMdw+Zundm4vBeicwyVrkP+mC3kwVfi03TUFpQUz3kRKdw/QEnxGTj+MouCPbtw==} engines: {node: '>=10'} @@ -6032,6 +7020,14 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm-gnueabihf@1.3.61: + resolution: {integrity: sha512-aalPlicYxHAn2PxNlo3JFEZkMXzCtUwjP27AgMqnfV4cSz7Omo56OtC+413e/kGyCH86Er9gJRQQsxNKP8Qbsg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + /@swc/core-linux-arm64-gnu@1.3.56: resolution: {integrity: sha512-GzsUy/4egJ4cMlxbM+Ub7AMi5CKAc+pxBxrh8MUPQbyStW8jGgnQsJouTnGy0LHawtdEnsCOl6PcO6OgvktXuQ==} engines: {node: '>=10'} @@ -6040,6 +7036,14 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm64-gnu@1.3.61: + resolution: {integrity: sha512-9hGdsbQrYNPo1c7YzWF57yl17bsIuuEQi3I1fOFSv3puL3l5M/C/oCD0Bz6IdKh6mEDC5UNJE4LWtV1gFA995A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@swc/core-linux-arm64-musl@1.3.56: resolution: {integrity: sha512-9gxL09BIiAv8zY0DjfnFf19bo8+P4T9tdhzPwcm+1yPJcY5yr1+YFWLNFzz01agtOj6VlZ2/wUJTaOfdjjtc+A==} engines: {node: '>=10'} @@ -6048,6 +7052,14 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-arm64-musl@1.3.61: + resolution: {integrity: sha512-mVmcNfFQRP4SYbGC08IPB3B9Xox+VpGIQqA3Qg7LMCcejLAQLi4Lfe8CDvvBPlQzXHso0Cv+BicJnQVKs8JLOA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@swc/core-linux-x64-gnu@1.3.56: resolution: {integrity: sha512-n0ORNknl50vMRkll3BDO1E4WOqY6iISlPV1ZQCRLWQ6YQ2q8/WAryBxc2OAybcGHBUFkxyACpJukeU1QZ/9tNw==} engines: {node: '>=10'} @@ -6056,6 +7068,14 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-x64-gnu@1.3.61: + resolution: {integrity: sha512-ZkRHs7GEikN6JiVL1/stvq9BVHKrSKoRn9ulVK2hMr+mAGNOKm3Y06NSzOO+BWwMaFOgnO2dWlszCUICsQ0kpg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@swc/core-linux-x64-musl@1.3.56: resolution: {integrity: sha512-r+D34WLAOAlJtfw1gaVWpHRwCncU9nzW9i7w9kSw4HpWYnHJOz54jLGSEmNsrhdTCz1VK2ar+V2ktFUsrlGlDA==} engines: {node: '>=10'} @@ -6064,6 +7084,14 @@ packages: requiresBuild: true optional: true + /@swc/core-linux-x64-musl@1.3.61: + resolution: {integrity: sha512-zK7VqQ5JlK20+7fxI4AgvIUckeZyX0XIbliGXNMR3i+39SJq1vs9scYEmq8VnAfvNdMU5BG+DewbFJlMfCtkxQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@swc/core-win32-arm64-msvc@1.3.56: resolution: {integrity: sha512-29Yt75Is6X24z3x8h/xZC1HnDPkPpyLH9mDQiM6Cuc0I9mVr1XSriPEUB2N/awf5IE4SA8c+3IVq1DtKWbkJIw==} engines: {node: '>=10'} @@ -6072,6 +7100,14 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-arm64-msvc@1.3.61: + resolution: {integrity: sha512-e9kVVPk5iVNhO41TvLvcExDHn5iATQ5/M4U7/CdcC7s0fK19TKSEUqkdoTLIJvHBFhgR7w3JJSErfnauO0xXoA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + /@swc/core-win32-ia32-msvc@1.3.56: resolution: {integrity: sha512-mplp0zbYDrcHtfvkniXlXdB04e2qIjz2Gq/XHKr4Rnc6xVORJjjXF91IemXKpavx2oZYJws+LNJL7UFQ8jyCdQ==} engines: {node: '>=10'} @@ -6080,6 +7116,14 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-ia32-msvc@1.3.61: + resolution: {integrity: sha512-7cJULfa6HvKqvFh6M/f7mKiNRhE2AjgFUCZfdOuy5r8vbtpk+qBK94TXwaDjJYDUGKzDVZw/tJ1eN4Y9n9Ls/Q==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + /@swc/core-win32-x64-msvc@1.3.56: resolution: {integrity: sha512-zp8MBnrw/bjdLenO/ifYzHrImSjKunqL0C2IF4LXYNRfcbYFh2NwobsVQMZ20IT0474lKRdlP8Oxdt+bHuXrzA==} engines: {node: '>=10'} @@ -6088,6 +7132,14 @@ packages: requiresBuild: true optional: true + /@swc/core-win32-x64-msvc@1.3.61: + resolution: {integrity: sha512-Jx8S+21WcKF/wlhW+sYpystWUyymDTEsbBpOgBRpXZelakVcNBCIIYSZOKW/A9PwWTpu6S8yvbs9nUOzKiVPqA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@swc/core@1.3.56: resolution: {integrity: sha512-yz/EeXT+PMZucUNrYceRUaTfuNS4IIu5EDZSOlvCEvm4jAmZi7CYH1B/kvzEzoAOzr7zkQiDPNJftcQXLkjbjA==} engines: {node: '>=10'} @@ -6109,6 +7161,27 @@ packages: '@swc/core-win32-ia32-msvc': 1.3.56 '@swc/core-win32-x64-msvc': 1.3.56 + /@swc/core@1.3.61: + resolution: {integrity: sha512-p58Ltdjo7Yy8CU3zK0cp4/eAgy5qkHs35znGedqVGPiA67cuYZM63DuTfmyrOntMRwQnaFkMLklDAPCizDdDng==} + engines: {node: '>=10'} + requiresBuild: true + peerDependencies: + '@swc/helpers': ^0.5.0 + peerDependenciesMeta: + '@swc/helpers': + optional: true + optionalDependencies: + '@swc/core-darwin-arm64': 1.3.61 + '@swc/core-darwin-x64': 1.3.61 + '@swc/core-linux-arm-gnueabihf': 1.3.61 + '@swc/core-linux-arm64-gnu': 1.3.61 + '@swc/core-linux-arm64-musl': 1.3.61 + '@swc/core-linux-x64-gnu': 1.3.61 + '@swc/core-linux-x64-musl': 1.3.61 + '@swc/core-win32-arm64-msvc': 1.3.61 + '@swc/core-win32-ia32-msvc': 1.3.61 + '@swc/core-win32-x64-msvc': 1.3.61 + /@swc/jest@0.2.26(@swc/core@1.3.56): resolution: {integrity: sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==} engines: {npm: '>= 7.0.0'} @@ -6120,14 +7193,25 @@ packages: jsonc-parser: 3.2.0 dev: true + /@swc/jest@0.2.26(@swc/core@1.3.61): + resolution: {integrity: sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==} + engines: {npm: '>= 7.0.0'} + peerDependencies: + '@swc/core': '*' + dependencies: + '@jest/create-cache-key-function': 27.5.1 + '@swc/core': 1.3.61 + jsonc-parser: 3.2.0 + dev: true + /@swc/wasm@1.2.130: resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==} requiresBuild: true dev: false optional: true - /@syuilo/aiscript@0.13.2: - resolution: {integrity: sha512-1aqQSH6U+vV01UDUotXUEjIwJKcZZPASJyIJ9msxXRpSInPGJJ/q1kGkZMgSpVhzYFT7/QBxo0UC1ZVEOsrDTw==} + /@syuilo/aiscript@0.13.3: + resolution: {integrity: sha512-0YFlWA+7YhyRRsp+9Nl72SoSUg5ghskthjCdLvj4qdGyLedeyanKZWJlH2A9d47Nes03UYY8CRDsMHHv64IWcg==} dependencies: autobind-decorator: 2.4.0 seedrandom: 3.0.5 @@ -6148,14 +7232,14 @@ packages: dependencies: defer-to-connect: 2.0.1 - /@tabler/icons-webfont@2.17.0: - resolution: {integrity: sha512-jgRZWiWCaG++jFTIU/dbOT+JmSgoFlALwBUUS31mt1b5py7B0YWelnfxf5s3ctE+0dlnoIS+r7rDOeDSAWx8SA==} + /@tabler/icons-webfont@2.21.0: + resolution: {integrity: sha512-WCa57zYBjD9NF3/g96WKePgKUkKKD95Y+mo27/fzXOGxuoP9lGRjd01UCeLTGVxdEPErwlCjHXSi8HoDX2jevg==} dependencies: - '@tabler/icons': 2.17.0 + '@tabler/icons': 2.21.0 dev: false - /@tabler/icons@2.17.0: - resolution: {integrity: sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==} + /@tabler/icons@2.21.0: + resolution: {integrity: sha512-XKrTEHMX6XzCOwcOU8ZNA+Xqm51sI+0abn2jk1fyQUpWeFnGsOEiC+fpQ4EISc+v+U9jqgTSbh8bZ6JBuKU5sw==} dev: false /@tensorflow/tfjs-backend-cpu@4.4.0(@tensorflow/tfjs-core@4.4.0): @@ -6215,7 +7299,7 @@ packages: dependencies: '@tensorflow/tfjs-core': 4.4.0 '@types/node-fetch': 2.6.2 - node-fetch: 2.6.7 + node-fetch: 2.6.11 seedrandom: 3.0.5 string_decoder: 1.3.0 transitivePeerDependencies: @@ -6274,7 +7358,7 @@ packages: resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==} engines: {node: '>=12'} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 '@babel/runtime': 7.21.0 '@types/aria-query': 5.0.1 aria-query: 5.1.3 @@ -6288,7 +7372,7 @@ packages: resolution: {integrity: sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==} engines: {node: '>=14'} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 '@babel/runtime': 7.21.0 '@types/aria-query': 5.0.1 aria-query: 5.1.3 @@ -6304,7 +7388,7 @@ packages: dependencies: '@adobe/css-tools': 4.2.0 '@babel/runtime': 7.20.7 - '@types/testing-library__jest-dom': 5.14.5 + '@types/testing-library__jest-dom': 5.14.6 aria-query: 5.1.3 chalk: 3.0.0 css.escape: 1.5.1 @@ -6323,7 +7407,7 @@ packages: '@testing-library/dom': 8.20.0 dev: true - /@testing-library/vue@7.0.0(@vue/compiler-sfc@3.3.1)(vue@3.3.1): + /@testing-library/vue@7.0.0(@vue/compiler-sfc@3.3.4)(vue@3.3.4): resolution: {integrity: sha512-JU/q93HGo2qdm1dCgWymkeQlfpC0/0/DBZ2nAHgEAsVZxX11xVIxT7gbXdI7HACQpUbsUWt1zABGU075Fzt9XQ==} engines: {node: '>=14'} peerDependencies: @@ -6332,9 +7416,9 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@testing-library/dom': 9.2.0 - '@vue/compiler-sfc': 3.3.1 - '@vue/test-utils': 2.3.2(vue@3.3.1) - vue: 3.3.1 + '@vue/compiler-sfc': 3.3.4 + '@vue/test-utils': 2.3.2(vue@3.3.4) + vue: 3.3.4 dev: true /@tokenizer/token@0.3.0: @@ -6353,7 +7437,7 @@ packages: /@types/accepts@1.3.5: resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/archiver@5.3.2: @@ -6373,31 +7457,27 @@ packages: /@types/babel__core@7.20.0: resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} dependencies: - '@babel/parser': 7.21.4 - '@babel/types': 7.21.4 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.18.3 - dev: true + '@types/babel__traverse': 7.20.0 /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.21.5 - dev: true + '@babel/types': 7.22.4 /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 - dev: true + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 - /@types/babel__traverse@7.18.3: - resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} + /@types/babel__traverse@7.20.0: + resolution: {integrity: sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==} dependencies: - '@babel/types': 7.21.5 - dev: true + '@babel/types': 7.22.4 /@types/bcryptjs@2.4.2: resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==} @@ -6407,44 +7487,35 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.1.3 - dev: true + '@types/node': 20.2.5 /@types/braces@3.0.1: resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==} dev: true - /@types/bull@4.10.0: - resolution: {integrity: sha512-RkYW8K2H3J76HT6twmHYbzJ0GtLDDotpLP9ah9gtiA7zfF6peBH1l5fEiK0oeIZ3/642M7Jcb9sPmor8Vf4w6g==} - dependencies: - bull: 4.10.4 - transitivePeerDependencies: - - supports-color - dev: true - /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/responselike': 1.0.0 dev: false /@types/cbor@6.0.0: resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==} dependencies: - cbor: 8.1.0 + cbor: 9.0.0 dev: true /@types/chai-subset@1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: - '@types/chai': 4.3.4 + '@types/chai': 4.3.5 dev: true - /@types/chai@4.3.4: - resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} + /@types/chai@4.3.5: + resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} dev: true /@types/color-convert@2.0.0: @@ -6460,8 +7531,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.1.3 - dev: true + '@types/node': 20.2.5 /@types/content-disposition@0.5.5: resolution: {integrity: sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==} @@ -6525,10 +7595,9 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 - dev: true /@types/express@4.17.17: resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} @@ -6537,7 +7606,6 @@ packages: '@types/express-serve-static-core': 4.17.33 '@types/qs': 6.9.7 '@types/serve-static': 1.15.1 - dev: true /@types/find-cache-dir@3.2.1: resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} @@ -6546,34 +7614,34 @@ packages: /@types/fluent-ffmpeg@2.1.21: resolution: {integrity: sha512-+n3dy/Tegt6n+YwGZUiGq6i8Jrnt8+MoyPiW1L6J5EWUl7GSt18a/VyReecfCsvTTNBXNMIKOMHDstiQM8nJLA==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/glob-stream@6.1.1: resolution: {integrity: sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==} dependencies: '@types/glob': 8.1.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/glob@8.1.0: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/gulp-rename@2.0.1: @@ -6586,7 +7654,7 @@ packages: /@types/gulp-rename@2.0.2: resolution: {integrity: sha512-CQsXqTVtAXqrPd4IbrrlJEEzRkUR3RXsyZbrVoOVqjlchDDmnyRDatAUisjpQjjCg/wjJrSiNg8T1uAbJ/7Qqg==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/vinyl': 2.0.7 dev: true @@ -6630,6 +7698,13 @@ packages: pretty-format: 29.5.0 dev: true + /@types/jest@29.5.2: + resolution: {integrity: sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==} + dependencies: + expect: 29.5.0 + pretty-format: 29.5.0 + dev: true + /@types/js-levenshtein@1.1.1: resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==} dev: true @@ -6641,7 +7716,7 @@ packages: /@types/jsdom@21.1.1: resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/tough-cookie': 4.0.2 parse5: 7.1.2 dev: true @@ -6665,7 +7740,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: false /@types/lodash@4.14.191: @@ -6676,8 +7751,8 @@ packages: resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} dev: false - /@types/matter-js@0.18.3: - resolution: {integrity: sha512-7DYI52ebEl6AD9+RV2jO/XHdlFlpozYbkURtYKKJ2tO34q49Y15cfl+JSJpoMglQCAL/PxBSHKVv3wkvfZZD7g==} + /@types/matter-js@0.18.5: + resolution: {integrity: sha512-CV8m/FUmjmFNFcI7fUnsKcCLeqbf0kzWdKOTLGrpfKwWwrF6ggLaQlHNsg8267TkkiUAPoXY/7q6H9qwmR5TZg==} dev: true /@types/mdx@2.0.3: @@ -6696,7 +7771,6 @@ packages: /@types/mime@3.0.1: resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} - dev: true /@types/minimatch@5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -6713,7 +7787,7 @@ packages: /@types/node-fetch@2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 form-data: 3.0.1 /@types/node-fetch@3.0.3: @@ -6741,13 +7815,13 @@ packages: resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==} dev: true - /@types/node@20.1.3: - resolution: {integrity: sha512-NP2yfZpgmf2eDRPmgGq+fjGjSwFgYbihA8/gK+ey23qT9RkxsgNTZvGOEpXgzIGqesTYkElELLgtKoMQTys5vA==} + /@types/node@20.2.5: + resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==} - /@types/nodemailer@6.4.7: - resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==} + /@types/nodemailer@6.4.8: + resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/normalize-package-data@2.4.1: @@ -6761,7 +7835,7 @@ packages: /@types/oauth@0.9.1: resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/offscreencanvas@2019.3.0: @@ -6772,12 +7846,12 @@ packages: resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==} dev: false - /@types/pg@8.6.6: - resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + /@types/pg@8.10.1: + resolution: {integrity: sha512-AmEHA/XxMxemQom5iDwP62FYNkv+gDDnetRG7v2N2dPtju7UKI7FknUimcZo7SodKTHtckYPzaTqUEvUKbVJEA==} dependencies: - '@types/node': 20.1.3 - pg-protocol: 1.5.0 - pg-types: 2.2.0 + '@types/node': 20.2.5 + pg-protocol: 1.6.0 + pg-types: 4.0.1 dev: true /@types/prettier@2.7.2: @@ -6803,12 +7877,11 @@ packages: /@types/qrcode@1.5.0: resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/qs@6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - dev: true /@types/random-seed@0.3.3: resolution: {integrity: sha512-kHsCbIRHNXJo6EN5W8EA5b4i1hdT6jaZke5crBPLUcLqaLdZ0QBq8QVMbafHzhjFF83Cl9qlee2dChD18d/kPg==} @@ -6816,7 +7889,6 @@ packages: /@types/range-parser@1.2.4: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - dev: true /@types/ratelimiter@3.4.4: resolution: {integrity: sha512-GSMb93iSA8KKFDgVL2Wzs/kqrHMJcU8xhLdwI5omoACcj7K18SacklLtY1C4G02HC5drd6GygtsIaGbfxJSe0g==} @@ -6833,7 +7905,7 @@ packages: /@types/readdir-glob@1.1.1: resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/redis@4.0.11: @@ -6849,7 +7921,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: false /@types/sanitize-html@2.9.0: @@ -6877,8 +7949,7 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 20.1.3 - dev: true + '@types/node': 20.2.5 /@types/serviceworker@0.0.67: resolution: {integrity: sha512-7TCH7iNsCSNb+aUD9M/36TekrWFSLCjNK8zw/3n5kOtRjbLtDfGYMXTrDnGhSfqXNwpqmt9Vd90w5C/ad1tX6Q==} @@ -6887,7 +7958,7 @@ packages: /@types/set-cookie-parser@2.4.2: resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/sharp@0.32.0: @@ -6919,10 +7990,10 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true - /@types/testing-library__jest-dom@5.14.5: - resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==} + /@types/testing-library__jest-dom@5.14.6: + resolution: {integrity: sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA==} dependencies: - '@types/jest': 29.5.1 + '@types/jest': 29.5.2 dev: true /@types/throttle-debounce@5.0.0: @@ -6952,7 +8023,7 @@ packages: /@types/undertaker@1.2.8: resolution: {integrity: sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/undertaker-registry': 1.0.1 async-done: 1.3.2 dev: true @@ -6961,10 +8032,10 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true - /@types/unzipper@0.10.5: - resolution: {integrity: sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA==} + /@types/unzipper@0.10.6: + resolution: {integrity: sha512-zcBj329AHgKLQyz209N/S9R0GZqXSkUQO4tJSYE3x02qg4JuDFpgKMj50r82Erk1natCWQDIvSccDddt7jPzjA==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/uuid@9.0.1: @@ -6974,14 +8045,14 @@ packages: /@types/vary@1.1.0: resolution: {integrity: sha512-LQWqrIa0dvEOOH37lGksMEXbypRLUFqu6Gx0pmX7zIUisD2I/qaVgEX/vJ/PSVSW0Hk6yz1BNkFpqg6dZm3Wug==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/vinyl-fs@2.4.12: resolution: {integrity: sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==} dependencies: '@types/glob-stream': 6.1.1 - '@types/node': 20.1.3 + '@types/node': 20.2.5 '@types/vinyl': 2.0.7 dev: true @@ -6989,12 +8060,12 @@ packages: resolution: {integrity: sha512-4UqPv+2567NhMQuMLdKAyK4yzrfCqwaTt6bLhHEs8PFcxbHILsrxaY63n4wgE/BRLDWDQeI+WcTmkXKExh9hQg==} dependencies: '@types/expect': 1.20.4 - '@types/node': 20.1.3 + '@types/node': 20.2.5 /@types/web-push@3.3.2: resolution: {integrity: sha512-JxWGVL/m7mWTIg4mRYO+A6s0jPmBkr4iJr39DqJpRJAc+jrPiEe1/asmkwerzRon8ZZDxaZJpsxpv0Z18Wo9gw==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/webgl-ext@0.0.30: @@ -7008,13 +8079,13 @@ packages: /@types/websocket@1.0.5: resolution: {integrity: sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/ws@8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /@types/yargs-parser@21.0.0: @@ -7037,7 +8108,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true optional: true @@ -7069,6 +8140,34 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3): + resolution: {integrity: sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.5.0 + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.1.3) + '@typescript-eslint/scope-manager': 5.59.8 + '@typescript-eslint/type-utils': 5.59.8(eslint@8.41.0)(typescript@5.1.3) + '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.1.3) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.41.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + semver: 7.5.1 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.59.5(eslint@8.40.0)(typescript@5.0.4): resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7089,6 +8188,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@5.59.8(eslint@8.41.0)(typescript@5.1.3): + resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.8 + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.1.3) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.41.0 + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.59.5: resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7097,6 +8216,14 @@ packages: '@typescript-eslint/visitor-keys': 5.59.5 dev: true + /@typescript-eslint/scope-manager@5.59.8: + resolution: {integrity: sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/visitor-keys': 5.59.8 + dev: true + /@typescript-eslint/type-utils@5.59.5(eslint@8.40.0)(typescript@5.0.4): resolution: {integrity: sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7117,13 +8244,59 @@ packages: - supports-color dev: true - /@typescript-eslint/types@5.59.5: - resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@typescript-eslint/typescript-estree@5.59.5(typescript@5.0.4): - resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} + /@typescript-eslint/type-utils@5.59.8(eslint@8.41.0)(typescript@5.1.3): + resolution: {integrity: sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.1.3) + '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.1.3) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.41.0 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@5.59.5: + resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/types@5.59.8: + resolution: {integrity: sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree@5.59.5(typescript@5.0.4): + resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/visitor-keys': 5.59.5 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.1 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@5.59.8(typescript@5.1.3): + resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -7131,14 +8304,14 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/visitor-keys': 5.59.5 + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/visitor-keys': 5.59.8 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.0 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + semver: 7.5.1 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - supports-color dev: true @@ -7157,7 +8330,27 @@ packages: '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.0.4) eslint: 8.40.0 eslint-scope: 5.1.1 - semver: 7.5.0 + semver: 7.5.1 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/utils@5.59.8(eslint@8.41.0)(typescript@5.1.3): + resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + '@types/json-schema': 7.0.11 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 5.59.8 + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.1.3) + eslint: 8.41.0 + eslint-scope: 5.1.1 + semver: 7.5.1 transitivePeerDependencies: - supports-color - typescript @@ -7171,78 +8364,86 @@ packages: eslint-visitor-keys: 3.4.1 dev: true - /@vitejs/plugin-react@3.1.0(vite@4.3.5): + /@typescript-eslint/visitor-keys@5.59.8: + resolution: {integrity: sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.8 + eslint-visitor-keys: 3.4.1 + dev: true + + /@vitejs/plugin-react@3.1.0(vite@4.3.9): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.1.0-beta.0 dependencies: - '@babel/core': 7.21.3 - '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.3) + '@babel/core': 7.22.1 + '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.22.1) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) transitivePeerDependencies: - supports-color dev: true - /@vitejs/plugin-vue@4.2.2(vite@4.3.5)(vue@3.3.1): - resolution: {integrity: sha512-kNH4wMAqs13UiZe/2If1ioO0Mjz71rr2oALTl2c5ajBIox9Vz/UGW/wGkr7GA3SC6Eb29c1HtzAtxdGfbXAkfQ==} + /@vitejs/plugin-vue@4.2.3(vite@4.3.9)(vue@3.3.4): + resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 vue: ^3.2.25 dependencies: - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) - vue: 3.3.1 + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) + vue: 3.3.4 - /@vitest/coverage-c8@0.31.0(vitest@0.31.0): - resolution: {integrity: sha512-h72qN1D962AO7UefQVulm9JFP5ACS7OfhCdBHioXU8f7ohH/+NTZCgAqmgcfRNHHO/8wLFxx+93YVxhodkEJVA==} + /@vitest/coverage-c8@0.31.4(vitest@0.31.4): + resolution: {integrity: sha512-VPx368m4DTcpA/P0v3YdVxl4QOSh1DbUcXURLRvDShrIB5KxOgfzw4Bn2R8AhAe/GyiWW/FIsJ/OJdYXCCiC1w==} peerDependencies: vitest: '>=0.30.0 <1' dependencies: - '@ampproject/remapping': 2.2.0 + '@ampproject/remapping': 2.2.1 c8: 7.13.0 magic-string: 0.30.0 picocolors: 1.0.0 std-env: 3.3.2 - vitest: 0.31.0(happy-dom@9.16.0)(sass@1.62.1) + vitest: 0.31.4(happy-dom@9.20.3)(sass@1.62.1) dev: true - /@vitest/expect@0.31.0: - resolution: {integrity: sha512-Jlm8ZTyp6vMY9iz9Ny9a0BHnCG4fqBa8neCF6Pk/c/6vkUk49Ls6UBlgGAU82QnzzoaUs9E/mUhq/eq9uMOv/g==} + /@vitest/expect@0.31.4: + resolution: {integrity: sha512-tibyx8o7GUyGHZGyPgzwiaPaLDQ9MMuCOrc03BYT0nryUuhLbL7NV2r/q98iv5STlwMgaKuFJkgBW/8iPKwlSg==} dependencies: - '@vitest/spy': 0.31.0 - '@vitest/utils': 0.31.0 + '@vitest/spy': 0.31.4 + '@vitest/utils': 0.31.4 chai: 4.3.7 dev: true - /@vitest/runner@0.31.0: - resolution: {integrity: sha512-H1OE+Ly7JFeBwnpHTrKyCNm/oZgr+16N4qIlzzqSG/YRQDATBYmJb/KUn3GrZaiQQyL7GwpNHVZxSQd6juLCgw==} + /@vitest/runner@0.31.4: + resolution: {integrity: sha512-Wgm6UER+gwq6zkyrm5/wbpXGF+g+UBB78asJlFkIOwyse0pz8lZoiC6SW5i4gPnls/zUcPLWS7Zog0LVepXnpg==} dependencies: - '@vitest/utils': 0.31.0 + '@vitest/utils': 0.31.4 concordance: 5.0.4 p-limit: 4.0.0 pathe: 1.1.0 dev: true - /@vitest/snapshot@0.31.0: - resolution: {integrity: sha512-5dTXhbHnyUMTMOujZPB0wjFjQ6q5x9c8TvAsSPUNKjp1tVU7i9pbqcKPqntyu2oXtmVxKbuHCqrOd+Ft60r4tg==} + /@vitest/snapshot@0.31.4: + resolution: {integrity: sha512-LemvNumL3NdWSmfVAMpXILGyaXPkZbG5tyl6+RQSdcHnTj6hvA49UAI8jzez9oQyE/FWLKRSNqTGzsHuk89LRA==} dependencies: magic-string: 0.30.0 pathe: 1.1.0 pretty-format: 27.5.1 dev: true - /@vitest/spy@0.31.0: - resolution: {integrity: sha512-IzCEQ85RN26GqjQNkYahgVLLkULOxOm5H/t364LG0JYb3Apg0PsYCHLBYGA006+SVRMWhQvHlBBCyuByAMFmkg==} + /@vitest/spy@0.31.4: + resolution: {integrity: sha512-3ei5ZH1s3aqbEyftPAzSuunGICRuhE+IXOmpURFdkm5ybUADk+viyQfejNk6q8M5QGX8/EVKw+QWMEP3DTJDag==} dependencies: tinyspy: 2.1.0 dev: true - /@vitest/utils@0.31.0: - resolution: {integrity: sha512-kahaRyLX7GS1urekRXN2752X4gIgOGVX4Wo8eDUGUkTWlGpXzf5ZS6N9RUUS+Re3XEE8nVGqNyxkSxF5HXlGhQ==} + /@vitest/utils@0.31.4: + resolution: {integrity: sha512-DobZbHacWznoGUfYU8XDPY78UubJxXfMNY1+SUdOp1NsI34eopSA6aZMeaGu10waSOeYwE8lxrd/pLfT0RMxjQ==} dependencies: concordance: 5.0.4 loupe: 2.3.6 @@ -7261,166 +8462,158 @@ packages: muggle-string: 0.2.2 dev: true - /@volar/typescript@1.4.1(typescript@5.0.4): - resolution: {integrity: sha512-phTy6p9yG6bgMIKQWEeDOi/aeT0njZsb1a/G1mrEuDsLmAn24Le4gDwSsGNhea6Uhu+3gdpUZn2PmZXa+WG2iQ==} + /@volar/typescript@1.4.1-patch.2(typescript@5.1.3): + resolution: {integrity: sha512-lPFYaGt8OdMEzNGJJChF40uYqMO4Z/7Q9fHPQC/NRVtht43KotSXLrkPandVVMf9aPbiJ059eAT+fwHGX16k4w==} peerDependencies: typescript: '*' dependencies: '@volar/language-core': 1.4.1 - typescript: 5.0.4 + typescript: 5.1.3 dev: true - /@volar/vue-language-core@1.6.4: - resolution: {integrity: sha512-1o+cAtN2DIDNAX/HS8rkjZc8wTMTK+zCab/qtYbvEVlmokhZiDrQeoD9/l0Ug7YCNg+mVuMNHKNBY7pX8U2/Jw==} + /@volar/vue-language-core@1.6.5: + resolution: {integrity: sha512-IF2b6hW4QAxfsLd5mePmLgtkXzNi+YnH6ltCd80gb7+cbdpFMjM1I+w+nSg2kfBTyfu+W8useCZvW89kPTBpzg==} dependencies: '@volar/language-core': 1.4.1 '@volar/source-map': 1.4.1 - '@vue/compiler-dom': 3.3.1 - '@vue/compiler-sfc': 3.3.1 - '@vue/reactivity': 3.3.1 - '@vue/shared': 3.3.1 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-sfc': 3.3.4 + '@vue/reactivity': 3.3.4 + '@vue/shared': 3.3.4 minimatch: 9.0.0 muggle-string: 0.2.2 vue-template-compiler: 2.7.14 dev: true - /@volar/vue-typescript@1.6.4(typescript@5.0.4): - resolution: {integrity: sha512-qKwgP0KVQR/aaH/SN3AP7RB8NnXPWDn3tjyXP6IT6etxkDeZLBLsXWUD9KMak/RvV1DgbXDuz4F9yuZlbt29rA==} + /@volar/vue-typescript@1.6.5(typescript@5.1.3): + resolution: {integrity: sha512-er9rVClS4PHztMUmtPMDTl+7c7JyrxweKSAEe/o/Noeq2bQx6v3/jZHVHBe8ZNUti5ubJL/+Tg8L3bzmlalV8A==} peerDependencies: typescript: '*' dependencies: - '@volar/typescript': 1.4.1(typescript@5.0.4) - '@volar/vue-language-core': 1.6.4 - typescript: 5.0.4 + '@volar/typescript': 1.4.1-patch.2(typescript@5.1.3) + '@volar/vue-language-core': 1.6.5 + typescript: 5.1.3 dev: true - /@vue-macros/common@1.3.1(rollup@3.21.6)(vue@3.3.1): - resolution: {integrity: sha512-Lc5aP/8HNJD1XrnvpeNuWcCf82bZdR3auN/chA1b/1rKZgSnmQkH9f33tKO9qLwXSy+u4hpCi8Rw+oUuF1KCeg==} - engines: {node: '>=14.19.0'} + /@vue-macros/common@1.3.3(rollup@3.23.0)(vue@3.3.4): + resolution: {integrity: sha512-bjHomaf3mu+ARMD4DX22C/lLVVocbmwgcLH7bg1rK4kB5ghesgShZTQIrNR6ZjifQmdGc/2jjZ/25kSb364uEA==} + engines: {node: '>=16.14.0'} peerDependencies: vue: ^2.7.0 || ^3.2.25 peerDependenciesMeta: vue: optional: true dependencies: - '@babel/types': 7.21.5 - '@rollup/pluginutils': 5.0.2(rollup@3.21.6) - '@vue/compiler-sfc': 3.3.1 + '@babel/types': 7.22.4 + '@rollup/pluginutils': 5.0.2(rollup@3.23.0) + '@vue/compiler-sfc': 3.3.4 local-pkg: 0.4.3 magic-string-ast: 0.1.2 - vue: 3.3.1 + vue: 3.3.4 transitivePeerDependencies: - rollup dev: false - /@vue-macros/reactivity-transform@0.3.6(rollup@3.21.6)(vue@3.3.1): - resolution: {integrity: sha512-PFJRXHEdIP03LeNnfcwjUk8pKWjvyeOZjCNwbLgfqunI9tknG4IQMfl86qswK83f+DoOTMCoeMFoUnmlbr+yUw==} - engines: {node: '>=14.19.0'} + /@vue-macros/reactivity-transform@0.3.9(rollup@3.23.0)(vue@3.3.4): + resolution: {integrity: sha512-lzzH2qzIxc1LWRrSR+ax0TVeBTgwTpG9qTZOo4Au+ODgJyXpIWHGCnc9rjcxGfu6LitjZ75NmyjbEnaEkomefw==} + engines: {node: '>=16.14.0'} peerDependencies: vue: ^2.7.0 || ^3.2.25 dependencies: - '@babel/parser': 7.21.8 - '@vue-macros/common': 1.3.1(rollup@3.21.6)(vue@3.3.1) - '@vue/compiler-core': 3.3.1 - '@vue/shared': 3.3.1 + '@babel/parser': 7.22.4 + '@vue-macros/common': 1.3.3(rollup@3.23.0)(vue@3.3.4) + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 magic-string: 0.30.0 unplugin: 1.3.1 - vue: 3.3.1 + vue: 3.3.4 transitivePeerDependencies: - rollup dev: false - /@vue/compiler-core@3.3.1: - resolution: {integrity: sha512-5le1qYSBgLWg2jdLrbydlhnPJkkzMw46UrRUvTnOKlfg6pThtm9ohhqBhNPHbr0RcM1MCbK5WZe/3Ghz0SZjpQ==} + /@vue/compiler-core@3.3.4: + resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} dependencies: - '@babel/parser': 7.21.8 - '@vue/shared': 3.3.1 + '@babel/parser': 7.22.4 + '@vue/shared': 3.3.4 estree-walker: 2.0.2 source-map-js: 1.0.2 - /@vue/compiler-dom@3.3.1: - resolution: {integrity: sha512-VmgIsoLivCft3+oNc5KM7b9wd0nZxP/g2qilMwi1hJyGA624KWnNKHn4hzBQs4FpzydUVpNy+TWVT8KiRCh3MQ==} + /@vue/compiler-dom@3.3.4: + resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==} dependencies: - '@vue/compiler-core': 3.3.1 - '@vue/shared': 3.3.1 + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 - /@vue/compiler-sfc@2.7.14: - resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} + /@vue/compiler-sfc@3.3.4: + resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} dependencies: '@babel/parser': 7.21.8 - postcss: 8.4.23 - source-map: 0.6.1 - dev: false - - /@vue/compiler-sfc@3.3.1: - resolution: {integrity: sha512-G+FPwBbXSLaA4+Ry5/bdD9Oda+sRslQcE9o6JSZaougRiT4OjVL0vtkbQHPrGRTULZV28OcrAjRfSZOSB0OTXQ==} - dependencies: - '@babel/parser': 7.21.4 - '@vue/compiler-core': 3.3.1 - '@vue/compiler-dom': 3.3.1 - '@vue/compiler-ssr': 3.3.1 - '@vue/reactivity-transform': 3.3.1 - '@vue/shared': 3.3.1 + '@vue/compiler-core': 3.3.4 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-ssr': 3.3.4 + '@vue/reactivity-transform': 3.3.4 + '@vue/shared': 3.3.4 estree-walker: 2.0.2 magic-string: 0.30.0 postcss: 8.4.23 source-map-js: 1.0.2 - /@vue/compiler-ssr@3.3.1: - resolution: {integrity: sha512-QOQWGNCWuSeyKx4KvWSJlnIMGg+/2oCHgkFUYo7aJ+9Uaaz45yRgKQ+FNigy50NYBQIhpXn2e4OSR8GXh4knrQ==} + /@vue/compiler-ssr@3.3.4: + resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==} dependencies: - '@vue/compiler-dom': 3.3.1 - '@vue/shared': 3.3.1 + '@vue/compiler-dom': 3.3.4 + '@vue/shared': 3.3.4 - /@vue/reactivity-transform@3.3.1: - resolution: {integrity: sha512-MkOrJauAGH4MNdxGW/PmrDegMyOGX0wGIdKUZJRBXOTpotDONg7/TPJe2QeGeBCow/5v9iOqZOWCfvmOWIaDMg==} + /@vue/reactivity-transform@3.3.4: + resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} dependencies: - '@babel/parser': 7.21.8 - '@vue/compiler-core': 3.3.1 - '@vue/shared': 3.3.1 + '@babel/parser': 7.22.4 + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 estree-walker: 2.0.2 magic-string: 0.30.0 - /@vue/reactivity@3.3.1: - resolution: {integrity: sha512-zCfmazOtyUdC1NS/EPiSYJ4RqojqmTAviJyBbyVvY8zAv5NhK44Yfw0E1tt+m5vz0ZO1ptI9jDKBr3MWIEkpgw==} + /@vue/reactivity@3.3.4: + resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} dependencies: - '@vue/shared': 3.3.1 + '@vue/shared': 3.3.4 - /@vue/runtime-core@3.3.1: - resolution: {integrity: sha512-Ljb37LYafhQqKIasc0r32Cva8gIh6VeSMjlwO6V03tCjHd18gmjP0F4UD+8/a59sGTysAgA8Rb9lIC2DVxRz2Q==} + /@vue/runtime-core@3.3.4: + resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==} dependencies: - '@vue/reactivity': 3.3.1 - '@vue/shared': 3.3.1 + '@vue/reactivity': 3.3.4 + '@vue/shared': 3.3.4 - /@vue/runtime-dom@3.3.1: - resolution: {integrity: sha512-NBjYbQPtMklb7lsJsM2Juv5Ygry6mvZP7PdH1GZqrzfLkvlplQT3qCtQMd/sib6yiy8t9m/Y4hVU7X9nzb9Oeg==} + /@vue/runtime-dom@3.3.4: + resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==} dependencies: - '@vue/runtime-core': 3.3.1 - '@vue/shared': 3.3.1 + '@vue/runtime-core': 3.3.4 + '@vue/shared': 3.3.4 csstype: 3.1.1 - /@vue/server-renderer@3.3.1(vue@3.3.1): - resolution: {integrity: sha512-sod8ggOwbkQXw3lBjfzrbdxRS9lw/lNHoMaXghHawNYowf+4WoaLWD5ouz6fPZadUqNKAsqK95p8DYb1vcVfPA==} + /@vue/server-renderer@3.3.4(vue@3.3.4): + resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==} peerDependencies: - vue: 3.3.1 + vue: 3.3.4 dependencies: - '@vue/compiler-ssr': 3.3.1 - '@vue/shared': 3.3.1 - vue: 3.3.1 + '@vue/compiler-ssr': 3.3.4 + '@vue/shared': 3.3.4 + vue: 3.3.4 - /@vue/shared@3.3.1: - resolution: {integrity: sha512-ybDBtQ+479HL/bkeIOIAwgpeAEACzztkvulJLbK3JMFuTOv4qDivmV3AIsR8RHYJ+RD9tQxcHWBsX4GqEcYrfw==} + /@vue/shared@3.3.4: + resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} - /@vue/test-utils@2.3.2(vue@3.3.1): + /@vue/test-utils@2.3.2(vue@3.3.4): resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==} peerDependencies: vue: ^3.0.1 dependencies: js-beautify: 1.14.6 - vue: 3.3.1 + vue: 3.3.4 optionalDependencies: - '@vue/compiler-dom': 3.3.1 - '@vue/server-renderer': 3.3.1(vue@3.3.1) + '@vue/compiler-dom': 3.3.4 + '@vue/server-renderer': 3.3.4(vue@3.3.4) dev: true /@webgpu/types@0.1.30: @@ -7461,7 +8654,7 @@ packages: p-limit: 2.3.0 pluralize: 7.0.0 pretty-bytes: 5.6.0 - semver: 7.5.0 + semver: 7.5.1 stream-to-promise: 2.2.0 tar-stream: 2.2.0 treeify: 1.1.0 @@ -7476,7 +8669,7 @@ packages: esbuild: '>=0.10.0' dependencies: esbuild: 0.17.18 - tslib: 2.5.0 + tslib: 2.5.2 dev: true /@yarnpkg/fslib@2.10.2: @@ -7570,13 +8763,6 @@ packages: mime-types: 2.1.35 negotiator: 0.6.3 - /acorn-globals@7.0.1: - resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} - dependencies: - acorn: 8.8.2 - acorn-walk: 8.2.0 - dev: false - /acorn-jsx@5.3.2(acorn@7.4.1): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -7601,6 +8787,7 @@ packages: /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} + dev: true /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} @@ -7750,7 +8937,6 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 - dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -8015,7 +9201,6 @@ packages: is-nan: 1.3.2 object-is: 1.1.5 util: 0.12.5 - dev: true /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -8029,32 +9214,31 @@ packages: resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} engines: {node: '>=4'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: true /ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: true /ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} dependencies: - tslib: 2.5.0 - dev: true + tslib: 2.5.2 /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} dev: true - /astring@1.8.4: - resolution: {integrity: sha512-97a+l2LBU3Op3bBQEff79i/E4jMD2ZLFD8rHx9B6mXyB2uQwhJQYfiDqUwtfjF4QA1F2qs//N6Cw8LetMbQjcw==} + /astring@1.8.6: + resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} hasBin: true - dev: true + dev: false /async-done@1.3.2: resolution: {integrity: sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==} @@ -8113,8 +9297,8 @@ packages: postcss-value-parser: 3.3.1 dev: false - /autosize@5.0.2: - resolution: {integrity: sha512-FPVt5ynkqUAA9gcMZnJHka1XfQgr1WNd/yRfIjmj5WGmjua+u5Hl9hn8M2nU5CNy2bEIcj1ZUwXq7IOHsfZG9w==} + /autosize@6.0.1: + resolution: {integrity: sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==} dev: false /autwh@0.1.0: @@ -8126,7 +9310,6 @@ packages: /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - dev: true /avvio@8.2.0: resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==} @@ -8169,12 +9352,12 @@ packages: - debug dev: true - /babel-core@7.0.0-bridge.0(@babel/core@7.21.3): + /babel-core@7.0.0-bridge.0(@babel/core@7.22.1): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.22.1 dev: true /babel-jest@29.5.0(@babel/core@7.21.3): @@ -8212,10 +9395,10 @@ packages: resolution: {integrity: sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.21.5 + '@babel/template': 7.21.9 + '@babel/types': 7.22.4 '@types/babel__core': 7.20.0 - '@types/babel__traverse': 7.18.3 + '@types/babel__traverse': 7.20.0 dev: true /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.21.3): @@ -8223,7 +9406,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.21.4 + '@babel/compat-data': 7.22.3 '@babel/core': 7.21.3 '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.3) semver: 6.3.0 @@ -8231,6 +9414,19 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.22.1): + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.22.3 + '@babel/core': 7.22.1 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.22.1) + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.21.3): resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} peerDependencies: @@ -8243,6 +9439,18 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.22.1): + resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.22.1) + core-js-compat: 3.29.1 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.21.3): resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} peerDependencies: @@ -8254,6 +9462,17 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.22.1): + resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.1 + '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.22.1) + transitivePeerDependencies: + - supports-color + dev: true + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.21.3): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: @@ -8274,6 +9493,26 @@ packages: '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.3) dev: true + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.1): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.22.1 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.1) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.1) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.1) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.1) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.1) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.1) + dev: true + /babel-preset-jest@29.5.0(@babel/core@7.21.3): resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8289,7 +9528,7 @@ packages: resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.22.4 /bach@1.2.0: resolution: {integrity: sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==} @@ -8361,7 +9600,7 @@ packages: engines: {node: '>=12'} dependencies: bin-version: 6.0.0 - semver: 7.5.0 + semver: 7.5.1 semver-truncate: 2.0.0 dev: false @@ -8507,10 +9746,10 @@ packages: dependencies: fill-range: 7.0.1 - /broadcast-channel@4.20.2: - resolution: {integrity: sha512-v0lJgMzC+MX4e2KCFWYXChZ2mKTqm5mnJGId6tqJp3NfylggbNd8c2uKeP4MQxD2ucKOesY68aN98zwl9d6Tvg==} + /broadcast-channel@5.1.0: + resolution: {integrity: sha512-wAbP+mtQ28N+iX3scX6Q97UN39ER5jRWOtM3r1BNPLWFOMt3AGmwN9kS3fqwgaUW0tbWHRSfTpsT+pAvrzQz0Q==} dependencies: - '@babel/runtime': 7.20.7 + '@babel/runtime': 7.21.0 oblivious-set: 1.1.1 p-queue: 6.6.2 rimraf: 3.0.2 @@ -8609,22 +9848,21 @@ packages: requiresBuild: true dependencies: node-gyp-build: 4.6.0 - dev: false - /bull@4.10.4: - resolution: {integrity: sha512-o9m/7HjS/Or3vqRd59evBlWCXd9Lp+ALppKseoSKHaykK46SmRjAilX98PgmOz1yeVaurt8D5UtvEt4bUjM3eA==} - engines: {node: '>=12'} + /bullmq@3.15.0: + resolution: {integrity: sha512-U0LSRjuoyIBpnE62T4maCWMYEt3qdBCa1lnlPxYKQmRF/Y+FQ9W6iW5JvNNN+NA5Jet7k0uX71a93EX1zGnrhw==} dependencies: - cron-parser: 4.7.1 - debuglog: 1.0.1 - get-port: 5.1.1 + cron-parser: 4.8.1 + glob: 8.1.0 ioredis: 5.3.2 lodash: 4.17.21 - msgpackr: 1.8.1 - semver: 7.5.0 - uuid: 8.3.2 + msgpackr: 1.9.2 + semver: 7.5.1 + tslib: 2.5.2 + uuid: 9.0.0 transitivePeerDependencies: - supports-color + dev: false /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -8813,9 +10051,9 @@ packages: /caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - /cbor@8.1.0: - resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} - engines: {node: '>=12.19'} + /cbor@9.0.0: + resolution: {integrity: sha512-87cFgOKxjUOnGpNeQMBVER4Mc/rZAk9xC+Ygfx5FLCAUt/tpVHphuZC5fJmp/KSDsEsBEDIPtEt0YbD/GFQw8Q==} + engines: {node: '>=16'} dependencies: nofilter: 3.1.0 @@ -8863,7 +10101,6 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true /chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} @@ -9022,11 +10259,12 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - /chromatic@6.17.4: - resolution: {integrity: sha512-vnlvsv2lkp8BVtTn1OumJzqkDk2qB3pcGxEDIfZtVboKtzIPjnIlGa+c1fVKQe8NvHDU8R39k8klqgKHIXUVJw==} + /chromatic@6.18.0: + resolution: {integrity: sha512-Sj7xMFGQ6jSTBrsdgMMjSQAP2OMNogg4GXV4djf4kAp6Dp+pY4FwByIagvbtQRjC33kQVi592FS52vMBOBMEzw==} hasBin: true dependencies: '@discoveryjs/json-ext': 0.5.7 + '@storybook/csf-tools': 7.0.18 '@types/webpack-env': 1.18.0 snyk-nodejs-lockfile-parser: 1.49.0 transitivePeerDependencies: @@ -9209,6 +10447,7 @@ packages: /cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + dev: false /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} @@ -9414,7 +10653,7 @@ packages: js-string-escape: 1.0.1 lodash: 4.17.21 md5-hex: 3.0.1 - semver: 7.5.0 + semver: 7.5.1 well-known-symbols: 2.0.0 dev: true @@ -9435,8 +10674,8 @@ packages: /constantinople@4.0.1: resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} dependencies: - '@babel/parser': 7.21.4 - '@babel/types': 7.21.4 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} @@ -9510,11 +10749,12 @@ packages: readable-stream: 3.6.0 dev: false - /cron-parser@4.7.1: - resolution: {integrity: sha512-WguFaoQ0hQ61SgsCZLHUcNbAvlK0lypKXu62ARguefYmjzaOXIVRNrAmyXzabTwUn4sQvQLkk6bjH+ipGfw8bA==} + /cron-parser@4.8.1: + resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==} engines: {node: '>=12.0.0'} dependencies: - luxon: 3.2.1 + luxon: 3.3.0 + dev: false /cropperjs@2.0.0-beta.2: resolution: {integrity: sha512-jDRSODDGKmi9vp3p/+WXkxMqV/AE+GpSld1U3cHZDRdLy9UykRzurSe8k1dR0TExn45ygCMrv31qkg+K3EeXXw==} @@ -9536,6 +10776,15 @@ packages: node-fetch: 2.6.7 transitivePeerDependencies: - encoding + dev: true + + /cross-fetch@3.1.6: + resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==} + dependencies: + node-fetch: 2.6.11 + transitivePeerDependencies: + - encoding + dev: false /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -9639,18 +10888,14 @@ packages: /csstype@3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} - /custom-event-polyfill@1.0.7: - resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==} - dev: false - /cwise-compiler@1.1.3: resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==} dependencies: uniq: 1.0.1 dev: false - /cypress@12.12.0: - resolution: {integrity: sha512-UU5wFQ7SMVCR/hyKok/KmzG6fpZgBHHfrXcHzDmPHWrT+UUetxFzQgt7cxCszlwfozckzwkd22dxMwl/vNkWRw==} + /cypress@12.13.0: + resolution: {integrity: sha512-QJlSmdPk+53Zhy69woJMySZQJoWfEWun3X5OOenGsXjRPVfByVTHorxNehbzhZrEzH9RDUDqVcck0ahtlS+N/Q==} engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0} hasBin: true requiresBuild: true @@ -9692,7 +10937,7 @@ packages: pretty-bytes: 5.6.0 proxy-from-env: 1.0.0 request-progress: 3.0.0 - semver: 7.5.0 + semver: 7.5.1 supports-color: 8.1.1 tmp: 0.2.1 untildify: 4.0.0 @@ -9784,9 +11029,6 @@ packages: ms: 2.1.2 supports-color: 8.1.1 - /debuglog@1.0.1: - resolution: {integrity: sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==} - /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -9880,6 +11122,7 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true /deepmerge@4.2.2: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} @@ -9978,6 +11221,7 @@ packages: /denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + dev: false /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} @@ -10240,10 +11484,6 @@ packages: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: false - /entities@4.4.0: - resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} - engines: {node: '>=0.12'} - /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -10351,7 +11591,6 @@ packages: /es6-object-assign@1.1.0: resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} - dev: true /es6-promise@4.2.8: resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} @@ -10464,6 +11703,7 @@ packages: optionator: 0.8.3 optionalDependencies: source-map: 0.6.1 + dev: true /eslint-formatter-pretty@4.1.0: resolution: {integrity: sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==} @@ -10518,6 +11758,35 @@ packages: - supports-color dev: true + /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0): + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.1.3) + debug: 3.2.7(supports-color@8.1.1) + eslint: 8.41.0 + eslint-import-resolver-node: 0.3.7 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} @@ -10551,58 +11820,140 @@ packages: - supports-color dev: true - /eslint-plugin-vue@9.12.0(eslint@8.40.0): - resolution: {integrity: sha512-xH8PgpDW2WwmFSmRfs/3iWogef1CJzQqX264I65zz77jDuxF2yLy7+GA2diUM8ZNATuSl1+UehMQkb5YEyau5w==} + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0): + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.1.3) + array-includes: 3.1.6 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 8.41.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0) + has: 1.0.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.6 + resolve: 1.22.1 + semver: 6.3.0 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-vue@9.14.1(eslint@8.41.0): + resolution: {integrity: sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) - eslint: 8.40.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + eslint: 8.41.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.0.11 - semver: 7.5.0 - vue-eslint-parser: 9.2.1(eslint@8.40.0) + semver: 7.5.1 + vue-eslint-parser: 9.3.0(eslint@8.41.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color dev: true - /eslint-rule-docs@1.1.235: - resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==} - dev: true - - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.1: - resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint@8.40.0: - resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==} + /eslint-rule-docs@1.1.235: + resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==} + dev: true + + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.40.0: + resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) + '@eslint-community/regexpp': 4.5.0 + '@eslint/eslintrc': 2.0.3 + '@eslint/js': 8.40.0 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4(supports-color@8.1.1) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.0 + eslint-visitor-keys: 3.4.1 + espree: 9.5.2 + esquery: 1.4.2 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.2.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint@8.41.0: + resolution: {integrity: sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) '@eslint-community/regexpp': 4.5.0 '@eslint/eslintrc': 2.0.3 - '@eslint/js': 8.40.0 + '@eslint/js': 8.41.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -10622,13 +11973,12 @@ packages: find-up: 5.0.0 glob-parent: 6.0.2 globals: 13.19.0 - grapheme-splitter: 1.0.4 + graphemer: 1.4.0 ignore: 5.2.4 import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-sdsl: 4.2.0 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -10685,13 +12035,14 @@ packages: /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + dev: true /estree-to-babel@3.2.1: resolution: {integrity: sha512-YNF+mZ/Wu2FU/gvmzuWtYc8rloubL7wfXCTgouFrnjGVXPA/EeYYA7pupXWrb3Iv1cTBeSSxxJIbK23l4MRNqg==} engines: {node: '>=8.3.0'} dependencies: - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.5 + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 c8: 7.13.0 transitivePeerDependencies: - supports-color @@ -10700,9 +12051,16 @@ packages: /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.1 + dev: false + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + dev: true /etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} @@ -10712,7 +12070,7 @@ packages: /event-loop-spinner@2.2.0: resolution: {integrity: sha512-KB44sV4Mv7uLIkJHJ5qhiZe5um6th2g57nHQL/uqnPHKP2IswoTRWUteEXTJQL4gW++1zqWUni+H2hGkP51c9w==} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: false /event-stream@3.3.4: @@ -11037,6 +12395,7 @@ packages: /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true /fast-querystring@1.1.0: resolution: {integrity: sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==} @@ -11091,7 +12450,7 @@ packages: proxy-addr: 2.0.7 rfdc: 1.3.0 secure-json-parse: 2.7.0 - semver: 7.5.0 + semver: 7.5.1 tiny-lru: 11.0.1 transitivePeerDependencies: - supports-color @@ -11157,7 +12516,6 @@ packages: dependencies: fs-extra: 11.1.0 ramda: 0.28.0 - dev: true /file-type@17.1.6: resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==} @@ -11381,7 +12739,6 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - dev: true /for-in@1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} @@ -11473,7 +12830,6 @@ packages: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.0 - dev: true /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} @@ -11647,6 +13003,7 @@ packages: /get-port@5.1.1: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} + dev: true /get-stream@3.0.0: resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} @@ -11840,7 +13197,6 @@ packages: /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - dev: true /globals@13.19.0: resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} @@ -11876,7 +13232,6 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.0 - dev: true /got@11.8.5: resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==} @@ -11911,16 +13266,16 @@ packages: p-cancelable: 3.0.0 responselike: 3.0.0 - /graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: false - /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + /graphql@16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12041,8 +13396,8 @@ packages: uglify-js: 3.17.4 dev: true - /happy-dom@9.16.0: - resolution: {integrity: sha512-goq7grRjIiV2Svb251LWQOo/xm04za2mJ9+assbZJx1KnaVOX1gZBBp4MHbiFNkR6JW7UL81iCtZxCVu+qU5ng==} + /happy-dom@9.20.3: + resolution: {integrity: sha512-eBsgauT435fXFvQDNcmm5QbGtYzxEzOaX35Ia+h6yP/wwa4xSWZh1CfP+mGby8Hk6Xu59mTkpyf72rUXHNxY7A==} dependencies: css.escape: 1.5.1 entities: 4.5.0 @@ -12088,7 +13443,6 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -12463,8 +13817,9 @@ packages: resolution: {integrity: sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ==} dev: false - /install-artifact-from-github@1.3.2: - resolution: {integrity: sha512-yCFcLvqk0yQdxx0uJz4t9Z3adDMLAYrcGYv546uRXCSvxE+GqNYhhz/KmrGcUKGI/gVLR9n/e/zM9jX/+ASMJQ==} + /install-artifact-from-github@1.3.3: + resolution: {integrity: sha512-x79SL0d8WOi1ZjXSTUqqs0GPQZ92YArJAN9O46wgU9wdH2U9ecyyhB9YGDbPe2OLV4ptmt6AZYRQZ2GydQZosQ==} + hasBin: true dev: false /internal-slot@1.0.5: @@ -12500,6 +13855,7 @@ packages: standard-as-callback: 2.1.0 transitivePeerDependencies: - supports-color + dev: false /iota-array@1.0.0: resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} @@ -12593,7 +13949,6 @@ packages: dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 - dev: true /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} @@ -12747,7 +14102,6 @@ packages: engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 - dev: true /is-glob@3.1.0: resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} @@ -12804,7 +14158,6 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - dev: true /is-negated-glob@1.0.0: resolution: {integrity: sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==} @@ -12952,7 +14305,6 @@ packages: for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 - dev: true /is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -13036,7 +14388,7 @@ packages: /isomorphic-unfetch@3.1.0: resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} dependencies: - node-fetch: 2.6.7 + node-fetch: 2.6.11 unfetch: 4.2.0 transitivePeerDependencies: - encoding @@ -13054,8 +14406,8 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.21.3 - '@babel/parser': 7.21.8 + '@babel/core': 7.22.1 + '@babel/parser': 7.22.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.0 @@ -13130,7 +14482,7 @@ packages: '@jest/expect': 29.5.0 '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -13178,7 +14530,7 @@ packages: - ts-node dev: true - /jest-cli@29.5.0(@types/node@20.1.3): + /jest-cli@29.5.0(@types/node@20.2.5): resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13195,7 +14547,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 29.5.0(@types/node@20.1.3) + jest-config: 29.5.0(@types/node@20.2.5) jest-util: 29.5.0 jest-validate: 29.5.0 prompts: 2.4.2 @@ -13245,7 +14597,7 @@ packages: - supports-color dev: true - /jest-config@29.5.0(@types/node@20.1.3): + /jest-config@29.5.0(@types/node@20.2.5): resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -13260,7 +14612,7 @@ packages: '@babel/core': 7.21.3 '@jest/test-sequencer': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 babel-jest: 29.5.0(@babel/core@7.21.3) chalk: 4.1.2 ci-info: 3.7.1 @@ -13329,7 +14681,7 @@ packages: '@jest/environment': 29.5.0 '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 jest-mock: 29.5.0 jest-util: 29.5.0 dev: true @@ -13359,7 +14711,7 @@ packages: dependencies: '@jest/types': 29.5.0 '@types/graceful-fs': 4.1.6 - '@types/node': 20.1.3 + '@types/node': 20.2.5 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -13394,7 +14746,7 @@ packages: resolution: {integrity: sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 '@jest/types': 29.5.0 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -13410,7 +14762,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.1.3 + '@types/node': 20.2.5 dev: true /jest-mock@29.5.0: @@ -13418,7 +14770,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 jest-util: 29.5.0 dev: true @@ -13473,7 +14825,7 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -13504,7 +14856,7 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -13527,18 +14879,18 @@ packages: resolution: {integrity: sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.21.3 - '@babel/generator': 7.21.3 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.21.3) - '@babel/traverse': 7.21.3 - '@babel/types': 7.21.4 + '@babel/core': 7.22.1 + '@babel/generator': 7.22.3 + '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.22.1) + '@babel/traverse': 7.22.4 + '@babel/types': 7.22.4 '@jest/expect-utils': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/babel__traverse': 7.18.3 + '@types/babel__traverse': 7.20.0 '@types/prettier': 2.7.2 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.3) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.1) chalk: 4.1.2 expect: 29.5.0 graceful-fs: 4.2.11 @@ -13549,7 +14901,7 @@ packages: jest-util: 29.5.0 natural-compare: 1.4.0 pretty-format: 29.5.0 - semver: 7.5.0 + semver: 7.5.1 transitivePeerDependencies: - supports-color dev: true @@ -13559,7 +14911,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 chalk: 4.1.2 ci-info: 3.7.1 graceful-fs: 4.2.11 @@ -13584,7 +14936,7 @@ packages: dependencies: '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 20.1.3 + '@types/node': 20.2.5 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -13603,7 +14955,7 @@ packages: resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 jest-util: 29.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -13629,7 +14981,7 @@ packages: - ts-node dev: true - /jest@29.5.0(@types/node@20.1.3): + /jest@29.5.0(@types/node@20.2.5): resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13642,7 +14994,7 @@ packages: '@jest/core': 29.5.0 '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.5.0(@types/node@20.1.3) + jest-cli: 29.5.0(@types/node@20.2.5) transitivePeerDependencies: - '@types/node' - supports-color @@ -13705,7 +15057,6 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} @@ -13744,17 +15095,17 @@ packages: peerDependencies: '@babel/preset-env': ^7.1.6 dependencies: - '@babel/core': 7.21.3 - '@babel/parser': 7.21.8 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3) - '@babel/preset-env': 7.21.4(@babel/core@7.21.3) - '@babel/preset-flow': 7.18.6(@babel/core@7.21.3) - '@babel/preset-typescript': 7.21.0(@babel/core@7.21.3) - '@babel/register': 7.21.0(@babel/core@7.21.3) - babel-core: 7.0.0-bridge.0(@babel/core@7.21.3) + '@babel/core': 7.22.1 + '@babel/parser': 7.22.4 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.22.1) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1) + '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.22.1) + '@babel/preset-env': 7.21.4(@babel/core@7.22.1) + '@babel/preset-flow': 7.18.6(@babel/core@7.22.1) + '@babel/preset-typescript': 7.21.0(@babel/core@7.22.1) + '@babel/register': 7.21.0(@babel/core@7.22.1) + babel-core: 7.0.0-bridge.0(@babel/core@7.22.1) chalk: 4.1.2 flow-parser: 0.202.0 graceful-fs: 4.2.11 @@ -13768,9 +15119,9 @@ packages: - supports-color dev: true - /jsdom@21.1.1: - resolution: {integrity: sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==} - engines: {node: '>=14'} + /jsdom@22.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): + resolution: {integrity: sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==} + engines: {node: '>=16'} peerDependencies: canvas: ^2.5.0 peerDependenciesMeta: @@ -13778,19 +15129,16 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.8.2 - acorn-globals: 7.0.1 cssstyle: 3.0.0 data-urls: 4.0.0 decimal.js: 10.4.3 domexception: 4.0.0 - escodegen: 2.0.0 form-data: 4.0.0 html-encoding-sniffer: 3.0.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.2 + nwsapi: 2.2.5 parse5: 7.1.2 rrweb-cssom: 0.6.0 saxes: 6.0.0 @@ -13801,7 +15149,7 @@ packages: whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 whatwg-url: 12.0.1 - ws: 8.13.0 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -13818,7 +15166,6 @@ packages: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true - dev: true /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -13888,16 +15235,15 @@ packages: universalify: 2.0.0 optionalDependencies: graceful-fs: 4.2.11 - dev: true - /jsonld@8.1.1: - resolution: {integrity: sha512-TbtV1hlnoDYxbscazbxcS7seDGV+pc0yktxpMySh0OBFvnLw/TIth0jiQtP/9r+ywuCbtj10XjDNBIkRgiyeUg==} + /jsonld@8.2.0: + resolution: {integrity: sha512-qHUa9pn3/cdAZw26HY1Jmy9+sHOxaLrveTRWUcrSDx5apTa20bBTe+X4nzI7dlqc+M5GkwQW6RgRdqO6LF5nkw==} engines: {node: '>=14'} dependencies: - '@digitalbazaar/http-client': 3.2.0 + '@digitalbazaar/http-client': 3.4.1 canonicalize: 1.0.8 lru-cache: 6.0.0 - rdf-canonize: 3.3.0 + rdf-canonize: 3.4.0 transitivePeerDependencies: - web-streams-polyfill dev: false @@ -13989,24 +15335,24 @@ packages: engines: {node: '>=6'} dev: true - /ky-universal@0.10.1(ky@0.30.0): - resolution: {integrity: sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g==} - engines: {node: '>=14'} + /ky-universal@0.11.0(ky@0.33.3): + resolution: {integrity: sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==} + engines: {node: '>=14.16'} peerDependencies: - ky: '>=0.26.0' - web-streams-polyfill: '>=3.0.1' + ky: '>=0.31.4' + web-streams-polyfill: '>=3.2.1' peerDependenciesMeta: web-streams-polyfill: optional: true dependencies: abort-controller: 3.0.0 - ky: 0.30.0 + ky: 0.33.3 node-fetch: 3.3.1 dev: false - /ky@0.30.0: - resolution: {integrity: sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==} - engines: {node: '>=12'} + /ky@0.33.3: + resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} + engines: {node: '>=14.16'} dev: false /last-run@1.1.1: @@ -14063,6 +15409,7 @@ packages: dependencies: prelude-ls: 1.1.2 type-check: 0.3.2 + dev: true /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -14135,10 +15482,6 @@ packages: strip-bom: 2.0.0 dev: false - /loadjs@4.2.0: - resolution: {integrity: sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==} - dev: false - /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} @@ -14182,6 +15525,7 @@ packages: /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false /lodash.difference@4.5.0: resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} @@ -14213,6 +15557,7 @@ packages: /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + dev: false /lodash.isempty@4.4.0: resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==} @@ -14366,9 +15711,10 @@ packages: engines: {node: '>=16.14'} dev: true - /luxon@3.2.1: - resolution: {integrity: sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==} + /luxon@3.3.0: + resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==} engines: {node: '>=12'} + dev: false /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} @@ -14529,10 +15875,10 @@ packages: engines: {node: '>= 0.6'} dev: true - /meilisearch@0.32.3: - resolution: {integrity: sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==} + /meilisearch@0.32.5: + resolution: {integrity: sha512-pVccjGAGP1IDSLg3lx9VhyQjUo7kN8x/HVjSurtb8U24V5/pALpf5H2hj6f60QhJd0Ea4tnGRv8fGr2YqWMo9A==} dependencies: - cross-fetch: 3.1.5 + cross-fetch: 3.1.6 transitivePeerDependencies: - encoding dev: false @@ -14844,24 +16190,27 @@ packages: engines: {node: '>=12.13'} dev: false - /msgpackr-extract@2.2.0: - resolution: {integrity: sha512-0YcvWSv7ZOGl9Od6Y5iJ3XnPww8O7WLcpYMDwX+PAA/uXLDtyw94PJv9GLQV/nnp3cWlDhMoyKZIQLrx33sWog==} + /msgpackr-extract@3.0.2: + resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} + hasBin: true requiresBuild: true dependencies: - node-gyp-build-optional-packages: 5.0.3 + node-gyp-build-optional-packages: 5.0.7 optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 2.2.0 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 2.2.0 - '@msgpackr-extract/msgpackr-extract-linux-arm': 2.2.0 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 2.2.0 - '@msgpackr-extract/msgpackr-extract-linux-x64': 2.2.0 - '@msgpackr-extract/msgpackr-extract-win32-x64': 2.2.0 + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2 + dev: false optional: true - /msgpackr@1.8.1: - resolution: {integrity: sha512-05fT4J8ZqjYlR4QcRDIhLCYKUOHXk7C/xa62GzMKj74l3up9k2QZ3LgFc6qWdsPHl91QA2WLWqWc8b8t7GLNNw==} + /msgpackr@1.9.2: + resolution: {integrity: sha512-xtDgI3Xv0AAiZWLRGDchyzBwU6aq0rwJ+W+5Y4CZhEWtkl/hJtFFLc+3JtGTw7nz1yquxs7nL8q/yA2aqpflIQ==} optionalDependencies: - msgpackr-extract: 2.2.0 + msgpackr-extract: 3.0.2 + dev: false /msw-storybook-addon@1.8.0(msw@1.2.1): resolution: {integrity: sha512-dw3vZwqjixmiur0vouRSOax7wPSu9Og2Hspy9JZFHf49bZRjwDiLF0Pfn2NXEkGviYJOJiGxS1ejoTiUwoSg4A==} @@ -14869,10 +16218,10 @@ packages: msw: '>=0.35.0 <2.0.0' dependencies: is-node-process: 1.0.1 - msw: 1.2.1(typescript@5.0.4) + msw: 1.2.1(typescript@5.1.3) dev: true - /msw@1.2.1(typescript@5.0.4): + /msw@1.2.1(typescript@5.1.3): resolution: {integrity: sha512-bF7qWJQSmKn6bwGYVPXOxhexTCGD5oJSZg8yt8IBClxvo3Dx/1W0zqE1nX9BSWmzRsCKWfeGWcB/vpqV6aclpw==} engines: {node: '>=14'} hasBin: true @@ -14901,7 +16250,7 @@ packages: path-to-regexp: 6.2.1 strict-event-emitter: 0.4.6 type-fest: 2.19.0 - typescript: 5.0.4 + typescript: 5.1.3 yargs: 17.6.2 transitivePeerDependencies: - encoding @@ -15044,7 +16393,7 @@ packages: resolution: {integrity: sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==} dependencies: '@sinonjs/commons': 2.0.0 - '@sinonjs/fake-timers': 10.0.2 + '@sinonjs/fake-timers': 10.2.0 '@sinonjs/text-encoding': 0.7.2 just-extend: 4.2.1 path-to-regexp: 1.8.0 @@ -15054,7 +16403,7 @@ packages: resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==} engines: {node: '>=10'} dependencies: - semver: 7.5.0 + semver: 7.5.1 /node-addon-api@5.0.0: resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==} @@ -15083,6 +16432,17 @@ packages: resolution: {integrity: sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ==} dev: true + /node-fetch@2.6.11: + resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + /node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -15102,17 +16462,20 @@ packages: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - /node-gyp-build-optional-packages@5.0.3: - resolution: {integrity: sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==} + /node-gyp-build-optional-packages@5.0.7: + resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==} + hasBin: true + dev: false optional: true /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} - dev: false + hasBin: true /node-gyp@9.3.1: resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==} engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true dependencies: env-paths: 2.2.1 glob: 7.2.3 @@ -15121,7 +16484,7 @@ packages: nopt: 6.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.5.0 + semver: 7.5.1 tar: 6.1.13 which: 2.0.2 transitivePeerDependencies: @@ -15145,8 +16508,8 @@ packages: is: 3.3.0 dev: false - /nodemailer@6.9.2: - resolution: {integrity: sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==} + /nodemailer@6.9.3: + resolution: {integrity: sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==} engines: {node: '>=6.0.0'} dev: false @@ -15184,7 +16547,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.11.0 - semver: 7.5.0 + semver: 7.5.1 validate-npm-package-license: 3.0.4 dev: true @@ -15291,8 +16654,8 @@ packages: engines: {node: '>=0.10.0'} dev: false - /nwsapi@2.2.2: - resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} + /nwsapi@2.2.5: + resolution: {integrity: sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==} dev: false /oauth-sign@0.9.0: @@ -15334,7 +16697,6 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - dev: true /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -15404,6 +16766,10 @@ packages: resolution: {integrity: sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==} dev: false + /obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + dev: true + /omggif@1.0.10: resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} dev: false @@ -15479,6 +16845,7 @@ packages: prelude-ls: 1.1.2 type-check: 0.3.2 word-wrap: 1.2.3 + dev: true /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} @@ -15684,7 +17051,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -15727,7 +17094,7 @@ packages: /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: - entities: 4.4.0 + entities: 4.5.0 /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -15856,29 +17223,35 @@ packages: /performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - /pg-connection-string@2.5.0: - resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} + /pg-cloudflare@1.1.0: + resolution: {integrity: sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.0: + resolution: {integrity: sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg==} dev: false /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - /pg-pool@3.6.0(pg@8.10.0): + /pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + dev: true + + /pg-pool@3.6.0(pg@8.11.0): resolution: {integrity: sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==} peerDependencies: pg: '>=8.0' dependencies: - pg: 8.10.0 + pg: 8.11.0 dev: false - /pg-protocol@1.5.0: - resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==} - dev: true - /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - dev: false /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -15889,9 +17262,23 @@ packages: postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 + dev: false + + /pg-types@4.0.1: + resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==} + engines: {node: '>=10'} + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.0.1 + postgres-interval: 3.0.0 + postgres-range: 1.1.3 + dev: true - /pg@8.10.0: - resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==} + /pg@8.11.0: + resolution: {integrity: sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==} engines: {node: '>= 8.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -15901,11 +17288,13 @@ packages: dependencies: buffer-writer: 2.0.0 packet-reader: 1.0.0 - pg-connection-string: 2.5.0 - pg-pool: 3.6.0(pg@8.10.0) + pg-connection-string: 2.6.0 + pg-pool: 3.6.0(pg@8.11.0) pg-protocol: 1.6.0 pg-types: 2.2.0 pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.0 dev: false /pgpass@1.0.5: @@ -16302,20 +17691,50 @@ packages: /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} + dev: false + + /postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + dev: true /postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} + dev: false + + /postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + dependencies: + obuf: 1.1.2 + dev: true /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@2.0.1: + resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==} + engines: {node: '>=12'} + dev: true /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} dependencies: xtend: 4.0.2 + dev: false + + /postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + dev: true + + /postgres-range@1.1.3: + resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} + dev: true /prebuild-install@7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} @@ -16338,6 +17757,7 @@ packages: /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} + dev: true /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -16755,7 +18175,6 @@ packages: /ramda@0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} - dev: true /random-seed@0.3.0: resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==} @@ -16773,10 +18192,6 @@ packages: resolution: {integrity: sha512-9CRCUX/w4+fNMzlYgA8GeJz7BZwBPwaGm3FhAm9Hi50k8wNy2CyiJQa8awygWJay87uVVCV0/FwbLcD6+/A9KQ==} dev: false - /rangetouch@2.0.1: - resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==} - dev: false - /ratelimiter@3.4.1: resolution: {integrity: sha512-5FJbRW/Jkkdk29ksedAfWFkQkhbUrMx3QJGwMKAypeIiQf4yrLW+gtPKZiaWt4zPrtw1uGufOjGO7UGM6VllsQ==} dev: false @@ -16800,18 +18215,18 @@ packages: minimist: 1.2.8 strip-json-comments: 2.0.1 - /rdf-canonize@3.3.0: - resolution: {integrity: sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==} + /rdf-canonize@3.4.0: + resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==} engines: {node: '>=12'} dependencies: setimmediate: 1.0.5 dev: false - /re2@1.18.0: - resolution: {integrity: sha512-MoCYZlJ9YUgksND9asyNF2/x532daXU/ARp1UeJbQ5flMY6ryKNEhrWt85aw3YluzOJlC3vXpGgK2a1jb0b4GA==} + /re2@1.19.0: + resolution: {integrity: sha512-y0LcLZgBF3L7mDtNfbghb7dCmChYQO2QsUGklNueAJUH+HAZO8UZUubgNsf6OxRTAQpeE4KMPR7vcpK3+Q+GiA==} requiresBuild: true dependencies: - install-artifact-from-github: 1.3.2 + install-artifact-from-github: 1.3.3 nan: 2.17.0 node-gyp: 9.3.1 transitivePeerDependencies: @@ -16829,12 +18244,12 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /react-docgen-typescript@2.2.2(typescript@5.0.4): + /react-docgen-typescript@2.2.2(typescript@5.1.3): resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} peerDependencies: typescript: '>= 4.3.x' dependencies: - typescript: 5.0.4 + typescript: 5.1.3 dev: true /react-docgen@6.0.0-alpha.3: @@ -16842,8 +18257,8 @@ packages: engines: {node: '>=12.0.0'} hasBin: true dependencies: - '@babel/core': 7.21.3 - '@babel/generator': 7.21.3 + '@babel/core': 7.22.1 + '@babel/generator': 7.22.3 ast-types: 0.14.2 commander: 2.20.3 doctrine: 3.0.0 @@ -17033,7 +18448,7 @@ packages: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.5.0 + tslib: 2.5.2 dev: true /recast@0.22.0: @@ -17044,7 +18459,7 @@ packages: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.5.0 + tslib: 2.5.2 dev: true /recast@0.23.1: @@ -17055,8 +18470,7 @@ packages: ast-types: 0.16.1 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.5.0 - dev: true + tslib: 2.5.2 /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} @@ -17079,6 +18493,7 @@ packages: /redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} + dev: false /redis-info@3.1.0: resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==} @@ -17096,6 +18511,7 @@ packages: engines: {node: '>=4'} dependencies: redis-errors: 1.2.0 + dev: false /redis@4.5.1: resolution: {integrity: sha512-oxXSoIqMJCQVBTfxP6BNTCtDMyh9G6Vi5wjdPdV/sRKkufyZslDqCScSGcOr6XGR/reAWZefz7E4leM31RgdBA==} @@ -17444,8 +18860,8 @@ packages: seedrandom: 2.4.2 dev: false - /rollup@3.21.6: - resolution: {integrity: sha512-SXIICxvxQxR3D4dp/3LDHZIJPC8a4anKMHd4E3Jiz2/JnY+2bEjqrOokAauc5ShGVNFHlEFjBXAXlaxkJqIqSg==} + /rollup@3.23.0: + resolution: {integrity: sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -17617,6 +19033,14 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 + dev: true + + /semver@7.5.1: + resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -17708,7 +19132,7 @@ packages: detect-libc: 2.0.1 node-addon-api: 5.0.0 prebuild-install: 7.1.1 - semver: 7.5.0 + semver: 7.5.1 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 @@ -17723,7 +19147,7 @@ packages: detect-libc: 2.0.1 node-addon-api: 6.1.0 prebuild-install: 7.1.1 - semver: 7.5.0 + semver: 7.5.1 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 @@ -17816,8 +19240,8 @@ packages: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true - /slacc-android-arm-eabi@0.0.7: - resolution: {integrity: sha512-6TikZlR1jsQscxwphhrf0U4xbsRy6zKJ0zmEULopTzbohgo5OLdZ7L3tQazkYlaaFe3YjGnVLW3FfGhhrajVog==} + /slacc-android-arm-eabi@0.0.9: + resolution: {integrity: sha512-T5P5kJ5UwW3UMoPXqqHh9TpCnuCJDCoivoiuONDXrYPYKF8sKDPMVVg1x/KU9/m7e562x9vAMBrIyqFFbEW0Jw==} engines: {node: '>= 10'} cpu: [arm] os: [android] @@ -17825,8 +19249,8 @@ packages: dev: false optional: true - /slacc-android-arm64@0.0.7: - resolution: {integrity: sha512-aol/9Rg0Hfqu81hpK+HXcx9sGYu4qqYU+djBCgLtb8I6ZMdWUdE0dp8ACBoTOmYn34hYGcUu4FlJUZ8r7Utucg==} + /slacc-android-arm64@0.0.9: + resolution: {integrity: sha512-bcKB3ukcI5wWJa2clK/5cy6a4TKp51DRkdRuFgKLG05gBj1jbH+7+8iBPojljeY28LC2frmwVHGj3vDmkFUeYg==} engines: {node: '>= 10'} cpu: [arm64] os: [android] @@ -17834,8 +19258,8 @@ packages: dev: false optional: true - /slacc-darwin-arm64@0.0.7: - resolution: {integrity: sha512-PkV7rO/c9AImNYDacP+kxtOjVuxjy06IIOAxbWerIWvoeqsCNRtiF/dh+OqIACRFBuHIDe0oAyUCEMGUTnzjyQ==} + /slacc-darwin-arm64@0.0.9: + resolution: {integrity: sha512-EspX0Hj6t0Afxbsyc6rY9mTOUQQrPVtWPwwNRaljGRorPyRDDefrU1OnJXRcwcIp0oCZrRrivRYlO7lai63EMw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -17843,16 +19267,16 @@ packages: dev: false optional: true - /slacc-darwin-universal@0.0.7: - resolution: {integrity: sha512-Y9zXpL40m4Yq3dE5vdnAgfmn0Fxc0Bf0ixC9TSl96gKeIZEd6drkjfpHFdsIDNImzOksIAUo0HHiDdbEfE7zdQ==} + /slacc-darwin-universal@0.0.9: + resolution: {integrity: sha512-oQySg+9MPyKI9rwwwhmSZQkPks2/rq3k1P5HKwUgnaFZDvDtS/hpDycB3BxSDqWdD5kVA8PLCVa8pt9T5KyKfg==} engines: {node: '>= 10'} os: [darwin] requiresBuild: true dev: false optional: true - /slacc-darwin-x64@0.0.7: - resolution: {integrity: sha512-yKaGjX2YJl1QHe4NgqQVsY83jees3hjFxEUPoKpuZEQzWbMNn0XSyceFRGXIk1oDqiKU40UcsdcCedjYjSEd0Q==} + /slacc-darwin-x64@0.0.9: + resolution: {integrity: sha512-9Xp7mVKKF2QvDiIZOBgwsDdL/+95KBiFTdbo+XtH6YKoh6zNw0aPpkA3JojsdSMYdGHUrxl8b7avhzI0USqeEg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -17860,8 +19284,17 @@ packages: dev: false optional: true - /slacc-linux-arm-gnueabihf@0.0.7: - resolution: {integrity: sha512-pdWMdQeX6uA9JfSoWo9EHH0yRiwXKMbaKoS9gflDSyt/hjeR3Qx/KK7Wihd7HeXx7njlNdpr9ycTRmm5NgapQQ==} + /slacc-freebsd-x64@0.0.9: + resolution: {integrity: sha512-jRd8WmXZLU2mcxV7SN8CzZzGiwbpxtaTjLwrYMTryQZ2TFr1xd1r5mQfTN5sBiwu3tnyK5dmHnRAPy+215mOkQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /slacc-linux-arm-gnueabihf@0.0.9: + resolution: {integrity: sha512-nhP6+jgd30sq+zFxFW7fGhnPwCfCCU0l1JKk3ORGFMl7wH7ippTDd1xGapKq7N+zgdgURbyj83P3wWb2gcRZ1w==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -17869,8 +19302,8 @@ packages: dev: false optional: true - /slacc-linux-arm64-gnu@0.0.7: - resolution: {integrity: sha512-hz9TK/w6fxeNZXyFzuLq5cJD/XRyJbo6BaIdW+VrKKnb9nkLnWlqDQtdtJk7Fw7zHjdY3Uqufjwm0iT6qBVpUQ==} + /slacc-linux-arm64-gnu@0.0.9: + resolution: {integrity: sha512-x7v0rDe0KNVe1Hl6/XCtkCpqdT283pyVaUmk+af0AnoesNRjYEK8DBc8i53N4nhotionHzPIZfu5gPAFkf6RhA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -17878,8 +19311,8 @@ packages: dev: false optional: true - /slacc-linux-arm64-musl@0.0.7: - resolution: {integrity: sha512-wCDAYL7e+lh3XL7g87Ui/Bb2Ap9GcBqeJuj2yHIx6MYC8ontwFSXhqRTmd2zmPLmZA5Nc11aKGN11YNu0Pnwlw==} + /slacc-linux-arm64-musl@0.0.9: + resolution: {integrity: sha512-jyq/ylITHIXTQX5ZqAbi7Mn5SdRgYJi+uEoUCi5UhoXb9LjpNzhkFuY29Je3IkVIIV7AEcAxIlvjdymXdzcF5w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -17887,8 +19320,8 @@ packages: dev: false optional: true - /slacc-linux-x64-gnu@0.0.7: - resolution: {integrity: sha512-E5+2cveizpfHXCk/Hu5VfslWFeDVw47nywODiJ8CsofT2l5ITfYPMFEBXm9ORY25mGBTgsO6lJYiF9Hz4FlS9Q==} + /slacc-linux-x64-gnu@0.0.9: + resolution: {integrity: sha512-Xs/F81H7cKhlIBigFID6CJlgjy0NeDUGV1CI1MI5mSVHsVI8dUO8zXWETjo6o8krJPgfjT5Jd4tAgvUFct5hng==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -17896,8 +19329,8 @@ packages: dev: false optional: true - /slacc-win32-arm64-msvc@0.0.7: - resolution: {integrity: sha512-3a+qnkZbP+Pr5RZuzd0Vi1uCal137QiJajRAWT4r7qwu+Zidd50x2oikQ4rAegqZVTm8qTwVmWA+WmH8WHI7iw==} + /slacc-win32-arm64-msvc@0.0.9: + resolution: {integrity: sha512-C+H0VkKbEEnRbcXRIG5rIaXlg7IZw3o1BbvqA71B8ouQRCu/dNRuH9EQsOYXWltndY42zZi8IupNIwydTUg+Mg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -17905,8 +19338,8 @@ packages: dev: false optional: true - /slacc-win32-x64-msvc@0.0.7: - resolution: {integrity: sha512-ydFdZ7wEXQPsw2Tg+yG9uJdCGTehyPtrWBVUMa7fojr3j1gbtThXS2l9Ad/6fYYi2VwdaYPLWbwV3GYElPGL8g==} + /slacc-win32-x64-msvc@0.0.9: + resolution: {integrity: sha512-bElMnBbeMatCtVp2/+hBS6Z+846nQImEul9nBEr4gfezHotOM6MqR6PI7UQQzGhozpgwiDg2l1ub1MdOIgYizg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -17914,21 +19347,22 @@ packages: dev: false optional: true - /slacc@0.0.7: - resolution: {integrity: sha512-rwi2F3oJaGPST9JdCoUd5fnSZaoZFgTL00GFKhKufT48uwtUEAHlOL0t8gEVmon71X+53f9nEdsGWhwtOutJTQ==} + /slacc@0.0.9: + resolution: {integrity: sha512-BwhjD3daQB3VIx7GxkComMYrnkWuMt4YmDAueMMchblfUUBbP8EcuonJ1Bz9nqtRn1mAH2YPrrRDP95akM+ZuQ==} engines: {node: '>= 10'} optionalDependencies: - slacc-android-arm-eabi: 0.0.7 - slacc-android-arm64: 0.0.7 - slacc-darwin-arm64: 0.0.7 - slacc-darwin-universal: 0.0.7 - slacc-darwin-x64: 0.0.7 - slacc-linux-arm-gnueabihf: 0.0.7 - slacc-linux-arm64-gnu: 0.0.7 - slacc-linux-arm64-musl: 0.0.7 - slacc-linux-x64-gnu: 0.0.7 - slacc-win32-arm64-msvc: 0.0.7 - slacc-win32-x64-msvc: 0.0.7 + slacc-android-arm-eabi: 0.0.9 + slacc-android-arm64: 0.0.9 + slacc-darwin-arm64: 0.0.9 + slacc-darwin-universal: 0.0.9 + slacc-darwin-x64: 0.0.9 + slacc-freebsd-x64: 0.0.9 + slacc-linux-arm-gnueabihf: 0.0.9 + slacc-linux-arm64-gnu: 0.0.9 + slacc-linux-arm64-musl: 0.0.9 + slacc-linux-x64-gnu: 0.0.9 + slacc-win32-arm64-msvc: 0.0.9 + slacc-win32-x64-msvc: 0.0.9 dev: false /slash@3.0.0: @@ -18020,7 +19454,7 @@ packages: lodash.topairs: 4.3.0 micromatch: 4.0.5 p-map: 4.0.0 - semver: 7.5.0 + semver: 7.5.1 snyk-config: 5.1.0 tslib: 1.14.1 uuid: 8.3.2 @@ -18204,6 +19638,7 @@ packages: /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + dev: false /start-server-and-test@2.0.0: resolution: {integrity: sha512-UqKLw0mJbfrsG1jcRLTUlvuRi9sjNuUiDOLI42r7R5fA9dsFoywAy9DoLXNYys9B886E4RCKb+qM1Gzu96h7DQ==} @@ -18248,11 +19683,11 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook@7.0.10: - resolution: {integrity: sha512-L36+Um+Ra8AKTvv84ODFJfuthmWnR1Lc6pjslcb8LYO+PVlqEOeqSknmTcKntDYwgvKx5lg62urtJxzGdwO0yw==} + /storybook@7.0.18: + resolution: {integrity: sha512-FXMmTiomSlLPTHty7vGLr0prPf6pCV07EwAmNOYYYTskitEYV0R7hlhawByd7HuobjIhHvSTKesa1Whl86zLNA==} hasBin: true dependencies: - '@storybook/cli': 7.0.10 + '@storybook/cli': 7.0.18 transitivePeerDependencies: - bufferutil - encoding @@ -18516,7 +19951,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -18570,8 +20004,8 @@ packages: resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} dev: true - /systeminformation@5.17.12: - resolution: {integrity: sha512-I3pfMW2vue53u+X08BNxaJieaHkRoMMKjWetY9lbYJeWFaeWPO6P4FkNc4XOCX8F9vbQ0HqQ25RJoz3U/B7liw==} + /systeminformation@5.17.16: + resolution: {integrity: sha512-dl2QLa7yp9QbBl9um+51CAr3p/40tbz+f34X1lUXkk1SnDcNeJR2iWu/8HD7GM2yRukmy3RCRXFYcPZs0lCs0Q==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -18708,8 +20142,8 @@ packages: real-require: 0.2.0 dev: false - /three@0.151.3: - resolution: {integrity: sha512-+vbuqxFy8kzLeO5MgpBHUvP/EAiecaDwDuOPPDe6SbrZr96kccF0ktLngXc7xA7bzyd3N0t2f6mw3Z9y6JCojQ==} + /three@0.153.0: + resolution: {integrity: sha512-OCP2/uQR6GcDpSLnJt/3a4mdS0kNWcbfUXIwLoEMgLzEUIVIYsSDwskpmOii/AkDM+BBwrl6+CKgrjX9+E2aWg==} dev: false /throttle-debounce@5.0.0: @@ -18767,8 +20201,8 @@ packages: engines: {node: '>=12'} dev: false - /tinybench@2.4.0: - resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==} + /tinybench@2.5.0: + resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} dev: true /tinycolor2@1.6.0: @@ -18922,7 +20356,6 @@ packages: /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} - dev: true /ts-map@1.0.3: resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==} @@ -18978,6 +20411,9 @@ packages: /tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + /tslib@2.5.2: + resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==} + /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -18988,6 +20424,16 @@ packages: typescript: 5.0.4 dev: true + /tsutils@3.21.0(typescript@5.1.3): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.1.3 + dev: true + /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -19010,6 +20456,7 @@ packages: engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.1.2 + dev: true /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -19055,7 +20502,6 @@ packages: /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - dev: true /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} @@ -19073,16 +20519,10 @@ packages: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} dev: false - /typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - dependencies: - is-typedarray: 1.0.0 - dev: false - /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - /typeorm@0.3.16(ioredis@5.3.2)(pg@8.10.0): + /typeorm@0.3.16(ioredis@5.3.2)(pg@8.11.0): resolution: {integrity: sha512-wJ4Qy1oqRKNDdZiBTTaVMqwo/XxC52Q7uNPTjltPgLhvIW173bL6Iad0lhptMOsFlpixFPaUu3PNziaRBwX2Zw==} engines: {node: '>= 12.9.0'} hasBin: true @@ -19151,7 +20591,7 @@ packages: glob: 8.1.0 ioredis: 5.3.2 mkdirp: 2.1.6 - pg: 8.10.0 + pg: 8.11.0 reflect-metadata: 0.1.13 sha.js: 2.4.11 tslib: 2.5.0 @@ -19171,6 +20611,12 @@ packages: resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} hasBin: true + dev: true + + /typescript@5.1.3: + resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} + engines: {node: '>=14.17'} + hasBin: true /ufo@1.1.1: resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} @@ -19230,16 +20676,16 @@ packages: undertaker-registry: 1.0.1 dev: false - /undici@5.16.0: - resolution: {integrity: sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ==} + /undici@5.21.0: + resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 dev: false - /undici@5.21.0: - resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==} - engines: {node: '>=12.18'} + /undici@5.22.1: + resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} + engines: {node: '>=14.0'} dependencies: busboy: 1.6.0 dev: false @@ -19347,7 +20793,6 @@ packages: /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} - dev: true /unload@2.4.1: resolution: {integrity: sha512-IViSAm8Z3sRBYA+9wc0fLQmU9Nrxb16rcDmIiR6Y9LJSZzI7QY5QsDhqPpKOjAn0O9/kfK1TfNEMMAGPTIraPw==} @@ -19388,8 +20833,8 @@ packages: engines: {node: '>=8'} dev: true - /unzipper@0.10.11: - resolution: {integrity: sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==} + /unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} dependencies: big-integer: 1.6.51 binary: 0.3.0 @@ -19397,7 +20842,7 @@ packages: buffer-indexof-polyfill: 1.0.2 duplexer2: 0.1.4 fstream: 1.0.12 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 listenercount: 1.0.1 readable-stream: 2.3.7 setimmediate: 1.0.5 @@ -19430,10 +20875,6 @@ packages: requires-port: 1.0.0 dev: false - /url-polyfill@1.1.12: - resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==} - dev: false - /urlsafe-base64@1.0.0: resolution: {integrity: sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==} dev: false @@ -19453,13 +20894,12 @@ packages: resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} engines: {node: '>=0.10.0'} - /utf-8-validate@5.0.10: - resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + /utf-8-validate@6.0.3: + resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} engines: {node: '>=6.14.2'} requiresBuild: true dependencies: node-gyp-build: 4.6.0 - dev: false /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -19472,7 +20912,6 @@ packages: is-generator-function: 1.0.10 is-typed-array: 1.1.10 which-typed-array: 1.1.9 - dev: true /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} @@ -19598,8 +21037,8 @@ packages: replace-ext: 1.0.1 dev: false - /vite-node@0.31.0(@types/node@20.1.3)(sass@1.62.1): - resolution: {integrity: sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g==} + /vite-node@0.31.4(@types/node@20.2.5)(sass@1.62.1): + resolution: {integrity: sha512-uzL377GjJtTbuc5KQxVbDu2xfU/x0wVjUtXQR2ihS21q/NK6ROr4oG0rsSkBBddZUVCwzfx22in76/0ZZHXgkQ==} engines: {node: '>=v14.18.0'} hasBin: true dependencies: @@ -19608,7 +21047,7 @@ packages: mlly: 1.2.0 pathe: 1.1.0 picocolors: 1.0.0 - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) transitivePeerDependencies: - '@types/node' - less @@ -19623,8 +21062,8 @@ packages: resolution: {integrity: sha512-irjKcKXRn7v5bPAg4mAbsS6DgibpP1VUFL9tlgxU6lloK6V9yw9qCZkS+s2PtbkZpWNzr3TN3zVJAc6J7gJZmA==} dev: true - /vite@4.3.5(@types/node@20.1.3)(sass@1.62.1): - resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==} + /vite@4.3.9(@types/node@20.2.5)(sass@1.62.1): + resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -19648,28 +21087,28 @@ packages: terser: optional: true dependencies: - '@types/node': 20.1.3 + '@types/node': 20.2.5 esbuild: 0.17.18 postcss: 8.4.23 - rollup: 3.21.6 + rollup: 3.23.0 sass: 1.62.1 optionalDependencies: fsevents: 2.3.2 - /vitest-fetch-mock@0.2.2(vitest@0.31.0): + /vitest-fetch-mock@0.2.2(vitest@0.31.4): resolution: {integrity: sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==} engines: {node: '>=14.14.0'} peerDependencies: vitest: '>=0.16.0' dependencies: cross-fetch: 3.1.5 - vitest: 0.31.0(happy-dom@9.16.0)(sass@1.62.1) + vitest: 0.31.4(happy-dom@9.20.3)(sass@1.62.1) transitivePeerDependencies: - encoding dev: true - /vitest@0.31.0(happy-dom@9.16.0)(sass@1.62.1): - resolution: {integrity: sha512-JwWJS9p3GU9GxkG7eBSmr4Q4x4bvVBSswaCFf1PBNHiPx00obfhHRJfgHcnI0ffn+NMlIh9QGvG75FlaIBdKGA==} + /vitest@0.31.4(happy-dom@9.20.3)(sass@1.62.1): + resolution: {integrity: sha512-GoV0VQPmWrUFOZSg3RpQAPN+LPmHg2/gxlMNJlyxJihkz6qReHDV6b0pPDcqFLNEPya4tWJ1pgwUNP9MLmUfvQ==} engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: @@ -19699,31 +21138,31 @@ packages: webdriverio: optional: true dependencies: - '@types/chai': 4.3.4 + '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 - '@types/node': 20.1.3 - '@vitest/expect': 0.31.0 - '@vitest/runner': 0.31.0 - '@vitest/snapshot': 0.31.0 - '@vitest/spy': 0.31.0 - '@vitest/utils': 0.31.0 + '@types/node': 20.2.5 + '@vitest/expect': 0.31.4 + '@vitest/runner': 0.31.4 + '@vitest/snapshot': 0.31.4 + '@vitest/spy': 0.31.4 + '@vitest/utils': 0.31.4 acorn: 8.8.2 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 concordance: 5.0.4 debug: 4.3.4(supports-color@8.1.1) - happy-dom: 9.16.0 + happy-dom: 9.20.3 local-pkg: 0.4.3 magic-string: 0.30.0 pathe: 1.1.0 picocolors: 1.0.0 std-env: 3.3.2 strip-literal: 1.0.1 - tinybench: 2.4.0 + tinybench: 2.5.0 tinypool: 0.5.0 - vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1) - vite-node: 0.31.0(@types/node@20.1.3)(sass@1.62.1) + vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1) + vite-node: 0.31.4(@types/node@20.2.5)(sass@1.62.1) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -19738,64 +21177,61 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} - /vue-docgen-api@4.64.1(vue@3.3.1): + /vue-component-type-helpers@1.6.5: + resolution: {integrity: sha512-iGdlqtajmiqed8ptURKPJ/Olz0/mwripVZszg6tygfZSIL9kYFPJTNY6+Q6OjWGznl2L06vxG5HvNvAnWrnzbg==} + dev: true + + /vue-docgen-api@4.64.1(vue@3.3.4): resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==} dependencies: - '@babel/parser': 7.21.8 - '@babel/types': 7.21.4 - '@vue/compiler-dom': 3.3.1 - '@vue/compiler-sfc': 3.3.1 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-sfc': 3.3.4 ast-types: 0.14.2 hash-sum: 2.0.0 lru-cache: 8.0.4 pug: 3.0.2 recast: 0.22.0 ts-map: 1.0.3 - vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.3.1) + vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.3.4) transitivePeerDependencies: - vue dev: true - /vue-eslint-parser@9.2.1(eslint@8.40.0): - resolution: {integrity: sha512-tPOex4n6jit4E7h68auOEbDMwE58XiP4dylfaVTCOVCouR45g+QFDBjgIdEU52EXJxKyjgh91dLfN2rxUcV0bQ==} + /vue-eslint-parser@9.3.0(eslint@8.41.0): + resolution: {integrity: sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' dependencies: debug: 4.3.4(supports-color@8.1.1) - eslint: 8.40.0 + eslint: 8.41.0 eslint-scope: 7.2.0 eslint-visitor-keys: 3.4.1 espree: 9.5.2 esquery: 1.4.2 lodash: 4.17.21 - semver: 7.5.0 + semver: 7.5.1 transitivePeerDependencies: - supports-color dev: true - /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.3.1): + /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.3.4): resolution: {integrity: sha512-Hn32n07XZ8j9W8+fmOXPQL+i+W2e/8i6mkH4Ju3H6nR0+cfvmWM95GhczYi5B27+Y8JlCKgAo04IUiYce4mKAw==} peerDependencies: vue: '>=2' dependencies: - vue: 3.3.1 + vue: 3.3.4 dev: true - /vue-plyr@7.0.0: - resolution: {integrity: sha512-NvbO/ZzV1IxlBQQbQlon5Sk8hKuGAj3k4k0XVdi7gM4oSqu8mZMhJ3WM3FfAtNfV790jbLnb8P3dHYqaBqIv6g==} - dependencies: - plyr: github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658 - vue: 2.7.14 - dev: false - - /vue-prism-editor@2.0.0-alpha.2(vue@3.3.1): + /vue-prism-editor@2.0.0-alpha.2(vue@3.3.4): resolution: {integrity: sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==} engines: {node: '>=10'} peerDependencies: vue: ^3.0.0 dependencies: - vue: 3.3.1 + vue: 3.3.4 dev: false /vue-template-compiler@2.7.14: @@ -19805,41 +21241,34 @@ packages: he: 1.2.0 dev: true - /vue-tsc@1.6.4(typescript@5.0.4): - resolution: {integrity: sha512-8rg8S1AhRJ6/WriENQEhyqH5wsxSxuD5iaD+QnkZn2ArZ6evlhqfBAIcVN8mfSyCV9DeLkQXkOSv/MaeJiJPAQ==} + /vue-tsc@1.6.5(typescript@5.1.3): + resolution: {integrity: sha512-Wtw3J7CC+JM2OR56huRd5iKlvFWpvDiU+fO1+rqyu4V2nMTotShz4zbOZpW5g9fUOcjnyZYfBo5q5q+D/q27JA==} hasBin: true peerDependencies: typescript: '*' dependencies: - '@volar/vue-language-core': 1.6.4 - '@volar/vue-typescript': 1.6.4(typescript@5.0.4) - semver: 7.5.0 - typescript: 5.0.4 + '@volar/vue-language-core': 1.6.5 + '@volar/vue-typescript': 1.6.5(typescript@5.1.3) + semver: 7.5.1 + typescript: 5.1.3 dev: true - /vue@2.7.14: - resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==} - dependencies: - '@vue/compiler-sfc': 2.7.14 - csstype: 3.1.1 - dev: false - - /vue@3.3.1: - resolution: {integrity: sha512-3Rwy4I5idbPVSDZu6I+fFh6tdDSZbauImCTqLxE7y0LpHtiDvPeY01OI7RkFPbva1nk4hoO0sv/NzosH2h60sg==} + /vue@3.3.4: + resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==} dependencies: - '@vue/compiler-dom': 3.3.1 - '@vue/compiler-sfc': 3.3.1 - '@vue/runtime-dom': 3.3.1 - '@vue/server-renderer': 3.3.1(vue@3.3.1) - '@vue/shared': 3.3.1 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-sfc': 3.3.4 + '@vue/runtime-dom': 3.3.4 + '@vue/server-renderer': 3.3.4(vue@3.3.4) + '@vue/shared': 3.3.4 - /vuedraggable@4.1.0(vue@3.3.1): + /vuedraggable@4.1.0(vue@3.3.4): resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} peerDependencies: vue: ^3.0.1 dependencies: sortablejs: 1.14.0 - vue: 3.3.1 + vue: 3.3.4 dev: false /w3c-xmlserializer@4.0.0: @@ -19929,20 +21358,6 @@ packages: resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} dev: false - /websocket@1.0.34: - resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==} - engines: {node: '>=4.0.0'} - dependencies: - bufferutil: 4.0.7 - debug: 2.6.9 - es5-ext: 0.10.62 - typedarray-to-buffer: 3.1.5 - utf-8-validate: 5.0.10 - yaeti: 0.0.6 - transitivePeerDependencies: - - supports-color - dev: false - /well-known-symbols@2.0.0: resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} engines: {node: '>=6'} @@ -20013,7 +21428,6 @@ packages: gopd: 1.0.1 has-tostringtag: 1.0.0 is-typed-array: 1.1.10 - dev: true /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} @@ -20054,14 +21468,15 @@ packages: resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/parser': 7.21.4 - '@babel/types': 7.21.4 + '@babel/parser': 7.22.4 + '@babel/types': 7.22.4 assert-never: 1.2.1 babel-walk: 3.0.0-canary-5 /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} + dev: true /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -20133,7 +21548,7 @@ packages: async-limiter: 1.0.1 dev: true - /ws@8.13.0: + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} peerDependencies: @@ -20144,6 +21559,9 @@ packages: optional: true utf-8-validate: optional: true + dependencies: + bufferutil: 4.0.7 + utf-8-validate: 6.0.3 /xev@3.0.2: resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==} @@ -20191,11 +21609,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - /yaeti@0.0.6: - resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} - engines: {node: '>=0.10.32'} - dev: false - /yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} @@ -20352,6 +21765,12 @@ packages: version: 2.2.1-misskey.3 dev: false + github.com/misskey-dev/buraha/92b20c1ab15c5cb5a224cf3b1ecd4f6baca12b7c: + resolution: {tarball: https://codeload.github.com/misskey-dev/buraha/tar.gz/92b20c1ab15c5cb5a224cf3b1ecd4f6baca12b7c} + name: buraha + version: 0.0.0 + dev: false + github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01: resolution: {tarball: https://codeload.github.com/misskey-dev/sharp-read-bmp/tar.gz/02d9dc189fa7df0c4bea09330be26741772dac01} name: sharp-read-bmp @@ -20362,7 +21781,7 @@ packages: sharp: 0.31.3 dev: false - github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.10)(@storybook/components@7.0.10)(@storybook/core-events@7.0.10)(@storybook/manager-api@7.0.10)(@storybook/preview-api@7.0.10)(@storybook/theming@7.0.10)(@storybook/types@7.0.10)(react-dom@18.2.0)(react@18.2.0): + github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.18)(@storybook/components@7.0.18)(@storybook/core-events@7.0.18)(@storybook/manager-api@7.0.18)(@storybook/preview-api@7.0.18)(@storybook/theming@7.0.18)(@storybook/types@7.0.18)(react-dom@18.2.0)(react@18.2.0): resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640} id: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640 name: storybook-addon-misskey-theme @@ -20383,13 +21802,13 @@ packages: react-dom: optional: true dependencies: - '@storybook/blocks': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.10 - '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.10 - '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.10 + '@storybook/blocks': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.18 + '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.18 + '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.18 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -20407,15 +21826,3 @@ packages: jschardet: 3.0.0 private-ip: 2.3.3 trace-redirect: 1.0.6 - - github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658: - resolution: {tarball: https://codeload.github.com/sampotts/plyr/tar.gz/d434c9af16e641400aaee93188594208d88f2658} - name: plyr - version: 3.7.0 - dependencies: - core-js: 3.29.1 - custom-event-polyfill: 1.0.7 - loadjs: 4.2.0 - rangetouch: 2.0.1 - url-polyfill: 1.1.12 - dev: false diff --git a/scripts/dev.js b/scripts/dev.js index db7bc11febd14cb336053e283b2cb7dddbc0d6e0..2f20d8f07c6415ca9193cc1e17a90119885366fc 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -44,11 +44,17 @@ const fs = require('fs'); if (!stat) throw new Error('not exist yet'); if (stat.size === 0) throw new Error('not built yet'); - await execa('pnpm', ['start'], { + const subprocess = await execa('pnpm', ['start'], { cwd: __dirname + '/../', stdout: process.stdout, stderr: process.stderr, }); + + // ãªãœã‹workerã ã‘ãŒçµ‚了ã—ã¦masterãŒæ®‹ã‚‹ã®ã§ãã®å¯¾ç– + process.on('SIGINT', () => { + subprocess.kill('SIGINT'); + process.exit(0); + }); } catch (e) { await new Promise(resolve => setTimeout(resolve, 3000)); start();