diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-01-27 00:17:13 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-01-27 00:17:13 +0900 |
| commit | 5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff (patch) | |
| tree | 51e9e6179f6d1bda3013d1412f6e43f9f8f70e86 | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.102.0 (diff) | |
| download | misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.gz misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.bz2 misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.zip | |
Merge branch 'develop'
955 files changed, 16122 insertions, 33915 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a684eaa48..3e9585f96d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,14 +17,14 @@ jobs: services: postgres: - image: postgres:12.2-alpine + image: postgres:13 ports: - 54312:5432 env: POSTGRES_DB: test-misskey POSTGRES_HOST_AUTH_METHOD: trust redis: - image: redis:4.0-alpine + image: redis:6 ports: - 56312:6379 @@ -51,19 +51,21 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version: [16.x] + browser: [chrome] services: postgres: - image: postgres:12.2-alpine + image: postgres:13 ports: - 54312:5432 env: POSTGRES_DB: test-misskey POSTGRES_HOST_AUTH_METHOD: trust redis: - image: redis:4.0-alpine + image: redis:6 ports: - 56312:6379 @@ -71,6 +73,12 @@ jobs: - uses: actions/checkout@v2 with: submodules: true + # https://github.com/cypress-io/cypress-docker-images/issues/150 + #- name: Install mplayer for FireFox + # run: sudo apt install mplayer -y + # if: ${{ matrix.browser == 'firefox' }} + #- uses: browser-actions/setup-firefox@latest + # if: ${{ matrix.browser == 'firefox' }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: @@ -87,5 +95,24 @@ jobs: run: cp .github/misskey/test.yml .config - name: Build run: yarn build - - name: Test - run: yarn e2e + # https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091 + - name: ALSA Env + run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc + - name: Cypress run + uses: cypress-io/github-action@v2 + with: + install: false + start: npm run start:test + wait-on: 'http://localhost:61812' + headless: false + browser: ${{ matrix.browser }} + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: ${{ matrix.browser }}-cypress-screenshots + path: cypress/screenshots + - uses: actions/upload-artifact@v2 + if: always() + with: + name: ${{ matrix.browser }}-cypress-videos + path: cypress/videos diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 04544f46b9..9adb0d0697 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,5 @@ "eg2.vscode-npm-script", "dbaeumer.vscode-eslint", "johnsoncodehk.volar", - "sysoev.language-stylus" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd61442e4..718c7d97c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,28 @@ --> +## 12.102.0 (2022/01/27) + +### Changes +- Room機能ãŒå‰Šé™¤ã•れã¾ã—㟠+ - 後日別リãƒã‚¸ãƒˆãƒªã¨ã—ã¦å¾©æ´»äºˆå®šã§ã™ +- リãƒãƒ¼ã‚·æ©Ÿèƒ½ãŒå‰Šé™¤ã•れã¾ã—㟠+ - 後日別リãƒã‚¸ãƒˆãƒªã¨ã—ã¦å¾©æ´»äºˆå®šã§ã™ +- Chat UIãŒå‰Šé™¤ã•れã¾ã—㟠+- ãƒŽãƒ¼ãƒˆã«æ·»ä»˜ã§ãã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®æ•°ãŒ16ã«å¢—ãˆã¾ã—㟠+- カスタム絵文å—ã«SVGを指定ã—ãŸå ´åˆã€PNGã«å¤‰æ›ã•れã¦è¡¨ç¤ºã•れるよã†ã«ãªã‚Šã¾ã—㟠+ +### Improvements +- カスタム絵文å—一括編集機能 +- カスタム絵文å—一括インãƒãƒ¼ãƒˆ +- 投稿フォームã§ä¸€æ™‚çš„ã«æŠ•ç¨¿ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’切り替ãˆã‚‰ã‚Œã‚‹ã‚ˆã†ã« +- Unifying Misskey-specific IRIs in JSON-LD `@context` +- クライアントã®ãƒ‘フォーマンスå‘上 +- ã‚»ã‚ュリティã®å‘上 + +### Bugfixes +- アップãƒãƒ¼ãƒ‰ã‚¨ãƒ©ãƒ¼æ™‚ã®å‡¦ç†ã‚’ä¿®æ£ + ## 12.101.1 (2021/12/29) ### Bugfixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 633995c947..27f5598a66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,25 +87,18 @@ Configuration files are located in [`/.github/workflows`](/.github/workflows). ## Vue Misskey uses Vue(v3) as its front-end framework. -**When creating a new component, please use the Composition API (and [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html)) instead of the Options API.** -Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. - -## Adding MisskeyRoom items -* Use English for material, object and texture names. -* Use meter for unit of length. -* Your PR should include all source files (e.g. `.png`, `.blend`) of your models (for later editing). -* Your PR must include the glTF binary files (`.glb`) of your models. -* Add a locale key `room.furnitures.YOUR_ITEM` at [`/locales/ja-JP.yml`](/locales/ja-JP.yml). -* Add a furniture definition at [`src/client/scripts/room/furnitures.json5`](src/client/scripts/room/furnitures.json5). - -If you have no experience on 3D modeling, we suggest to use the free 3DCG software [Blender](https://www.blender.org/). -You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html). +- Use TypeScript. +- **When creating a new component, please use the Composition API (with [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html) and [ref sugar](https://github.com/vuejs/rfcs/discussions/369)) instead of the Options API.** + - Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. ## Notes ### How to resolve conflictions occurred at yarn.lock? Just execute `yarn` to fix it. +### INSERTã™ã‚‹ã¨ãã«ã¯saveã§ã¯ãªãinsertを使用ã™ã‚‹ +#6441 + ### placeholder SQLをクエリビルダã§çµ„ã¿ç«‹ã¦ã‚‹éš›ã€ä½¿ç”¨ã™ã‚‹ãƒ—レースホルダã¯é‡è¤‡ã—ã¦ã¯ãªã‚‰ãªã„ 例ãˆã° diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js index a754f41b98..aca44ef15d 100644 --- a/cypress/integration/basic.js +++ b/cypress/integration/basic.js @@ -41,8 +41,6 @@ describe('After setup instance', () => { username: 'admin', password: 'pass', }).its('body').as('admin'); - - cy.get('@admin'); }); afterEach(() => { @@ -82,15 +80,11 @@ describe('After user signup', () => { password: 'pass', }).its('body').as('admin'); - cy.get('@admin').then(() => { - // ãƒ¦ãƒ¼ã‚¶ãƒ¼ä½œæˆ - cy.request('POST', '/api/signup', { - username: 'alice', - password: 'alice1234', - }).its('body').as('alice'); - }); - - cy.get('@alice'); + // ãƒ¦ãƒ¼ã‚¶ãƒ¼ä½œæˆ + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'alice1234', + }).its('body').as('alice'); }); afterEach(() => { @@ -145,27 +139,21 @@ describe('After user singed in', () => { password: 'pass', }).its('body').as('admin'); - cy.get('@admin').then(() => { - // ãƒ¦ãƒ¼ã‚¶ãƒ¼ä½œæˆ - cy.request('POST', '/api/signup', { - username: 'alice', - password: 'alice1234', - }).its('body').as('alice'); - }); - - cy.get('@alice').then(() => { - cy.visit('/'); + // ãƒ¦ãƒ¼ã‚¶ãƒ¼ä½œæˆ + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'alice1234', + }).its('body').as('alice'); - cy.intercept('POST', '/api/signin').as('signin'); + cy.visit('/'); - cy.get('[data-cy-signin]').click(); - cy.get('[data-cy-signin-username] input').type('alice'); - cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); + cy.intercept('POST', '/api/signin').as('signin'); - cy.wait('@signin').as('signedIn'); - }); + cy.get('[data-cy-signin]').click(); + cy.get('[data-cy-signin-username] input').type('alice'); + cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - cy.get('@signedIn'); + cy.wait('@signin').as('signedIn'); }); afterEach(() => { diff --git a/cypress/support/index.js b/cypress/support/index.js index a9ac34476d..9185be344c 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -20,7 +20,13 @@ import './commands' // require('./commands') Cypress.on('uncaught:exception', (err, runnable) => { - if (err.message.includes('ResizeObserver loop limit exceeded')) { - return false - } + if ([ + // Chrome + 'ResizeObserver loop limit exceeded', + + // Firefox + 'ResizeObserver loop completed with undelivered notifications', + ].some(msg => err.message.includes(msg))) { + return false; + } }); diff --git a/docker-compose.yml b/docker-compose.yml index 717b756c7d..e1d51668a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,6 +40,7 @@ services: # image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2 # environment: # - "ES_JAVA_OPTS=-Xms512m -Xmx512m" +# - "TAKE_FILE_OWNERSHIP=111" # networks: # - internal_network # volumes: diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 8942f9a553..5a053cdee9 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -237,7 +237,6 @@ uploadFromUrlDescription: "رابط المل٠المراد Ø±ÙØ¹Ù‡" uploadFromUrlRequested: "Ø§Ù„Ø±ÙØ¹ مطلوب" uploadFromUrlMayTakeTime: "سيستغرق بعض الوقت لاتمام Ø§Ù„Ø±ÙØ¹ " explore: "استكشاÙ" -games: "ألعاب ميسكي" messageRead: "مقروءة" noMoreHistory: "لا يوجد المزيد من التاريخ" startMessaging: "ابدأ Ù…ØØ§Ø¯Ø«Ø©" @@ -515,7 +514,6 @@ yourAccountSuspendedDescription: "عÙلق Ø§Ù„ØØ³Ø§Ø¨ بسبب انتهاك Ø´ menu: "القائمة" divider: "ÙØ§ØµÙ„" addItem: "Ø¥Ø¶Ø§ÙØ© عنصر" -rooms: "Ø§Ù„ØºØ±ÙØ©" relays: "Ø§Ù„Ù…ÙØ±ÙŽØÙ„Ø§Øª" addRelay: "Ø¥Ø¶Ø§ÙØ© Ù…ÙØ±ØÙ‘Ù„" addedRelays: "المرØÙ„ات Ø§Ù„Ù…Ø¶Ø§ÙØ©" @@ -690,6 +688,7 @@ notRecommended: "غير Ù…Ø³ØªØØ³Ù†" botProtection: "الØÙ…اية من Ø§Ù„ØØ³Ø§Ø¨Ø§Øª الآلية" instanceBlocking: "المثيلات Ø§Ù„Ù…ØØ¬ÙˆØ¨Ø©" selectAccount: "اختر ØØ³Ø§Ø¨Ù‹Ø§" +switchAccount: "تغيير Ø§Ù„ØØ³Ø§Ø¨" enabled: "Ù…ÙØ¹Ù‘Ù„" disabled: "معطّل" quickAction: "الإجراءات السّريعة" @@ -736,6 +735,7 @@ keepCw: "أبق٠على ØªØØ°ÙŠØ±Ø§Øª Ø§Ù„Ù…ØØªÙˆÙ‰" lastCommunication: "آخر تواصل" resolved: "عولج" unresolved: "لم يعالج" +breakFollow: "إلغاء الاشتراك" itsOn: "Ù…ÙØ¹Ù‘Ù„" itsOff: "معطّل" emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل" @@ -751,6 +751,8 @@ unmuteThread: "Ø§Ø±ÙØ¹ الكتم عن النقاش" deleteAccountConfirm: "Ø³ÙŠØØ°Ù ØØ³Ø§Ø¨Ùƒ نهائيًا، أتريد المتابعة؟" incorrectPassword: "كلمة السر خاطئة." hide: "Ø¥Ø®ÙØ§Ø¡" +leaveGroup: "مغادرة Ø§Ù„ÙØ±ÙŠÙ‚" +welcomeBackWithName: "Ù…Ø±ØØ¨Ù‹Ø§ بك مجددًا {name}" _emailUnavailable: used: "هذا البريد الإلكتروني مستخدم" format: "صيغة البريد الإلكتروني غير ØµØ§Ù„ØØ©" @@ -758,6 +760,7 @@ _emailUnavailable: smtp: "خادم البريد الإلكتروتي لا يستجيب" _ffVisibility: public: "علني" + followers: "مرئية Ù„Ù…ØªØ§Ø¨ÙØ¹ÙŠÙƒ Ùقط" private: "خاص" _signup: almostThere: "كدت تنتهي" @@ -842,34 +845,6 @@ _mfm: rainbow: "قوس قزØ" rainbowDescription: "اجعل Ø§Ù„Ù…ØØªÙˆÙ‰ يظهر بألوان الطيÙ" rotate: "تدوير" -_reversi: - gameSettings: "إعدادات اللعبة" - chooseBoard: "اختر اللوØ" - blackOrWhite: "أسود/أبيض" - blackIs: "{name} سيلعب بالأسود" - rules: "القوانين" - botSettings: "خيارات Ø§Ù„ØØ³Ø§Ø¨Ø§Øª الآلية" - thisGameIsStartedSoon: "ستبدأ اللعبة خلال بضع ثوانÙ" - waitingForOther: "ينتظر دور الخصم" - waitingForMe: "ينتظر دورك" - waitingBoth: "استعد" - ready: "جاهز" - cancelReady: "ألغ الجهوزية" - opponentTurn: "دور الخصم" - myTurn: "دورك" - turnOf: "دور {name}" - pastTurnOf: "دور {name}" - surrender: "استسلم" - drawn: "تعادل" - won: "ÙØ§Ø² {name}" - black: "أسود" - white: "أبيض" - total: "المجموع" - turnCount: "الدور {count}" - myGames: "جولاتي" - allGames: "كل الجولات" - ended: "انتهت" - playing: "ÙŠÙلعب الآن" _instanceTicker: remote: "أظهر للمستخدمين Ø§Ù„Ø¨ÙØ¹Ø§Ø¯" _serverDisconnectedBehavior: @@ -886,6 +861,8 @@ _channel: usersCount: "{n} منتسب" notesCount: "{n} Ù…Ù„Ø§ØØ¸Ø©" _menuDisplay: + sideFull: "جانبي" + top: "الأعلى" hide: "Ø¥Ø®ÙØ§Ø¡" _wordMute: muteWords: "الكلمات Ø§Ù„Ù…ØØ¸ÙˆØ±Ø©" @@ -1152,50 +1129,6 @@ _timelines: local: "المØÙ„ÙŠ" social: "الاجتماعي" global: "الشامل" -_rooms: - roomOf: "ØºØ±ÙØ© {user}" - translate: "أنقل" - rotate: "تدوير" - exit: "رجوع" - remove: "أزل" - clear: "أزل الكل" - clearConfirm: "أتريد إزالة كل الأثاث من Ø§Ù„ØºØ±ÙØ©ØŸ" - leaveConfirm: "لديك تغييرات غير Ù…ØÙوظة. أتريد المتابعة دون ØÙظها؟" - chooseImage: "اختر صورة" - roomType: "نوع Ø§Ù„ØºØ±ÙØ©" - carpetColor: "لون السّجاد" - _roomType: - default: "Ø§ÙØªØ±Ø§Ø¶ÙŠ" - washitsu: "الأسلوب الياباني" - _furnitures: - milk: "علبة ØÙ„يب" - bed: "سرير" - low-table: "طاولة قصيرة" - desk: "مكتب" - chair: "كرسي" - chair2: "كرسي 2" - fan: "Ù…Ø±ÙˆØØ©" - pc: "ØØ§Ø³ÙˆØ¨" - plant: "نبات زينة" - plant2: "نبات زينة 2" - eraser: "Ù…Ù…ØØ§Ø©" - pencil: "قلم رصاص" - pudding: "بودينغ" - book: "كتاب" - book2: "كتاب 2" - piano: "بيانو" - server: "خادم" - moon: "قمر" - monitor: "شاشة التØÙƒÙ…" - keyboard: "Ù„ÙˆØØ© Ù…ÙØ§ØªÙŠØ" - wall-clock: "ساعة ØØ§Ø¦Ø·" - photoframe: "إطار صورة" - cube: "مكعب" - tv: "ØªÙ„ÙØ§Ø²" - pinguin: "بطريق" - sofa: "أريكة" - bin: "سلة مهملات" - banknote: "أوراق نقدية" _pages: newPage: "أنشئ ØµÙØØ© جديدة" editPage: "عدّل Ø§Ù„ØµÙØØ©" @@ -1204,16 +1137,21 @@ _pages: updated: "Ù†Ø¬Ø ØªØ¹Ø¯ÙŠÙ„ Ø§Ù„ØµÙØØ©" deleted: "Ù†Ø¬Ø ØØ°Ù Ø§Ù„ØµÙØØ©" pageSetting: "إعدادات Ø§Ù„ØµÙØØ©" + viewSource: "اظهر المصدر" viewPage: "اعرض ØµÙØØ§ØªÙƒ" like: "أعجبني" unlike: "أزل الإعجاب" my: "ØµÙØØ§ØªÙŠ" + featured: "الأكثر شعبية" contents: "Ø§Ù„Ù…ØØªÙˆÙ‰" + title: "العنوان" + summary: "ملخص Ø§Ù„ØµÙØØ©" alignCenter: "توسيط العناصر" hideTitleWhenPinned: "اخ٠عنوان Ø§Ù„ØµÙØØ© عند تدبيسها ÙÙŠ مل٠الشخصي" font: "الخط" fontSerif: "Serif" fontSansSerif: "Sans Serif" + chooseBlock: "Ø¥Ø¶Ø§ÙØ© كتلة" selectType: "اختر النوع" enterVariableName: "أدخل اسم المتغيّر" variableNameIsAlreadyUsed: "هذا الاسم Ù…ØØ¬ÙˆØ²" @@ -1222,6 +1160,8 @@ _pages: specialBlocks: "خاص" blocks: text: "نص" + textarea: "ØÙ‚Ù„ نصي" + section: "قسم" image: "الصور" button: "زرّ" _if: diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml new file mode 100644 index 0000000000..ed97d539c0 --- /dev/null +++ b/locales/bn-BD.yml @@ -0,0 +1 @@ +--- diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 90e8dbc9c1..dcbbfcab82 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -207,7 +207,6 @@ uploadFromUrl: "Nahrát z URL adresy" uploadFromUrlDescription: "URL adresa souboru, který chcete nahrát" uploadFromUrlMayTakeTime: "Může trvat nÄ›jakou dobu, dokud nebude dokonÄeno nahrávánÃ." explore: "Objevovat" -games: "Misskey hry" messageRead: "PÅ™eÄtené" noMoreHistory: "To je vÅ¡e" startMessaging: "Zahájit chat" @@ -272,6 +271,8 @@ monthX: "{month}" yearX: "{year}" pages: "Stránky" integration: "Integrace" +connectService: "PÅ™ipojit" +disconnectService: "Odpojit" enableLocalTimeline: "Povolit lokálnà Äas" enableGlobalTimeline: "Povolit globálnà Äas" registration: "Registrace" @@ -280,8 +281,10 @@ invite: "Pozvat" inMb: "V megabajtech" iconUrl: "Favicon URL" bannerUrl: "Baner URL" +backgroundImageUrl: "Adresa URL obrázku pozadÃ" basicInfo: "Základnà informace" hcaptcha: "hCaptcha" +enableHcaptcha: "Aktivovat hCaptchu" hcaptchaSecretKey: "Tajný KlÃÄ (Secret Key)" recaptcha: "reCAPTCHA" enableRecaptcha: "Zapnout ReCAPTCHu" @@ -293,6 +296,7 @@ antennaSource: "Zdroj Antény" enableServiceworker: "Povolit ServiceWorker" caseSensitive: "RozliÅ¡uje malá a velká pÃsmena" connectedTo: "NásledujÃcà úÄty jsou pÅ™ipojeny" +popularTags: "Populárnà tagy" userList: "Seznamy" about: "Informace" aboutMisskey: "O Misskey" @@ -336,6 +340,9 @@ next: "DalÅ¡Ã" retype: "Zadejte znovu" noteOf: "{user} poznámky" inviteToGroup: "Pozvat do skupiny" +newMessageExists: "Máte novou zprávu" +onlyOneFileCanBeAttached: "Ke zprávÄ› můžete pÅ™iložit jenom jeden soubor" +signinRequired: "PÅ™ihlaÅ¡te se, prosÃm" invitations: "Pozvat" checking: "Ověřuji" available: "K dispozici" @@ -363,10 +370,13 @@ signinHistory: "Historie pÅ™ihlášenÃ" category: "Kategorie" tags: "Å tÃtky" createAccount: "VytvoÅ™it úÄet" +existingAccount: "ExistujÃcà úÄet" +regenerate: "Obnovit" fontSize: "Velikost pÃsma" openImageInNewTab: "OtevÅ™Ãt obrázek v novém panelu" dashboard: "PÅ™ehled" local: "LokálnÃ" +remote: "Vzdálené" total: "Celkem" weekOverWeekChanges: "TýdnÄ›" dayOverDayChanges: "DennÄ›" @@ -376,6 +386,9 @@ accountSettings: "Nastavenà úÄtu" promotion: "Propagace" promote: "Propagovat" numberOfDays: "PoÄet dnÃ" +deleteAll: "Smazat vÅ¡e" +showFixedPostForm: "Zobrazit formulář pro nové pÅ™ÃspÄ›vky nad Äasovou osou" +masterVolume: "Celková hlasitost" chooseEmoji: "Vybrat emotikon" unableToProcess: "Operace nebyla dokonÄena." recentUsed: "Naposledy použité" @@ -385,25 +398,57 @@ installedApps: "Autorizované aplikace" nothing: "Nic nebylo nalezeno" lastUsedDate: "Poslednà použitÃ" state: "Stav" +sort: "SeÅ™adit" ascendingOrder: "VzestupnÄ›" descendingOrder: "SestupnÄ›" scratchpad: "ZápisnÃk" output: "Výstup" script: "Skript" +updateRemoteUser: "Aktualizovat informace o vzdáleném úÄtu" deleteAllFiles: "Smazat vÅ¡echny soubory" deleteAllFilesConfirm: "Jste si jistà že chcete smazat vÅ¡echny soubory?" userSuspended: "Tomuto uživateli byl pozastaven úÄet." +menu: "Menu" addItem: "PÅ™idat položku" -rooms: "MÃstnost" inboxUrl: "Inbox URL" deletedNote: "OdstranÄ›né pÅ™ÃspÄ›vky" invisibleNote: "Skryté pÅ™ÃspÄ›vky" +description: "Popis" +author: "Autor" +manage: "Administrace" +small: "Malé" +generateAccessToken: "Vygenerovat pÅ™Ãstupový token" +permission: "OprávnÄ›nÃ" +enableAll: "Povolit vÅ¡e" +disableAll: "Vypnout vÅ¡e" +notificationType: "Typy oznámenÃ" +edit: "Upravit" +emailServer: "Mailový server" +enableEmail: "Zapnout email dystribuci" +email: "Email" +emailAddress: "Emailová adresa" +smtpConfig: "Konfigurace SMTP serveru" smtpHost: "Hostitel" +smtpPort: "Port" smtpUser: "Uživatelské jméno" smtpPass: "Heslo" +smtpSecureInfo: "Toto vypnÄ›te pokud použÃváte STARTTLS" +makeActive: "Aktivovat" +display: "Zobrazit" +copy: "KopÃrovat" +logs: "Logy" +database: "Databáze" +create: "VytvoÅ™it" +notificationSetting: "Nastavenà oznámenÃ" +useGlobalSetting: "PoužÃt globálnà nastavenÃ" +other: "OstatnÃ" +fileIdOrUrl: "ID nebo URL souboru" +behavior: "ChovánÃ" +sample: "Ukázka" clearCache: "Vyprázdnit mezipaměť" info: "Informace" user: "Uživatelé" +administration: "Administrace" _email: _follow: title: "Máte nového následovnÃka" @@ -412,9 +457,8 @@ _mfm: quote: "Citovat" emoji: "Vlastnà emoji" search: "VyhledávánÃ" -_reversi: - total: "Celkem" _theme: + description: "Popis" keys: mention: "ZmÃnÄ›nÃ" renote: "PÅ™eposlat" @@ -442,11 +486,6 @@ _exportOrImport: userLists: "Seznamy" _timelines: home: "Domů" -_rooms: - _roomType: - default: "VýchozÃ" - _furnitures: - monitor: "Monitorovat" _pages: blocks: image: "Obrázky" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 240a67778f..2f327a905c 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -242,7 +242,6 @@ uploadFromUrlDescription: "URL der hochzuladenden Datei" uploadFromUrlRequested: "Upload angefordert" uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschlossen ist." explore: "Erkunden" -games: "Misskey-Spiele" messageRead: "Gelesen" noMoreHistory: "Kein weiterer Verlauf vorhanden" startMessaging: "Neuen Chat erstellen" @@ -537,7 +536,6 @@ yourAccountSuspendedDescription: "Dieses Benutzerkonto wurde gesperrt, da es geg menu: "Menü" divider: "Trenner" addItem: "Element hinzufügen" -rooms: "Raum" relays: "Relays" addRelay: "Relay hinzufügen" inboxUrl: "inbox-URL" @@ -621,8 +619,11 @@ reportAbuse: "Melden" reportAbuseOf: "{name} melden" fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an." abuseReported: "Die Meldung wurde versendet. Vielen Dank." +reporter: "Melder" reporteeOrigin: "Herkunft des Gemeldeten" reporterOrigin: "Herkunft des Meldenden" +forwardReport: "Meldung an fremde Instanz weiterleiten" +forwardReportIsAnonymous: "Anstatt deines Benutzerkontos wird bei der fremden Instanz ein anonymes Systemkonto als Melder angezeigt." send: "Senden" abuseMarkAsResolved: "Meldung als gelöst markieren" openInNewTab: "In neuem Tab öffnen" @@ -670,7 +671,6 @@ emailVerified: "Email-Adresse bestätigt" noteFavoritesCount: "Anzahl an als Favorit markierter Notizen" pageLikesCount: "Anzahl an als \"Gefällt mir\" markierter Seiten" pageLikedCount: "Anzahl erhaltener \"Gefällt mir\" auf Seiten" -reversiCount: "Anzahl an Reversi-Runden" contact: "Kontakt" useSystemFont: "Standardschriftart des Systems verwenden" clips: "Clips" @@ -746,6 +746,7 @@ notRecommended: "Nicht empfohlen" botProtection: "Bot-Schutz" instanceBlocking: "Blockierte Instanzen" selectAccount: "Benutzerkonto auswählen" +switchAccount: "Konto wechseln" enabled: "Aktiviert" disabled: "Deaktiviert" quickAction: "Schnellaktionen" @@ -944,39 +945,6 @@ _mfm: sparkleDescription: "Verleiht Inhalt einen glitzernden Partikeleffekt." rotate: "Drehen" rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel" -_reversi: - reversi: "Reversi" - gameSettings: "Spieleinstellungen" - chooseBoard: "Spielbrett auswählen" - blackOrWhite: "Schwarz/Weiß" - blackIs: "{name} spielt Schwarz" - rules: "Regeln" - botSettings: "Optionen des Computergegners" - thisGameIsStartedSoon: "Dieses Spiel beginnt in wenigen Sekunden" - waitingForOther: "Warte auf den Zug des Gegenspielers" - waitingForMe: "Warte auf deinen Zug" - waitingBoth: "Mach dich bereit" - ready: "Bereit" - cancelReady: "Nicht bereit" - opponentTurn: "Zug deines Gegners" - myTurn: "Dein Zug" - turnOf: "{name} ist am Zug" - pastTurnOf: "Zug von {name}" - surrender: "Aufgeben" - surrendered: "Durch Aufgabe" - drawn: "Unentschieden" - won: "{name} gewinnt" - black: "Schwarz" - white: "Weiß" - total: "Gesamt" - turnCount: " Zug {count}" - myGames: "Meine Runden" - allGames: "Alle Runden" - ended: "Beendet" - playing: "Laufend" - isLlotheo: "Der mit weniger Steinen gewinnt (Llotheo)" - loopedMap: "Wiederholendes Spielbrett" - canPutEverywhere: "Steine können überall platziert werden" _instanceTicker: none: "Nie anzeigen" remote: "Für Benutzer fremder Instanzen anzeigen" @@ -1096,8 +1064,6 @@ _sfx: chatBg: "Chat (Hintergrund)" antenna: "Antennen" channel: "Kanalbenachrichtigung" - reversiPutBlack: "Reversi: Schwarz macht einen Zug" - reversiPutWhite: "Reversi: Weiß macht einen Zug" _ago: unknown: "Unbekannt" future: "Zukunft" @@ -1320,68 +1286,6 @@ _timelines: local: "Lokal" social: "Sozial" global: "Global" -_rooms: - roomOf: "{user}'s Raum" - addFurniture: "Möbel hinzufügen" - translate: "Bewegen" - rotate: "Drehen" - exit: "Zurück" - remove: "Entfernen" - clear: "Aufräumen" - clearConfirm: "Möchtest du wirklich alle Möbel entfernen?" - leaveConfirm: "Es gibt ungespeicherte Änderungen. Möchtest du wirklich gehen?" - chooseImage: "Bild auswählen" - roomType: "Raumart" - carpetColor: "Teppichfarbe" - _roomType: - default: "Standard" - washitsu: "Japanischer Stil" - _furnitures: - milk: "Milchkarton" - bed: "Bett" - low-table: "Niedrigtisch" - desk: "Schreibtisch" - chair: "Stuhl" - chair2: "Stuhl 2" - fan: "Ventilator" - pc: "Computer" - plant: "Deko-Pflanze" - plant2: "Deko-Pflanze 2" - eraser: "Radiergummi" - pencil: "Bleistift" - pudding: "Pudding" - cardboard-box: "Pappkarton" - cardboard-box2: "Pappkarton 2" - cardboard-box3: "Pappkarton 3" - book: "Buch" - book2: "Buch 2" - piano: "Piano" - facial-tissue: "Taschentücher" - server: "Server" - moon: "Mond" - corkboard: "Pinnwand" - mousepad: "Mauspad" - monitor: "Monitor" - keyboard: "Tastatur" - carpet-stripe: "Gestreifter Teppich" - mat: "Matte" - color-box: "Regal" - wall-clock: "Wanduhr" - photoframe: "Bilderrahmen" - cube: "Würfel" - tv: "Fernseher" - pinguin: "Pinguin" - rubik-cube: "Zauberwürfel" - poster-h: "Poster (Horizontal)" - poster-v: "Poster (Vertikal)" - sofa: "Sofa" - spiral: "Spiraltreppe" - bin: "Papierkorb" - cup-noodle: "Instantnudeln" - holo-display: "Holographischer Bildschirm" - energy-drink: "Energy Drink" - doll-ai: "Ai-Puppe" - banknote: "Geldscheine" _pages: newPage: "Seite erstellen" editPage: "Seite bearbeiten" diff --git a/locales/en-US.yml b/locales/en-US.yml index 9286e78cc3..6bbe848210 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -242,7 +242,6 @@ uploadFromUrlDescription: "URL of the file you want to upload" uploadFromUrlRequested: "Upload requested" uploadFromUrlMayTakeTime: "It may take some time until the upload is complete." explore: "Explore" -games: "Misskey Games" messageRead: "Read" noMoreHistory: "There is no further history" startMessaging: "Start a new chat" @@ -537,7 +536,6 @@ yourAccountSuspendedDescription: "This account has been suspended due to breakin menu: "Menu" divider: "Divider" addItem: "Add Item" -rooms: "Room" relays: "Relays" addRelay: "Add Relay" inboxUrl: "Inbox URL" @@ -621,8 +619,11 @@ reportAbuse: "Report" reportAbuseOf: "Report {name}" fillAbuseReportDescription: "Please fill in details regarding this report. If it is about a specific note, please include its URL." abuseReported: "Your report has been sent. Thank you very much." +reporter: "Reporter" reporteeOrigin: "Reportee Origin" reporterOrigin: "Reporter Origin" +forwardReport: "Forward report to remote instance" +forwardReportIsAnonymous: "Instead of your account, an anonymous system account will be displayed as reporter at the remote instance." send: "Send" abuseMarkAsResolved: "Mark report as resolved" openInNewTab: "Open in new tab" @@ -670,7 +671,6 @@ emailVerified: "Email has been verified" noteFavoritesCount: "Number of favorite notes" pageLikesCount: "Number of liked Pages" pageLikedCount: "Number of received Page likes" -reversiCount: "Number of Reversi matches" contact: "Contact" useSystemFont: "Use the system's default font" clips: "Clips" @@ -746,6 +746,7 @@ notRecommended: "Not recommended" botProtection: "Bot Protection" instanceBlocking: "Blocked Instances" selectAccount: "Select account" +switchAccount: "Switch account" enabled: "Enabled" disabled: "Disabled" quickAction: "Quick actions" @@ -794,6 +795,7 @@ pubSub: "Pub/Sub Accounts" lastCommunication: "Last communication" resolved: "Resolved" unresolved: "Unresolved" +breakFollow: "Unfollow" itsOn: "Enabled" itsOff: "Disabled" emailRequiredForSignup: "Require email address for sign-up" @@ -943,39 +945,6 @@ _mfm: sparkleDescription: "Gives content a sparkling particle effect." rotate: "Rotate" rotateDescription: "Turns content by a specified angle." -_reversi: - reversi: "Reversi" - gameSettings: "Game settings" - chooseBoard: "Choose a board" - blackOrWhite: "Black/White" - blackIs: "{name} is playing Black" - rules: "Rules" - botSettings: "Bot options" - thisGameIsStartedSoon: "The game will start in a few seconds" - waitingForOther: "Waiting for the opponent's turn" - waitingForMe: "Waiting for your turn" - waitingBoth: "Get ready" - ready: "Ready" - cancelReady: "Cancel ready" - opponentTurn: "Opponent's turn" - myTurn: "Your turn" - turnOf: "It's {name}'s turn" - pastTurnOf: "{name}'s turn" - surrender: "Surrender" - surrendered: "By surrender" - drawn: "Draw" - won: "{name} wins" - black: "Black" - white: "White" - total: "Total" - turnCount: "Turn {count}" - myGames: "My rounds" - allGames: "All rounds" - ended: "Ended" - playing: "Currently playing" - isLlotheo: "The one with fewer stones wins (Llotheo)" - loopedMap: "Looping map" - canPutEverywhere: "Tiles are placeable everywhere" _instanceTicker: none: "Never show" remote: "Show for remote users" @@ -1095,8 +1064,6 @@ _sfx: chatBg: "Chat (Background)" antenna: "Antennas" channel: "Channel notifications" - reversiPutBlack: "Reversi: Black makes a move" - reversiPutWhite: "Reversi: White makes a move" _ago: unknown: "Unknown" future: "Future" @@ -1319,68 +1286,6 @@ _timelines: local: "Local" social: "Social" global: "Global" -_rooms: - roomOf: "{user}'s room" - addFurniture: "Place furniture" - translate: "Move" - rotate: "Rotate" - exit: "Back" - remove: "Remove" - clear: "Remove All" - clearConfirm: "Do you really want to remove all furniture from your room?" - leaveConfirm: "There are unsaved changes. Do you really want to leave?" - chooseImage: "Select an image" - roomType: "Room type" - carpetColor: "Carpet color" - _roomType: - default: "Default" - washitsu: "Japanese-style" - _furnitures: - milk: "Milk carton" - bed: "Bed" - low-table: "Low Table" - desk: "Desk" - chair: "Chair" - chair2: "Chair 2" - fan: "Fan" - pc: "Computer" - plant: "Houseplant" - plant2: "Houseplant 2" - eraser: "Eraser" - pencil: "Pencil" - pudding: "Pudding" - cardboard-box: "Cardboard Box" - cardboard-box2: "Cardboard Box 2" - cardboard-box3: "Cardboard Box 3" - book: "Book" - book2: "Book 2" - piano: "Piano" - facial-tissue: "Tissues" - server: "Server" - moon: "Moon" - corkboard: "Cork board" - mousepad: "Mousepad" - monitor: "Monitor" - keyboard: "Keyboard" - carpet-stripe: "Carpet (striped)" - mat: "Mat" - color-box: "Bookshelf" - wall-clock: "Wall clock" - photoframe: "Picture frame" - cube: "Cube" - tv: "TV" - pinguin: "Penguin" - rubik-cube: "Puzzle Cube" - poster-h: "Poster (Horizontal)" - poster-v: "Poster (Vertical)" - sofa: "Sofa" - spiral: "Spiral Staircase" - bin: "Garbage can" - cup-noodle: "Cup noodles" - holo-display: "Holographic display" - energy-drink: "Energy drink" - doll-ai: "Ai doll" - banknote: "Pile of money" _pages: newPage: "Create a new Page" editPage: "Edit this Page" diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml index 15a8440a23..0689834a1e 100644 --- a/locales/eo-UY.yml +++ b/locales/eo-UY.yml @@ -1,8 +1,8 @@ --- _lang_: "Esperanto" headlineMisskey: "Reto konektita per notoj" -introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza etbloga servo.\nKreu \"noto\"n por diskonigi nunan aferon, aÅ por paroli vian penson al ĉiuj ĉirkaÅ vi. 📡\nLa funkcion \"reago\" ebligas esprimi rapide vian senton pri la noto de la alia en la Fediverso. ðŸ‘\nBonvole esploru novan mondon. 🚀" -monthAndDay: "La {day}a de la {month}a monato" +introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza etbloga servo.\nKreu \"noto\"n por diskonigi nunan aferon, aÅ por paroli vian penson al ĉiuj ĉirkaÅ vi. 📡\nLa funkcio \"reago\" ebligas esprimi rapide vian senton pri la noto de la alia en la Fediverso. ðŸ‘\nBonvole esploru novan mondon. 🚀" +monthAndDay: "La {day}a de l' {month}a" search: "Serĉi" notifications: "Sciigoj" username: "Uzantnomo" @@ -23,7 +23,7 @@ otherSettings: "Aliaj agordoj" openInWindow: "Malfermi en nova fenestro" profile: "Profilo" timeline: "Templinio" -noAccountDescription: "La uzanto ankoraÅ ne skribis la sinprezenton en sia profilo." +noAccountDescription: "La uzanto ankoraÅ ne skribis la prion de sia profilo." login: "Saluti" loggingIn: "Salutado…" logout: "AdiaÅi" @@ -41,10 +41,10 @@ cantFavorite: "Oni ne povis aldoni al viaj preferaĵoj." pin: "Alpingli" unpin: "Depingli" copyContent: "Kopii enhavon" -copyLink: "Kopii ligilon" +copyLink: "Kopii la ligilon" delete: "ForviÅi" deleteAndEdit: "ForviÅi kaj redakti" -deleteAndEditConfirm: "Ĉu vi certas ke vi volas redakti foriginte la noton? Tio forviÅos reagojn, plusendojn, kaj respondojn ĉiujn apartenantajn al Äi." +deleteAndEditConfirm: "Ĉu vi certas ke vi volas foriginte redakti la noton? Vi perdos ĉiujn reagojn, plusendojn, kaj respondojn je Äi." addToList: "Aldoni al listo" sendMessage: "Sendi mesaÄon" copyUsername: "Kopii uzantnomon" @@ -69,7 +69,7 @@ lists: "Listoj" noLists: "Neniu listo" note: "Noti" notes: "Notoj" -following: "Sekvatoj" +following: "Sekvata" followers: "Sekvantoj" followsYou: "Sekvas vin" createList: "Krei liston" @@ -97,7 +97,7 @@ quote: "Citi" pinnedNote: "Alpinglita noto" pinned: "Alpingli" you: "Vi" -clickToShow: "Klaku por malkaÅu" +clickToShow: "Klaki por malkaÅi" sensitive: "Enhavo ne estas deca por laborejo (NSFW)" add: "Aldoni" reaction: "Reagoj" @@ -122,7 +122,7 @@ selectAntenna: "Elekti antenon" selectWidget: "Elekti enestraĵon" editWidgets: "Redakti fenestraĵon" editWidgetsExit: "Fini la redaktadon" -customEmojis: "Federaj emoĵioj" +customEmojis: "Propraj emoÄioj" emoji: "Emoĵio" emojis: "Emoĵio" emojiName: "Nomo de la emoĵio" @@ -132,11 +132,10 @@ settingGuide: "Agordaj rekomendoj" cacheRemoteFiles: "Stapli forajn dosierojn" flagAsBot: "Marki kiel esti uzanto de roboto" flagAsCat: "Marki kiel esti kato" -flagAsCatDescription: "Se vi estas kato, faru ĉi tiun flagon" autoAcceptFollowed: "AÅtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas" addAccount: "Aldoni konton" loginFailed: "Saluto malsukcesis" -showOnRemote: "Vidi ĉe la surloka nodo" +showOnRemote: "Vidi pli al la originala profilo" general: "Äœenerala" wallpaper: "Ekranfonoj" setWallpaper: "Apliki ekranfonon" @@ -167,7 +166,7 @@ withNFiles: "{n} dosiero(j)" monitor: "Monitoro" network: "Reto" disk: "Disko" -instanceInfo: "Informoj pri la nodo" +instanceInfo: "Informoj sur la nodo" statistics: "Statistikoj" clearCachedFiles: "Malplenigi la staplon" clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviÅi ĉiujn forajn dosierojn en la staplo?" @@ -223,7 +222,6 @@ uploadFromUrl: "AlÅuti de URL" uploadFromUrlDescription: "URL de la dosiero kiun vi volas alÅuti" uploadFromUrlRequested: "La alÅutado estis patita" explore: "Esplori" -games: "Miskiaj Ludoj" messageRead: "Legita" noMoreHistory: "Ne plu de la historio" startMessaging: "Komenci babiladon" @@ -232,7 +230,7 @@ agreeTo: "Mi akceptas {0}" tos: "Kondiĉoj de uzado" start: "KomenciÄi" home: "Hejma" -remoteUserCaution: "Ĉi tiuj infomoj ne estas kompletaj, ĉar ili estas pri uzanto el la fora." +remoteUserCaution: "Pro fora uzanto, la infomoj ne estas tuto." activity: "Aktiveco" images: "Bildoj" birthday: "NaskiÄdato" @@ -283,7 +281,7 @@ normal: "Normala" instanceName: "Nomo de la nodo" instanceDescription: "Priskribo de la nodo " maintainerName: "Nomo de la administranto" -maintainerEmail: "RetpoÅta adreso de la administranto" +maintainerEmail: "RetpoÅtadreso de la administranto" tosUrl: "URL de kondiĉoj de uzado" thisYear: "Ĉi-jare" thisMonth: "Ĉi-monate" @@ -307,9 +305,9 @@ bannerUrl: "URL de standardo" backgroundImageUrl: "URL de la fona bildo" basicInfo: "Baza informo" pinnedUsers: "Alpinglita uzanto" -pinnedUsersDescription: "Listigu uzantnomojn apartige en ĉiu linio por alpingli al la paÄoj ekz \"Esplori\"." +pinnedUsersDescription: "LaÅlinigu uzantnomojn en ĉiu linio, por alpingli al la paÄoj ekz \"Esplori\"." pinnedPages: "Alpinglitaj paÄoj" -pinnedPagesDescription: "Listigu dosierindiko apartige en ĉiu linio por alpingli al la ĉefpaÄo de la nodo." +pinnedPagesDescription: "LaÅlinigu dosierindikojn de paÄo en ĉiu linio, por alpingli al la ĉefpaÄo de la nodo." pinnedNotes: "Alpinglita noto" hcaptcha: "hCaptcha" enableHcaptcha: "Ebligi hCaptcha" @@ -367,7 +365,7 @@ cacheClear: "Malplenigi staplon" markAsReadAllNotifications: "Marki ĉiujn sciigojn kiel legita" markAsReadAllTalkMessages: "Marki ĉiujn retbabiladojn kiel legita" help: "Manlibro de uzado" -inputMessageHere: "Entajpu masaÄo tie ĉi" +inputMessageHere: "Entajpu mesaÄon tie" close: "Fermi" group: "Grupo" groups: "Grupoj" @@ -378,7 +376,7 @@ invites: "Inviti" groupName: "Grupa nomo" members: "Membroj" transfer: "Movi" -messagingWithUser: "Babili private" +messagingWithUser: "Private babili " messagingWithGroup: "Babili grupe" title: "Titolo" text: "Teksto" @@ -434,6 +432,7 @@ clientSettings: "Agordoj de kliento" accountSettings: "Agordoj de konto" numberOfDays: "Nombro de tagoj" hideThisNote: "KaÅi la noton" +showFeaturedNotesInTimeline: "Montri en via templinio notojn de la tendenco" objectStorageBaseUrl: "Baza URL" objectStoragePrefix: "Prefix" objectStorageRegion: "Regiono" @@ -469,11 +468,10 @@ disablePagesScript: "Malebligi AiScript en la paÄoj" deleteAllFiles: "ForviÅi ĉiujn dosierojn" deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviÅi ĉiujn dosierojn?" removeAllFollowing: "Ĉesi sekvi ĉiujn sekvatojn" -userSuspended: "Ĉi tiu uzanto estas flostigita." -userSilenced: "Ĉi tiu uzanto estas mutigita." +userSuspended: "La uzanto estas flostigita." +userSilenced: "La uzanto estas mutigita." menu: "Menuo" addItem: "Aldoni novaĵon" -rooms: "Ĉambro" deletedNote: "ForviÅita noto" invisibleNote: "Malpublikigita noto" enableInfiniteScroll: "Ebligi infinitan rulumon" @@ -504,9 +502,10 @@ disableAll: "Malebligi ĉiujn" notificationType: "Tipo de sciigoj" edit: "Redakti" emailServer: "RetpoÅta servilo" -enableEmail: "Ebligi dissendon el retpoÅto" +enableEmail: "Ebligi dissendon de retpoÅto" +emailConfigInfo: "Uzata por konfirmi vian retadreson kiam registri kaj por restarigi vian pasvorton" email: "RetpoÅto" -emailAddress: "RetpoÅta adreso" +emailAddress: "RetpoÅtadreso" smtpConfig: "Agordoj de SMTP servilo" smtpHost: "Transa servilo" smtpPort: "Pordo" @@ -531,12 +530,9 @@ regenerateLoginToken: "Regeneri la aÅtentikigan pecon" fileIdOrUrl: "Dosiera identigilo aÅ URL" behavior: "Konduto" sample: "Ekzemplo" -abuseReports: "Signaloj" -reportAbuse: "Signalo" -reportAbuseOf: "Signali kontraÅ {name}" send: "Sendi" openInNewTab: "Malfermi en nova langeto" -editTheseSettingsMayBreakAccount: "Redakti ĉi tiujn agordojn povas damaÄi vian konton." +editTheseSettingsMayBreakAccount: "Redakti tiujn agordojn povas damaÄi vian konton." instanceTicker: "Nomo de la nodo sendinta notojn" waitingFor: "Atendado pro {x}" random: "Hazarde" @@ -559,17 +555,17 @@ sentReactionsCount: "La nombro de la reagoj senditaj" receivedReactionsCount: "La nombro de la reagoj ricevitaj" yes: "Jes" no: "Ne" -driveFilesCount: "La nombro de la dosieroj ĉe la disko" +driveFilesCount: "La nombro de la dosieroj sur la disko" notSet: "Ne elektita" -emailVerified: "Via retpoÅto estis kontrolita." +emailVerified: "Via retpoÅtadreso estis kontrolita." noteFavoritesCount: "La nombro de notoj preferataj" -pageLikesCount: "La nombro de paÄoj kiun la uzanto preferas" -pageLikedCount: "La nombro de uzantoj, kiuj preferas paÄon de ĉi tiu uzanto" +pageLikesCount: "La nombro de paÄa plaĉon" +pageLikedCount: "La nombro de la ricevita \"Mi plaĉas\"" contact: "Kontakto" useSystemFont: "Uzi la tiparon implicitan de la sistemo" developer: "Evoluiganto" -makeExplorable: "Videbligi konton sur la paÄo \"Esplori\"" -makeExplorableDescription: "Se vi elÅaltas tiun, via konto ne montros en la paÄo \"Esplori\"." +makeExplorable: "La konton videbligi sur la paÄo \"Esplori\"" +makeExplorableDescription: "Se vi elÅaltas tiun, via konto ne montros sur la paÄo \"Esplori\"." duplicate: "Duobligi" left: "Maldekstra" center: "Centra" @@ -592,7 +588,7 @@ updatedAt: "Laste Äisdatigita" saveConfirm: "Ĉu vi konservas la ÅanÄon?" deleteConfirm: "Ĉu certas forviÅi?" closeAccount: "Forigi konton" -currentVersion: "Nuna versio" +currentVersion: "La aktuala versio" latestVersion: "La plej nova versio" youAreRunningUpToDateClient: "Vi uzas la plej novan version de via kliento." newVersionOfClientAvailable: "Nova versio de via kliento estas disponebla." @@ -624,6 +620,7 @@ memo: "Memorigilo" high: "Alta" middle: "Meza" low: "Malalta" +emailNotConfiguredWarning: "Vi ne agordis retpoÅtadreso." customCss: "Personecigita CSS" global: "Malloka" sent: "Sendi" @@ -636,19 +633,21 @@ translate: "Traduki" translatedFrom: "Tradukita el {x}" breakFollow: "Ĉesigi la sekvadon al vi" itsOn: "Åœaltita" +emailRequiredForSignup: "Registri konton devas konformi retpoÅtadreson" unread: "Nelegita" controlPanel: "Åœaltpodio" manageAccounts: "Bonteni la kontojn" classic: "Klasika" muteThread: "Silentigi la mesaÄaron" unmuteThread: "Malsilentigi la mesaÄaron" -ffVisibility: "Videbleco de la sekvadoj pri vi" -ffVisibilityDescription: "Tie ĉi vi povas agordi la videblecon pri kiuj povas vidi tiujn, kiujn vi sekvas kaj kiuj sekvas vin." +ffVisibility: "Videbleco de viaj sekvatoj/sekvantoj" +ffVisibilityDescription: "Oni permesas agordi tiuln kiuj povas vidi la homojn kiujn vi sekvas, kaj la homojn kiuj sekvas vin." continueThread: "Pli vidi la mesaÄaron" incorrectPassword: "Nevalida pasvorto" leaveGroup: "EliÄi el la grupo" leaveGroupConfirm: "Ĉu vi certas ke vi volas eliÄi el la grupo {name}?" -welcomeBackWithName: "Alvenbenon! {name}" +welcomeBackWithName: "Bonrevenon, {name}!" +clickToFinishEmailVerification: "Volu klaki [{ok}] por fini la konfirmon de vian retadreson" _emailUnavailable: used: "La retpoÅto jam estas uzita." format: "Nevalida formato." @@ -665,7 +664,7 @@ _accountDelete: _ad: back: "Nuligi" _forgotPassword: - enterEmail: "Entajpu la retpoÅton kiun vi registrigis al via konto. Ligilo por restarigi pasvorton estos sendita al la retpoÅto." + enterEmail: "Entajpu la retpoÅton kiun vi registrigis al via konto. Ligilo por restarigi pasvorton estos sendita al la retadreso." _gallery: liked: "Åœatitaj notoj" like: "Åœati" @@ -703,7 +702,7 @@ _mfm: inlineMath: "Formulo (en linio)" blockMath: "Formulo (bloko)" quote: "Citi" - emoji: "Federaj emoĵioj" + emoji: "Propraj emoÄioj" search: "Serĉi" flip: "Inversa" x2: "Granda" @@ -711,8 +710,6 @@ _mfm: x4: "Pli grandega" font: "Presliteraro" rotate: "OrientiÄo" -_reversi: - total: "Entute" _instanceTicker: none: "Ne montri" remote: "Montri al foraj uzantoj" @@ -725,7 +722,7 @@ _channel: setBanner: "Apliki standardan bildon" removeBanner: "ForviÅi la standardan bildon" owned: "Bontenitaj de vi" - following: "Sekvante" + following: "Sekvado" usersCount: "{n} partoprenantoj" _menuDisplay: sideFull: "Flanko" @@ -783,12 +780,12 @@ _time: _tutorial: title: "Uzado de Misskey" step1_1: "Bonvenon." - step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}." + step7_2: "Se vi volas pli scii pri Misskey, vidu la fakon {help}." step7_3: "Do, bonvolu amuziÄi sur Misskey🚀" _2fa: registerKey: "Nove registri Ålosilon" _permissions: - "read:account": "Legado de la informoj pri via konto" + "read:account": "Vidi la informojn de via konto" "write:account": "Redatado de la informoj de via konto" "read:blocks": "Vidi vian liston de uzantoj blokitaj" "write:blocks": "Redakti vian liston de blokitoj" @@ -796,8 +793,8 @@ _permissions: "write:drive": "Ĉia operacio por skribi, forviÅi, aÅ alimaniere ÅanÄi la informon de dosiero en via disko de Misskey" "read:favorites": "Vidi vian liston de preferaĵoj" "write:favorites": "Redakti vian liston de preferaĵoj" - "read:following": "Vidi la infomojn pri la sekvadoj pri vi" - "write:following": "Sekvi/Malsekvi alian uzanton" + "read:following": "Vidi la informojn de sekvo" + "write:following": "Sekvi/ Ĉesi sekvi alian uzanton" "read:messaging": "Vidi viajn retbabiladojn" "write:messaging": "Administri viajn retbabiladojn" "read:mutes": "Vidi vian liston de silentigitoj" @@ -812,7 +809,7 @@ _permissions: "read:channels": "Vidi kanalojn" _antennaSources: all: "Ĉiuj notoj" - homeTimeline: "Notoj de uzantoj kiujn vi sekvas" + homeTimeline: "Notoj de la uzantoj kiujn vi sekvas" _weekday: sunday: "Dimanĉo" monday: "Lundo" @@ -856,18 +853,19 @@ _visibility: _postForm: replyPlaceholder: "Respondi la noton…" quotePlaceholder: "Citi la noton…" - channelPlaceholder: "Mencii en la kanalo…" + channelPlaceholder: "AfiÅi en la kanalo…" _placeholders: a: "Kiel vi fartas?" b: "Kio okazis ĉirkaÅ vi?" - c: "Kion vi pensas?" - d: "Kion vi parolos?" - e: "Komencu skribi…" + c: "Kio estas sur via penso?" + d: "Kion vi volas diri?" + e: "Komencu skribi tie" + f: "Atendanta de vi skribon…" _profile: name: "Nomo" username: "Uzantnomo" description: "Sinprezento" - metadata: "Kromaj informoj" + metadata: "Kromaj Informoj" metadataEdit: "Redakti kromajn informojn" changeAvatar: "ÅœanÄi profilbildon" changeBanner: "ÅœanÄi standardon" @@ -888,42 +886,18 @@ _timelines: local: "Loka" social: "Sociala" global: "Malloka" -_rooms: - translate: "Movi" - chooseImage: "Elekti bildon" - _roomType: - default: "Implicitaĵo" - _furnitures: - bed: "Lito" - low-table: "Malaltotablo" - desk: "Skribotablo" - chair: "SeÄo" - chair2: "SeÄo 2" - pc: "Komputilo" - eraser: "Skrapileto" - pencil: "Krajono" - pudding: "FlaÅno" - book: "Libro" - book2: "Libro 2" - piano: "Piano" - facial-tissue: "Tualetpaperejo" - server: "Servilo" - moon: "Luno" - monitor: "Monitoro" - keyboard: "Klavaro" - doll-ai: "Pupa Ai" _pages: newPage: "Krei novan paÄon" editPage: "Redakti paÄon" deleted: "Oni forviÅis la paÄon." editThisPage: "Redakti la paÄon" - viewPage: "Vidi viajn paÄojn" + viewPage: "Vidi paÄojn" my: "Miaj paÄoj" featured: "Ravaĵoj" contents: "Enhavo" - content: "PaÄo en bloko" + content: "Bloko de paÄo" title: "Temlinio" - url: "URL de paÄo" + url: "URL de la paÄo" alignCenter: "Centrigi" hideTitleWhenPinned: "KaÅi la titolon de la paÄo kiam alpinglita" chooseBlock: "Aldoni blokon" @@ -1039,7 +1013,7 @@ _notification: youWereInvitedToGroup: "Invitita al grupo" _types: all: "Ĉio" - follow: "Novaj sekvatoj" + follow: "Novaj sekvantoj" mention: "Mencioj" reply: "Respondoj" renote: "Plusendoj" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index a8b2f5b720..a9339acf7b 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -81,6 +81,8 @@ somethingHappened: "Ocurrió un error" retry: "Reintentar" pageLoadError: "Error al leer la página" pageLoadErrorDescription: "Normalmente es debido a la red o al caché del navegador. Por favor limpie el caché o intente más tarde." +serverIsDead: "No hay respuesta del servidor. Espere un momento y vuelva a intentarlo." +youShouldUpgradeClient: "Para ver esta página, por favor refrezca el navegador y utiliza una versión más reciente del cliente." enterListName: "Ingrese nombre de lista" privacy: "Privacidad" makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento" @@ -104,6 +106,7 @@ clickToShow: "Click para ver" sensitive: "Marcado como sensible" add: "Agregar" reaction: "Reacción" +reactionSetting: "Reacciones para mostrar en el menú de reacciones" reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir." rememberNoteVisibility: "Recordar visibilidad" attachCancel: "Quitar adjunto" @@ -239,7 +242,6 @@ uploadFromUrlDescription: "URL del fichero que quieres subir" uploadFromUrlRequested: "Subida solicitada" uploadFromUrlMayTakeTime: "Subir el fichero puede tardar un tiempo." explore: "Explorar" -games: "Misskey Games" messageRead: "Ya leÃdo" noMoreHistory: "El historial se ha acabado" startMessaging: "Iniciar chat" @@ -533,7 +535,6 @@ yourAccountSuspendedDescription: "Esta cuenta ha sido suspendida debido a violac menu: "Menú" divider: "Divisor" addItem: "Agregar elemento" -rooms: "Cuartos" relays: "Relés" addRelay: "Agregar relé" inboxUrl: "Inbox URL" @@ -589,6 +590,7 @@ smtpSecure: "Usar SSL/TLS implÃcito en la conexión SMTP" smtpSecureInfo: "Apagar cuando se use STARTTLS" testEmail: "Prueba de envÃo" wordMute: "Silenciar palabras" +instanceMute: "Instancias silenciadas" userSaysSomething: "{name} dijo algo" makeActive: "Activar" display: "Apariencia" @@ -663,7 +665,6 @@ emailVerified: "Su dirección de correo electrónico ha sido verificada." noteFavoritesCount: "Número de notas favoritas" pageLikesCount: "Número de favoritos en la página" pageLikedCount: "Número de favoritos de su página" -reversiCount: "Numero de partidas Reversi" contact: "Contacto" useSystemFont: "Utilizar la tipografÃa por defecto del sistema" clips: "Clip" @@ -707,12 +708,27 @@ usageAmount: "Uso" capacity: "Capacidad" inUse: "Usado" editCode: "Editar código" +apply: "Aplicar" +publish: "Publicar" +inChannelSearch: "Buscar en el canal" +markAllAsRead: "Marcar todo como leÃdo" goBack: "Deseleccionar" info: "Información" +online: "En lÃnea" +offline: "Sin conexión" user: "Usuarios" administration: "Administrar" +gallery: "GalerÃa" +recentPosts: "Posts recientes" +popularPosts: "Más vistos" expiration: "Termina el" +high: "Alta" middle: "Mediano" +low: "Baja" +emailNotConfiguredWarning: "No se ha configurado una dirección de correo electrónico." +ratio: "Proporción" +previewNoteText: "Mostrar vista preliminar" +customCss: "CSS personalizado" customCssWarn: "Este ajuste sólo debe utilizarse si se sabe lo que hace. Introducir valores inadecuados puede hacer que el cliente deje de funcionar con normalidad." global: "Global" squareAvatars: "Mostrar iconos cuadrados" @@ -735,13 +751,28 @@ pubSub: "Cuentas Pub/Sub" lastCommunication: "Última comunicación" resolved: "Resuelto" unresolved: "Sin resolver" +itsOn: "¡Está encendido!" +itsOff: "¡Está apagado!" +emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro de la cuenta" +unread: "No leÃdo" +filter: "Filtro" controlPanel: "Panel de control" +manageAccounts: "Administrar cuenta" +makeReactionsPublic: "Hacer el historial de reacciones público" +makeReactionsPublicDescription: "Todas las reacciones que hayas hecho serán públicamente visibles." +classic: "Clásico" +muteThread: "Ocultar hilo" +unmuteThread: "Mostrar hilo" +ffVisibility: "Visibilidad de seguidores y seguidos" hide: "Ocultar" +_ffVisibility: + public: "Publicar" _accountDelete: accountDelete: "Eliminar Cuenta" _ad: back: "Deseleccionar" _gallery: + my: "Mi galerÃa" unlike: "Quitar me gusta" _email: _follow: @@ -768,39 +799,6 @@ _mfm: flipDescription: "Voltea el contenido hacia arriba / abajo o hacia la izquierda / derecha." font: "Fuente" rotate: "Rotar" -_reversi: - reversi: "Reversi" - gameSettings: "Configuración del juego" - chooseBoard: "Elegir tablero" - blackOrWhite: "Blancas/Negras" - blackIs: "{name} juega con fichas negras" - rules: "Reglas" - botSettings: "Opciones del bot" - thisGameIsStartedSoon: "El juego empezará en segundos" - waitingForOther: "Esperando el turno del adversario" - waitingForMe: "Esperando mi turno" - waitingBoth: "Prepárate" - ready: "Listo" - cancelReady: "No estoy listo" - opponentTurn: "Turno del adversario" - myTurn: "Mi turno" - turnOf: "Turno de {name}" - pastTurnOf: "Turno de {name}" - surrender: "Rendirse" - surrendered: "Por rendirse" - drawn: "Empate" - won: "{name} ha ganado" - black: "Negro" - white: "Blanco" - total: "Total" - turnCount: "Turno {count}" - myGames: "Mis juegos" - allGames: "Todos los juegos" - ended: "Finalizado" - playing: "Jugando" - isLlotheo: "El que tenga menos fichas gana (LLoTheO)" - loopedMap: "Mapa en bucle" - canPutEverywhere: "Puedes colocar donde quieras" _instanceTicker: none: "No mostrar" remote: "Mostrar a usuarios remotos" @@ -820,6 +818,8 @@ _channel: usersCount: "{n} participantes" notesCount: "{n} notas" _menuDisplay: + sideFull: "Horizontal" + sideIcon: "Horizontal (Ãcono)" hide: "Ocultar" _wordMute: muteWords: "Palabras que silenciar" @@ -830,6 +830,11 @@ _wordMute: soft: "Suave" hard: "Duro" mutedNotes: "Notas silenciadas" +_instanceMute: + instanceMuteDescription: "Silencia todas las notas y reposts de la instancias seleccionadas, incluyendo respuestas a los usuarios de las mismas" + instanceMuteDescription2: "Separar por lÃneas" + title: "Oculta las notas de las instancias listadas." + heading: "Instancias a silenciar" _theme: explore: "Explorar temas" install: "Instalar tema" @@ -1116,68 +1121,6 @@ _timelines: local: "Local" social: "Social" global: "Global" -_rooms: - roomOf: "Cuarto de {user}" - addFurniture: "Colocar muebles" - translate: "Mover" - rotate: "Rotar" - exit: "Deseleccionar" - remove: "Quitar" - clear: "Quitar todo" - clearConfirm: "¿Quiere quitar todos los muebles?" - leaveConfirm: "Hay modificaciones sin guardar. ¿Desea irse?" - chooseImage: "Escoger una imagen" - roomType: "Estilo de cuarto" - carpetColor: "Color de piso" - _roomType: - default: "Predeterminado" - washitsu: "Estilo japonés" - _furnitures: - milk: "Cartón de leche" - bed: "Cama" - low-table: "Mesa chica" - desk: "Escritorio" - chair: "Silla" - chair2: "Silla 2" - fan: "Ventilador" - pc: "Computadora" - plant: "Planta decorativa" - plant2: "Planta decorativa 2" - eraser: "Goma de borrar" - pencil: "lápiz" - pudding: "PudÃn" - cardboard-box: "Caja de cartón" - cardboard-box2: "Caja de cartón 2" - cardboard-box3: "Caja de cartón 3" - book: "Libro" - book2: "Libro 2" - piano: "Piano" - facial-tissue: "Caja de pañuelos" - server: "Servidor" - moon: "Luna" - corkboard: "Pizarra de corcho" - mousepad: "Alfombrilla de ratón" - monitor: "Monitor" - keyboard: "Teclado" - carpet-stripe: "Alfombra (a rayas)" - mat: "Tapete" - color-box: "Caja de colores" - wall-clock: "Reloj de pared" - photoframe: "Fotograma" - cube: "Cubo" - tv: "Televisor" - pinguin: "Pinguino" - rubik-cube: "Cubo rubik" - poster-h: "Poster (horizontal)" - poster-v: "Poster (vertical)" - sofa: "Sillón" - spiral: "Escalera en espiral" - bin: "Papelera" - cup-noodle: "Taza de sopa de fideos" - holo-display: "Poster holográfico" - energy-drink: "Bebida energética" - doll-ai: "Muñeca" - banknote: "Billetes" _pages: newPage: "Crear página" editPage: "Editar página" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index d7e436492d..58dd000ccc 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -19,7 +19,7 @@ noNotifications: "Aucune notification" instance: "Instance" settings: "Paramètres" basicSettings: "Paramètres généraux" -otherSettings: "Autres paramètres" +otherSettings: "Paramètres avancés" openInWindow: "Ouvrir dans une nouvelle fenêtre" profile: "Profil" timeline: "Fil" @@ -106,6 +106,7 @@ clickToShow: "Cliquer pour afficher" sensitive: "Contenu sensible" add: "Ajouter" reaction: "Réactions" +reactionSetting: "Réactions à afficher dans le sélecteur de réactions" reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser « + » pour ajouter." rememberNoteVisibility: "Activer l'option \" se souvenir de la visibilité des notes \" vous permet de réutiliser automatiquement la visibilité utilisée lors de la publication de votre note précédente." attachCancel: "Supprimer le fichier attaché" @@ -241,7 +242,6 @@ uploadFromUrlDescription: "URL du fichier que vous souhaitez téléverser" uploadFromUrlRequested: "Téléversement demandé" uploadFromUrlMayTakeTime: "Le téléversement de votre fichier peut prendre un certain temps." explore: "Découvrir" -games: "Jeux de Misskey" messageRead: "Lu" noMoreHistory: "Il n’y a plus d’historique" startMessaging: "Commencer à discuter" @@ -535,7 +535,6 @@ yourAccountSuspendedDescription: "Ce compte est suspendu car vous avez enfreint menu: "Menu" divider: "Séparateur" addItem: "Ajouter un élément" -rooms: "Chambre" relays: "Relais" addRelay: "Ajouter un relais" inboxUrl: "Inbox URL" @@ -591,6 +590,7 @@ smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP" smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé" testEmail: "Tester la distribution de courriel" wordMute: "Filtre de mots" +instanceMute: "Instance en sourdine" userSaysSomething: "{name} a dit quelque chose" makeActive: "Activer" display: "Affichage" @@ -618,6 +618,9 @@ reportAbuse: "Signaler" reportAbuseOf: "Signaler {name}" fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien." abuseReported: "Le rapport est envoyé. Merci." +reporteeOrigin: "Origine du signalement" +reporterOrigin: "Signalé par" +forwardReport: "Transférer le signalement à l’instance distante" send: "Envoyer" abuseMarkAsResolved: "Marquer le signalement comme résolu" openInNewTab: "Ouvrir dans un nouvel onglet" @@ -665,7 +668,6 @@ emailVerified: "Votre adresse e-mail a été vérifiée." noteFavoritesCount: "Nombre de notes dans les favoris" pageLikesCount: "Nombre de pages aimées" pageLikedCount: "Nombre de vos pages aimées" -reversiCount: "Nombre de parties de Reversi" contact: "Contact" useSystemFont: "Utiliser la police par défaut du système" clips: "Clips" @@ -680,6 +682,7 @@ center: "Centrer" wide: "Large" narrow: "Condensé" reloadToApplySetting: "Vos paramètres seront appliqués lorsque vous rechargerez la page. Souhaitez-vous recharger ?" +needReloadToApply: "Ce paramètre s'appliquera après un rechargement." showTitlebar: "Afficher la barre de titre" clearCache: "Vider le cache" onlineUsersCount: "{n} utilisateur(s) en ligne" @@ -788,6 +791,7 @@ pubSub: "Comptes Pub/Sub" lastCommunication: "Dernière communication" resolved: "Résolu" unresolved: "En attente" +breakFollow: "Ne plus suivre" itsOn: "Activé" itsOff: "Désactivé" emailRequiredForSignup: "Une adresse e-mail est nécessaire pour créer un compte" @@ -795,9 +799,22 @@ unread: "Non lu" filter: "Filtre" controlPanel: "Panneau de contrôle" manageAccounts: "Gérer les comptes" +makeReactionsPublic: "Rendre les réactions publiques" +makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données publique." classic: "Classique" +muteThread: "Mettre ce thread en sourdine" +ffVisibility: "Visibilité des abonnés/abonnements" +ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu suis et les personnes qui te suivent." +continueThread: "Afficher la suite du fil" +deleteAccountConfirm: "Votre compte sera supprimé. Êtes vous certain ?" +incorrectPassword: "Le mot de passe est incorrect." hide: "Masquer" +leaveGroup: "Quitter le groupe" +leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?" +welcomeBackWithName: "Heureux de vous revoir, {name}" +clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la vérification par courriel." _emailUnavailable: + used: "Non disponible" format: "Le format de cette adresse de courriel est invalide" mx: "Ce serveur de courriels est invalide" smtp: "Ce serveur de courriels ne répond pas" @@ -919,39 +936,6 @@ _mfm: sparkle: "Paillettes" sparkleDescription: "Ajoute un effet scintillant au contenu." rotate: "Pivoter" -_reversi: - reversi: "Reversi" - gameSettings: "Réglages de la partie" - chooseBoard: "Choix du plateau" - blackOrWhite: "Pions blancs/Pions noirs" - blackIs: "{name} joue les pions noirs" - rules: "Règles" - botSettings: "Options du bot" - thisGameIsStartedSoon: "La partie commencera dans quelques secondes" - waitingForOther: "En attente que l'adversaire soit prêt" - waitingForMe: "En attente que vous soyez prêt" - waitingBoth: "Préparez-vous" - ready: "Prêt" - cancelReady: "Recommencer la préparation" - opponentTurn: "Tour de l’adversaire" - myTurn: "C’est votre tour" - turnOf: "Tour de {name}" - pastTurnOf: "Tour de {name}" - surrender: "Abandonner" - surrendered: "Par abandon" - drawn: "Match nul" - won: "{name} a gagné" - black: "Noirs" - white: "Blancs" - total: "Total" - turnCount: "Tour {count}" - myGames: "Mes parties" - allGames: "Toutes les parties" - ended: "Fin de partie" - playing: "En cours" - isLlotheo: "Celui ou celle qui a le moins de pièces gagne (Llotheo)" - loopedMap: "Carte en boucle" - canPutEverywhere: "Les pions peuvent être placés partout " _instanceTicker: none: "Cacher " remote: "Montrer pour les utilisateur·ice·s distant·e·s" @@ -984,6 +968,8 @@ _wordMute: soft: "Doux" hard: "Strict" mutedNotes: "Notes filtrées" +_instanceMute: + heading: "Instances à mettre en sourdine" _theme: explore: "Explorer les thèmes" install: "Installer un thème" @@ -1066,8 +1052,6 @@ _sfx: chatBg: "Discussion (arrière-plan)" antenna: "Réception de l’antenne" channel: "Notifications de canal" - reversiPutBlack: "Reversi : les pions noirs ont joué" - reversiPutWhite: "Reversi : les pions blancs ont joué" _ago: unknown: "Inconnu" future: "Futur" @@ -1257,6 +1241,7 @@ _exportOrImport: muteList: "Comptes masqués" blockingList: "Comptes bloqués" userLists: "Listes" + excludeInactiveUsers: "Exclure les utilisateur·rice·s inactifs" _charts: federationInstancesIncDec: "Variation du nombre d'instances fédérées" federationInstancesTotal: "Nombre total d'instances fédérées" @@ -1288,68 +1273,6 @@ _timelines: local: "Local" social: "Social" global: "Global" -_rooms: - roomOf: "Chambre de {user}" - addFurniture: "Placer des meubles" - translate: "Déplacer" - rotate: "Pivoter" - exit: "Retour" - remove: "Enlever" - clear: "Tout enlever" - clearConfirm: "Souhaitez-vous enlever tous les meubles de votre chambre ?" - leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous vraiment quitter ?" - chooseImage: "Sélectionnez une image" - roomType: "Type de chambre" - carpetColor: "Couleur du tapis" - _roomType: - default: "Par défaut" - washitsu: "Style japonnais" - _furnitures: - milk: "Brique de lait" - bed: "Lit" - low-table: "Table basse" - desk: "Bureau" - chair: "Chaise" - chair2: "Chaise 2" - fan: "Ventilateur" - pc: "Ordinateur" - plant: "Plante d’intérieur" - plant2: "Plante d’intérieur 2" - eraser: "Gomme" - pencil: "Crayon" - pudding: "Pudding" - cardboard-box: "Boîte en carton" - cardboard-box2: "Boîte en carton 2" - cardboard-box3: "Boîte en carton 3" - book: "Livre" - book2: "Livre 2" - piano: "Piano" - facial-tissue: "Boîte de mouchoirs" - server: "Serveurs" - moon: "Lune" - corkboard: "Tableau en liège" - mousepad: "Tapis de souris" - monitor: "Écran de contrôle" - keyboard: "Clavier" - carpet-stripe: "Tapis (zébré)" - mat: "Tapis" - color-box: "Étagère" - wall-clock: "Horloge murale" - photoframe: "Cadre photo" - cube: "Cube" - tv: "Télé" - pinguin: "Pingouin" - rubik-cube: "Cube de Rubik" - poster-h: "Affiche (horizontale)" - poster-v: "Affiche (verticale)" - sofa: "Canapé" - spiral: "Escaliers en spirale" - bin: "Corbeille" - cup-noodle: "Bol de nouilles" - holo-display: "Affichage holographique" - energy-drink: "Boisson énergétique" - doll-ai: "Poupée Ai" - banknote: "Billets de banque" _pages: newPage: "Créer une page" editPage: "Modifier une page" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index f4f0ec8ce4..a1d52f6ef1 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -241,7 +241,6 @@ uploadFromUrlDescription: "URL berkas yang ingin kamu unggah" uploadFromUrlRequested: "Pengunggahan telah diminta" uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesai" explore: "Jelajahi" -games: "Permainan Misskey" messageRead: "Telah dibaca" noMoreHistory: "Tidak ada sejarah lagi" startMessaging: "Mulai mengirim pesan" @@ -535,7 +534,6 @@ yourAccountSuspendedDescription: "Akun ini dibekukan karena melanggar ketentuan menu: "Menu" divider: "Pembagi" addItem: "Tambahkan item" -rooms: "Ruang" relays: "Relay" addRelay: "Tambahkan relay" inboxUrl: "URL Kotak masuk" @@ -667,7 +665,6 @@ emailVerified: "Surel telah diverifikasi" noteFavoritesCount: "Jumlah catatan yang difavoritkan" pageLikesCount: "Jumlah suka yang diterima Halaman" pageLikedCount: "Jumlah Halaman yang disukai" -reversiCount: "Jumlah pertandingan Reversi" contact: "Kontak" useSystemFont: "Gunakan font bawaan sistem operasi" clips: "Klip" @@ -934,39 +931,6 @@ _mfm: sparkleDescription: "Memberikan konten efek partikel kelap-kelip." rotate: "Putar" rotateDescription: "Putar konten sesuai sudut yang ditentukan." -_reversi: - reversi: "Reversi" - gameSettings: "Pengaturan permainan" - chooseBoard: "Pilih papan" - blackOrWhite: "Hitam/Putih" - blackIs: "{name} bermain Hitam" - rules: "Peraturan" - botSettings: "Opsi bot" - thisGameIsStartedSoon: "Permainan akan mulai dalam beberapa detik" - waitingForOther: "Menunggu giliran lawan" - waitingForMe: "Menunggu giliran kamu" - waitingBoth: "Bersiap" - ready: "Siap" - cancelReady: "Batalkan siap" - opponentTurn: "Giliran lawan" - myTurn: "Giliran kamu" - turnOf: "Giliran {name}" - pastTurnOf: "Giliran {name}" - surrender: "Menyerah" - surrendered: "Karena menyerah" - drawn: "Seri" - won: "Kemenangan {name}" - black: "Hitam" - white: "Putih" - total: "Jumlah" - turnCount: "Giliran {count}" - myGames: "Rondeku" - allGames: "Semua ronde" - ended: "Selesai" - playing: "Sedang bermain" - isLlotheo: "Pemain dengan batu paling sedikitlah yang menang (Llotheo)" - loopedMap: "Peta melingkar" - canPutEverywhere: "Keping dapat ditaruh dimana saja" _instanceTicker: none: "Jangan tampilkan" remote: "Tampilkan untuk pengguna luar" @@ -1081,8 +1045,6 @@ _sfx: chatBg: "Obrolan (Latar Belakang)" antenna: "Penerimaan Antenna" channel: "Pemberitahuan saluran" - reversiPutBlack: "Reversi: Hitam bergerak" - reversiPutWhite: "Reversi: Putih bergerak" _ago: unknown: "Tidak diketahui" future: "Masa depan" @@ -1303,68 +1265,6 @@ _timelines: local: "Lokal" social: "Sosial" global: "Global" -_rooms: - roomOf: "Ruangan {user}" - addFurniture: "Letakkan perabotan" - translate: "Pindah" - rotate: "Putar" - exit: "Kembali" - remove: "Hapus" - clear: "Bersihkan" - clearConfirm: "Apakah kamu yakin ingin menghapus semua perabotan di ruanganmu?" - leaveConfirm: "Ada perubahan yang belum tersimpan. Apakah kamu ingin pergi?" - chooseImage: "Pilih gambar" - roomType: "Tipe ruangan" - carpetColor: "Warna karpet" - _roomType: - default: "Bawaan" - washitsu: "Gaya Jepang" - _furnitures: - milk: "Kardus susu" - bed: "Tempat tidur" - low-table: "Meja pendek" - desk: "Meja tulis" - chair: "Kursi" - chair2: "Kursi 2" - fan: "Kipas angin" - pc: "Komputer" - plant: "Tanaman" - plant2: "Tanaman 2" - eraser: "Karet Penghapus" - pencil: "Pensil" - pudding: "Puding" - cardboard-box: "Kotak Kardus" - cardboard-box2: "Kotak Kardus 2" - cardboard-box3: "Kotak Kardus 3" - book: "Buku" - book2: "Buku 2" - piano: "Piano" - facial-tissue: "Tisu Wajah" - server: "Server" - moon: "Bulan" - corkboard: "Papan buletin" - mousepad: "Mousepad" - monitor: "Layar Monitor" - keyboard: "Papan tombol" - carpet-stripe: "Karpet (Bergaris)" - mat: "Keset" - color-box: "Rak buku" - wall-clock: "Jam dinding" - photoframe: "Bingkai foto" - cube: "Kubus" - tv: "Televisi" - pinguin: "Pinguin" - rubik-cube: "Rubik" - poster-h: "Poster (Horizontal)" - poster-v: "Poster (Vertical)" - sofa: "Sofa" - spiral: "Tangga spiral" - bin: "Tempat sampah" - cup-noodle: "Migelas" - holo-display: "Layar hologram" - energy-drink: "Minuman energi" - doll-ai: "Boneka Ai" - banknote: "Uang" _pages: newPage: "Buat halaman baru" editPage: "Sunting halaman" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index ff5b01a585..d13e53625f 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -80,6 +80,9 @@ error: "Errore" somethingHappened: "Si è verificato un problema" retry: "Riprova" pageLoadError: "Caricamento pagina non riuscito. " +pageLoadErrorDescription: "Questo viene normalmente causato dalla rete o dalla cache del browser. Si prega di pulire la cache, o di attendere e riprovare più tardi." +serverIsDead: "Il server non risponde. Si prega di attendere e riprovare più tardi." +youShouldUpgradeClient: "Per visualizzare la pagina è necessario aggiornare il client alla nuova versione e ricaricare." enterListName: "Nome della lista" privacy: "Privacy" makeFollowManuallyApprove: "Richiedi di approvare i follower manualmente" @@ -103,6 +106,7 @@ clickToShow: "Clicca per visualizzare" sensitive: "Contenuto sensibile" add: "Aggiungi" reaction: "Reazione" +reactionSetting: "Reazioni visualizzate sul pannello" reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere." rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note" attachCancel: "Rimuovi allegato" @@ -132,6 +136,7 @@ emojiUrl: "URL dell'emoji" addEmoji: "Aggiungi un emoji" settingGuide: "Configurazione suggerita" cacheRemoteFiles: "Memorizzazione nella cache dei file remoti" +cacheRemoteFilesDescription: "Disabilitando questa opzione, i file remoti verranno linkati direttamente senza essere memorizzati nella cache. Sarà possibile risparmiare spazio di archiviazione sul server, ma il traffico aumenterà in quanto non verranno generate anteprime." flagAsBot: "Io sono un robot" flagAsBotDescription: "Se l'account esegue principalmente operazioni automatiche, attiva quest'opzione. Quando attivata, opera come un segnalatore per gli altri sviluppatori allo scopo di prevenire catene d’interazione senza fine con altri bot, e di adeguare i sistemi interni di Misskey perché trattino questo account come un bot." flagAsCat: "Io sono un gatto" @@ -148,12 +153,14 @@ searchWith: "Cerca: {q}" youHaveNoLists: "Non hai ancora creato nessuna lista" followConfirm: "Sei sicur@ di voler seguire {name}?" proxyAccount: "Account proxy" +proxyAccountDescription: "Un account proxy è un account che funziona da follower remoto per gli utenti sotto certe condizioni. Ad esempio, quando un utente aggiunge un utente remoto alla lista, dato che se nessun utente locale segue quell'utente le sue attività non verranno distribuite, al suo posto lo seguirà un account proxy." host: "Server remoto" selectUser: "Seleziona utente" recipient: "Destinatario" annotation: "Descrizione" federation: "Federazione" instances: "Istanza" +registeredAt: "Registrato presso" latestRequestSentAt: "Ultima richiesta inviata" latestRequestReceivedAt: "Ultima richiesta ricevuta" latestStatus: "Ultimo stato" @@ -161,6 +168,7 @@ storageUsage: "Volume di dischi" charts: "Grafici" perHour: "All'ora" perDay: "al giorno" +stopActivityDelivery: "Interrompi la distribuzione di attività " blockThisInstance: "Blocca l'istanza" operations: "Operazioni" software: "Software" @@ -234,7 +242,6 @@ uploadFromUrlDescription: "URL del file che vuoi caricare" uploadFromUrlRequested: "Caricamento richiesto" uploadFromUrlMayTakeTime: "Il caricamento del file può richiedere tempo." explore: "Esplora" -games: "Misskey Giochi" messageRead: "Visualizzato" noMoreHistory: "Non c'è più cronologia da visualizzare" startMessaging: "Nuovo messaggio" @@ -315,11 +322,13 @@ registration: "Iscriviti" enableRegistration: "Permettere nuove registrazioni" invite: "Invita" proxyRemoteFiles: "Usare file remoti come proxy" +proxyRemoteFilesDescription: "Attivando questa opzione i file remoti non salvati o cancellati perché eccedenti il limite di archiviazione verranno inoltrati tramite proxy, inclusa la generazione di anteprime. Non ha effetto sullo spazio di archiviazione del server." driveCapacityPerLocalAccount: "Volume del Drive per utente locale" driveCapacityPerRemoteAccount: "Volume del Drive per utente remoto" inMb: "in Megabytes" iconUrl: "URL di icona (favicon, ecc.)" bannerUrl: "URL dell'immagine d'intestazione" +backgroundImageUrl: "URL dello sfondo" basicInfo: "Informazioni fondamentali" pinnedUsers: "Utenti in evidenza" pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina \"Esplora\", un@ per riga." @@ -438,10 +447,12 @@ uiLanguage: "Lingua di visualizzazione dell'interfaccia" groupInvited: "Invitat@ al gruppo" aboutX: "Informazioni su {x}" useOsNativeEmojis: "Usare le emoji native del sistema operativo" +disableDrawer: "Non mostrare il menù sul drawer" youHaveNoGroups: "Nessun gruppo" joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono." noHistory: "Nessuna cronologia" signinHistory: "Cronologia di accesso all'account" +disableAnimatedMfm: "Disabilità i MFM animati" doing: "In corso..." category: "Categoria" tags: "Tag" @@ -469,12 +480,17 @@ showFeaturedNotesInTimeline: "Mostrare le note di tendenza nella tua timeline" objectStorage: "Stoccaggio oggetti" useObjectStorage: "Utilizza stoccaggio oggetti" objectStorageBaseUrl: "Base URL" +objectStorageBaseUrlDesc: "URL di riferimento. In caso di utilizzo di proxy o CDN l'URL è 'https://<bucket>.s3.amazonaws.com' per S3, 'https://storage.googleapis.com/<bucket>' per GCS eccetera. " objectStorageBucket: "Bucket" +objectStorageBucketDesc: "Specificare il nome del bucket utilizzato dal provider." objectStoragePrefix: "Prefix" objectStoragePrefixDesc: "I file saranno conservati sotto la directory di questo prefisso." objectStorageEndpoint: "Endpoint" +objectStorageEndpointDesc: "Lasciare vuoto se si sta utilizzando S3. In caso contrario si prega di specificare l'endpoint come '<host>' oppure '<host>:<port>' a seconda del servizio utilizzato." objectStorageRegion: "Region" +objectStorageRegionDesc: "Specificate una regione, quale 'xx-east-1'. Se il servizio in utilizzo non distingue tra regioni, lasciate vuoto o inserite 'us-east-1'." objectStorageUseSSL: "Usare SSL" +objectStorageUseSSLDesc: "Disabilita quest'opzione se non utilizzi HTTPS per le connessioni API." objectStorageUseProxy: "Usa proxy" objectStorageUseProxyDesc: "Disabilita quest'opzione se non usi proxy per la connessione API." objectStorageSetPublicRead: "Imposta \"visibilità pubblica\" al momento di caricare" @@ -504,6 +520,7 @@ sort: "Ordina per" ascendingOrder: "Ascendente" descendingOrder: "Discendente" scratchpad: "ScratchPad" +scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey." output: "Uscita" script: "Script" disablePagesScript: "Disabilita AiScript nelle pagine" @@ -514,9 +531,11 @@ removeAllFollowing: "Cancella tutti i follows" removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più." userSuspended: "L'utente è sospes@." userSilenced: "L'utente è silenziat@." +yourAccountSuspendedTitle: "Questo account è sospeso." +yourAccountSuspendedDescription: "Questo account è stato sospeso a causa di una violazione dei termini di servizio del server. Contattare l'amministrazione per i dettagli. Si prega di non creare un nuovo account." +menu: "Menù" divider: "Linea di separazione" addItem: "Aggiungi elemento" -rooms: "Camera" relays: "Ripetitori" addRelay: "Aggiungi ripetitore" inboxUrl: "Inbox URL" @@ -541,6 +560,7 @@ manage: "Gestione" plugins: "Estensioni" deck: "Deck" undeck: "Esci dal deck" +useBlurEffectForModal: "Utilizza effetto sfocatura per i modali" useFullReactionPicker: "Usa la totalità del pannello di reazioni" width: "Larghezza" height: "Altezza" @@ -571,6 +591,7 @@ smtpSecure: "Usare la porta SSL/TLS implicito per le connessioni SMTP" smtpSecureInfo: "Disabilitare quando è attivo STARTTLS." testEmail: "Testare la consegna di posta elettronica" wordMute: "Filtri parole" +instanceMute: "Silenzia l'istanza" userSaysSomething: "{name} ha detto qualcosa" makeActive: "Attiva" display: "Visualizza" @@ -589,13 +610,18 @@ useGlobalSettingDesc: "Se abilitato, le impostazioni notifiche dell'account verr other: "Avanzate" regenerateLoginToken: "Genera di nuovo un token di connessione" regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi." +setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi." fileIdOrUrl: "ID o URL del file" behavior: "Comportamento" +sample: "Esempio" abuseReports: "Segnalazioni" reportAbuse: "Segnalazioni" reportAbuseOf: "Segnala {name}" fillAbuseReportDescription: "Si prega di spiegare il motivo della segnalazione. Se riguarda una nota precisa, si prega di collegare anche l'URL della nota." abuseReported: "La segnalazione è stata inviata. Grazie." +reporter: "il corrispondente" +reporteeOrigin: "Origine del segnalato" +reporterOrigin: "Origine del segnalatore" send: "Inviare" abuseMarkAsResolved: "Contrassegna la segnalazione come risolta" openInNewTab: "Apri in una nuova scheda" @@ -643,7 +669,6 @@ emailVerified: "Il tuo indirizzo email è stato verificato" noteFavoritesCount: "Conteggio note tra i preferiti" pageLikesCount: "Numero di pagine che ti piacciono" pageLikedCount: "Numero delle tue pagine che hanno ricevuto \"Mi piace\"" -reversiCount: "Numero di partite a Reversi" contact: "Contatti" useSystemFont: "Usa il carattere predefinito del sistema" clips: "Clip" @@ -657,6 +682,7 @@ left: "Sinistra" center: "Centro" wide: "Largo" reloadToApplySetting: "Le tue preferenze verranno impostate dopo il ricaricamento della pagina. Vuoi ricaricare adesso?" +needReloadToApply: "È necessario riavviare per rendere effettive le modifiche." showTitlebar: "Visualizza la barra del titolo" clearCache: "Svuota cache" onlineUsersCount: "{n} utenti online" @@ -739,13 +765,65 @@ middle: "Media" low: "Bassa" emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail." ratio: "Rapporto" +previewNoteText: "Anteprima del testo" +customCss: "CSS personalizzato" global: "Federata" +squareAvatars: "Mostra l'immagine del profilo come quadrato" sent: "Inviare" +searchResult: "Risultati della Ricerca" hashtags: "Hashtag" troubleshooting: "Risoluzione problemi" +useBlurEffect: "Utilizza effetto sfocatura per l'interfaccia utente" +learnMore: "Più dettagli" +misskeyUpdated: "Misskey è stato aggiornato!" +whatIsNew: "Visualizza le informazioni sull'aggiornamento" +translate: "Traduzione" +translatedFrom: "Tradotto da {x}" +accountDeletionInProgress: "La cancellazione dell'account è in corso" +usernameInfo: "Un nome per identificare univocamente il tuo account sul server. È possibile utilizzare caratteri alfanumerici (a~z, A~Z, 0~9) e il trattino basso (_). Non sarà possibile cambiare il nome utente in seguito." +aiChanMode: "Modalità Ai" +keepCw: "Mantieni il CW" +resolved: "Risolto" +unresolved: "Non risolto" +breakFollow: "Smetti di seguire" +itsOn: "Abilitato" +itsOff: "Disabilitato" +emailRequiredForSignup: "È necessario un indirizzo mail per registrare un account" +unread: "Non letto" +filter: "Filtri" +controlPanel: "Pannello di controllo" +manageAccounts: "Gestisci account" +classic: "Classico" +muteThread: "Silenzia la discussione" +unmuteThread: "Riattiva la discussione" +deleteAccountConfirm: "L'account verrà cancellato. Procedere?" +incorrectPassword: "La password è errata." +voteConfirm: "Votare per「{choice}ã€?" hide: "Nascondere" +leaveGroup: "Esci dal gruppo" +leaveGroupConfirm: "Uscire da「{name}ã€?" +useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile" +welcomeBackWithName: "Bentornato/a, {name}" +clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email." +_emailUnavailable: + used: "Email già in uso" + format: "Formato email non valido" + disposable: "Email non riutilizzabile" + mx: "Server email non corretto" + smtp: "Il server email non risponde" _ffVisibility: public: "Pubblico" + followers: "Mostra solo ai follower" + private: "Invisibile" +_signup: + almostThere: "Quasi completo" + emailAddressInfo: "Inserisci il tuo indirizzo email. Non verrà reso pubblico." +_accountDelete: + accountDelete: "Cancellazione account" + sendEmail: "Al termine della cancellazione dell'account, verrà inviata una mail all'indirizzo a cui era registrato." + requestAccountDelete: "Richiesta di cancellazione account" + started: "Il processo di cancellazione è iniziato." + inProgress: "Cancellazione in corso" _ad: back: "Indietro" reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso" @@ -801,19 +879,27 @@ _mfm: quote: "Cita il nota" emoji: "Emoji personalizzati" search: "Cerca" + flip: "Inverti" + jump: "Animazione(salto)" + jumpDescription: "Da un animazione che salta su e giù." + bounce: "Animazione(rimbalzo)" + bounceDescription: "Rende il testo rimbalzante" + shake: "rimbalzante" + shakeDescription: "Rende il testo traballante" + twitch: "testo" + twitchDescription: "Fa tremare il testo" + x2: "Più grande" + x2Description: "Mostra il contenuto ingrandito." + x3: "Molto più grande" + x3Description: "Mostra il contenuto molto più ingrandito." + x4: "Estremamente più grande" + x4Description: "Mostra il contenuto estremamente più ingrandito." blur: "Sfocatura" + blurDescription: "È possibile rendere sfocato il contenuto. Spostando il cursore su di esso tornerà visibile chiaramente." font: "Tipo di carattere" fontDescription: "Puoi scegliere il tipo di carattere per il contenuto." rainbow: "Arcobaleno" rotate: "Ruota" -_reversi: - reversi: "Reversi" - gameSettings: "Impostazioni di gioco" - botSettings: "Opzioni del bot" - black: "Nero" - white: "Bianco" - total: "Totale" - ended: "Esci" _instanceTicker: none: "Nascondi" remote: "Mostra solo per gli/le utenti remotÉ™" @@ -865,6 +951,7 @@ _theme: func: "Funzione" funcKind: "Tipo di funzione" argument: "Argomento" + alpha: "Opacità " darken: "Scuro" lighten: "Chiaro" inputConstantName: "Inserisci un nome per la costante" @@ -902,6 +989,7 @@ _theme: inputBorder: "Inquadra casella di testo" listItemHoverBg: "Sfondo della voce di elenco (sorvolato)" driveFolderBg: "Sfondo della cartella di disco" + badge: "Distintivo" messageBg: "Sfondo della chat" _sfx: note: "Nota" @@ -1119,68 +1207,6 @@ _timelines: local: "Locale" social: "Sociale" global: "Federata" -_rooms: - roomOf: "Camera di {user}" - addFurniture: "Disponi mobilia" - translate: "Sposta" - rotate: "Ruota" - exit: "Indietro" - remove: "Togli" - clear: "Rimuovi tutto" - clearConfirm: "Sei sicur@ di voler rimuovere tutti i mobili dalla tua camera?" - leaveConfirm: "Hai fatto modifiche ancora non salvate. Vuoi davvero uscire?" - chooseImage: "Seleziona immagine" - roomType: "Tipo di stanza" - carpetColor: "Colore del suolo" - _roomType: - default: "Predefinito" - washitsu: "Washitsu" - _furnitures: - milk: "Cartone del latte" - bed: "Letto" - low-table: "Tavolino" - desk: "Tavolo" - chair: "Sedia" - chair2: "Sedia 2" - fan: "Ventilatore" - pc: "Computer" - plant: "Pianta da appartamento" - plant2: "Pianta da appartamento2" - eraser: "Gomma" - pencil: "Matita" - pudding: "Pudding" - cardboard-box: "Scatola di cartone" - cardboard-box2: "Scatola di cartone 2" - cardboard-box3: "Scatola di cartone 3" - book: "Libro" - book2: "Libro2" - piano: "Pianoforte" - facial-tissue: "Scatola di fazzolettini" - server: "Server" - moon: "Luna" - corkboard: "Bacheca" - mousepad: "Tappetino per il mouse" - monitor: "Monitor " - keyboard: "Tastiera" - carpet-stripe: "Tappeto (a strisce)" - mat: "Zerbino" - color-box: "Libreria" - wall-clock: "Orologio da parete" - photoframe: "Cornice" - cube: "Cubo" - tv: "TV" - pinguin: "Pinguino" - rubik-cube: "Cubo di Rubik" - poster-h: "Poster (orizzontale)" - poster-v: "Poster (verticale)" - sofa: "Divano" - spiral: "Scale a chiocciola" - bin: "Cestino" - cup-noodle: "Noodle istantanei" - holo-display: "Visualizzazione olografica" - energy-drink: "Bevanda energetica" - doll-ai: "Bambola Ai" - banknote: "Mazzetta di banconote" _pages: newPage: "Crea pagina" editPage: "Modifica pagina" @@ -1378,6 +1404,10 @@ _pages: string: "Testo" array: "Liste" stringArray: "Lista di testo" +_relayStatus: + requesting: "In attesa di approvazione" + accepted: "Approvato" + rejected: "Respinto" _notification: fileUploaded: "File caricato correttamente" youGotMention: "{name} ti ha menzionato" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 62aade568d..b3279d78b8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -242,7 +242,6 @@ uploadFromUrlDescription: "アップãƒãƒ¼ãƒ‰ã—ãŸã„ファイルã®URL" uploadFromUrlRequested: "アップãƒãƒ¼ãƒ‰ã‚’リクエストã—ã¾ã—ãŸ" uploadFromUrlMayTakeTime: "アップãƒãƒ¼ãƒ‰ãŒå®Œäº†ã™ã‚‹ã¾ã§æ™‚é–“ãŒã‹ã‹ã‚‹å ´åˆãŒã‚りã¾ã™ã€‚" explore: "ã¿ã¤ã‘ã‚‹" -games: "Misskey Games" messageRead: "æ—¢èª" noMoreHistory: "ã“れよりéŽåŽ»ã®å±¥æ´ã¯ã‚りã¾ã›ã‚“" startMessaging: "ãƒãƒ£ãƒƒãƒˆã‚’é–‹å§‹" @@ -537,7 +536,6 @@ yourAccountSuspendedDescription: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ã®åˆ©ç menu: "メニュー" divider: "分割線" addItem: "é …ç›®ã‚’è¿½åŠ " -rooms: "ルーム" relays: "リレー" addRelay: "リレーã®è¿½åŠ " inboxUrl: "inboxã®URL" @@ -621,8 +619,11 @@ reportAbuse: "é€šå ±" reportAbuseOf: "{name}ã‚’é€šå ±ã™ã‚‹" fillAbuseReportDescription: "é€šå ±ç†ç”±ã®è©³ç´°ã‚’記入ã—ã¦ãã ã•ã„。対象ã®ãƒŽãƒ¼ãƒˆãŒã‚ã‚‹å ´åˆã¯ãã®URLも記入ã—ã¦ãã ã•ã„。" abuseReported: "内容ãŒé€ä¿¡ã•れã¾ã—ãŸã€‚ã”å ±å‘Šã‚りãŒã¨ã†ã”ã–ã„ã¾ã—ãŸã€‚" +reporter: "é€šå ±è€…" reporteeOrigin: "é€šå ±å…ˆ" reporterOrigin: "é€šå ±å…ƒ" +forwardReport: "リモートインスタンスã«é€šå ±ã‚’転é€ã™ã‚‹" +forwardReportIsAnonymous: "リモートインスタンスã‹ã‚‰ã¯ã‚ãªãŸã®æƒ…å ±ã¯è¦‹ã‚Œãšã€åŒ¿åã®ã‚·ã‚¹ãƒ†ãƒ アカウントã¨ã—ã¦è¡¨ç¤ºã•れã¾ã™ã€‚" send: "é€ä¿¡" abuseMarkAsResolved: "対応済ã¿ã«ã™ã‚‹" openInNewTab: "æ–°ã—ã„タブã§é–‹ã" @@ -670,7 +671,6 @@ emailVerified: "メールアドレスãŒç¢ºèªã•れã¾ã—ãŸ" noteFavoritesCount: "ãŠæ°—ã«å…¥ã‚ŠãƒŽãƒ¼ãƒˆã®æ•°" pageLikesCount: "Pageã«ã„ã„ãã—ãŸæ•°" pageLikedCount: "Pageã«ã„ã„ãã•ã‚ŒãŸæ•°" -reversiCount: "リãƒãƒ¼ã‚·ã®å¯¾å±€æ•°" contact: "連絡先" useSystemFont: "システムã®ãƒ‡ãƒ•ォルトã®ãƒ•ォントを使ã†" clips: "クリップ" @@ -743,9 +743,10 @@ online: "オンライン" active: "アクティブ" offline: "オフライン" notRecommended: "éžæŽ¨å¥¨" -botProtection: "Bot防御" +botProtection: "Botプãƒãƒ†ã‚¯ã‚·ãƒ§ãƒ³" instanceBlocking: "インスタンスブãƒãƒƒã‚¯" selectAccount: "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’é¸æŠž" +switchAccount: "アカウントを切り替ãˆ" enabled: "有効" disabled: "無効" quickAction: "クイックアクション" @@ -754,7 +755,7 @@ administration: "管ç†" accounts: "アカウント" switch: "切り替ãˆ" noMaintainerInformationWarning: "管ç†è€…æƒ…å ±ãŒè¨å®šã•れã¦ã„ã¾ã›ã‚“。" -noBotProtectionWarning: "Bot防御ãŒè¨å®šã•れã¦ã„ã¾ã›ã‚“。" +noBotProtectionWarning: "Botプãƒãƒ†ã‚¯ã‚·ãƒ§ãƒ³ãŒè¨å®šã•れã¦ã„ã¾ã›ã‚“。" configure: "è¨å®šã™ã‚‹" postToGallery: "ã‚®ãƒ£ãƒ©ãƒªãƒ¼ã¸æŠ•ç¨¿" gallery: "ギャラリー" @@ -958,40 +959,6 @@ _mfm: rotate: "回転" rotateDescription: "指定ã—ãŸè§’度ã§å›žè»¢ã•ã›ã¾ã™ã€‚" -_reversi: - reversi: "リãƒãƒ¼ã‚·" - gameSettings: "対局ã®è¨å®š" - chooseBoard: "ãƒœãƒ¼ãƒ‰ã‚’é¸æŠž" - blackOrWhite: "先行/後攻" - blackIs: "{name}ãŒé»’(先行)" - rules: "ルール" - botSettings: "Botã®ã‚ªãƒ—ション" - thisGameIsStartedSoon: "å¯¾å±€ã¯æ•°ç§’後ã«é–‹å§‹ã•れã¾ã™" - waitingForOther: "ç›¸æ‰‹ã®æº–å‚™ãŒå®Œäº†ã™ã‚‹ã®ã‚’å¾…ã£ã¦ã„ã¾ã™" - waitingForMe: "ã‚ãªãŸã®æº–å‚™ãŒå®Œäº†ã™ã‚‹ã®ã‚’å¾…ã£ã¦ã„ã¾ã™" - waitingBoth: "準備ã—ã¦ãã ã•ã„" - ready: "準備完了" - cancelReady: "準備をå†é–‹" - opponentTurn: "相手ã®ã‚¿ãƒ¼ãƒ³ã§ã™" - myTurn: "ã‚ãªãŸã®ã‚¿ãƒ¼ãƒ³ã§ã™" - turnOf: "{name}ã®ã‚¿ãƒ¼ãƒ³ã§ã™" - pastTurnOf: "{name}ã®ã‚¿ãƒ¼ãƒ³" - surrender: "投了" - surrendered: "投了ã«ã‚ˆã‚Š" - drawn: "引ã分ã‘" - won: "{name}ã®å‹ã¡" - black: "é»’" - white: "白" - total: "åˆè¨ˆ" - turnCount: "{count}ターン目" - myGames: "自分ã®å¯¾å±€" - allGames: "ã¿ã‚“ãªã®å¯¾å±€" - ended: "終了" - playing: "対局ä¸" - isLlotheo: "石ã®å°‘ãªã„æ–¹ãŒå‹ã¡(ãƒã‚»ã‚ª)" - loopedMap: "ループマップ" - canPutEverywhere: "ã©ã“ã§ã‚‚ç½®ã‘るモード" - _instanceTicker: none: "表示ã—ãªã„" remote: "リモートユーザーã«è¡¨ç¤º" @@ -1119,8 +1086,6 @@ _sfx: chatBg: "ãƒãƒ£ãƒƒãƒˆ(ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰)" antenna: "アンテナå—ä¿¡" channel: "ãƒãƒ£ãƒ³ãƒãƒ«é€šçŸ¥" - reversiPutBlack: "リãƒãƒ¼ã‚·: é»’ãŒæ‰“ã£ãŸã¨ã" - reversiPutWhite: "リãƒãƒ¼ã‚·: ç™½ãŒæ‰“ã£ãŸã¨ã" _ago: unknown: "謎" @@ -1362,69 +1327,6 @@ _timelines: social: "ソーシャル" global: "ã‚°ãƒãƒ¼ãƒãƒ«" -_rooms: - roomOf: "{user}ã®ãƒ«ãƒ¼ãƒ " - addFurniture: "å®¶å…·ã‚’ç½®ã" - translate: "移動" - rotate: "回転" - exit: "戻る" - remove: "ã—ã¾ã†" - clear: "片付ã‘" - clearConfirm: "å…¨ã¦ã®å®¶å…·ã‚’ã—ã¾ã„ã¾ã™ã‹ï¼Ÿ" - leaveConfirm: "未ä¿å˜ã®å¤‰æ›´ãŒã‚りã¾ã™ã€ç§»å‹•ã—ã¾ã™ã‹ï¼Ÿ" - chooseImage: "ç”»åƒã‚’é¸æŠž" - roomType: "部屋ã®ã‚¿ã‚¤ãƒ—" - carpetColor: "床ã®è‰²" - _roomType: - default: "デフォルト" - washitsu: "和室" - _furnitures: - milk: "牛乳パック" - bed: "ベッド" - low-table: "ãƒãƒ¼ãƒ†ãƒ¼ãƒ–ル" - desk: "デスク" - chair: "ãƒã‚§ã‚¢" - chair2: "ãƒã‚§ã‚¢2" - fan: "æ›æ°—扇" - pc: "パソコン" - plant: "観葉æ¤ç‰©" - plant2: "観葉æ¤ç‰©2" - eraser: "消ã—ゴム" - pencil: "鉛ç†" - pudding: "プリン" - cardboard-box: "段ボール箱" - cardboard-box2: "段ボール箱2" - cardboard-box3: "段ボール箱3" - book: "本" - book2: "本2" - piano: "ピアノ" - facial-tissue: "ティッシュボックス" - server: "サーãƒãƒ¼" - moon: "月" - corkboard: "コルクボード" - mousepad: "マウスパッド" - monitor: "モニター" - keyboard: "ã‚ーボード" - carpet-stripe: "カーペット(縞)" - mat: "マット" - color-box: "カラーボックス" - wall-clock: "å£æŽ›ã‘æ™‚計" - photoframe: "é¡ç¸" - cube: "ã‚ューブ" - tv: "テレビ" - pinguin: "ピンギン" - rubik-cube: "ルービックã‚ューブ" - poster-h: "ãƒã‚¹ã‚¿ãƒ¼(横長)" - poster-v: "ãƒã‚¹ã‚¿ãƒ¼(縦長)" - sofa: "ソファ" - spiral: "螺旋階段" - bin: "ゴミ箱" - cup-noodle: "カップ麺" - holo-display: "ホãƒã‚°ãƒ©ãƒ•ィックディスプレイ" - energy-drink: "エナジードリンク" - doll-ai: "è—ã¡ã‚ƒã‚“人形" - banknote: "æœæŸ" - _pages: newPage: "ページã®ä½œæˆ" editPage: "ページã®ç·¨é›†" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 25cc93b28a..45ab9684d2 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -239,7 +239,6 @@ uploadFromUrlDescription: "ã“ã®URLã®ãƒ•ァイルをアップãƒãƒ¼ãƒ‰ã—ãŸã uploadFromUrlRequested: "アップãƒãƒ¼ãƒ‰ã—ãŸã„言ã†ã¨ã„ãŸã§" uploadFromUrlMayTakeTime: "アップãƒãƒ¼ãƒ‰çµ‚ã‚ã‚‹ã‚“ã«ã¡ã‚‡ã„時間ã‹ã‹ã‚‹ã‹ã‚‚ã—れã¸ã‚“ã‚。" explore: "ã¿ã¤ã‘ã‚‹" -games: "Misskey Games" messageRead: "ã‚‚ã†èªã‚“ã " noMoreHistory: "ã“れよりéŽåŽ»ã®å±¥æ´ã¯ã‚らã¸ã‚“ã§" startMessaging: "ãƒãƒ£ãƒƒãƒˆã‚„ã‚‹ã§" @@ -515,7 +514,6 @@ removeAllFollowingDescription: "{host}ã‹ã‚‰ã®ãƒ•ã‚©ãƒãƒ¼ã‚’ã™ã¹ã¦è§£é™¤ã™ userSuspended: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯...å‡çµã•れã¨ã‚‹ã€‚" userSilenced: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯...サイレンスã•れã¨ã‚‹ã€‚" divider: "分割線" -rooms: "ルーム" relays: "リレー" addRelay: "リレーã®è¿½åŠ " inboxUrl: "inboxã®URL" @@ -701,29 +699,6 @@ _mfm: blur: "ã¼ã‹ã—" font: "フォント" rotate: "回転" -_reversi: - reversi: "リãƒãƒ¼ã‚·" - gameSettings: "対局ã®è¨å®š" - chooseBoard: "ãƒœãƒ¼ãƒ‰ã‚’é¸æŠž" - blackOrWhite: "先行/後攻" - blackIs: "{name}ãŒé»’(先行)" - rules: "ルール" - botSettings: "Botã®ã‚ªãƒ—ション" - pastTurnOf: "{name}ã®ã‚¿ãƒ¼ãƒ³" - surrender: "投了" - surrendered: "投了ã«ã‚ˆã‚Š" - drawn: "引ã分ã‘" - won: "{name}ã®å‹ã¡" - black: "é»’" - white: "白" - total: "åˆè¨ˆ" - turnCount: "{count}ターン目" - myGames: "自分ã®å¯¾å±€" - allGames: "ã¿ã‚“ãªã®å¯¾å±€" - ended: "終了" - playing: "対局ä¸" - isLlotheo: "石ã®å°‘ãªã„æ–¹ãŒå‹ã¡(ãƒã‚»ã‚ª)" - loopedMap: "ループマップ" _instanceTicker: none: "表示ã›ã‚“" remote: "リモートユーザーã«è¡¨ç¤º" @@ -936,68 +911,6 @@ _timelines: local: "ãƒãƒ¼ã‚«ãƒ«" social: "ソーシャル" global: "ã‚°ãƒãƒ¼ãƒãƒ«" -_rooms: - roomOf: "{user}ã®ãƒ«ãƒ¼ãƒ " - addFurniture: "å®¶å…·ã‚’ç½®ã" - translate: "移動" - rotate: "回転" - exit: "戻る" - remove: "ã—ã¾ã†" - clear: "片付ã‘" - clearConfirm: "å®¶å…·ãœã‚“ã¶ã—ã¾ã†ã‘ã©ã€ãƒ›ãƒ³ãƒžã«ãˆãˆã‚“?" - leaveConfirm: "未ä¿å˜ã®å¤‰æ›´ãŒã‚ã‚‹ã‘ã©ã€ç§»å‹•ã—ã¦ãˆãˆã‹ï¼Ÿ" - chooseImage: "ç”»åƒã‚’é¸ã¶" - roomType: "部屋ã®ã‚¿ã‚¤ãƒ—" - carpetColor: "床ã®è‰²" - _roomType: - default: "デフォルト" - washitsu: "和室" - _furnitures: - milk: "牛乳パック" - bed: "ベッド" - low-table: "ãƒãƒ¼ãƒ†ãƒ¼ãƒ–ル" - desk: "デスク" - chair: "ãƒã‚§ã‚¢" - chair2: "ãƒã‚§ã‚¢2" - fan: "æ›æ°—扇" - pc: "パソコン" - plant: "観葉æ¤ç‰©" - plant2: "観葉æ¤ç‰©2" - eraser: "消ã—ゴム" - pencil: "鉛ç†" - pudding: "プリン" - cardboard-box: "段ボール箱" - cardboard-box2: "段ボール箱2" - cardboard-box3: "段ボール箱3" - book: "本" - book2: "本2" - piano: "ピアノ" - facial-tissue: "ティッシュボックス" - server: "サーãƒãƒ¼" - moon: "月" - corkboard: "コルクボード" - mousepad: "マウスパッド" - monitor: "モニター" - keyboard: "ã‚ーボード" - carpet-stripe: "カーペット(縞)" - mat: "マット" - color-box: "カラーボックス" - wall-clock: "å£æŽ›ã‘æ™‚計" - photoframe: "é¡ç¸" - cube: "ã‚ューブ" - tv: "テレビ" - pinguin: "ピンギン" - rubik-cube: "ルービックã‚ューブ" - poster-h: "ルービックã‚ューブ" - poster-v: "ãƒã‚¹ã‚¿ãƒ¼(縦長)" - sofa: "ソファ" - spiral: "螺旋階段" - bin: "ゴミ箱" - cup-noodle: "カップ麺" - holo-display: "ホãƒã‚°ãƒ©ãƒ•ィックディスプレイ" - energy-drink: "エナジードリンク" - doll-ai: "è—ã¡ã‚ƒã‚“人形" - banknote: "æœæŸ" _pages: newPage: "ページを作る" editPage: "ページã®ç·¨é›†" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 1fee5f5728..7451603a66 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -241,7 +241,6 @@ uploadFromUrlDescription: "ì—…ë¡œë“œí•˜ë ¤ëŠ” 파ì¼ì˜ URL" uploadFromUrlRequested: "업로드를 ìš”ì²í–ˆìŠµë‹ˆë‹¤" uploadFromUrlMayTakeTime: "업로드가 완료ë 때까지 ì‹œê°„ì´ ì†Œìš”ë 수 있습니다." explore: "발견하기" -games: "Misskey Games" messageRead: "ì½ìŒ" noMoreHistory: "ì´ê²ƒë³´ë‹¤ ê³¼ê±°ì˜ ê¸°ë¡ì´ 없습니다" startMessaging: "대화 시작하기" @@ -535,7 +534,6 @@ yourAccountSuspendedDescription: "ì´ ê³„ì •ì€ ì„œë²„ì˜ ì´ìš© ì•½ê´€ì„ ìœ„ë° menu: "메뉴" divider: "êµ¬ë¶„ì„ " addItem: "í•목 추가" -rooms: "ë°©" relays: "ë¦´ë ˆì´" addRelay: "ë¦´ë ˆì´ ì¶”ê°€" inboxUrl: "Inbox 주소" @@ -668,7 +666,6 @@ emailVerified: "ë©”ì¼ ì£¼ì†Œê°€ 확ì¸ë˜ì—ˆìŠµë‹ˆë‹¤." noteFavoritesCount: "ì¦ê²¨ì°¾ê¸°í•œ 노트 수" pageLikesCount: "좋아요 한 Page 수" pageLikedCount: "Pageì— ë°›ì€ ì¢‹ì•„ìš” 수" -reversiCount: "리버시 ëŒ€êµ íšŸìˆ˜" contact: "ì—°ë½ì²˜" useSystemFont: "시스템 기본 ê¸€ê¼´ì„ ì‚¬ìš©" clips: "í´ë¦½" @@ -936,39 +933,6 @@ _mfm: sparkleDescription: "ë°˜ì§ì´ëŠ” íŒŒí‹°í´ íš¨ê³¼ë¥¼ 추가합니다." rotate: "íšŒì „" rotateDescription: "ì§€ì •í•œ ê°ë„로 íšŒì „ì‹œí‚µë‹ˆë‹¤." -_reversi: - reversi: "리버시" - gameSettings: "ëŒ€êµ ì„¤ì •" - chooseBoard: "보드 ì„ íƒ" - blackOrWhite: "ì„ ê³µ/후공" - blackIs: "{name}ë‹˜ì´ í‘(ì„ ê³µ)" - rules: "규칙" - botSettings: "Bot ì„¤ì •" - thisGameIsStartedSoon: "ìž ì‹œ í›„ì— ëŒ€êµì´ 시작ë©ë‹ˆë‹¤" - waitingForOther: "ìƒëŒ€ì˜ 준비가 완료ë 때까지 ê¸°ë‹¤ë¦¬ê³ ìžˆìŠµë‹ˆë‹¤" - waitingForMe: "ë‹¹ì‹ ì˜ ì¤€ë¹„ 완료를 ê¸°ë‹¤ë¦¬ê³ ìžˆìŠµë‹ˆë‹¤" - waitingBoth: "준비해 주세요" - ready: "준비 완료" - cancelReady: "준비 취소" - opponentTurn: "ìƒëŒ€ì˜ 차례입니다" - myTurn: "ë‹¹ì‹ ì˜ ì°¨ë¡€ìž…ë‹ˆë‹¤" - turnOf: "{name}ë‹˜ì˜ ì°¨ë¡€ìž…ë‹ˆë‹¤" - pastTurnOf: "{name}ë‹˜ì˜ ì°¨ë¡€" - surrender: "기권" - surrendered: "ê¸°ê¶Œì— ì˜í•´" - drawn: "무승부" - won: "{name}ë‹˜ì˜ ìŠ¹ë¦¬" - black: "í‘" - white: "ë°±" - total: "합계" - turnCount: "{count}í„´ 째" - myGames: "ë‚´ 대êµ" - allGames: "모ë‘ì˜ ëŒ€êµ" - ended: "종료" - playing: "지금 ëŒ€êµ ì¤‘" - isLlotheo: "ëŒì´ ì ì€ ì‚¬ëžŒì´ ìŠ¹ë¦¬ (llotheo)" - loopedMap: "루프 ì§€ë„" - canPutEverywhere: "ì–´ë””ì—나 ë†“ì„ ìˆ˜ 있ìŒ" _instanceTicker: none: "ë³´ì´ì§€ 않ìŒ" remote: "리모트 ìœ ì €ì—게만 ë³´ì´ê¸°" @@ -1088,8 +1052,6 @@ _sfx: chatBg: "대화 (백그ë¼ìš´ë“œ)" antenna: "안테나 ìˆ˜ì‹ " channel: "ì±„ë„ ì•Œë¦¼" - reversiPutBlack: "리버시: í‘ëŒì„ ë‘ì—ˆì„ ë•Œ" - reversiPutWhite: "리버시: ë°±ëŒì„ ë‘ì—ˆì„ ë•Œ" _ago: unknown: "알 수 ì—†ìŒ" future: "미래" @@ -1312,68 +1274,6 @@ _timelines: local: "로컬" social: "소셜" global: "글로벌" -_rooms: - roomOf: "{user}ì˜ ë°©" - addFurniture: "가구를 배치" - translate: "ì´ë™" - rotate: "íšŒì „" - exit: "뒤로" - remove: "치우기" - clear: "ëª¨ë‘ ì¹˜ìš°ê¸°" - clearConfirm: "ì •ë§ ë°© ì•ˆì˜ ëª¨ë“ ê°€êµ¬ë¥¼ ì¹˜ìš°ì‹œê² ìŠµë‹ˆê¹Œ?" - leaveConfirm: "ì €ìž¥ë˜ì§€ ì•Šì€ ë³€ê²½ 사í•ì´ ìžˆìŠµë‹ˆë‹¤. ì •ë§ ë‚˜ê°€ì‹œê² ìŠµë‹ˆê¹Œ?" - chooseImage: "ì´ë¯¸ì§€ ì„ íƒ" - roomType: "ë°© 스타ì¼" - carpetColor: "바닥 색ìƒ" - _roomType: - default: "기본값" - washitsu: "ì¼ë³¸ì‹" - _furnitures: - milk: "ìš°ìœ íŒ©" - bed: "침대" - low-table: "ë‚®ì€ í…Œì´ë¸”" - desk: "ì±…ìƒ" - chair: "ì˜ìž" - chair2: "ì˜ìž 2" - fan: "환기구" - pc: "컴퓨터" - plant: "관엽ì‹ë¬¼" - plant2: "관엽ì‹ë¬¼ 2" - eraser: "지우개" - pencil: "ì—°í•„" - pudding: "푸딩" - cardboard-box: "골íŒì§€ ìƒìž" - cardboard-box2: "골íŒì§€ ìƒìž 2" - cardboard-box3: "골íŒì§€ ìƒìž 3" - book: "ì±…" - book2: "ì±… 2" - piano: "피아노" - facial-tissue: "휴지 ìƒìž" - server: "서버" - moon: "달" - corkboard: "게시íŒ" - mousepad: "마우스 패드" - monitor: "모니터" - keyboard: "키보드" - carpet-stripe: "카페트 (줄무늬)" - mat: "매트" - color-box: "책장" - wall-clock: "ë²½ê±¸ì´ ì‹œê³„" - photoframe: "ì•¡ìž" - cube: "í브" - tv: "TV" - pinguin: "íŽê·„" - rubik-cube: "루빅스 í브" - poster-h: "í¬ìŠ¤í„° (가로)" - poster-v: "í¬ìŠ¤í„° (세로)" - sofa: "소파" - spiral: "ë‚˜ì„ í˜• 계단" - bin: "휴지통" - cup-noodle: "컵ë¼ë©´" - holo-display: "홀로그램" - energy-drink: "ì—너지 드ë§í¬" - doll-ai: "ì•„ì´ì¨© ì¸í˜•" - banknote: "ì§€íë‰ì¹˜" _pages: newPage: "페ì´ì§€ 만들기" editPage: "페ì´ì§€ ìˆ˜ì •" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 09e0fb627f..386357f2d3 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -212,7 +212,6 @@ uploadFromUrlDescription: "URL van het bestand dat je wil uploaden" uploadFromUrlRequested: "Uploadverzoek" uploadFromUrlMayTakeTime: "Het kan even duren voordat het uploaden voltooid is." explore: "Verkennen" -games: "Misskey spellen" messageRead: "Lezen" noMoreHistory: "Er is geen verdere geschiedenis" startMessaging: "Start een gesprek" @@ -294,11 +293,6 @@ _exportOrImport: excludeInactiveUsers: "Negeer inactieve gebruikers" _timelines: home: "Startpagina" -_rooms: - _roomType: - default: "Standaard" - _furnitures: - monitor: "Monitor" _pages: blocks: image: "Afbeeldingen" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index a4f8f3efea..27772663bc 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -241,7 +241,6 @@ uploadFromUrlDescription: "Adres URL pliku, który chcesz wysÅ‚ać" uploadFromUrlRequested: "Zażądano wysÅ‚ania" uploadFromUrlMayTakeTime: "WysyÅ‚anie może chwilÄ™ potrwać." explore: "Eksploruj" -games: "Gry Misskey" messageRead: "Przeczytano" noMoreHistory: "Nie ma dalszej historii" startMessaging: "Rozpocznij czat" @@ -528,7 +527,6 @@ userSuspended: "To konto zostaÅ‚o zawieszone." userSilenced: "Ten użytkownik zostaÅ‚ wyciszony." divider: "Rozdzielacz" addItem: "Dodaj element" -rooms: "Pokój" relays: "Przekaźniki" addRelay: "Dodaj przekaźnik" inboxUrl: "Adres URL skrzynki nadawczej" @@ -656,7 +654,6 @@ emailVerified: "Adres e-mail zostaÅ‚ potwierdzony" noteFavoritesCount: "Liczba polubionych wpisów" pageLikesCount: "Liczba otrzymanych polubieÅ„ stron" pageLikedCount: "Liczba polubionych stron" -reversiCount: "Liczba rozgrywek Reversi" contact: "Kontakt" useSystemFont: "Używaj domyÅ›lnej czcionki systemu" experimentalFeatures: "Eksperymentalne funkcje" @@ -842,36 +839,6 @@ _mfm: font: "Czcionka" fontDescription: "Wybiera czcionkÄ™ do wyÅ›wietlania treÅ›ci." rotate: "Obróć" -_reversi: - reversi: "Reversi" - gameSettings: "Ustawienia gry" - chooseBoard: "Wybierz tablicÄ™" - blackOrWhite: "Czarne/biaÅ‚e" - blackIs: "{name} gra czarnymi" - rules: "Zasady" - botSettings: "Opcje bota" - thisGameIsStartedSoon: "Gra rozpocznie siÄ™ za kilka sekund" - waitingForOther: "Oczekiwanie na ruch przeciwnika" - waitingForMe: "Oczekiwanie na Twój ruch" - waitingBoth: "Przygotuj siÄ™" - ready: "Gotowy(-a)" - cancelReady: "Anuluj gotowość" - opponentTurn: "Kolej przeciwnika" - myTurn: "Twoja kolej" - turnOf: "Kolej {name}" - pastTurnOf: "Kolej {name}" - surrender: "Poddaj siÄ™" - surrendered: "Przez poddanie siÄ™" - drawn: "Remis" - won: "{name} wygraÅ‚(a)" - black: "Czarny" - white: "BiaÅ‚y" - total: "ÅÄ…cznie" - turnCount: "Ruch {count}" - myGames: "Moje gry" - allGames: "Wszystkie gry" - ended: "ZakoÅ„czono" - playing: "W trakcie gry" _instanceTicker: none: "Nigdy nie pokazuj" remote: "Pokaż dla zdalnych użytkowników" @@ -979,8 +946,6 @@ _sfx: chat: "WiadomoÅ›ci" chatBg: "Rozmowy (tÅ‚o)" channel: "Powiadomienia kanaÅ‚u" - reversiPutBlack: "Reversi: Czarny wykonuje ruch" - reversiPutWhite: "Reversi: BiaÅ‚y wykonuje ruch" _ago: unknown: "Nieznane" future: "W przyszÅ‚oÅ›ci" @@ -1136,65 +1101,6 @@ _timelines: local: "Lokalne" social: "SpoÅ‚eczność" global: "Globalna" -_rooms: - roomOf: "Pokój {user}" - addFurniture: "Umieść meble" - translate: "PrzenieÅ›" - rotate: "Obróć" - exit: "Wróć" - remove: "UsuÅ„" - clear: "UsuÅ„ wszystkie" - clearConfirm: "Czy na pewno chcesz usunąć wszystkie meble ze swojego pokoju?" - leaveConfirm: "Masz niezapisane zmiany. Czy na pewno chcesz wyjść?" - chooseImage: "Wybierz obraz" - roomType: "Typ pokoju" - carpetColor: "Kolor dywanu" - _roomType: - default: "DomyÅ›lne" - washitsu: "W japoÅ„skim stylu" - _furnitures: - milk: "Karton mleka" - bed: "Åóżko" - low-table: "Niski stolik" - desk: "Biurko" - chair: "KrzesÅ‚o" - chair2: "KrzesÅ‚o 2" - fan: "ChÅ‚odzenie" - pc: "Komputer" - plant: "RoÅ›lina domowa" - plant2: "RoÅ›lina domowa 2" - eraser: "Gumka" - pencil: "Ołówek" - pudding: "BudyÅ„" - cardboard-box: "PudÅ‚o tekturowe" - cardboard-box2: "PudÅ‚o tekturowe 2" - cardboard-box3: "PudÅ‚o tekturowe 3" - book: "Książka" - book2: "Książka 2" - piano: "Fortepian" - server: "Serwery" - moon: "Księżyc" - corkboard: "Tablica korkowa" - mousepad: "PodkÅ‚adka pod mysz" - monitor: "Monitor" - keyboard: "Klawiatura" - carpet-stripe: "Dywan (w paski)" - color-box: "Biblioteczka" - wall-clock: "Zegar Å›cienny" - photoframe: "Ramka do zdjęć" - cube: "Kostka" - tv: "Telewizor" - pinguin: "Pingwin" - rubik-cube: "Kostka Rubika" - poster-h: "Plakat (poziomy)" - poster-v: "Plakat (pionowy)" - sofa: "Kanapa" - spiral: "Schody spiralne" - bin: "Kosz" - holo-display: "WyÅ›wietlacz holograficzny" - energy-drink: "Napój energetyczny" - doll-ai: "Lalka AI" - banknote: "Banknot" _pages: newPage: "Utwórz stronÄ™" editPage: "Edytuj tÄ™ stronÄ™" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 588fb91c40..7de9f8ff95 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -106,6 +106,7 @@ clickToShow: "Ðажмите Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра" sensitive: "Содержимое не Ð´Ð»Ñ Ð²Ñех" add: "Добавить" reaction: "Реакции" +reactionSetting: "Реакции, отображаемые в палитре" reactionSettingDescription2: "РаÑÑтавлÑйте перетаÑкиванием, удалÑйте нажатием, добавлÑйте кнопкой «+»." rememberNoteVisibility: "Запоминать видимоÑть заметок" attachCancel: "Удалить вложение" @@ -127,7 +128,7 @@ selectAntenna: "Выберите антенну" selectWidget: "Выберите виджет" editWidgets: "Редактировать виджеты" editWidgetsExit: "Готово" -customEmojis: "Ðмодзи пользователÑ" +customEmojis: "СобÑтвенные Ñмодзи" emoji: "Ðмодзи" emojis: "Ðмодзи" emojiName: "Ðазвание Ñмодзи" @@ -200,7 +201,7 @@ done: "Готово" processing: "Обработка" preview: "ПредпроÑмотр" default: "По умолчанию" -noCustomEmojis: "Ðмодзи Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¾Ñ‚ÑутÑтвуют" +noCustomEmojis: "СобÑтвенные Ñмодзи отÑутÑтвуют" noJobs: "Ðет заданий" federating: "ФедерируетÑÑ" blocked: "Заблокировано" @@ -241,7 +242,6 @@ uploadFromUrlDescription: "СÑылка на файл, который хотит uploadFromUrlRequested: "Загрузка выбранного" uploadFromUrlMayTakeTime: "Загрузка может занÑть некоторое времÑ." explore: "Обзор" -games: "Игры Misskey" messageRead: "Прочитали" noMoreHistory: "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð·Ð°ÐºÐ¾Ð½Ñ‡Ð¸Ð»Ð°ÑÑŒ" startMessaging: "Ðачать общение" @@ -447,6 +447,7 @@ uiLanguage: "Язык интерфейÑа" groupInvited: "Приглашение в группу" aboutX: "ОпиÑание {x}" useOsNativeEmojis: "ИÑпользовать Ñмодзи операционной ÑиÑтемы" +disableDrawer: "Ðе иÑпользовать выдвижные меню" youHaveNoGroups: "У Ð²Ð°Ñ Ð½ÐµÑ‚ ни одной группы" joinOrCreateGroup: "Получайте Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ Ð² группы или Ñоздавайте Ñвои ÑобÑтвенные" noHistory: "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¿Ð¾ÐºÐ° пуÑта" @@ -535,7 +536,6 @@ yourAccountSuspendedDescription: "Ðта ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ была з menu: "Меню" divider: "ЛиниÑ-разделитель" addItem: "Добавить Ñлемент" -rooms: "Комната" relays: "РетранÑлÑторы" addRelay: "Добавить ретранÑлÑтор" inboxUrl: "URL Ñщика входÑщих Ñообщений" @@ -591,6 +591,7 @@ smtpSecure: "ИÑпользовать SSL/TLS Ð´Ð»Ñ SMTP-Ñоединений" smtpSecureInfo: "Выключите при иÑпользовании STARTTLS." testEmail: "Проверка доÑтавки Ñлектронной почты" wordMute: "Скрытие Ñлов" +instanceMute: "Глушение инÑтанÑов" userSaysSomething: "{name} что-то Ñообщает" makeActive: "Ðктивировать" display: "Отображение" @@ -618,8 +619,8 @@ reportAbuse: "Жалоба" reportAbuseOf: "ПожаловатьÑÑ Ð½Ð° Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ {name}" fillAbuseReportDescription: "Опишите, пожалуйÑта, причину жалобы подробнее. ЕÑли речь о конкретной заметке, будьте добры приложить ÑÑылку на неё." abuseReported: "Жалоба отправлена. Большое ÑпаÑибо за информацию." -reporteeOrigin: "Куда Ñообщать" -reporterOrigin: "Сообщено" +reporteeOrigin: "О ком Ñообщено" +reporterOrigin: "Кто Ñообщил" send: "Отправить" abuseMarkAsResolved: "Отметить жалобу как решённую" openInNewTab: "Открыть в новой вкладке" @@ -667,7 +668,6 @@ emailVerified: "ÐÐ´Ñ€ÐµÑ Ñлектронной почты Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´Ñ noteFavoritesCount: "КоличеÑтво добавленного в избранное" pageLikesCount: "КоличеÑтво понравившихÑÑ Ñтраниц" pageLikedCount: "КоличеÑтво Ñтраниц, понравившихÑÑ Ð´Ñ€ÑƒÐ³Ð¸Ð¼" -reversiCount: "КоличеÑтво Ñыгранных игр в реверÑи" contact: "Как ÑвÑзатьÑÑ" useSystemFont: "ИÑпользовать шрифт, предлагаемый ÑиÑтемой" clips: "Подборки" @@ -682,7 +682,7 @@ center: "По центру" wide: "ТолÑтый" narrow: "Тонкий" reloadToApplySetting: "Ðто наÑтройка вÑтупает в Ñилу при загрузке Ñтраницы. Перезагрузить ÑейчаÑ?" -needReloadToApply: "Чтобы Ñто вÑтупило в Ñилу, требуетÑÑ Ð¿ÐµÑ€ÐµÐ·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°." +needReloadToApply: "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð²ÑтупÑÑ‚ в Ñилу поÑле перезагрузки Ñтраницы." showTitlebar: "Показать заголовок" clearCache: "ОчиÑтить кÑш" onlineUsersCount: "Пользователей ÑÐµÐ¹Ñ‡Ð°Ñ Ð² Ñети: {n}" @@ -767,7 +767,7 @@ middle: "Средне" low: "Ðизкий" emailNotConfiguredWarning: "Ðе указан Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" ratio: "Соотношение" -previewNoteText: "Предварительный проÑмотр текÑта" +previewNoteText: "Предварительный проÑмотр" customCss: "Индивидуальный CSS" customCssWarn: "ИÑпользуйте Ñту наÑтройку только еÑли знаете, что делаете. Ошибки здеÑÑŒ чреваты тем, что Ñайт переÑтанет нормально работать у ваÑ." global: "Ð’ÑеобщаÑ" @@ -782,16 +782,19 @@ learnMore: "Подробнее" misskeyUpdated: "Misskey обновилÑÑ!" whatIsNew: "Что новенького?" translate: "Перевод" -translatedFrom: "{x}Перевод Ñ Ð°Ð½Ð³Ð»Ð¸Ð¹Ñкого" +translatedFrom: "Перевод. Язык оригинала — {x}" accountDeletionInProgress: "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÑетÑÑ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ðµ учетной запиÑи" usernameInfo: "ИмÑ, которое отличает вашу учетную запиÑÑŒ от других на Ñтом Ñервере. Ð’Ñ‹ можете иÑпользовать алфавит (a~z, A~Z), цифры (0~9) или Ñимволы Ð¿Ð¾Ð´Ñ‡ÐµÑ€ÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (_). Имена пользователей не могут быть изменены позже." aiChanMode: "ИИ режим" keepCw: "СохранÑйте ÐŸÑ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¾ Ñодержимом" +pubSub: "Учётные запиÑи Pub/Sub" lastCommunication: "ПоÑледнее Ñообщение" -resolved: "Решен" -unresolved: "Ðеразрешенные" -itsOff: "Он выключен!" -emailRequiredForSignup: "ТребуетÑÑ Ð°Ð´Ñ€ÐµÑ Ñлектронной почты Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации аккаунта" +resolved: "Решено" +unresolved: "Без решениÑ" +breakFollow: "ОтпиÑка" +itsOn: "Включено" +itsOff: "Выключено" +emailRequiredForSignup: "Ð”Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации учётной запиÑи нужен Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" unread: "Ðепрочитанное" filter: "Фильтры" controlPanel: "Панель управлениÑ" @@ -799,30 +802,38 @@ manageAccounts: "Управление аккаунтом" makeReactionsPublic: "Опубликовать ÑпиÑок реакций" makeReactionsPublicDescription: "СпиÑок Ñделанных вами реакций доÑтупен Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра вÑем желающим." classic: "КлаÑÑика" -unmuteThread: "Отключить звук" -ffVisibilityDescription: "Ð’Ñ‹ можете уÑтановить объем вашей Ñледующей/поÑледней информации." -voteConfirm: "Ð’Ñ‹ бы проголоÑовали за \"{choice}\"?" +muteThread: "Заглушить цепочку" +unmuteThread: "Отменить глушение цепочки" +ffVisibility: "ВидимоÑть подпиÑок и подпиÑчиков" +ffVisibilityDescription: "ЗдеÑÑŒ можно наÑтроить, кто будет видеть ваши подпиÑки и подпиÑчиков." +continueThread: "Показать Ñледующие ответы" +deleteAccountConfirm: "Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет безвозвратно удалена. Подтверждаете?" +incorrectPassword: "Пароль неверен." +voteConfirm: "Отдать Ð³Ð¾Ð»Ð¾Ñ Ð·Ð° «{choice}»?" hide: "СпрÑтать" leaveGroup: "Покинуть группу" -leaveGroupConfirm: "Ð’Ñ‹ хотите оÑтавить \"{name}\"?" +leaveGroupConfirm: "Покинуть группу «{name}»?" +useDrawerReactionPickerForMobile: "Ð’Ñ‹Ð´Ð²Ð¸Ð¶Ð½Ð°Ñ Ð¿Ð°Ð»Ð¸Ñ‚Ñ€Ð° на мобильном уÑтройÑтве" welcomeBackWithName: "С возвращением, {name}!" clickToFinishEmailVerification: "ПожалуйÑта, нажмите [{ok}], чтобы завершить подтверждение адреÑа Ñлектронной почты." _emailUnavailable: used: "Уже иÑпользуетÑÑ" - format: "Ðеправильный формат" - mx: "Ðто неправильный почтовый Ñервер!" + format: "Ðеверный формат" + disposable: "Временный Ð°Ð´Ñ€ÐµÑ Ñлектронной почты не принимаетÑÑ" + mx: "Ðеверный почтовый Ñервер" smtp: "Почтовый Ñервер не отвечает" _ffVisibility: - public: "Опубликовать" - private: "ЧаÑтный" + public: "ОбщедоÑтупны" + followers: "ПоказываютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ подпиÑчикам" + private: "ПоказываютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ вам" _signup: almostThere: "Почти готово!" - emailAddressInfo: "ПожалуйÑта, введите Ð°Ð´Ñ€ÐµÑ Ñлектронной почты, который вы иÑпользуете." - emailSent: "Ðа указанный вами Ð°Ð´Ñ€ÐµÑ Ñлектронной почты ({email}) было отправлено пиÑьмо Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼. Перейдите по ÑÑылке в Ñлектронном пиÑьме, чтобы завершить Ñоздание учетной запиÑи." + emailAddressInfo: "Введите ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты." + emailSent: "Ðа указанный вами Ð°Ð´Ñ€ÐµÑ Ñлектронной почты ({email}) отправлено пиÑьмо. Перейдите по ÑÑылке в пиÑьме, чтобы завершить региÑтрацию." _accountDelete: - accountDelete: "Удалить Ñвой аккаунт" - mayTakeTime: "Удаление учетной запиÑи - Ñто Ñ‚Ñжелый процеÑÑ, который может занÑть много времени, еÑли у Ð²Ð°Ñ Ñоздано много контента или загружено много файлов." - sendEmail: "Мы отправим уведомление на зарегиÑтрированный вами Ð°Ð´Ñ€ÐµÑ Ñлектронной почты, когда ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет удалена." + accountDelete: "Удалить Ñвою учётную запиÑÑŒ" + mayTakeTime: "Удаление учётной запиÑи — реÑурÑозатратный процеÑÑ. Он может занÑть много времени, еÑли вы много пиÑали и загружали файлов." + sendEmail: "Когда ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет удалена, мы Ñообщим на указанную вами Ñлектронную почту." requestAccountDelete: "ЗапроÑить удаление вашей учетной запиÑи" started: "ПроцеÑÑ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð½Ð°Ñ‡Ð°Ð»ÑÑ." inProgress: "Удаление в процеÑÑе" @@ -894,7 +905,7 @@ _mfm: blockMathDescription: "ОформлÑет математичеÑкое выражение (KaTeX) на отдельной Ñтроке." quote: "Цитата" quoteDescription: "Так можно процитировать чей-то текÑÑ‚." - emoji: "Ðмодзи пользователÑ" + emoji: "СобÑтвенные Ñмодзи" emojiDescription: "Можно вÑтавить Ñмодзи в текÑÑ‚, окружив название двоеточиÑми." search: "ПоиÑк" searchDescription: "Можно добавить форму Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка, Ñразу задав, что иÑкать." @@ -926,43 +937,10 @@ _mfm: fontDescription: "Так можно пиÑать произвольным шрифтом." rainbow: "Радуга" rainbowDescription: "ЗаÑтавлÑть Ñодержимое отображатьÑÑ Ð² цветах радуги." - sparkle: "БлеÑк" - sparkleDescription: "Добавьте Ñффект иÑкрÑщихÑÑ Ñ‡Ð°Ñтиц." + sparkle: "ИÑкры" + sparkleDescription: "ДобавлÑет Ñффект иÑкрÑщихÑÑ Ñ‡Ð°Ñтиц." rotate: "Повернуть" - rotateDescription: "Повернуть на указанный угол." -_reversi: - reversi: "РеверÑи" - gameSettings: "ÐаÑтройки игры" - chooseBoard: "Выберите доÑку" - blackOrWhite: "Черные/Белые" - blackIs: "{name} за чёрных" - rules: "Правила" - botSettings: "ÐаÑтройки бота" - thisGameIsStartedSoon: "Игра Ñкоро начнётÑÑ." - waitingForOther: "Ожидание Ñоперника..." - waitingForMe: "Ð’ ожидании, когда будете готовы." - waitingBoth: "ПриготовьтеÑÑŒ." - ready: "Готово" - cancelReady: "Возврат к подготовке" - opponentTurn: "Ход Ñоперника" - myTurn: "Ваш ход" - turnOf: "Ходит {name}." - pastTurnOf: "Ходит {name}." - surrender: "СдатьÑÑ" - surrendered: "Противник ÑдалÑÑ" - drawn: "ÐичьÑ" - won: "Победитель — {name}" - black: "Чёрные" - white: "Белые" - total: "Ð’Ñего" - turnCount: "Ход {count}" - myGames: "Сыгранное вами" - allGames: "Ð’Ñе игры" - ended: "Завершена" - playing: "Идёт игра" - isLlotheo: "Выигрывает меньшее чиÑло камней (LLoTheO)" - loopedMap: "Ð—Ð°Ð¼ÐºÐ½ÑƒÑ‚Ð°Ñ Ð² кольцо доÑка" - canPutEverywhere: "Камни можно Ñтавить везде" + rotateDescription: "Поворачивает на заданный угол." _instanceTicker: none: "Ðе показывать" remote: "Только Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов" @@ -995,6 +973,8 @@ _wordMute: soft: "МÑгкий" hard: "ЖёÑткий" mutedNotes: "Скрытые заметки" +_instanceMute: + heading: "СпиÑок заглушенных инÑтанÑов" _theme: explore: "Обзор" install: "УÑтановить тему" @@ -1077,8 +1057,6 @@ _sfx: chatBg: "Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ (фон)" antenna: "Ðнтенна" channel: "Канал" - reversiPutBlack: "РеверÑи — ход чёрных" - reversiPutWhite: "РеверÑи — ход белых" _ago: unknown: "Когда-то" future: "Из будущего" @@ -1156,10 +1134,10 @@ _permissions: "write:user-groups": "ИзменÑть и удалÑть группы пользователей" "read:channels": "Смотреть каналы" "write:channels": "ИзменÑть каналы" - "read:gallery": "Смотреть галерею" - "write:gallery": "Работа Ñ Ð³Ð°Ð»ÐµÑ€ÐµÐµÐ¹" - "read:gallery-likes": "ПоÑмотреть галерею лайков" - "write:gallery-likes": "Манипулируйте понравившейÑÑ Ð³Ð°Ð»ÐµÑ€ÐµÐµÐ¹" + "read:gallery": "ПроÑмотр галереи" + "write:gallery": "Редактирование галереи" + "read:gallery-likes": "ПроÑмотр ÑпиÑка понравившегоÑÑ Ð² галерее" + "write:gallery-likes": "Изменение ÑпиÑка понравившегоÑÑ Ð² галерее" _auth: shareAccess: "Дать доÑтуп Ð´Ð»Ñ Â«{name}» к вашей учётной запиÑи?" shareAccessAsk: "Уверены, что хотите дать приложению доÑтуп к Ñвоей учётной запиÑи?" @@ -1268,7 +1246,8 @@ _exportOrImport: muteList: "Скрытые" blockingList: "Заблокированные" userLists: "СпиÑки" - excludeMutingUsers: "ИÑключение отключенных пользователей" + excludeMutingUsers: "За иÑключением заглушенных пользователей" + excludeInactiveUsers: "Без неактивных учётных запиÑей" _charts: federationInstancesIncDec: "Изменение внешних ÑвÑзей" federationInstancesTotal: "КоличеÑтво внешних ÑвÑзей" @@ -1300,68 +1279,6 @@ _timelines: local: "МеÑтнаÑ" social: "СоциальнаÑ" global: "Ð’ÑеобщаÑ" -_rooms: - roomOf: "Комната {user}" - addFurniture: "Добавить обÑтановку" - translate: "Передвинуть" - rotate: "Повернуть" - exit: "Выход" - remove: "ВыброÑить" - clear: "ОчиÑтить" - clearConfirm: "Уверены что Ñтоит убрать вÑÑŽ обÑтановку из вашей комнаты?" - leaveConfirm: "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ Ñохранены, правда хотите покинуть комнату?" - chooseImage: "Выберите изображение" - roomType: "Стиль комнаты" - carpetColor: "Цвет ковра" - _roomType: - default: "По умолчанию" - washitsu: "ЯпонÑкаÑ" - _furnitures: - milk: "Пакет молока" - bed: "Кровать" - low-table: "Журнальный Ñтол" - desk: "ПиÑьменный Ñтол" - chair: "Стул" - chair2: "Стул 2" - fan: "ВентилÑтор" - pc: "СиÑтемный блок" - plant: "РаÑтение в горшке" - plant2: "РаÑтение в горшке 2" - eraser: "ЛаÑтик" - pencil: "Карандаш" - pudding: "Пудинг" - cardboard-box: "ÐšÐ°Ñ€Ñ‚Ð¾Ð½Ð½Ð°Ñ ÐºÐ¾Ñ€Ð¾Ð±ÐºÐ°" - cardboard-box2: "ÐšÐ°Ñ€Ñ‚Ð¾Ð½Ð½Ð°Ñ ÐºÐ¾Ñ€Ð¾Ð±ÐºÐ° 2" - cardboard-box3: "ÐšÐ°Ñ€Ñ‚Ð¾Ð½Ð½Ð°Ñ ÐºÐ¾Ñ€Ð¾Ð±ÐºÐ° 3" - book: "Книга" - book2: "Книга про Misskey" - piano: "Пианино" - facial-tissue: "Салфетки" - server: "Сервер" - moon: "Луна" - corkboard: "ÐŸÑ€Ð¾Ð±ÐºÐ¾Ð²Ð°Ñ Ð´Ð¾Ñка" - mousepad: "Коврик Ð´Ð»Ñ Ð¼Ñ‹ÑˆÐ¸" - monitor: "Монитор" - keyboard: "Клавиатура" - carpet-stripe: "ПолоÑатый ковёр" - mat: "Мат" - color-box: "ÐšÐ½Ð¸Ð¶Ð½Ð°Ñ Ð¿Ð¾Ð»ÐºÐ°" - wall-clock: "ÐаÑтенные чаÑÑ‹" - photoframe: "Картина в раме" - cube: "Куб" - tv: "Телевизор" - pinguin: "Пингвин" - rubik-cube: "Кубик Рубика" - poster-h: "Плакат (Ð°Ð»ÑŒÐ±Ð¾Ð¼Ð½Ð°Ñ Ð¾Ñ€Ð¸ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ)" - poster-v: "Плакат (Ð¿Ð¾Ñ€Ñ‚Ñ€ÐµÑ‚Ð½Ð°Ñ Ð¾Ñ€Ð¸ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ)" - sofa: "Диван" - spiral: "Ð¡Ð¿Ð¸Ñ€Ð°Ð»ÑŒÐ½Ð°Ñ Ð»ÐµÑтница" - bin: "МуÑорное ведро" - cup-noodle: "Стакан лапши" - holo-display: "ГолографичеÑкий проектор" - energy-drink: "Банка ÑнергетичеÑкого напитка" - doll-ai: "Кукла Ðй-Ñ‚Ñн" - banknote: "Пачка денег" _pages: newPage: "Создать Ñтраницу" editPage: "Править Ñтраницу" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 8cb0e2e2aa..2dd6056011 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -236,7 +236,6 @@ uploadFromUrlDescription: "ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° файл Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð uploadFromUrlRequested: "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ€Ð¾Ð·Ð¿Ð¾Ñ‡Ð°Ð»Ð¾ÑÑŒ" uploadFromUrlMayTakeTime: "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¼Ð¾Ð¶Ðµ зайнÑти деÑкий чаÑ." explore: "ОглÑд" -games: "Ігри Misskey" messageRead: "Прочитано" noMoreHistory: "Подальшої Ñ–Ñторії немає" startMessaging: "Розпочати діалог" @@ -519,7 +518,6 @@ userSuspended: "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¸Ð¹." userSilenced: "Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»ÑƒÑˆÐµÐ½Ð¸Ð¹." divider: "Розділювач" addItem: "Додати елемент" -rooms: "Кімнати" relays: "РетранÑлÑтори" addRelay: "Додати ретранÑлÑтор" inboxUrl: "Inbox URL" @@ -646,7 +644,6 @@ emailVerified: "Електронну пошту підтверджено." noteFavoritesCount: "КількіÑть улюблених нотаток" pageLikesCount: "КількіÑть отриманих вподобань Ñторінки" pageLikedCount: "КількіÑть вподобаних Ñторінок" -reversiCount: "КількіÑть матчів \"РеверÑÑ–\"" contact: "Контакт" useSystemFont: "ВикориÑтовувати Ñтандартний шрифт ÑиÑтеми" clips: "Добірка" @@ -771,37 +768,6 @@ _mfm: font: "Шрифт" fontDescription: "Ð’Ñтановлює шрифт Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚ÐµÐ½Ñ‚Ñƒ." rotate: "Обертати" -_reversi: - reversi: "РеверÑÑ–" - gameSettings: "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð³Ñ€Ð¸" - chooseBoard: "Вибір дошки" - blackOrWhite: "Чорні / Білі" - blackIs: "{name} грає чорними" - rules: "Правила" - botSettings: "Параметри бота" - thisGameIsStartedSoon: "Гра розпочнетьÑÑ Ñ‡ÐµÑ€ÐµÐ· кілька Ñекунд" - waitingForOther: "Чекаємо на хід Ñуперника" - waitingForMe: "Чекаємо на ваш хід" - waitingBoth: "ПриготуйтеÑÑŒ" - ready: "ГотовніÑть" - cancelReady: "СкаÑувати готовніÑть" - opponentTurn: "Хід Ñуперника" - myTurn: "Ваш хід" - turnOf: "Хід {name}" - pastTurnOf: "Хід {name}" - surrender: "ЗдатиÑÑ" - drawn: "ÐічиÑ" - won: "Перемога {name}" - black: "Чорні" - white: "Білі" - total: "Ð’Ñього" - turnCount: "Хід {count}" - myGames: "Мої ігри" - allGames: "УÑÑ– ігри" - ended: "Завершено" - playing: "Ð’ даний момент у процеÑÑ– гри" - isLlotheo: "Гравець з найменшою кількіÑтю фігур виграє (Llotheo)" - canPutEverywhere: "Фігури можна Ñтавити в будь Ñкії позиції" _instanceTicker: none: "Ðе відображати" remote: "Відображати Ð´Ð»Ñ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¸Ñ… кориÑтувачів" @@ -901,8 +867,6 @@ _sfx: chatBg: "Чати (фон)" antenna: "Прийом антени" channel: "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ°Ð½Ð°Ð»Ñƒ" - reversiPutBlack: "РеверÑÑ–: хід Чорного" - reversiPutWhite: "РеверÑÑ–: хід Білого" _ago: unknown: "Ðевідомо" future: "Майбутнє" @@ -1095,68 +1059,6 @@ _timelines: local: "Локальна" social: "Соціальна" global: "Глобальна" -_rooms: - roomOf: "Кімната {user}" - addFurniture: "РозміÑтити меблі" - translate: "ПереÑунути" - rotate: "Обертати" - exit: "Ðазад" - remove: "Видалити" - clear: "Видалити вÑе" - clearConfirm: "Ви дійÑно хочете позбутиÑÑŒ уÑÑ–Ñ… речей у вашій кімнаті?" - leaveConfirm: "Є незбережені зміни. Ви дійÑно хочете вийти?" - chooseImage: "Виберіть зображеннÑ" - roomType: "Тип кімнати" - carpetColor: "Колір килима" - _roomType: - default: "За замовчуваннÑм" - washitsu: "Ð’ ÑпонÑькому Ñтилі" - _furnitures: - milk: "Пакет молока" - bed: "Ліжко" - low-table: "Журнальний Ñтіл" - desk: "ПиÑьмовий Ñтіл" - chair: "Стілець" - chair2: "Стілець 2" - fan: "ВентилÑтор" - pc: "Комп’ютер" - plant: "Кімнатна роÑлина" - plant2: "Кімнатна роÑлина 2" - eraser: "ЛаÑтик" - pencil: "Олівець" - pudding: "Пудинг" - cardboard-box: "Картонна коробка" - cardboard-box2: "Картонна коробка 2" - cardboard-box3: "Картонна коробка 3" - book: "Книга" - book2: "Книга 2" - piano: "Піаніно" - facial-tissue: "Серветки" - server: "Сервер" - moon: "МіÑÑць" - corkboard: "Коркова дошка" - mousepad: "Килимок Ð´Ð»Ñ Ð¼Ð¸ÑˆÑ–" - monitor: "Монітор" - keyboard: "Клавіатура" - carpet-stripe: "СмугаÑтий килим" - mat: "Мат" - color-box: "Книжкова полицÑ" - wall-clock: "ÐаÑтінний годинник" - photoframe: "Фоторамка" - cube: "Куб" - tv: "Телевізор" - pinguin: "Пінгвін" - rubik-cube: "Кубик Рубіка" - poster-h: "Плакат (горизонтальний)" - poster-v: "Плакат (вертикальний)" - sofa: "Диван" - spiral: "Гвинтові Ñходи" - bin: "Смітник" - cup-noodle: "Локшина в чашці" - holo-display: "Голографічний диÑплей" - energy-drink: "Енергетичний напій" - doll-ai: "ЛÑлька ÐÑ–-Ñ‚Ñн" - banknote: "Пачка грошей" _pages: newPage: "Створити Ñторінку" editPage: "Редагувати Ñторінку" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c842b065a4..5815c92f43 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -242,7 +242,6 @@ uploadFromUrlDescription: "输入文件的URL" uploadFromUrlRequested: "è¯·æ±‚ä¸Šä¼ " uploadFromUrlMayTakeTime: "ä¸Šä¼ å¯èƒ½éœ€è¦ä¸€äº›æ—¶é—´å®Œæˆã€‚" explore: "å‘现" -games: "Misskey游æˆ" messageRead: "已读" noMoreHistory: "没有更多的历å²è®°å½•" startMessaging: "æ·»åŠ èŠå¤©" @@ -537,7 +536,6 @@ yourAccountSuspendedDescription: "由于è¿å了æœåŠ¡å™¨çš„æœåŠ¡æ¡æ¬¾æˆ–å…¶ä menu: "èœå•" divider: "分割线" addItem: "æ·»åŠ é¡¹ç›®" -rooms: "房间" relays: "ä¸ç»§" addRelay: "æ·»åŠ ä¸ç»§" inboxUrl: "Inbox URL" @@ -670,7 +668,6 @@ emailVerified: "电å邮件地å€å·²éªŒè¯" noteFavoritesCount: "æ”¶è—çš„å¸–åæ•°" pageLikesCount: "页é¢ç‚¹èµžæ¬¡æ•°" pageLikedCount: "页é¢è¢«ç‚¹èµžæ¬¡æ•°" -reversiCount: "黑白棋对战次数" contact: "è”系人" useSystemFont: "使用系统默认å—体" clips: "书ç¾" @@ -746,6 +743,7 @@ notRecommended: "䏿ލè" botProtection: "Bot防御" instanceBlocking: "被阻拦的实例" selectAccount: "选择账户" +switchAccount: "切æ¢è´¦æˆ·" enabled: "å·²å¯ç”¨" disabled: "å·²ç¦ç”¨ " quickAction: "å¿«æ·æ“作" @@ -944,39 +942,6 @@ _mfm: sparkleDescription: "æ·»åŠ å‘å…‰ç²’åæ•ˆæžœã€‚" rotate: "旋转" rotateDescription: "旋转指定的角度。" -_reversi: - reversi: "黑白棋" - gameSettings: "对局设置" - chooseBoard: "棋盘选择" - blackOrWhite: "先手/åŽæ‰‹" - blackIs: "{name}执黑(先走)" - rules: "规则" - botSettings: "机器人设置" - thisGameIsStartedSoon: "å¯¹å±€åœ¨å‡ ç§’åŽå¼€å§‹" - waitingForOther: "ç‰å¾…对手准备" - waitingForMe: "ç‰å¾…您的准备" - waitingBoth: "请准备" - ready: "准备就绪" - cancelReady: "釿–°å‡†å¤‡" - opponentTurn: "对手的会åˆ" - myTurn: "您的回åˆ" - turnOf: "{name}的回åˆ" - pastTurnOf: "{name}的回åˆ" - surrender: "认输 " - surrendered: "对手认输" - drawn: "平局" - won: "{name}获胜" - black: "黑" - white: "白" - total: "总计" - turnCount: "{count}回åˆ" - myGames: "我的对局" - allGames: "所有对局" - ended: "结æŸ" - playing: "对局ä¸" - isLlotheo: "棋å较少一方获胜(LLoTheO规则)" - loopedMap: "循环棋盘" - canPutEverywhere: "å¯ä»¥ä¸‹åœ¨ä»»æ„ä½ç½®" _instanceTicker: none: "䏿˜¾ç¤º" remote: "显示给远程用户" @@ -1096,8 +1061,6 @@ _sfx: chatBg: "èŠå¤©èƒŒæ™¯" antenna: "天线接收" channel: "频é“通知" - reversiPutBlack: "é»‘ç™½æ£‹ï¼šé»‘æ–¹ä¸‹åæ—¶" - reversiPutWhite: "é»‘ç™½æ£‹ï¼šç™½æ–¹ä¸‹åæ—¶" _ago: unknown: "未知" future: "未æ¥" @@ -1320,68 +1283,6 @@ _timelines: local: "本地" social: "社交" global: "全局" -_rooms: - roomOf: "{user}的房间" - addFurniture: "放置家具" - translate: "移动" - rotate: "旋转" - exit: "返回" - remove: "移除" - clear: "清ç†" - clearConfirm: "æ˜¯å¦æ¸…除所有家具?" - leaveConfirm: "有尚未ä¿å˜çš„修改。是å¦ç¦»å¼€ï¼Ÿ" - chooseImage: "选择图片" - roomType: "房间类型" - carpetColor: "地æ¿é¢œè‰²" - _roomType: - default: "默认" - washitsu: "å’Œå¼æˆ¿é—´" - _furnitures: - milk: "牛奶纸箱" - bed: "床" - low-table: "矮桌" - desk: "书桌" - chair: "椅å" - chair2: "椅å2" - fan: "æ¢æ°”扇" - pc: "电脑" - plant: "è§‚å¶æ¤ç‰©" - plant2: "è§‚å¶æ¤ç‰©2" - eraser: "橡皮擦" - pencil: "铅笔" - pudding: "布ä¸" - cardboard-box: "纸箱" - cardboard-box2: "纸箱2" - cardboard-box3: "纸箱3" - book: "书" - book2: "书2" - piano: "é’¢ç´" - facial-tissue: "纸巾盒" - server: "æœåС噍" - moon: "月亮" - corkboard: "软木æ¿" - mousepad: "é¼ æ ‡åž«" - monitor: "显示器" - keyboard: "键盘" - carpet-stripe: "地毯(æ¡çº¹)" - mat: "åž«å" - color-box: "收纳柜" - wall-clock: "挂钟" - photoframe: "相框" - cube: "立方体" - tv: "电视" - pinguin: "ä¼é¹…å›" - rubik-cube: "锿–¹" - poster-h: "海报(横å‘)" - poster-v: "海报(纵å‘)" - sofa: "æ²™å‘" - spiral: "螺旋楼梯" - bin: "垃圾箱" - cup-noodle: "æ¯é¢" - holo-display: "å…¨æ¯æ˜¾ç¤ºå™¨" - energy-drink: "能é‡é¥®æ–™" - doll-ai: "å°è“的玩å¶" - banknote: "钞票" _pages: newPage: "创建页é¢" editPage: "编辑页é¢" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index ad264822fa..798398a6a9 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -239,7 +239,6 @@ uploadFromUrlDescription: "您è¦ä¸Šå‚³çš„æ–‡ä»¶çš„URL" uploadFromUrlRequested: "已請求上傳" uploadFromUrlMayTakeTime: "還需è¦ä¸€äº›æ™‚é–“æ‰èƒ½å®Œæˆä¸Šå‚³ã€‚" explore: "探索" -games: "Misskey éŠæˆ²" messageRead: "已讀" noMoreHistory: "沒有更多æ·å²ç´€éŒ„" startMessaging: "開始傳é€è¨Šæ¯" @@ -525,7 +524,6 @@ userSuspended: "該使用者已被åœç”¨" userSilenced: "該用戶已被ç¦è¨€ã€‚" divider: "分割線" addItem: "æ–°å¢žé …ç›®" -rooms: "房間" relays: "ä¸ç¹¼" addRelay: "新增ä¸ç¹¼" inboxUrl: "收件夾URL" @@ -651,7 +649,6 @@ emailVerified: "å·²æˆåŠŸé©—è‰æ‚¨çš„電郵" noteFavoritesCount: "我的最愛貼文的數目" pageLikesCount: "é é¢è¢«æŒ‰è®šæ¬¡æ•¸" pageLikedCount: "é é¢è¢«æŒ‰è®šæ¬¡æ•¸" -reversiCount: "é»‘ç™½æ£‹å°æˆ°æ¬¡æ•¸" contact: "è¯çµ¡äºº" useSystemFont: "使用系統é è¨çš„å—åž‹" clips: "摘錄" @@ -840,37 +837,6 @@ _mfm: font: "å—åž‹" fontDescription: "您å¯ä»¥è¨å®šé¡¯ç¤ºå…§å®¹çš„å—åž‹" rotate: "旋轉" -_reversi: - reversi: "黑白棋" - gameSettings: "å°å¼ˆè¨å®š" - chooseBoard: "鏿“‡æ£‹ç›¤" - blackOrWhite: "黑棋/白棋" - blackIs: "{name}在玩黑棋" - rules: "è¦å‰‡" - botSettings: "機器人è¨å®š" - thisGameIsStartedSoon: "éŠæˆ²å³å°‡é–‹å§‹" - waitingForOther: "ç‰å¾…å°æ‰‹æº–å‚™" - waitingForMe: "ç‰å¾…您的準備" - waitingBoth: "請準備" - ready: "已就緒" - cancelReady: "釿–°æº–å‚™" - opponentTurn: "å°æ‰‹å›žåˆ" - myTurn: "ä½ çš„å›žåˆ" - turnOf: "{name}的回åˆ" - pastTurnOf: "{name}的回åˆ" - surrender: "èªè¼¸" - surrendered: "å°æ‰‹èªè¼¸" - drawn: "平手" - won: "{name}ç²å‹" - black: "黑" - white: "白" - total: "åˆè¨ˆ" - turnCount: "{count}回åˆ" - myGames: "我的å°å¼ˆ" - allGames: "所有å°å¼ˆ" - ended: "å·²çµæŸ" - playing: "æ£åœ¨å°å¼ˆ" - loopedMap: "循環棋盤" _instanceTicker: none: "éš±è—" remote: "å‘é 端使用者顯示" @@ -1176,67 +1142,6 @@ _timelines: local: "本地" social: "社群" global: "公開" -_rooms: - roomOf: "{user}的房間" - addFurniture: "擺放家具" - translate: "移動 " - rotate: "旋轉" - exit: "返回" - remove: "移除" - clear: "全部移除" - clearConfirm: "確定è¦ç§»é™¤å…¨éƒ¨å®¶å…·å—Žï¼Ÿ" - leaveConfirm: "修改未儲å˜ï¼Œæ˜¯å¦è¦é›¢é–‹ï¼Ÿ" - chooseImage: "鏿“‡åœ–åƒ" - roomType: "房間種類" - carpetColor: "地æ¿é¡è‰²" - _roomType: - default: "é è¨" - washitsu: "和室" - _furnitures: - milk: "牛奶盒" - bed: "床" - low-table: "咖啡桌" - desk: "書桌" - chair: "椅å" - chair2: "椅å2" - fan: "通風機" - pc: "電腦" - plant: "觀葉æ¤ç‰©" - plant2: "觀葉æ¤ç‰©2" - eraser: "橡皮擦" - pencil: "鉛ç†" - pudding: "布ä¸" - cardboard-box: "ç´™æ¿ç®±" - cardboard-box2: "ç´™æ¿ç®±2" - cardboard-box3: "ç´™æ¿ç®±3" - book: "讀物" - book2: "讀物2" - piano: "鋼ç´" - server: "伺æœå™¨" - moon: "月亮" - corkboard: "æœ¨æ “æ¿" - mousepad: "æ»‘é¼ å¢Š" - monitor: "監視器" - keyboard: "éµç›¤" - carpet-stripe: "æ¢ç´‹åœ°æ¯¯" - mat: "地毯" - color-box: "層架" - wall-clock: "å£é˜" - photoframe: "相框" - cube: "立方體" - tv: "電視" - pinguin: "ä¼éµè Ÿåƒ" - rubik-cube: "é”術方塊" - poster-h: "æµ·å ±(æ©«å‘)" - poster-v: "æµ·å ±(ç›´å‘)" - sofa: " 沙發" - spiral: "èžºæ—‹å¼æ¨“梯" - bin: "垃圾箱" - cup-noodle: "æ¯é¢" - holo-display: "投影機" - energy-drink: "能é‡é£²æ–™" - doll-ai: "å°è—的人å¶å…¬ä»”" - banknote: "大疊鈔票" _pages: newPage: "建立é é¢" editPage: "編輯é é¢" diff --git a/package.json b/package.json index c42b71582b..c8fc90c821 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.101.1", + "version": "12.102.0", "codename": "indigo", "repository": { "type": "git", @@ -42,12 +42,12 @@ "js-yaml": "4.1.0" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", - "@typescript-eslint/parser": "5.4.0", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", + "@typescript-eslint/parser": "5.10.0", "cross-env": "7.0.3", - "cypress": "9.1.0", + "cypress": "9.3.1", "start-server-and-test": "1.14.0", - "typescript": "4.5.2" + "typescript": "4.5.5" } } diff --git a/packages/backend/migration/1637320813000-forwarded-report.js b/packages/backend/migration/1637320813000-forwarded-report.js new file mode 100644 index 0000000000..4056f7b5f4 --- /dev/null +++ b/packages/backend/migration/1637320813000-forwarded-report.js @@ -0,0 +1,13 @@ +const { QueryRunner } = require('typeorm'); + +module.exports = class forwardedReport1637320813000 { + name = 'forwardedReport1637320813000'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "forwarded" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "forwarded"`); + } +}; diff --git a/packages/backend/migration/1642611822809-emoji-url.js b/packages/backend/migration/1642611822809-emoji-url.js new file mode 100644 index 0000000000..f229c403f4 --- /dev/null +++ b/packages/backend/migration/1642611822809-emoji-url.js @@ -0,0 +1,15 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class emojiUrl1642611822809 { + name = 'emojiUrl1642611822809' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "url" TO "originalUrl"`); + await queryRunner.query(`ALTER TABLE "emoji" ADD "publicUrl" character varying(512) NOT NULL DEFAULT ''`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "publicUrl"`); + await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "originalUrl" TO "url"`); + } +} diff --git a/packages/backend/migration/1642613870898-drive-file-webpublic-type.js b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js new file mode 100644 index 0000000000..e10c2ac2d2 --- /dev/null +++ b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js @@ -0,0 +1,13 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class driveFileWebpublicType1642613870898 { + name = 'driveFileWebpublicType1642613870898' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" ADD "webpublicType" character varying(128)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "webpublicType"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index ffd179dfa3..3d3a901f34 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -22,87 +22,78 @@ "@sinonjs/fake-timers": "7.1.2", "@syuilo/aiscript": "0.11.1", "@types/bcryptjs": "2.4.2", - "@types/bull": "3.15.5", + "@types/bull": "3.15.7", "@types/cbor": "6.0.0", "@types/dateformat": "3.0.1", - "@types/escape-regexp": "0.0.0", + "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", "@types/is-url": "1.2.30", - "@types/js-yaml": "4.0.4", - "@types/jsdom": "16.2.13", + "@types/js-yaml": "4.0.5", + "@types/jsdom": "16.2.14", "@types/jsonld": "1.5.6", "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.3", + "@types/koa-bodyparser": "4.3.5", "@types/koa-cors": "0.0.2", "@types/koa-favicon": "2.0.21", "@types/koa-logger": "3.1.2", "@types/koa-mount": "4.0.1", "@types/koa-send": "4.1.3", "@types/koa-views": "7.0.0", - "@types/koa__cors": "3.0.3", + "@types/koa__cors": "3.1.1", "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.8", + "@types/koa__router": "8.0.11", "@types/mocha": "8.2.3", - "@types/node": "16.11.7", - "@types/node-fetch": "2.5.12", + "@types/node": "17.0.10", + "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/portscanner": "2.1.1", - "@types/pug": "2.0.5", + "@types/pug": "2.0.6", "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.1", + "@types/qrcode": "1.4.2", "@types/random-seed": "0.3.3", - "@types/ratelimiter": "3.4.2", - "@types/redis": "2.8.32", + "@types/ratelimiter": "3.4.3", + "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/request-stats": "3.0.0", - "@types/sanitize-html": "2.5.0", + "@types/sanitize-html": "2.6.2", "@types/seedrandom": "2.4.28", - "@types/sharp": "0.29.3", + "@types/sharp": "0.29.5", "@types/sinonjs__fake-timers": "6.0.4", - "@types/speakeasy": "2.0.6", + "@types/speakeasy": "2.0.7", "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.2", - "@types/uuid": "8.3.1", + "@types/tmp": "0.2.3", + "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.4", - "@types/ws": "8.2.0", - "@typescript-eslint/eslint-plugin": "5.3.1", - "@typescript-eslint/parser": "5.1.0", + "@types/ws": "8.2.2", + "@typescript-eslint/eslint-plugin": "5.10.0", + "@typescript-eslint/parser": "5.10.0", "abort-controller": "3.0.0", "archiver": "5.3.0", "autobind-decorator": "2.4.0", - "autosize": "4.0.4", "autwh": "0.1.0", - "aws-sdk": "2.1013.0", + "aws-sdk": "2.1061.0", "bcryptjs": "2.4.3", "blurhash": "1.1.4", - "broadcast-channel": "4.5.0", - "bull": "4.1.0", + "broadcast-channel": "4.9.0", + "bull": "4.2.1", "cacheable-lookup": "6.0.4", "cafy": "15.2.1", "cbor": "8.1.0", "chalk": "4.1.2", - "chart.js": "3.6.0", - "chartjs-adapter-date-fns": "2.0.0", - "chartjs-plugin-zoom": "1.1.1", "cli-highlight": "2.1.11", - "compare-versions": "3.6.0", - "content-disposition": "0.5.3", + "content-disposition": "0.5.4", "crc-32": "1.2.0", - "css-loader": "6.5.1", - "cssnano": "5.0.10", - "date-fns": "2.25.0", "dateformat": "4.5.1", - "deep-email-validator": "0.1.18", + "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.2.0", - "eslint-plugin-import": "2.25.3", - "eslint-plugin-vue": "8.0.3", + "eslint": "8.7.0", + "eslint-plugin-import": "2.25.4", "eventemitter3": "4.0.7", "feed": "4.2.2", "file-type": "16.5.3", @@ -110,11 +101,9 @@ "glob": "7.2.0", "got": "11.8.2", "hpagent": "0.1.2", - "http-signature": "1.3.5", - "idb-keyval": "5.1.3", - "insert-text-at-cursor": "0.3.0", + "http-signature": "1.3.6", "ip-cidr": "3.0.4", - "is-svg": "4.3.1", + "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "16.7.0", "json5": "2.2.0", @@ -131,30 +120,29 @@ "koa-slow": "2.1.0", "koa-views": "7.0.2", "langmap": "0.0.16", - "mfm-js": "0.20.0", + "mfm-js": "0.21.0", "mime-types": "2.1.34", - "misskey-js": "0.0.8", + "misskey-js": "0.0.13", "mocha": "8.4.0", "ms": "3.0.0-canary.1", - "multer": "1.4.3", + "multer": "1.4.4", "nested-property": "4.0.0", "node-fetch": "2.6.1", - "nodemailer": "6.7.0", + "nodemailer": "6.7.2", "os-utils": "0.0.14", "parse5": "6.0.1", "pg": "8.7.1", "portscanner": "2.2.0", - "prismjs": "1.25.0", "private-ip": "2.3.3", - "probe-image-size": "7.2.1", + "probe-image-size": "7.2.2", "promise-limit": "2.7.0", "pug": "3.0.2", "punycode": "2.1.1", - "pureimage": "0.3.5", - "qrcode": "1.4.4", + "pureimage": "0.3.8", + "qrcode": "1.5.0", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.16.0", + "re2": "1.17.3", "redis": "3.1.2", "redis-lock": "0.1.4", "reflect-metadata": "0.1.13", @@ -163,9 +151,9 @@ "require-all": "3.0.0", "rndstr": "1.0.0", "s-age": "1.1.2", - "sanitize-html": "2.5.3", + "sanitize-html": "2.6.1", "seedrandom": "3.0.5", - "sharp": "0.29.2", + "sharp": "0.29.3", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", @@ -179,20 +167,21 @@ "ts-loader": "9.2.6", "ts-node": "10.4.0", "tsc-alias": "1.4.1", - "tsconfig-paths": "3.11.0", + "tsconfig-paths": "3.12.0", "twemoji-parser": "13.1.0", - "typeorm": "0.2.39", - "typescript": "4.4.4", + "typeorm": "0.2.41", + "typescript": "4.5.5", "ulid": "2.3.0", + "unzipper": "0.10.11", "uuid": "8.3.2", "web-push": "3.4.5", "websocket": "1.0.34", - "ws": "8.2.3", + "ws": "8.4.2", "xev": "2.0.1" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", "cross-env": "7.0.3", "execa": "6.0.0" } diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 43f59f1e4f..b00bd81655 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,2 +1,47 @@ export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days + +// ブラウザã§ç›´æŽ¥è¡¨ç¤ºã™ã‚‹ã“ã¨ã‚’許å¯ã™ã‚‹ãƒ•ァイルã®ç¨®é¡žã®ãƒªã‚¹ãƒˆ +// ã“ã“ã«å«ã¾ã‚Œãªã„ã‚‚ã®ã¯ application/octet-stream ã¨ã—ã¦ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã•れる +// SVGã¯XSSを生むã®ã§è¨±å¯ã—ãªã„ +export const FILE_TYPE_BROWSERSAFE = [ + // Images + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/x-icon', + + // OggS + 'audio/opus', + 'video/ogg', + 'audio/ogg', + 'application/ogg', + + // ISO/IEC base media file format + 'video/quicktime', + 'video/mp4', + 'audio/mp4', + 'video/x-m4v', + 'audio/x-m4a', + 'video/3gpp', + 'video/3gpp2', + + 'video/mpeg', + 'audio/mpeg', + + 'video/webm', + 'audio/webm', + + 'audio/aac', + 'audio/x-flac', + 'audio/vnd.wave', +]; +/* +https://github.com/sindresorhus/file-type/blob/main/supported.js +https://github.com/sindresorhus/file-type/blob/main/core.js +https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers +*/ diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index b5f228d919..69336c2a46 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -40,8 +40,6 @@ import { Signin } from '@/models/entities/signin'; import { AuthSession } from '@/models/entities/auth-session'; import { FollowRequest } from '@/models/entities/follow-request'; import { Emoji } from '@/models/entities/emoji'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; import { UserNotePining } from '@/models/entities/user-note-pining'; import { Poll } from '@/models/entities/poll'; import { UserKeypair } from '@/models/entities/user-keypair'; @@ -166,8 +164,6 @@ export const entities = [ AntennaNote, PromoNote, PromoRead, - ReversiGame, - ReversiMatching, Relay, MutedNote, Channel, @@ -224,7 +220,9 @@ export async function resetDb() { WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r' AND nspname !~ '^pg_toast';`); - await Promise.all(tables.map(t => t.table).map(x => conn.query(`DELETE FROM "${x}" CASCADE`))); + for (const table of tables) { + await conn.query(`DELETE FROM "${table.table}" CASCADE`); + } }; for (let i = 1; i <= 3; i++) { diff --git a/packages/backend/src/games/reversi/core.ts b/packages/backend/src/games/reversi/core.ts deleted file mode 100644 index 0cf7714543..0000000000 --- a/packages/backend/src/games/reversi/core.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { count, concat } from '@/prelude/array'; - -// MISSKEY REVERSI ENGINE - -/** - * true ... é»’ - * false ... 白 - */ -export type Color = boolean; -const BLACK = true; -const WHITE = false; - -export type MapPixel = 'null' | 'empty'; - -export type Options = { - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; -}; - -export type Undo = { - /** - * 色 - */ - color: Color; - - /** - * ã©ã“ã«æ‰“ã£ãŸã‹ - */ - pos: number; - - /** - * å転ã—ãŸçŸ³ã®ä½ç½®ã®é…列 - */ - effects: number[]; - - /** - * ターン - */ - turn: Color | null; -}; - -/** - * リãƒãƒ¼ã‚·ã‚¨ãƒ³ã‚¸ãƒ³ - */ -export default class Reversi { - public map: MapPixel[]; - public mapWidth: number; - public mapHeight: number; - public board: (Color | null | undefined)[]; - public turn: Color | null = BLACK; - public opts: Options; - - public prevPos = -1; - public prevColor: Color | null = null; - - private logs: Undo[] = []; - - /** - * ã‚²ãƒ¼ãƒ ã‚’åˆæœŸåŒ–ã—ã¾ã™ - */ - constructor(map: string[], opts: Options) { - //#region binds - this.put = this.put.bind(this); - //#endregion - - //#region Options - this.opts = opts; - if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; - if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; - if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; - //#endregion - - //#region Parse map data - this.mapWidth = map[0].length; - this.mapHeight = map.length; - const mapData = map.join(''); - - this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); - - this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); - //#endregion - - // ゲームãŒå§‹ã¾ã£ãŸæ™‚点ã§ç‰‡æ–¹ã®è‰²ã®çŸ³ã—ã‹ãªã„ã‹ã€å§‹ã¾ã£ãŸæ™‚点ã§å‹æ•—ãŒæ±ºå®šã™ã‚‹ã‚ˆã†ãªãƒžãƒƒãƒ—ã®å ´åˆãŒã‚ã‚‹ - if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; - } - - /** - * é»’çŸ³ã®æ•° - */ - public get blackCount() { - return count(BLACK, this.board); - } - - /** - * ç™½çŸ³ã®æ•° - */ - public get whiteCount() { - return count(WHITE, this.board); - } - - public transformPosToXy(pos: number): number[] { - const x = pos % this.mapWidth; - const y = Math.floor(pos / this.mapWidth); - return [x, y]; - } - - public transformXyToPos(x: number, y: number): number { - return x + (y * this.mapWidth); - } - - /** - * 指定ã®ãƒžã‚¹ã«çŸ³ã‚’打ã¡ã¾ã™ - * @param color 石ã®è‰² - * @param pos ä½ç½® - */ - public put(color: Color, pos: number) { - this.prevPos = pos; - this.prevColor = color; - - this.board[pos] = color; - - // å転ã•ã›ã‚‰ã‚Œã‚‹çŸ³ã‚’å–å¾— - const effects = this.effects(color, pos); - - // å転ã•ã›ã‚‹ - for (const pos of effects) { - this.board[pos] = color; - } - - const turn = this.turn; - - this.logs.push({ - color, - pos, - effects, - turn, - }); - - this.calcTurn(); - } - - private calcTurn() { - // ターン計算 - this.turn = - this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : - null; - } - - public undo() { - const undo = this.logs.pop()!; - this.prevColor = undo.color; - this.prevPos = undo.pos; - this.board[undo.pos] = null; - for (const pos of undo.effects) { - const color = this.board[pos]; - this.board[pos] = !color; - } - this.turn = undo.turn; - } - - /** - * 指定ã—ãŸä½ç½®ã®ãƒžãƒƒãƒ—データã®ãƒžã‚¹ã‚’å–å¾—ã—ã¾ã™ - * @param pos ä½ç½® - */ - public mapDataGet(pos: number): MapPixel { - const [x, y] = this.transformPosToXy(pos); - return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; - } - - /** - * 打ã¤ã“ã¨ãŒã§ãã‚‹å ´æ‰€ã‚’å–å¾—ã—ã¾ã™ - */ - public puttablePlaces(color: Color): number[] { - return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); - } - - /** - * 打ã¤ã“ã¨ãŒã§ãã‚‹å ´æ‰€ãŒã‚ã‚‹ã‹ã©ã†ã‹ã‚’å–å¾—ã—ã¾ã™ - */ - public canPutSomewhere(color: Color): boolean { - return this.puttablePlaces(color).length > 0; - } - - /** - * 指定ã®ãƒžã‚¹ã«çŸ³ã‚’打ã¤ã“ã¨ãŒã§ãã‚‹ã‹ã©ã†ã‹ã‚’å–å¾—ã—ã¾ã™ - * @param color 自分ã®è‰² - * @param pos ä½ç½® - */ - public canPut(color: Color, pos: number): boolean { - return ( - this.board[pos] !== null ? false : // æ—¢ã«çŸ³ãŒç½®ã„ã¦ã‚ã‚‹å ´æ‰€ã«ã¯æ‰“ã¦ãªã„ - this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んã§ãªãã¦ã‚‚ç½®ã‘るモード - this.effects(color, pos).length !== 0); // 相手ã®çŸ³ã‚’1ã¤ã§ã‚‚å転ã•ã›ã‚‰ã‚Œã‚‹ã‹ - } - - /** - * 指定ã®ãƒžã‚¹ã«çŸ³ã‚’ç½®ã„ãŸæ™‚ã®ã€å転ã•ã›ã‚‰ã‚Œã‚‹çŸ³ã‚’å–å¾—ã—ã¾ã™ - * @param color 自分ã®è‰² - * @param initPos ä½ç½® - */ - public effects(color: Color, initPos: number): number[] { - const enemyColor = !color; - - const diffVectors: [number, number][] = [ - [ 0, -1], // 上 - [ +1, -1], // å³ä¸Š - [ +1, 0], // å³ - [ +1, +1], // å³ä¸‹ - [ 0, +1], // 下 - [ -1, +1], // 左下 - [ -1, 0], // å·¦ - [ -1, -1], // 左上 - ]; - - const effectsInLine = ([dx, dy]: [number, number]): number[] => { - const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; - - const found: number[] = []; // 挟ã‚ã‚‹ã‹ã‚‚ã—れãªã„相手ã®çŸ³ã‚’入れã¦ãŠãé…列 - let [x, y] = this.transformPosToXy(initPos); - while (true) { - [x, y] = nextPos(x, y); - - // åº§æ¨™ãŒæŒ‡ã—示ã™ä½ç½®ãŒãƒœãƒ¼ãƒ‰å¤–ã«å‡ºãŸã¨ã - if (this.opts.loopedBoard && this.transformXyToPos( - (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), - (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) { - // 盤é¢ã®å¢ƒç•Œã§ãƒ«ãƒ¼ãƒ—ã—ã€è‡ªåˆ†ãŒçŸ³ã‚’ç½®ãä½ç½®ã«æˆ»ã£ã¦ããŸã¨ãã€æŒŸã‚るよã†ã«ã—ã¦ã„ã‚‹ (ref: Test4ã®ãƒžãƒƒãƒ—) - return found; - } else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) { - return []; // 挟ã‚ãªã„ã“ã¨ãŒç¢ºå®š (盤é¢å¤–ã«åˆ°é”) - } - - const pos = this.transformXyToPos(x, y); - if (this.mapDataGet(pos) === 'null') return []; // 挟ã‚ãªã„ã“ã¨ãŒç¢ºå®š (é…ç½®ä¸å¯èƒ½ãªãƒžã‚¹ã«åˆ°é”) - const stone = this.board[pos]; - if (stone === null) return []; // 挟ã‚ãªã„ã“ã¨ãŒç¢ºå®š (石ãŒç½®ã‹ã‚Œã¦ã„ãªã„マスã«åˆ°é”) - if (stone === enemyColor) found.push(pos); // 挟ã‚ã‚‹ã‹ã‚‚ã—れãªã„ (相手ã®çŸ³ã‚’発見) - if (stone === color) return found; // 挟ã‚ã‚‹ã“ã¨ãŒç¢ºå®š (対ã¨ãªã‚‹è‡ªåˆ†ã®çŸ³ã‚’発見) - } - }; - - return concat(diffVectors.map(effectsInLine)); - } - - /** - * ゲームãŒçµ‚了ã—ãŸã‹å¦ã‹ - */ - public get isEnded(): boolean { - return this.turn === null; - } - - /** - * ゲームã®å‹è€… (null = 引ã分ã‘) - */ - public get winner(): Color | null { - return this.isEnded ? - this.blackCount == this.whiteCount ? null : - this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined as never; - } -} diff --git a/packages/backend/src/games/reversi/maps.ts b/packages/backend/src/games/reversi/maps.ts deleted file mode 100644 index 8442c6d741..0000000000 --- a/packages/backend/src/games/reversi/maps.ts +++ /dev/null @@ -1,896 +0,0 @@ -/** - * 組ã¿è¾¼ã¿ãƒžãƒƒãƒ—定義 - * - * データ値: - * (スペース) ... マス無㗠- * - ... マス - * b ... åˆæœŸé…ç½®ã•れる黒石 - * w ... åˆæœŸé…ç½®ã•れる白石 - */ - -export type Map = { - name?: string; - category?: string; - author?: string; - data: string[]; -}; - -export const fourfour: Map = { - name: '4x4', - category: '4x4', - data: [ - '----', - '-wb-', - '-bw-', - '----', - ], -}; - -export const sixsix: Map = { - name: '6x6', - category: '6x6', - data: [ - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - ], -}; - -export const roundedSixsix: Map = { - name: '6x6 rounded', - category: '6x6', - author: 'syuilo', - data: [ - ' ---- ', - '------', - '--wb--', - '--bw--', - '------', - ' ---- ', - ], -}; - -export const roundedSixsix2: Map = { - name: '6x6 rounded 2', - category: '6x6', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - '--wb--', - '--bw--', - ' ---- ', - ' -- ', - ], -}; - -export const eighteight: Map = { - name: '8x8', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const eighteightH1: Map = { - name: '8x8 handicap 1', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const eighteightH2: Map = { - name: '8x8 handicap 2', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b', - ], -}; - -export const eighteightH3: Map = { - name: '8x8 handicap 3', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b', - ], -}; - -export const eighteightH4: Map = { - name: '8x8 handicap 4', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------b', - ], -}; - -export const eighteightH28: Map = { - name: '8x8 handicap 28', - category: '8x8', - data: [ - 'bbbbbbbb', - 'b------b', - 'b------b', - 'b--wb--b', - 'b--bw--b', - 'b------b', - 'b------b', - 'bbbbbbbb', - ], -}; - -export const roundedEighteight: Map = { - name: '8x8 rounded', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - ' ------ ', - ], -}; - -export const roundedEighteight2: Map = { - name: '8x8 rounded 2', - category: '8x8', - author: 'syuilo', - data: [ - ' ---- ', - ' ------ ', - '--------', - '---wb---', - '---bw---', - '--------', - ' ------ ', - ' ---- ', - ], -}; - -export const roundedEighteight3: Map = { - name: '8x8 rounded 3', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ---- ', - ' -- ', - ], -}; - -export const eighteightWithNotch: Map = { - name: '8x8 with notch', - category: '8x8', - author: 'syuilo', - data: [ - '--- ---', - '--------', - '--------', - ' --wb-- ', - ' --bw-- ', - '--------', - '--------', - '--- ---', - ], -}; - -export const eighteightWithSomeHoles: Map = { - name: '8x8 with some holes', - category: '8x8', - author: 'syuilo', - data: [ - '--- ----', - '----- --', - '-- -----', - '---wb---', - '---bw- -', - ' -------', - '--- ----', - '--------', - ], -}; - -export const circle: Map = { - name: 'Circle', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ------ ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ------ ', - ' -- ', - ], -}; - -export const smile: Map = { - name: 'Smile', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '-- -- --', - '---wb---', - '-- bw --', - '--- ---', - '--------', - ' ------ ', - ], -}; - -export const window: Map = { - name: 'Window', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '- -- -', - '- -- -', - '---wb---', - '---bw---', - '- -- -', - '- -- -', - '--------', - ], -}; - -export const reserved: Map = { - name: 'Reserved', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------w', - ], -}; - -export const x: Map = { - name: 'X', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '-w----b-', - '--w--b--', - '---wb---', - '---bw---', - '--b--w--', - '-b----w-', - 'b------w', - ], -}; - -export const parallel: Map = { - name: 'Parallel', - category: '8x8', - author: 'Aya', - data: [ - '--------', - '--------', - '--------', - '---bb---', - '---ww---', - '--------', - '--------', - '--------', - ], -}; - -export const lackOfBlack: Map = { - name: 'Lack of Black', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---w----', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const squareParty: Map = { - name: 'Square Party', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '-wwwbbb-', - '-w-wb-b-', - '-wwwbbb-', - '-bbbwww-', - '-b-bw-w-', - '-bbbwww-', - '--------', - ], -}; - -export const minesweeper: Map = { - name: 'Minesweeper', - category: '8x8', - author: 'syuilo', - data: [ - 'b-b--w-w', - '-w-wb-b-', - 'w-b--w-b', - '-b-wb-w-', - '-w-bw-b-', - 'b-w--b-w', - '-b-bw-w-', - 'w-w--b-b', - ], -}; - -export const tenthtenth: Map = { - name: '10x10', - category: '10x10', - data: [ - '----------', - '----------', - '----------', - '----------', - '----wb----', - '----bw----', - '----------', - '----------', - '----------', - '----------', - ], -}; - -export const hole: Map = { - name: 'The Hole', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '----------', - '--wb--wb--', - '--bw--bw--', - '---- ----', - '---- ----', - '--wb--wb--', - '--bw--bw--', - '----------', - '----------', - ], -}; - -export const grid: Map = { - name: 'Grid', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '- - -- - -', - '----------', - '- - -- - -', - '----wb----', - '----bw----', - '- - -- - -', - '----------', - '- - -- - -', - '----------', - ], -}; - -export const cross: Map = { - name: 'Cross', - category: '10x10', - author: 'Aya', - data: [ - ' ---- ', - ' ---- ', - ' ---- ', - '----------', - '----wb----', - '----bw----', - '----------', - ' ---- ', - ' ---- ', - ' ---- ', - ], -}; - -export const charX: Map = { - name: 'Char X', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - '----------', - '---- ----', - '--- ---', - ], -}; - -export const charY: Map = { - name: 'Char Y', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' ------ ', - ' ------ ', - ' ------ ', - ' ------ ', - ], -}; - -export const walls: Map = { - name: 'Walls', - category: '10x10', - author: 'Aya', - data: [ - ' bbbbbbbb ', - 'w--------w', - 'w--------w', - 'w--------w', - 'w---wb---w', - 'w---bw---w', - 'w--------w', - 'w--------w', - 'w--------w', - ' bbbbbbbb ', - ], -}; - -export const cpu: Map = { - name: 'CPU', - category: '10x10', - author: 'syuilo', - data: [ - ' b b b b ', - 'w--------w', - ' -------- ', - 'w--------w', - ' ---wb--- ', - ' ---bw--- ', - 'w--------w', - ' -------- ', - 'w--------w', - ' b b b b ', - ], -}; - -export const checker: Map = { - name: 'Checker', - category: '10x10', - author: 'Aya', - data: [ - '----------', - '----------', - '----------', - '---wbwb---', - '---bwbw---', - '---wbwb---', - '---bwbw---', - '----------', - '----------', - '----------', - ], -}; - -export const japaneseCurry: Map = { - name: 'Japanese curry', - category: '10x10', - author: 'syuilo', - data: [ - 'w-b-b-b-b-', - '-w-b-b-b-b', - 'w-w-b-b-b-', - '-w-w-b-b-b', - 'w-w-wwb-b-', - '-w-wbb-b-b', - 'w-w-w-b-b-', - '-w-w-w-b-b', - 'w-w-w-w-b-', - '-w-w-w-w-b', - ], -}; - -export const mosaic: Map = { - name: 'Mosaic', - category: '10x10', - author: 'syuilo', - data: [ - '- - - - - ', - ' - - - - -', - '- - - - - ', - ' - w w - -', - '- - b b - ', - ' - w w - -', - '- - b b - ', - ' - - - - -', - '- - - - - ', - ' - - - - -', - ], -}; - -export const arena: Map = { - name: 'Arena', - category: '10x10', - author: 'syuilo', - data: [ - '- - -- - -', - ' - - - - ', - '- ------ -', - ' -------- ', - '- --wb-- -', - '- --bw-- -', - ' -------- ', - '- ------ -', - ' - - - - ', - '- - -- - -', - ], -}; - -export const reactor: Map = { - name: 'Reactor', - category: '10x10', - author: 'syuilo', - data: [ - '-w------b-', - 'b- - - -w', - '- --wb-- -', - '---b w---', - '- b wb w -', - '- w bw b -', - '---w b---', - '- --bw-- -', - 'w- - - -b', - '-b------w-', - ], -}; - -export const sixeight: Map = { - name: '6x8', - category: 'Special', - data: [ - '------', - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - '------', - ], -}; - -export const spark: Map = { - name: 'Spark', - category: 'Special', - author: 'syuilo', - data: [ - ' - - ', - '----------', - ' -------- ', - ' -------- ', - ' ---wb--- ', - ' ---bw--- ', - ' -------- ', - ' -------- ', - '----------', - ' - - ', - ], -}; - -export const islands: Map = { - name: 'Islands', - category: 'Special', - author: 'syuilo', - data: [ - '-------- ', - '---wb--- ', - '---bw--- ', - '-------- ', - ' - - ', - ' - - ', - ' --------', - ' --------', - ' --------', - ' --------', - ], -}; - -export const galaxy: Map = { - name: 'Galaxy', - category: 'Special', - author: 'syuilo', - data: [ - ' ------ ', - ' --www--- ', - ' ------w--- ', - '---bbb--w---', - '--b---b-w-b-', - '-b--wwb-w-b-', - '-b-w-bww--b-', - '-b-w-b---b--', - '---w--bbb---', - ' ---w------ ', - ' ---www-- ', - ' ------ ', - ], -}; - -export const triangle: Map = { - name: 'Triangle', - category: 'Special', - author: 'syuilo', - data: [ - ' -- ', - ' -- ', - ' ---- ', - ' ---- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - ' -------- ', - '----------', - '----------', - ], -}; - -export const iphonex: Map = { - name: 'iPhone X', - category: 'Special', - author: 'syuilo', - data: [ - ' -- -- ', - '--------', - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - '--------', - ' ------ ', - ], -}; - -export const dealWithIt: Map = { - name: 'Deal with it!', - category: 'Special', - author: 'syuilo', - data: [ - '------------', - '--w-b-------', - ' --b-w------', - ' --w-b---- ', - ' ------- ', - ], -}; - -export const experiment: Map = { - name: 'Let\'s experiment', - category: 'Special', - author: 'syuilo', - data: [ - ' ------------ ', - '------wb------', - '------bw------', - '--------------', - ' - - ', - '------ ------', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'wwwwww bbbbbb', - ], -}; - -export const bigBoard: Map = { - name: 'Big board', - category: 'Special', - data: [ - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '-------wb-------', - '-------bw-------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - ], -}; - -export const twoBoard: Map = { - name: 'Two board', - category: 'Special', - author: 'Aya', - data: [ - '-------- --------', - '-------- --------', - '-------- --------', - '---wb--- ---wb---', - '---bw--- ---bw---', - '-------- --------', - '-------- --------', - '-------- --------', - ], -}; - -export const test1: Map = { - name: 'Test1', - category: 'Test', - data: [ - '--------', - '---wb---', - '---bw---', - '--------', - ], -}; - -export const test2: Map = { - name: 'Test2', - category: 'Test', - data: [ - '------', - '------', - '-b--w-', - '-w--b-', - '-w--b-', - ], -}; - -export const test3: Map = { - name: 'Test3', - category: 'Test', - data: [ - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '---', - 'b--', - ], -}; - -export const test4: Map = { - name: 'Test4', - category: 'Test', - data: [ - '-w--b-', - '-w--b-', - '------', - '-w--b-', - '-w--b-', - ], -}; - -// 検証用: ã“ã®ç›¤é¢ã§è—(lv3)ãŒé»’ã§å§‹ã‚ã‚‹ã¨ä½•æ•…ã‹(?)A1ã«æ‰“ã£ã¦ã—ã¾ã† -export const test6: Map = { - name: 'Test6', - category: 'Test', - data: [ - '--wwwww-', - 'wwwwwwww', - 'wbbbwbwb', - 'wbbbbwbb', - 'wbwbbwbb', - 'wwbwbbbb', - '--wbbbbb', - '-wwwww--', - ], -}; - -// 検証用: ã“ã®ç›¤é¢ã§è—(lv3)ãŒé»’ã§å§‹ã‚ã‚‹ã¨ä½•æ•…ã‹(?)G7ã«æ‰“ã£ã¦ã—ã¾ã† -export const test7: Map = { - name: 'Test7', - category: 'Test', - data: [ - 'b--w----', - 'b-wwww--', - 'bwbwwwbb', - 'wbwwwwb-', - 'wwwwwww-', - '-wwbbwwb', - '--wwww--', - '--wwww--', - ], -}; - -// 検証用: ã“ã®ç›¤é¢ã§è—(lv5)ãŒé»’ã§å§‹ã‚ã‚‹ã¨ä½•æ•…ã‹(?)A1ã«æ‰“ã£ã¦ã—ã¾ã† -export const test8: Map = { - name: 'Test8', - category: 'Test', - data: [ - '--------', - '-----w--', - 'w--www--', - 'wwwwww--', - 'bbbbwww-', - 'wwwwww--', - '--www---', - '--ww----', - ], -}; diff --git a/packages/backend/src/games/reversi/package.json b/packages/backend/src/games/reversi/package.json deleted file mode 100644 index a4415ad141..0000000000 --- a/packages/backend/src/games/reversi/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "misskey-reversi", - "version": "0.0.5", - "description": "Misskey reversi engine", - "keywords": [ - "misskey" - ], - "author": "syuilo <i@syuilo.com>", - "license": "MIT", - "repository": "https://github.com/misskey-dev/misskey.git", - "bugs": "https://github.com/misskey-dev/misskey/issues", - "main": "./built/core.js", - "types": "./built/core.d.ts", - "scripts": { - "build": "tsc" - }, - "dependencies": {} -} diff --git a/packages/backend/src/games/reversi/tsconfig.json b/packages/backend/src/games/reversi/tsconfig.json deleted file mode 100644 index 851fb6b7e4..0000000000 --- a/packages/backend/src/games/reversi/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "declaration": true, - "sourceMap": false, - "target": "es2017", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "./built", - "rootDir": "./" - }, - "compileOnSave": false, - "include": [ - "./core.ts" - ] -} diff --git a/packages/backend/src/misc/gen-avatar.ts b/packages/backend/src/misc/gen-identicon.ts index 8838ec8d15..5cedd7afaf 100644 --- a/packages/backend/src/misc/gen-avatar.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -1,5 +1,6 @@ /** - * Random avatar generator + * Identicon generator + * https://en.wikipedia.org/wiki/Identicon */ import * as p from 'pureimage'; @@ -34,9 +35,9 @@ const cellSize = actualSize / n; const sideN = Math.floor(n / 2); /** - * Generate buffer of random avatar by seed + * Generate buffer of an identicon by seed */ -export function genAvatar(seed: string, stream: WriteStream): Promise<void> { +export function genIdenticon(seed: string, stream: WriteStream): Promise<void> { const rand = gen.create(seed); const canvas = p.make(size, size); const ctx = canvas.getContext('2d'); diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index b021ec46eb..26c05e5fa6 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -62,7 +62,8 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu if (emoji == null) return null; const isLocal = emoji.host == null; - const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({ url: emoji.url })}`; + const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ + const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`; return { name: emojiName, @@ -116,7 +117,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null } const _emojis = emojisQuery.length > 0 ? await Emojis.find({ where: emojisQuery, - select: ['name', 'host', 'url'], + select: ['name', 'host', 'originalUrl', 'publicUrl'], }) : []; for (const emoji of _emojis) { cache.set(`${emoji.name} ${emoji.host}`, emoji); diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 4131875ef7..2dae954af9 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -1,32 +1,44 @@ -import { SimpleObj, SimpleSchema } from './simple-schema'; -import { packedUserSchema } from '@/models/repositories/user'; -import { packedNoteSchema } from '@/models/repositories/note'; -import { packedUserListSchema } from '@/models/repositories/user-list'; -import { packedAppSchema } from '@/models/repositories/app'; -import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message'; -import { packedNotificationSchema } from '@/models/repositories/notification'; -import { packedDriveFileSchema } from '@/models/repositories/drive-file'; -import { packedDriveFolderSchema } from '@/models/repositories/drive-folder'; -import { packedFollowingSchema } from '@/models/repositories/following'; -import { packedMutingSchema } from '@/models/repositories/muting'; -import { packedBlockingSchema } from '@/models/repositories/blocking'; -import { packedNoteReactionSchema } from '@/models/repositories/note-reaction'; -import { packedHashtagSchema } from '@/models/repositories/hashtag'; -import { packedPageSchema } from '@/models/repositories/page'; -import { packedUserGroupSchema } from '@/models/repositories/user-group'; -import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite'; -import { packedChannelSchema } from '@/models/repositories/channel'; -import { packedAntennaSchema } from '@/models/repositories/antenna'; -import { packedClipSchema } from '@/models/repositories/clip'; -import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance'; -import { packedQueueCountSchema } from '@/models/repositories/queue'; -import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; -import { packedEmojiSchema } from '@/models/repositories/emoji'; -import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game'; -import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching'; +import { + packedUserLiteSchema, + packedUserDetailedNotMeOnlySchema, + packedMeDetailedOnlySchema, + packedUserDetailedNotMeSchema, + packedMeDetailedSchema, + packedUserDetailedSchema, + packedUserSchema, +} from '@/models/schema/user'; +import { packedNoteSchema } from '@/models/schema/note'; +import { packedUserListSchema } from '@/models/schema/user-list'; +import { packedAppSchema } from '@/models/schema/app'; +import { packedMessagingMessageSchema } from '@/models/schema/messaging-message'; +import { packedNotificationSchema } from '@/models/schema/notification'; +import { packedDriveFileSchema } from '@/models/schema/drive-file'; +import { packedDriveFolderSchema } from '@/models/schema/drive-folder'; +import { packedFollowingSchema } from '@/models/schema/following'; +import { packedMutingSchema } from '@/models/schema/muting'; +import { packedBlockingSchema } from '@/models/schema/blocking'; +import { packedNoteReactionSchema } from '@/models/schema/note-reaction'; +import { packedHashtagSchema } from '@/models/schema/hashtag'; +import { packedPageSchema } from '@/models/schema/page'; +import { packedUserGroupSchema } from '@/models/schema/user-group'; +import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite'; +import { packedChannelSchema } from '@/models/schema/channel'; +import { packedAntennaSchema } from '@/models/schema/antenna'; +import { packedClipSchema } from '@/models/schema/clip'; +import { packedFederationInstanceSchema } from '@/models/schema/federation-instance'; +import { packedQueueCountSchema } from '@/models/schema/queue'; +import { packedGalleryPostSchema } from '@/models/schema/gallery-post'; +import { packedEmojiSchema } from '@/models/schema/emoji'; export const refs = { + UserLite: packedUserLiteSchema, + UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema, + MeDetailedOnly: packedMeDetailedOnlySchema, + UserDetailedNotMe: packedUserDetailedNotMeSchema, + MeDetailed: packedMeDetailedSchema, + UserDetailed: packedUserDetailedSchema, User: packedUserSchema, + UserList: packedUserListSchema, UserGroup: packedUserGroupSchema, App: packedAppSchema, @@ -49,16 +61,52 @@ export const refs = { FederationInstance: packedFederationInstanceSchema, GalleryPost: packedGalleryPostSchema, Emoji: packedEmojiSchema, - ReversiGame: packedReversiGameSchema, - ReversiMatching: packedReversiMatchingSchema, }; -export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; +// Packed = SchemaTypeDef<typeof refs[x]>; ã¨ã™ã‚‹ã¨å±•é–‹ã•れã¦ãƒžã‚¦ã‚¹ãƒ›ãƒãƒ¼æ™‚ã«åž‹è¡¨ç¤ºãŒä½¿ã„物ã«ãªã‚‰ãªããªã‚‹ +// ObjType<r['properties']>を指定ã™ã‚‹ã¨ï¼ˆãªãœã‹ï¼‰å±•é–‹ã•れãšã«Packed<'Hoge'>ã¨è¡¨ç¤ºã•れる +type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<MinimumSchema>; allOf?: ReadonlyArray<MinimumSchema> }> = + r['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> : + r['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<r['oneOf']> : + r['properties'] extends Obj ? ObjType<r['properties']> : + never; +export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>; + +type TypeStringef = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; +type StringDefToType<T extends TypeStringef> = + T extends 'boolean' ? boolean : + T extends 'number' ? number : + T extends 'string' ? string | Date : + T extends 'array' ? ReadonlyArray<any> : + T extends 'object' ? Record<string, any> : + any; + +// https://swagger.io/specification/?sbsearch=optional#schema-object +type OfSchema = { + readonly anyOf?: ReadonlyArray<MinimumSchema>; + readonly oneOf?: ReadonlyArray<MinimumSchema>; + readonly allOf?: ReadonlyArray<MinimumSchema>; +} + +export interface MinimumSchema extends OfSchema { + readonly type?: TypeStringef; + readonly nullable?: boolean; + readonly optional?: boolean; + readonly items?: MinimumSchema; + readonly properties?: Obj; + readonly description?: string; + readonly example?: any; + readonly format?: string; + readonly ref?: keyof typeof refs; + readonly enum?: ReadonlyArray<string>; + readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; + readonly maxLength?: number; + readonly minLength?: number; +} -export interface Schema extends SimpleSchema { - items?: Schema; - properties?: Obj; - ref?: keyof typeof refs; +export interface Schema extends MinimumSchema { + readonly nullable: boolean; + readonly optional: boolean; } type NonUndefinedPropertyNames<T extends Obj> = { @@ -69,22 +117,13 @@ type UndefinedPropertyNames<T extends Obj> = { [K in keyof T]: T[K]['optional'] extends true ? K : never }[keyof T]; -type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>; -type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>; - -export interface Obj extends SimpleObj { [key: string]: Schema; } +export interface Obj { [key: string]: Schema; } export type ObjType<s extends Obj> = - { [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } & - { [P in keyof OnlyRequired<s>]: SchemaType<s[P]> }; - -// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 -type MyType<T extends Schema> = { - 0: any; - 1: SchemaType<T>; -}[T extends Schema ? 1 : 0]; + { -readonly [P in UndefinedPropertyNames<s>]?: SchemaType<s[P]> } & + { -readonly [P in NonUndefinedPropertyNames<s>]: SchemaType<s[P]> }; -type NullOrUndefined<p extends Schema, T> = +type NullOrUndefined<p extends MinimumSchema, T> = p['nullable'] extends true ? p['optional'] extends true ? (T | null | undefined) @@ -93,15 +132,41 @@ type NullOrUndefined<p extends Schema, T> = ? (T | undefined) : T; -export type SchemaType<p extends Schema> = - p['type'] extends 'number' ? NullOrUndefined<p, number> : - p['type'] extends 'string' ? NullOrUndefined<p, string> : - p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> : - p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> : +// 共用体型を交差型ã«ã™ã‚‹åž‹ https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection +type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; + +// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 +// å˜ç´”ã«SchemaTypeDef<X>ã§åˆ¤å®šã™ã‚‹ã ã‘ã§ã¯ãƒ€ãƒ¡ +type UnionSchemaType<a extends readonly any[], X extends MinimumSchema = a[number]> = X extends any ? SchemaType<X> : never; +type ArrayUnion<T> = T extends any ? Array<T> : never; + +export type SchemaTypeDef<p extends MinimumSchema> = + p['type'] extends 'number' ? number : + p['type'] extends 'string' ? ( + p['enum'] extends readonly string[] ? + p['enum'][number] : + p['format'] extends 'date-time' ? string : // Dateã«ã™ã‚‹ï¼Ÿï¼Ÿ + string + ) : + p['type'] extends 'boolean' ? boolean : p['type'] extends 'object' ? ( - p['ref'] extends keyof typeof refs - ? NullOrUndefined<p, Packed<p['ref']>> - : NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> + p['ref'] extends keyof typeof refs ? Packed<p['ref']> : + p['properties'] extends NonNullable<Obj> ? ObjType<p['properties']> : + p['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> : + p['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : + any + ) : + p['type'] extends 'array' ? ( + p['items'] extends OfSchema ? ( + p['items']['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] : + p['items']['oneOf'] extends ReadonlyArray<MinimumSchema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> : + p['items']['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] : + never + ) : + p['items'] extends NonNullable<MinimumSchema> ? SchemaTypeDef<p['items']>[] : + any[] ) : - p['type'] extends 'any' ? NullOrUndefined<p, any> : + p['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['oneOf']> : any; + +export type SchemaType<p extends MinimumSchema> = NullOrUndefined<p, SchemaTypeDef<p>>; diff --git a/packages/backend/src/misc/simple-schema.ts b/packages/backend/src/misc/simple-schema.ts deleted file mode 100644 index abbb348e24..0000000000 --- a/packages/backend/src/misc/simple-schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface SimpleSchema { - type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; - nullable: boolean; - optional: boolean; - items?: SimpleSchema; - properties?: SimpleObj; - description?: string; - example?: any; - format?: string; - ref?: string; - enum?: string[]; - default?: boolean | null; -} - -export interface SimpleObj { [key: string]: SimpleSchema; } diff --git a/packages/backend/src/models/entities/abuse-user-report.ts b/packages/backend/src/models/entities/abuse-user-report.ts index 019d613f76..27c1e47fd8 100644 --- a/packages/backend/src/models/entities/abuse-user-report.ts +++ b/packages/backend/src/models/entities/abuse-user-report.ts @@ -51,6 +51,11 @@ export class AbuseUserReport { }) public resolved: boolean; + @Column('boolean', { + default: false + }) + public forwarded: boolean; + @Column('varchar', { length: 2048, }) diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 0af52d7cc0..cec86880f5 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -101,6 +101,11 @@ export class DriveFile { }) public webpublicUrl: string | null; + @Column('varchar', { + length: 128, nullable: true, + }) + public webpublicType: string | null; + @Index({ unique: true }) @Column('varchar', { length: 256, nullable: true, diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index 1146908a88..2e9c11d21c 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -32,13 +32,19 @@ export class Emoji { @Column('varchar', { length: 512, }) - public url: string; + public originalUrl: string; + + @Column('varchar', { + length: 512, + }) + public publicUrl: string; @Column('varchar', { length: 512, nullable: true, }) public uri: string | null; + // publicUrlã®æ–¹ã®typeãŒå…¥ã‚‹ @Column('varchar', { length: 64, nullable: true, }) diff --git a/packages/backend/src/models/entities/games/reversi/game.ts b/packages/backend/src/models/entities/games/reversi/game.ts deleted file mode 100644 index fe9b8a5ba5..0000000000 --- a/packages/backend/src/models/entities/games/reversi/game.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from '../../user'; -import { id } from '../../../id'; - -@Entity() -export class ReversiGame { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ReversiGame.', - }) - public createdAt: Date; - - @Column('timestamp with time zone', { - nullable: true, - comment: 'The started date of the ReversiGame.', - }) - public startedAt: Date | null; - - @Column(id()) - public user1Id: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user1: User | null; - - @Column(id()) - public user2Id: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user2: User | null; - - @Column('boolean', { - default: false, - }) - public user1Accepted: boolean; - - @Column('boolean', { - default: false, - }) - public user2Accepted: boolean; - - /** - * ã©ã¡ã‚‰ã®ãƒ—レイヤーãŒå…ˆè¡Œ(é»’)ã‹ - * 1 ... user1 - * 2 ... user2 - */ - @Column('integer', { - nullable: true, - }) - public black: number | null; - - @Column('boolean', { - default: false, - }) - public isStarted: boolean; - - @Column('boolean', { - default: false, - }) - public isEnded: boolean; - - @Column({ - ...id(), - nullable: true, - }) - public winnerId: User['id'] | null; - - @Column({ - ...id(), - nullable: true, - }) - public surrendered: User['id'] | null; - - @Column('jsonb', { - default: [], - }) - public logs: { - at: Date; - color: boolean; - pos: number; - }[]; - - @Column('varchar', { - array: true, length: 64, - }) - public map: string[]; - - @Column('varchar', { - length: 32, - }) - public bw: string; - - @Column('boolean', { - default: false, - }) - public isLlotheo: boolean; - - @Column('boolean', { - default: false, - }) - public canPutEverywhere: boolean; - - @Column('boolean', { - default: false, - }) - public loopedBoard: boolean; - - @Column('jsonb', { - nullable: true, default: null, - }) - public form1: any | null; - - @Column('jsonb', { - nullable: true, default: null, - }) - public form2: any | null; - - /** - * ãƒã‚°ã®posã‚’æ–‡å—列ã¨ã—ã¦ã™ã¹ã¦é€£çµã—ãŸã‚‚ã®ã®CRC32値 - */ - @Column('varchar', { - length: 32, nullable: true, - }) - public crc32: string | null; -} diff --git a/packages/backend/src/models/entities/games/reversi/matching.ts b/packages/backend/src/models/entities/games/reversi/matching.ts deleted file mode 100644 index 70bb555e2f..0000000000 --- a/packages/backend/src/models/entities/games/reversi/matching.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from '../../user'; -import { id } from '../../../id'; - -@Entity() -export class ReversiMatching { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ReversiMatching.', - }) - public createdAt: Date; - - @Index() - @Column(id()) - public parentId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public parent: User | null; - - @Index() - @Column(id()) - public childId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public child: User | null; -} diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index eb8cdadd19..d8317de8d3 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -124,6 +124,7 @@ export class UserProfile { }) public clientData: Record<string, any>; + // TODO: ãã®ã†ã¡æ¶ˆã™ @Column('jsonb', { default: {}, comment: 'The room data of the User.', diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 7154cca550..67da347395 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -18,7 +18,6 @@ import { AccessToken } from './entities/access-token'; import { UserNotePining } from './entities/user-note-pining'; import { SigninRepository } from './repositories/signin'; import { MessagingMessageRepository } from './repositories/messaging-message'; -import { ReversiGameRepository } from './repositories/games/reversi/game'; import { UserListRepository } from './repositories/user-list'; import { UserListJoining } from './entities/user-list-joining'; import { UserGroupRepository } from './repositories/user-group'; @@ -30,7 +29,6 @@ import { BlockingRepository } from './repositories/blocking'; import { NoteReactionRepository } from './repositories/note-reaction'; import { NotificationRepository } from './repositories/notification'; import { NoteFavoriteRepository } from './repositories/note-favorite'; -import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; import { UserPublickey } from './entities/user-publickey'; import { UserKeypair } from './entities/user-keypair'; import { AppRepository } from './repositories/app'; @@ -107,8 +105,6 @@ export const AuthSessions = getCustomRepository(AuthSessionRepository); export const AccessTokens = getRepository(AccessToken); export const Signins = getCustomRepository(SigninRepository); export const MessagingMessages = getCustomRepository(MessagingMessageRepository); -export const ReversiGames = getCustomRepository(ReversiGameRepository); -export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository); export const Pages = getCustomRepository(PageRepository); export const PageLikes = getCustomRepository(PageLikeRepository); export const GalleryPosts = getCustomRepository(GalleryPostRepository); diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 5e267b3c2b..943b65eb64 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -27,6 +27,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> { assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, { detail: true, }) : null, + forwarded: report.forwarded, }); } diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 548f44f1b7..3bf0645a7f 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -31,94 +31,3 @@ export class AntennaRepository extends Repository<Antenna> { }; } } - -export const packedAntennaSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - keywords: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - excludeKeywords: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - src: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['home', 'all', 'users', 'list', 'group'], - }, - userListId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - userGroupId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - users: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - caseSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - notify: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - withReplies: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - withFile: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - hasUnreadNote: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts index bec0765ac2..6bac4d9598 100644 --- a/packages/backend/src/models/repositories/app.ts +++ b/packages/backend/src/models/repositories/app.ts @@ -38,38 +38,3 @@ export class AppRepository extends Repository<App> { }; } } - -export const packedAppSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - callbackUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - permission: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - secret: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - isAuthorized: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/blocking.ts b/packages/backend/src/models/repositories/blocking.ts index a6895eabf4..c20b02f501 100644 --- a/packages/backend/src/models/repositories/blocking.ts +++ b/packages/backend/src/models/repositories/blocking.ts @@ -30,31 +30,3 @@ export class BlockingRepository extends Repository<Blocking> { return Promise.all(blockings.map(x => this.pack(x, me))); } } - -export const packedBlockingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - blockeeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - blockee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index 0a6b02f495..b3afb823ab 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -40,56 +40,3 @@ export class ChannelRepository extends Repository<Channel> { }; } } - -export const packedChannelSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - lastNotedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - nullable: true as const, optional: false as const, - }, - bannerUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: false as const, - }, - notesCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, - }, - usersCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, - }, - isFollowing: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - userId: { - type: 'string' as const, - nullable: true as const, optional: false as const, - format: 'id', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/clip.ts b/packages/backend/src/models/repositories/clip.ts index 7892811d48..6f9ceeb50a 100644 --- a/packages/backend/src/models/repositories/clip.ts +++ b/packages/backend/src/models/repositories/clip.ts @@ -29,42 +29,3 @@ export class ClipRepository extends Repository<Clip> { } } -export const packedClipSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - isPublic: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 79b890aa6e..44db9a0a58 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -3,7 +3,7 @@ import { DriveFile } from '@/models/entities/drive-file'; import { Users, DriveFolders } from '../index'; import { User } from '@/models/entities/user'; import { toPuny } from '@/misc/convert-host'; -import { awaitAll } from '@/prelude/await-all'; +import { awaitAll, Promiseable } from '@/prelude/await-all'; import { Packed } from '@/misc/schema'; import config from '@/config/index'; import { query, appendQuery } from '@/prelude/url'; @@ -126,7 +126,7 @@ export class DriveFileRepository extends Repository<DriveFile> { const meta = await fetchMeta(); - return await awaitAll({ + return await awaitAll<Packed<'DriveFile'>>({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, @@ -156,112 +156,3 @@ export class DriveFileRepository extends Repository<DriveFile> { return items.filter(x => x != null); } } - -export const packedDriveFileSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'lenna.jpg', - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'image/jpeg', - }, - md5: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'md5', - example: '15eca7fba0480996e2245f5185bf39f2', - }, - size: { - type: 'number' as const, - optional: false as const, nullable: false as const, - example: 51469, - }, - isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - blurhash: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - properties: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - width: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 1280, - }, - height: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 720, - }, - orientation: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 8, - }, - avgColor: { - type: 'string' as const, - optional: true as const, nullable: false as const, - example: 'rgb(40,65,87)', - }, - }, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - thumbnailUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - comment: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - folderId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - folder: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFolder' as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index 4ee4a68e08..b2e6cee9b8 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -48,44 +48,3 @@ export class DriveFolderRepository extends Repository<DriveFolder> { }); } } - -export const packedDriveFolderSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - foldersCount: { - type: 'number' as const, - optional: true as const, nullable: false as const, - }, - filesCount: { - type: 'number' as const, - optional: true as const, nullable: false as const, - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - parent: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFolder' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index b7529595a9..b9dc6ed0ac 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -15,7 +15,8 @@ export class EmojiRepository extends Repository<Emoji> { name: emoji.name, category: emoji.category, host: emoji.host, - url: emoji.url, + // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ + url: emoji.publicUrl || emoji.originalUrl, }; } @@ -25,41 +26,3 @@ export class EmojiRepository extends Repository<Emoji> { return Promise.all(emojis.map(x => this.pack(x))); } } - -export const packedEmojiSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - category: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - host: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/federation-instance.ts b/packages/backend/src/models/repositories/federation-instance.ts index 90dbbaab1c..426fd5bfc3 100644 --- a/packages/backend/src/models/repositories/federation-instance.ts +++ b/packages/backend/src/models/repositories/federation-instance.ts @@ -1,106 +1,2 @@ import config from '@/config/index'; -export const packedFederationInstanceSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - caughtAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - host: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'misskey.example.com', - }, - usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - notesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - followingCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - followersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - driveUsage: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - driveFiles: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - latestRequestSentAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - lastCommunicatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isNotResponding: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isSuspended: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - softwareName: { - type: 'string' as const, - optional: false as const, nullable: true as const, - example: 'misskey', - }, - softwareVersion: { - type: 'string' as const, - optional: false as const, nullable: true as const, - example: config.version, - }, - openRegistrations: { - type: 'boolean' as const, - optional: false as const, nullable: true as const, - example: true, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - maintainerName: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - maintainerEmail: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - iconUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - infoUpdatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/following.ts b/packages/backend/src/models/repositories/following.ts index 1dfaaf908a..9d20f442df 100644 --- a/packages/backend/src/models/repositories/following.ts +++ b/packages/backend/src/models/repositories/following.ts @@ -84,41 +84,3 @@ export class FollowingRepository extends Repository<Following> { return Promise.all(followings.map(x => this.pack(x, me, opts))); } } - -export const packedFollowingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - followeeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - followee: { - type: 'object' as const, - optional: true as const, nullable: false as const, - ref: 'User' as const, - }, - followerId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - follower: { - type: 'object' as const, - optional: true as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index 6d37e3120e..e9233bb91e 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -38,74 +38,3 @@ export class GalleryPostRepository extends Repository<GalleryPost> { return Promise.all(posts.map(x => this.pack(x, me))); } } - -export const packedGalleryPostSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - title: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - fileIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - files: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFile' as const, - }, - }, - tags: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/games/reversi/game.ts b/packages/backend/src/models/repositories/games/reversi/game.ts deleted file mode 100644 index a9e0496760..0000000000 --- a/packages/backend/src/models/repositories/games/reversi/game.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { User } from '@/models/entities/user'; -import { EntityRepository, Repository } from 'typeorm'; -import { Users } from '../../../index'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { Packed } from '@/misc/schema'; - -@EntityRepository(ReversiGame) -export class ReversiGameRepository extends Repository<ReversiGame> { - public async pack( - src: ReversiGame['id'] | ReversiGame, - me?: { id: User['id'] } | null | undefined, - options?: { - detail?: boolean - } - ): Promise<Packed<'ReversiGame'>> { - const opts = Object.assign({ - detail: true, - }, options); - - const game = typeof src === 'object' ? src : await this.findOneOrFail(src); - - return { - id: game.id, - createdAt: game.createdAt.toISOString(), - startedAt: game.startedAt && game.startedAt.toISOString(), - isStarted: game.isStarted, - isEnded: game.isEnded, - form1: game.form1, - form2: game.form2, - user1Accepted: game.user1Accepted, - user2Accepted: game.user2Accepted, - user1Id: game.user1Id, - user2Id: game.user2Id, - user1: await Users.pack(game.user1Id, me), - user2: await Users.pack(game.user2Id, me), - winnerId: game.winnerId, - winner: game.winnerId ? await Users.pack(game.winnerId, me) : null, - surrendered: game.surrendered, - black: game.black, - bw: game.bw, - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - ...(opts.detail ? { - logs: game.logs.map(log => ({ - at: log.at.toISOString(), - color: log.color, - pos: log.pos, - })), - map: game.map, - } : {}), - }; - } -} - -export const packedReversiGameSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User' as const, - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - logs: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: true as const, nullable: false as const, - properties: { - at: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - color: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - pos: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, - map: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/games/reversi/matching.ts b/packages/backend/src/models/repositories/games/reversi/matching.ts deleted file mode 100644 index b55f598068..0000000000 --- a/packages/backend/src/models/repositories/games/reversi/matching.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { EntityRepository, Repository } from 'typeorm'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; -import { Users } from '../../../index'; -import { awaitAll } from '@/prelude/await-all'; -import { User } from '@/models/entities/user'; -import { Packed } from '@/misc/schema'; - -@EntityRepository(ReversiMatching) -export class ReversiMatchingRepository extends Repository<ReversiMatching> { - public async pack( - src: ReversiMatching['id'] | ReversiMatching, - me: { id: User['id'] } - ): Promise<Packed<'ReversiMatching'>> { - const matching = typeof src === 'object' ? src : await this.findOneOrFail(src); - - return await awaitAll({ - id: matching.id, - createdAt: matching.createdAt.toISOString(), - parentId: matching.parentId, - parent: Users.pack(matching.parentId, me, { - detail: true, - }), - childId: matching.childId, - child: Users.pack(matching.childId, me, { - detail: true, - }), - }); - } -} - -export const packedReversiMatchingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - parent: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User' as const, - }, - childId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - child: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/hashtag.ts b/packages/backend/src/models/repositories/hashtag.ts index 6e513c7ebb..c4b8d50c4e 100644 --- a/packages/backend/src/models/repositories/hashtag.ts +++ b/packages/backend/src/models/repositories/hashtag.ts @@ -24,39 +24,3 @@ export class HashtagRepository extends Repository<Hashtag> { return Promise.all(hashtags.map(x => this.pack(x))); } } - -export const packedHashtagSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - tag: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'misskey', - }, - mentionedUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - mentionedLocalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - mentionedRemoteUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedLocalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedRemoteUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts index 1b2dd3a246..0a342430b9 100644 --- a/packages/backend/src/models/repositories/messaging-message.ts +++ b/packages/backend/src/models/repositories/messaging-message.ts @@ -42,78 +42,3 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> { }; } } - -export const packedMessagingMessageSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: true as const, nullable: false as const, - }, - text: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - fileId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - }, - file: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFile' as const, - }, - recipientId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - recipient: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'User' as const, - }, - groupId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - group: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'UserGroup' as const, - }, - isRead: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - reads: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/muting.ts b/packages/backend/src/models/repositories/muting.ts index b82d1f0daa..bdbe9b47da 100644 --- a/packages/backend/src/models/repositories/muting.ts +++ b/packages/backend/src/models/repositories/muting.ts @@ -30,31 +30,3 @@ export class MutingRepository extends Repository<Muting> { return Promise.all(mutings.map(x => this.pack(x, me))); } } - -export const packedMutingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - muteeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - mutee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index 47586a9116..c5de55c0c0 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -26,31 +26,3 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> { return Promise.all(favorites.map(x => this.pack(x, me))); } } - -export const packedNoteFavoriteSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - note: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'Note' as const, - }, - noteId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index dfb25cbea1..097574effa 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -31,30 +31,3 @@ export class NoteReactionRepository extends Repository<NoteReaction> { }; } } - -export const packedNoteReactionSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 96dfad70e9..9a7fef4977 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -218,7 +218,7 @@ export class NoteRepository extends Repository<Note> { const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); - const packed = await awaitAll({ + const packed: Packed<'Note'> = await awaitAll({ id: note.id, createdAt: note.createdAt.toISOString(), userId: note.userId, @@ -320,188 +320,3 @@ export class NoteRepository extends Repository<Note> { }))); } } - -export const packedNoteSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - text: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - cw: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - replyId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - renoteId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - reply: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'Note' as const, - }, - renote: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'Note' as const, - }, - isHidden: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - visibility: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - mentions: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - visibleUserIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - fileIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - files: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFile' as const, - }, - }, - tags: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - poll: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - channelId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - channel: { - type: 'object' as const, - optional: true as const, nullable: true as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - }, - }, - }, - localOnly: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - emojis: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - }, - }, - }, - reactions: { - type: 'object' as const, - optional: false as const, nullable: false as const, - }, - renoteCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - repliesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - uri: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - url: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - - myReaction: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index 47d569ed21..5e42798898 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -107,69 +107,3 @@ export class NotificationRepository extends Repository<Notification> { }))); } } - -export const packedNotificationSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isRead: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: [...notificationTypes], - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: true as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - }, - note: { - type: 'object' as const, - ref: 'Note' as const, - optional: true as const, nullable: true as const, - }, - reaction: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - choice: { - type: 'number' as const, - optional: true as const, nullable: true as const, - }, - invitation: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - body: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - header: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - icon: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 46b453cad9..ec76c2e418 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -87,56 +87,3 @@ export class PageRepository extends Repository<Page> { return Promise.all(pages.map(x => this.pack(x, me))); } } - -export const packedPageSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - title: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - summary: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - content: { - type: 'array' as const, - optional: false as const, nullable: false as const, - }, - variables: { - type: 'array' as const, - optional: false as const, nullable: false as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/queue.ts b/packages/backend/src/models/repositories/queue.ts deleted file mode 100644 index 521c634390..0000000000 --- a/packages/backend/src/models/repositories/queue.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const packedQueueCountSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - waiting: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - active: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - completed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - failed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - delayed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - paused: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user-group.ts b/packages/backend/src/models/repositories/user-group.ts index 02a0348885..3ed37ca0ed 100644 --- a/packages/backend/src/models/repositories/user-group.ts +++ b/packages/backend/src/models/repositories/user-group.ts @@ -23,39 +23,3 @@ export class UserGroupRepository extends Repository<UserGroup> { }; } } - -export const packedUserGroupSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - ownerId: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - userIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user-list.ts b/packages/backend/src/models/repositories/user-list.ts index 792a17cb49..a2bffe8357 100644 --- a/packages/backend/src/models/repositories/user-list.ts +++ b/packages/backend/src/models/repositories/user-list.ts @@ -22,34 +22,3 @@ export class UserListRepository extends Repository<UserList> { }; } } - -export const packedUserListSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - userIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 3dc7c67ec2..2b8398832d 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -4,11 +4,19 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user'; import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index'; import config from '@/config/index'; import { Packed } from '@/misc/schema'; -import { awaitAll } from '@/prelude/await-all'; +import { awaitAll, Promiseable } from '@/prelude/await-all'; import { populateEmojis } from '@/misc/populate-emojis'; import { getAntennas } from '@/misc/antenna-cache'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const'; +type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; +type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> = + Detailed extends true ? + ExpectsMe extends true ? Packed<'MeDetailed'> : + ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : + Packed<'UserDetailed'> : + Packed<'UserLite'>; + @EntityRepository(User) export class UserRepository extends Repository<User> { public async getRelation(me: User['id'], target: User['id']) { @@ -144,7 +152,7 @@ export class UserRepository extends Repository<User> { return count > 0; } - public getOnlineStatus(user: User): string { + public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; const elapsed = Date.now() - user.lastActiveDate.getTime(); @@ -159,18 +167,18 @@ export class UserRepository extends Repository<User> { if (user.avatarUrl) { return user.avatarUrl; } else { - return `${config.url}/random-avatar/${user.id}`; + return `${config.url}/identicon/${user.id}`; } } - public async pack( + public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( src: User['id'] | User, me?: { id: User['id'] } | null | undefined, options?: { - detail?: boolean, + detail?: D, includeSecrets?: boolean, } - ): Promise<Packed<'User'>> { + ): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> { const opts = Object.assign({ detail: false, includeSecrets: false, @@ -178,8 +186,9 @@ export class UserRepository extends Repository<User> { const user = typeof src === 'object' ? src : await this.findOneOrFail(src); const meId = me ? me.id : null; + const isMe = meId === user.id; - const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; + const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin') .where('pin.userId = :userId', { userId: user.id }) .innerJoinAndSelect('pin.note', 'note') @@ -188,12 +197,12 @@ export class UserRepository extends Repository<User> { const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; const followingCount = profile == null ? null : - (profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount : + (profile.ffVisibility === 'public') || isMe ? user.followingCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : null; const followersCount = profile == null ? null : - (profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount : + (profile.ffVisibility === 'public') || isMe ? user.followersCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; @@ -227,12 +236,11 @@ export class UserRepository extends Repository<User> { uri: user.uri, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, - lastFetchedAt: user.lastFetchedAt?.toISOString(), + lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, bannerUrl: user.bannerUrl, bannerBlurhash: user.bannerBlurhash, bannerColor: null, // å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ isLocked: user.isLocked, - isModerator: user.isModerator || falsy, isSilenced: user.isSilenced || falsy, isSuspended: user.isSuspended || falsy, description: profile!.description, @@ -260,7 +268,7 @@ export class UserRepository extends Repository<User> { : false, } : {}), - ...(opts.detail && meId === user.id ? { + ...(opts.detail && isMe ? { avatarId: user.avatarId, bannerId: user.bannerId, injectFeaturedNote: profile!.injectFeaturedNote, @@ -315,19 +323,19 @@ export class UserRepository extends Repository<User> { isBlocked: relation.isBlocked, isMuted: relation.isMuted, } : {}), - }; + } as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>; return await awaitAll(packed); } - public packMany( + public packMany<D extends boolean = false>( users: (User['id'] | User)[], me?: { id: User['id'] } | null | undefined, options?: { - detail?: boolean, + detail?: D, includeSecrets?: boolean, } - ) { + ): Promise<IsUserDetailed<D>[]> { return Promise.all(users.map(u => this.pack(u, me, options))); } @@ -352,313 +360,3 @@ export class UserRepository extends Repository<User> { public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/); //#endregion } - -export const packedUserSchema = { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - id: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - name: { - type: 'string' as const, - nullable: true as const, optional: false as const, - example: 'è—', - }, - username: { - type: 'string' as const, - nullable: false as const, optional: false as const, - example: 'ai', - }, - host: { - type: 'string' as const, - nullable: true as const, optional: false as const, - example: 'misskey.example.com', - }, - avatarUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: false as const, - }, - avatarBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, - }, - avatarColor: { - type: 'any' as const, - nullable: true as const, optional: false as const, - default: null, - }, - isAdmin: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - isModerator: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - isBot: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - isCat: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - emojis: { - type: 'array' as const, - nullable: false as const, optional: false as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - name: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - url: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'url', - }, - }, - }, - }, - url: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: true as const, - }, - createdAt: { - type: 'string' as const, - nullable: false as const, optional: true as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'date-time', - }, - bannerUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: true as const, - }, - bannerBlurhash: { - type: 'any' as const, - nullable: true as const, optional: true as const, - }, - bannerColor: { - type: 'any' as const, - nullable: true as const, optional: true as const, - default: null, - }, - isLocked: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - isSuspended: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - example: false, - }, - description: { - type: 'string' as const, - nullable: true as const, optional: true as const, - example: 'Hi masters, I am Ai!', - }, - location: { - type: 'string' as const, - nullable: true as const, optional: true as const, - }, - birthday: { - type: 'string' as const, - nullable: true as const, optional: true as const, - example: '2018-03-12', - }, - fields: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - name: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - value: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - }, - maxLength: 4, - }, - }, - followersCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - followingCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - notesCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - pinnedNoteIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - pinnedNotes: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - ref: 'Note' as const, - }, - }, - pinnedPageId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - }, - pinnedPage: { - type: 'object' as const, - nullable: true as const, optional: true as const, - ref: 'Page' as const, - }, - twoFactorEnabled: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - usePasswordLessLogin: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - securityKeys: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - avatarId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'id', - }, - bannerId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'id', - }, - autoWatch: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - injectFeaturedNote: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - alwaysMarkNsfw: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - carefulBot: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - autoAcceptFollowed: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadSpecifiedNotes: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadMentions: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadAnnouncement: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadAntenna: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadChannel: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadMessagingMessage: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadNotification: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasPendingReceivedFollowRequest: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - integrations: { - type: 'object' as const, - nullable: false as const, optional: true as const, - }, - mutedWords: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - mutedInstances: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - mutingNotificationTypes: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - isFollowing: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isFollowed: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isBlocking: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isBlocked: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isMuted: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/backend/src/models/schema/antenna.ts new file mode 100644 index 0000000000..9cf522802c --- /dev/null +++ b/packages/backend/src/models/schema/antenna.ts @@ -0,0 +1,89 @@ +export const packedAntennaSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + keywords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + excludeKeywords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + src: { + type: 'string', + optional: false, nullable: false, + enum: ['home', 'all', 'users', 'list', 'group'], + }, + userListId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + userGroupId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + users: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + caseSensitive: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + notify: { + type: 'boolean', + optional: false, nullable: false, + }, + withReplies: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + withFile: { + type: 'boolean', + optional: false, nullable: false, + }, + hasUnreadNote: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/app.ts b/packages/backend/src/models/schema/app.ts new file mode 100644 index 0000000000..c80dc81c33 --- /dev/null +++ b/packages/backend/src/models/schema/app.ts @@ -0,0 +1,33 @@ +export const packedAppSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + callbackUrl: { + type: 'string', + optional: false, nullable: true, + }, + permission: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + secret: { + type: 'string', + optional: true, nullable: false, + }, + isAuthorized: { + type: 'boolean', + optional: true, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/blocking.ts b/packages/backend/src/models/schema/blocking.ts new file mode 100644 index 0000000000..5532322420 --- /dev/null +++ b/packages/backend/src/models/schema/blocking.ts @@ -0,0 +1,26 @@ +export const packedBlockingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + blockeeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + blockee: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/channel.ts b/packages/backend/src/models/schema/channel.ts new file mode 100644 index 0000000000..7f4f2a48b8 --- /dev/null +++ b/packages/backend/src/models/schema/channel.ts @@ -0,0 +1,51 @@ +export const packedChannelSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + lastNotedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + nullable: true, optional: false, + }, + bannerUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + notesCount: { + type: 'number', + nullable: false, optional: false, + }, + usersCount: { + type: 'number', + nullable: false, optional: false, + }, + isFollowing: { + type: 'boolean', + optional: true, nullable: false, + }, + userId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/clip.ts b/packages/backend/src/models/schema/clip.ts new file mode 100644 index 0000000000..f0ee2ce0c4 --- /dev/null +++ b/packages/backend/src/models/schema/clip.ts @@ -0,0 +1,38 @@ +export const packedClipSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + isPublic: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/drive-file.ts b/packages/backend/src/models/schema/drive-file.ts new file mode 100644 index 0000000000..4359076612 --- /dev/null +++ b/packages/backend/src/models/schema/drive-file.ts @@ -0,0 +1,107 @@ +export const packedDriveFileSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + example: 'lenna.jpg', + }, + type: { + type: 'string', + optional: false, nullable: false, + example: 'image/jpeg', + }, + md5: { + type: 'string', + optional: false, nullable: false, + format: 'md5', + example: '15eca7fba0480996e2245f5185bf39f2', + }, + size: { + type: 'number', + optional: false, nullable: false, + example: 51469, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + blurhash: { + type: 'string', + optional: false, nullable: true, + }, + properties: { + type: 'object', + optional: false, nullable: false, + properties: { + width: { + type: 'number', + optional: true, nullable: false, + example: 1280, + }, + height: { + type: 'number', + optional: true, nullable: false, + example: 720, + }, + orientation: { + type: 'number', + optional: true, nullable: false, + example: 8, + }, + avgColor: { + type: 'string', + optional: true, nullable: false, + example: 'rgb(40,65,87)', + }, + }, + }, + url: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + thumbnailUrl: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + comment: { + type: 'string', + optional: false, nullable: true, + }, + folderId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + folder: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFolder', + }, + userId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + user: { + type: 'object', + optional: true, nullable: true, + ref: 'UserLite', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/drive-folder.ts b/packages/backend/src/models/schema/drive-folder.ts new file mode 100644 index 0000000000..88cb8ab4a2 --- /dev/null +++ b/packages/backend/src/models/schema/drive-folder.ts @@ -0,0 +1,39 @@ +export const packedDriveFolderSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + foldersCount: { + type: 'number', + optional: true, nullable: false, + }, + filesCount: { + type: 'number', + optional: true, nullable: false, + }, + parentId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + parent: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFolder', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts new file mode 100644 index 0000000000..5f9af88db4 --- /dev/null +++ b/packages/backend/src/models/schema/emoji.ts @@ -0,0 +1,36 @@ +export const packedEmojiSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + aliases: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + category: { + type: 'string', + optional: false, nullable: true, + }, + host: { + type: 'string', + optional: false, nullable: true, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts new file mode 100644 index 0000000000..eef2f9e24f --- /dev/null +++ b/packages/backend/src/models/schema/federation-instance.ts @@ -0,0 +1,105 @@ +import config from "@/config"; + +export const packedFederationInstanceSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + caughtAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + host: { + type: 'string', + optional: false, nullable: false, + example: 'misskey.example.com', + }, + usersCount: { + type: 'number', + optional: false, nullable: false, + }, + notesCount: { + type: 'number', + optional: false, nullable: false, + }, + followingCount: { + type: 'number', + optional: false, nullable: false, + }, + followersCount: { + type: 'number', + optional: false, nullable: false, + }, + driveUsage: { + type: 'number', + optional: false, nullable: false, + }, + driveFiles: { + type: 'number', + optional: false, nullable: false, + }, + latestRequestSentAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + lastCommunicatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + isNotResponding: { + type: 'boolean', + optional: false, nullable: false, + }, + isSuspended: { + type: 'boolean', + optional: false, nullable: false, + }, + softwareName: { + type: 'string', + optional: false, nullable: true, + example: 'misskey', + }, + softwareVersion: { + type: 'string', + optional: false, nullable: true, + example: config.version, + }, + openRegistrations: { + type: 'boolean', + optional: false, nullable: true, + example: true, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + maintainerName: { + type: 'string', + optional: false, nullable: true, + }, + maintainerEmail: { + type: 'string', + optional: false, nullable: true, + }, + iconUrl: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + infoUpdatedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/following.ts b/packages/backend/src/models/schema/following.ts new file mode 100644 index 0000000000..2bcffbfc4d --- /dev/null +++ b/packages/backend/src/models/schema/following.ts @@ -0,0 +1,36 @@ +export const packedFollowingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + followeeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + followee: { + type: 'object', + optional: true, nullable: false, + ref: 'UserDetailed', + }, + followerId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + follower: { + type: 'object', + optional: true, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/gallery-post.ts b/packages/backend/src/models/schema/gallery-post.ts new file mode 100644 index 0000000000..fc503d4a64 --- /dev/null +++ b/packages/backend/src/models/schema/gallery-post.ts @@ -0,0 +1,69 @@ +export const packedGalleryPostSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + fileIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + tags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/hashtag.ts b/packages/backend/src/models/schema/hashtag.ts new file mode 100644 index 0000000000..98f8827640 --- /dev/null +++ b/packages/backend/src/models/schema/hashtag.ts @@ -0,0 +1,34 @@ +export const packedHashtagSchema = { + type: 'object', + properties: { + tag: { + type: 'string', + optional: false, nullable: false, + example: 'misskey', + }, + mentionedUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + mentionedLocalUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + mentionedRemoteUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedLocalUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedRemoteUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/messaging-message.ts b/packages/backend/src/models/schema/messaging-message.ts new file mode 100644 index 0000000000..b1ffa45955 --- /dev/null +++ b/packages/backend/src/models/schema/messaging-message.ts @@ -0,0 +1,73 @@ +export const packedMessagingMessageSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: true, nullable: false, + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + fileId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + file: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFile', + }, + recipientId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + recipient: { + type: 'object', + optional: true, nullable: true, + ref: 'UserLite', + }, + groupId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + group: { + type: 'object', + optional: true, nullable: true, + ref: 'UserGroup', + }, + isRead: { + type: 'boolean', + optional: true, nullable: false, + }, + reads: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/muting.ts b/packages/backend/src/models/schema/muting.ts new file mode 100644 index 0000000000..d75a4fbfed --- /dev/null +++ b/packages/backend/src/models/schema/muting.ts @@ -0,0 +1,26 @@ +export const packedMutingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + muteeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + mutee: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note-favorite.ts b/packages/backend/src/models/schema/note-favorite.ts new file mode 100644 index 0000000000..d133f7367d --- /dev/null +++ b/packages/backend/src/models/schema/note-favorite.ts @@ -0,0 +1,26 @@ +export const packedNoteFavoriteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + noteId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note-reaction.ts b/packages/backend/src/models/schema/note-reaction.ts new file mode 100644 index 0000000000..0d8fc5449b --- /dev/null +++ b/packages/backend/src/models/schema/note-reaction.ts @@ -0,0 +1,25 @@ +export const packedNoteReactionSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts new file mode 100644 index 0000000000..cdf4b9a544 --- /dev/null +++ b/packages/backend/src/models/schema/note.ts @@ -0,0 +1,183 @@ +export const packedNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + cw: { + type: 'string', + optional: true, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + replyId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + renoteId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + reply: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + renote: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + isHidden: { + type: 'boolean', + optional: true, nullable: false, + }, + visibility: { + type: 'string', + optional: false, nullable: false, + }, + mentions: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + visibleUserIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + fileIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + tags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + poll: { + type: 'object', + optional: true, nullable: true, + }, + channelId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + channel: { + type: 'object', + optional: true, nullable: true, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + localOnly: { + type: 'boolean', + optional: true, nullable: false, + }, + emojis: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + name: { + type: 'string', + optional: false, nullable: false, + }, + url: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + reactions: { + type: 'object', + optional: false, nullable: false, + }, + renoteCount: { + type: 'number', + optional: false, nullable: false, + }, + repliesCount: { + type: 'number', + optional: false, nullable: false, + }, + uri: { + type: 'string', + optional: true, nullable: false, + }, + url: { + type: 'string', + optional: true, nullable: false, + }, + + myReaction: { + type: 'object', + optional: true, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/notification.ts b/packages/backend/src/models/schema/notification.ts new file mode 100644 index 0000000000..f3c293c480 --- /dev/null +++ b/packages/backend/src/models/schema/notification.ts @@ -0,0 +1,66 @@ +import { notificationTypes } from "@/types"; + +export const packedNotificationSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + isRead: { + type: 'boolean', + optional: false, nullable: false, + }, + type: { + type: 'string', + optional: false, nullable: false, + enum: [...notificationTypes], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: true, nullable: true, + }, + userId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: true, nullable: true, + }, + reaction: { + type: 'string', + optional: true, nullable: true, + }, + choice: { + type: 'number', + optional: true, nullable: true, + }, + invitation: { + type: 'object', + optional: true, nullable: true, + }, + body: { + type: 'string', + optional: true, nullable: true, + }, + header: { + type: 'string', + optional: true, nullable: true, + }, + icon: { + type: 'string', + optional: true, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/page.ts b/packages/backend/src/models/schema/page.ts new file mode 100644 index 0000000000..55ba3ce7f7 --- /dev/null +++ b/packages/backend/src/models/schema/page.ts @@ -0,0 +1,51 @@ +export const packedPageSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + summary: { + type: 'string', + optional: false, nullable: true, + }, + content: { + type: 'array', + optional: false, nullable: false, + }, + variables: { + type: 'array', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/queue.ts b/packages/backend/src/models/schema/queue.ts new file mode 100644 index 0000000000..7ceeda26af --- /dev/null +++ b/packages/backend/src/models/schema/queue.ts @@ -0,0 +1,25 @@ +export const packedQueueCountSchema = { + type: 'object', + properties: { + waiting: { + type: 'number', + optional: false, nullable: false, + }, + active: { + type: 'number', + optional: false, nullable: false, + }, + completed: { + type: 'number', + optional: false, nullable: false, + }, + failed: { + type: 'number', + optional: false, nullable: false, + }, + delayed: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user-group.ts b/packages/backend/src/models/schema/user-group.ts new file mode 100644 index 0000000000..a73bf82bb8 --- /dev/null +++ b/packages/backend/src/models/schema/user-group.ts @@ -0,0 +1,34 @@ +export const packedUserGroupSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + ownerId: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + userIds: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user-list.ts b/packages/backend/src/models/schema/user-list.ts new file mode 100644 index 0000000000..3ba5dc4a8a --- /dev/null +++ b/packages/backend/src/models/schema/user-list.ts @@ -0,0 +1,29 @@ +export const packedUserListSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + userIds: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts new file mode 100644 index 0000000000..616bedc0dc --- /dev/null +++ b/packages/backend/src/models/schema/user.ts @@ -0,0 +1,467 @@ +export const packedUserLiteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + nullable: false, optional: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + name: { + type: 'string', + nullable: true, optional: false, + example: 'è—', + }, + username: { + type: 'string', + nullable: false, optional: false, + example: 'ai', + }, + host: { + type: 'string', + nullable: true, optional: false, + example: 'misskey.example.com', + }, + avatarUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + avatarBlurhash: { + type: 'any', + nullable: true, optional: false, + }, + avatarColor: { + type: 'any', + nullable: true, optional: false, + default: null, + }, + isAdmin: { + type: 'boolean', + nullable: false, optional: true, + default: false, + }, + isModerator: { + type: 'boolean', + nullable: false, optional: true, + default: false, + }, + isBot: { + type: 'boolean', + nullable: false, optional: true, + }, + isCat: { + type: 'boolean', + nullable: false, optional: true, + }, + emojis: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + url: { + type: 'string', + nullable: false, optional: false, + format: 'url', + }, + }, + }, + }, + onlineStatus: { + type: 'string', + format: 'url', + nullable: true, optional: false, + enum: ['unknown', 'online', 'active', 'offline'], + }, + }, +} as const; + +export const packedUserDetailedNotMeOnlySchema = { + type: 'object', + properties: { + url: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + uri: { + type: 'string', + format: 'uri', + nullable: true, optional: false, + }, + createdAt: { + type: 'string', + nullable: false, optional: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + nullable: true, optional: false, + format: 'date-time', + }, + lastFetchedAt: { + type: 'string', + nullable: true, optional: false, + format: 'date-time', + }, + bannerUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + bannerBlurhash: { + type: 'any', + nullable: true, optional: false, + }, + bannerColor: { + type: 'any', + nullable: true, optional: false, + default: null, + }, + isLocked: { + type: 'boolean', + nullable: false, optional: false, + }, + isSilenced: { + type: 'boolean', + nullable: false, optional: false, + }, + isSuspended: { + type: 'boolean', + nullable: false, optional: false, + example: false, + }, + description: { + type: 'string', + nullable: true, optional: false, + example: 'Hi masters, I am Ai!', + }, + location: { + type: 'string', + nullable: true, optional: false, + }, + birthday: { + type: 'string', + nullable: true, optional: false, + example: '2018-03-12', + }, + lang: { + type: 'string', + nullable: true, optional: false, + example: 'ja-JP', + }, + fields: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + value: { + type: 'string', + nullable: false, optional: false, + }, + }, + maxLength: 4, + }, + }, + followersCount: { + type: 'number', + nullable: false, optional: false, + }, + followingCount: { + type: 'number', + nullable: false, optional: false, + }, + notesCount: { + type: 'number', + nullable: false, optional: false, + }, + pinnedNoteIds: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + pinnedNotes: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + ref: 'Note', + }, + }, + pinnedPageId: { + type: 'string', + nullable: true, optional: false, + }, + pinnedPage: { + type: 'object', + nullable: true, optional: false, + ref: 'Page', + }, + publicReactions: { + type: 'boolean', + nullable: false, optional: false, + }, + twoFactorEnabled: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + usePasswordLessLogin: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + securityKeys: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + //#region relations + isFollowing: { + type: 'boolean', + nullable: false, optional: true, + }, + isFollowed: { + type: 'boolean', + nullable: false, optional: true, + }, + hasPendingFollowRequestFromYou: { + type: 'boolean', + nullable: false, optional: true, + }, + hasPendingFollowRequestToYou: { + type: 'boolean', + nullable: false, optional: true, + }, + isBlocking: { + type: 'boolean', + nullable: false, optional: true, + }, + isBlocked: { + type: 'boolean', + nullable: false, optional: true, + }, + isMuted: { + type: 'boolean', + nullable: false, optional: true, + }, + //#endregion + }, +} as const; + +export const packedMeDetailedOnlySchema = { + type: 'object', + properties: { + avatarId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + bannerId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + injectFeaturedNote: { + type: 'boolean', + nullable: true, optional: false, + }, + receiveAnnouncementEmail: { + type: 'boolean', + nullable: true, optional: false, + }, + alwaysMarkNsfw: { + type: 'boolean', + nullable: true, optional: false, + }, + carefulBot: { + type: 'boolean', + nullable: true, optional: false, + }, + autoAcceptFollowed: { + type: 'boolean', + nullable: true, optional: false, + }, + noCrawle: { + type: 'boolean', + nullable: true, optional: false, + }, + isExplorable: { + type: 'boolean', + nullable: false, optional: false, + }, + isDeleted: { + type: 'boolean', + nullable: false, optional: false, + }, + hideOnlineStatus: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadSpecifiedNotes: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadMentions: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadAnnouncement: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadAntenna: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadChannel: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadMessagingMessage: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadNotification: { + type: 'boolean', + nullable: false, optional: false, + }, + hasPendingReceivedFollowRequest: { + type: 'boolean', + nullable: false, optional: false, + }, + integrations: { + type: 'object', + nullable: true, optional: false, + }, + mutedWords: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + }, + mutedInstances: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + mutingNotificationTypes: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + emailNotificationTypes: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + //#region secrets + email: { + type: 'string', + nullable: true, optional: true, + }, + emailVerified: { + type: 'boolean', + nullable: true, optional: true, + }, + securityKeysList: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'object', + nullable: false, optional: false, + }, + }, + //#endregion + }, +} as const; + +export const packedUserDetailedNotMeSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailedNotMeOnly', + }, + ], +} as const; + +export const packedMeDetailedSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailedNotMeOnly', + }, + { + type: 'object', + ref: 'MeDetailedOnly', + }, + ], +} as const; + +export const packedUserDetailedSchema = { + oneOf: [ + { + type: 'object', + ref: 'UserDetailedNotMe', + }, + { + type: 'object', + ref: 'MeDetailed', + }, + ], +} as const; + +export const packedUserSchema = { + oneOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailed', + }, + ], +} as const; diff --git a/packages/backend/src/prelude/await-all.ts b/packages/backend/src/prelude/await-all.ts index 24795f3ae5..b955c3a5d8 100644 --- a/packages/backend/src/prelude/await-all.ts +++ b/packages/backend/src/prelude/await-all.ts @@ -1,13 +1,11 @@ -type Await<T> = T extends Promise<infer U> ? U : T; - -type AwaitAll<T> = { - [P in keyof T]: Await<T[P]>; +export type Promiseable<T> = { + [K in keyof T]: Promise<T[K]> | T[K]; }; -export async function awaitAll<T>(obj: T): Promise<AwaitAll<T>> { - const target = {} as any; - const keys = Object.keys(obj); - const values = Object.values(obj); +export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> { + const target = {} as T; + const keys = Object.keys(obj) as unknown as (keyof T)[]; + const values = Object.values(obj) as any[]; const resolvedValues = await Promise.all(values.map(value => (!value || !value.constructor || value.constructor.name !== 'Object') diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 2fbc1b1c01..f9994c3b59 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -213,6 +213,16 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id'] }); } +export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { + return dbQueue.add('importCustomEmojis', { + user: user, + fileId: fileId, + }, { + removeOnComplete: true, + removeOnFail: true, + }); +} + export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return dbQueue.add('deleteAccount', { user: user, diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 8c886d3b4e..01edaaeb63 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Blockings } from '@/models/index'; @@ -86,7 +86,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P logger.succ(`Exported to: ${path}`); const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 3930b9d6d4..240a542fec 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -6,11 +6,12 @@ import { ulid } from 'ulid'; const mime = require('mime-types'); const archiver = require('archiver'); import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { Users, Emojis } from '@/models/index'; import { } from '@/queue/types'; import { downloadUrl } from '@/misc/download-url'; +import config from '@/config/index'; const logger = queueLogger.createSubLogger('export-custom-emojis'); @@ -52,7 +53,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); }; - await writeMeta(`{"metaVersion":1,"emojis":[`); + await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`); const customEmojis = await Emojis.find({ where: { @@ -64,21 +65,25 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); for (const emoji of customEmojis) { - const exportId = ulid().toLowerCase(); const ext = mime.extension(emoji.type); - const emojiPath = path + '/' + exportId + (ext ? '.' + ext : ''); + const fileName = emoji.name + (ext ? '.' + ext : ''); + const emojiPath = path + '/' + fileName; fs.writeFileSync(emojiPath, '', 'binary'); let downloaded = false; try { - await downloadUrl(emoji.url, emojiPath); + await downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度ã‹å†è©¦è¡Œ logger.error(e); } + if (!downloaded) { + fs.unlinkSync(emojiPath); + } + const content = JSON.stringify({ - id: exportId, + fileName: fileName, downloaded: downloaded, emoji: emoji, }); @@ -106,7 +111,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi logger.succ(`Exported to: ${archivePath}`); const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.zip'; - const driveFile = await addFile(user, archivePath, fileName, null, null, true); + const driveFile = await addFile({ user, path: archivePath, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index fbb9e25247..06572acec1 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Followings, Mutings } from '@/models/index'; @@ -87,7 +87,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () => logger.succ(`Exported to: ${path}`); const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 0b1fd24fe0..4a856f8ef9 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Mutings } from '@/models/index'; @@ -86,7 +86,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi logger.succ(`Exported to: ${path}`); const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index e64e763513..305abf44cf 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { Users, Notes, Polls } from '@/models/index'; import { MoreThan } from 'typeorm'; @@ -95,7 +95,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom logger.succ(`Exported to: ${path}`); const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 44a8f9f671..f907cf9526 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, UserLists, UserListJoinings } from '@/models/index'; @@ -63,7 +63,7 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): logger.succ(`Exported to: ${path}`); const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts new file mode 100644 index 0000000000..04e93671ed --- /dev/null +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -0,0 +1,85 @@ +import * as Bull from 'bull'; +import * as tmp from 'tmp'; +import * as fs from 'fs'; +const unzipper = require('unzipper'); +import { getConnection } from 'typeorm'; + +import { queueLogger } from '../../logger'; +import { downloadUrl } from '@/misc/download-url'; +import { DriveFiles, Emojis } from '@/models/index'; +import { DbUserImportJobData } from '@/queue/types'; +import { addFile } from '@/services/drive/add-file'; +import { genId } from '@/misc/gen-id'; + +const logger = queueLogger.createSubLogger('import-custom-emojis'); + +// TODO: åå‰è¡çªæ™‚ã®å‹•作をé¸ã¹ã‚‹ã‚ˆã†ã«ã™ã‚‹ +export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { + logger.info(`Importing custom emojis ...`); + + const file = await DriveFiles.findOne({ + id: job.data.fileId, + }); + if (file == null) { + done(); + return; + } + + // Create temp dir + const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { + tmp.dir((e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); + + logger.info(`Temp dir is ${path}`); + + const destPath = path + '/emojis.zip'; + + try { + fs.writeFileSync(destPath, '', 'binary'); + await downloadUrl(file.url, destPath); + } catch (e) { // TODO: 何度ã‹å†è©¦è¡Œ + logger.error(e); + throw e; + } + + const outputPath = path + '/emojis'; + const unzipStream = fs.createReadStream(destPath); + const extractor = unzipper.Extract({ path: outputPath }); + extractor.on('close', async () => { + const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8'); + const meta = JSON.parse(metaRaw); + + for (const record of meta.emojis) { + if (!record.downloaded) continue; + const emojiInfo = record.emoji; + const emojiPath = outputPath + '/' + record.fileName; + await Emojis.delete({ + name: emojiInfo.name, + }); + const driveFile = await addFile({ user: null, path: emojiPath, name: record.fileName, force: true }); + const emoji = await Emojis.insert({ + id: genId(), + updatedAt: new Date(), + name: emojiInfo.name, + category: emojiInfo.category, + host: null, + aliases: emojiInfo.aliases, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, + }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + cleanup(); + + logger.succ('Imported'); + done(); + }); + unzipStream.pipe(extractor); + logger.succ(`Unzipping to ${outputPath}`); +} diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index 8245010de0..e060e86dd8 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -46,13 +46,13 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done: }); if (list == null) { - list = await UserLists.save({ + list = await UserLists.insert({ id: genId(), createdAt: new Date(), userId: user.id, name: listName, userIds: [], - }); + }).then(x => UserLists.findOneOrFail(x.identifiers[0])); } let target = isSelfHost(host!) ? await Users.findOne({ diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts index 1542f401ef..5fffa378f5 100644 --- a/packages/backend/src/queue/processors/db/index.ts +++ b/packages/backend/src/queue/processors/db/index.ts @@ -12,6 +12,7 @@ import { importUserLists } from './import-user-lists'; import { deleteAccount } from './delete-account'; import { importMuting } from './import-muting'; import { importBlocking } from './import-blocking'; +import { importCustomEmojis } from './import-custom-emojis'; const jobs = { deleteDriveFiles, @@ -25,6 +26,7 @@ const jobs = { importMuting, importBlocking, importUserLists, + importCustomEmojis, deleteAccount, } as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index 902eb36a17..6f60b7827d 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,4 +1,4 @@ -import uploadFromUrl from '@/services/drive/upload-from-url'; +import { uploadFromUrl } from '@/services/drive/upload-from-url'; import { IRemoteUser } from '@/models/entities/user'; import Resolver from '../resolver'; import { fetchMeta } from '@/misc/fetch-meta'; @@ -28,9 +28,15 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive logger.info(`Creating the Image: ${image.url}`); const instance = await fetchMeta(); - const cache = instance.cacheRemoteFiles; - let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)); + let file = await uploadFromUrl({ + url: image.url, + user: actor, + uri: image.url, + sensitive: image.sensitive, + isLink: !instance.cacheRemoteFiles, + comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH) + }); if (file.isLink) { // URLãŒç•°ãªã£ã¦ã„ã‚‹å ´åˆã€åŒã˜ç”»åƒãŒä»¥å‰ã«ç•°ãªã‚‹URLã§ç™»éŒ²ã•れã¦ã„ãŸã¨ã„ã†ã“ã¨ãªã®ã§ã€ diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index cca054b52e..6847925a51 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -320,14 +320,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr if ((tag.updated != null && exists.updatedAt == null) || (tag.id != null && exists.uri == null) || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - || (tag.icon!.url !== exists.url) + || (tag.icon!.url !== exists.originalUrl) ) { await Emojis.update({ host, name, }, { uri: tag.id, - url: tag.icon!.url, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, updatedAt: new Date(), }); @@ -342,14 +343,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr logger.info(`register emoji host=${host}, name=${name}`); - return await Emojis.save({ + return await Emojis.insert({ id: genId(), host, name, uri: tag.id, - url: tag.icon!.url, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, updatedAt: new Date(), aliases: [], - } as Partial<Emoji>); + } as Partial<Emoji>).then(x => Emojis.findOneOrFail(x.identifiers[0])); })); } diff --git a/packages/backend/src/remote/activitypub/renderer/emoji.ts b/packages/backend/src/remote/activitypub/renderer/emoji.ts index 9d08c8ba81..e7ae7d959a 100644 --- a/packages/backend/src/remote/activitypub/renderer/emoji.ts +++ b/packages/backend/src/remote/activitypub/renderer/emoji.ts @@ -9,6 +9,6 @@ export default (emoji: Emoji) => ({ icon: { type: 'Image', mediaType: emoji.type || 'image/png', - url: emoji.url, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ }, }); diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts new file mode 100644 index 0000000000..60ac496509 --- /dev/null +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -0,0 +1,15 @@ +import config from '@/config/index'; +import { IObject, IActivity } from '@/remote/activitypub/type'; +import { ILocalUser, IRemoteUser } from '@/models/entities/user'; +import { getInstanceActor } from '@/services/instance-actor'; + +// to anonymise reporters, the reporting actor must be a system user +// object has to be a uri or array of uris +export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => { + return { + type: 'Flag', + actor: `${config.url}/users/${user.id}`, + content, + object, + }; +}; diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index 911118e4c9..cffc9bfe04 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -32,7 +32,7 @@ export const renderActivity = (x: any): IActivity | null => { PropertyValue: 'schema:PropertyValue', value: 'schema:value', // Misskey - misskey: `${config.url}/ns#`, + misskey: 'https://misskey-hub.net/ns#', '_misskey_content': 'misskey:_misskey_content', '_misskey_quote': 'misskey:_misskey_quote', '_misskey_reaction': 'misskey:_misskey_reaction', diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index df6226cc50..747735ecaa 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -37,7 +37,7 @@ export async function resolveUser(username: string, host: string | null, option? }); } - const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser; + const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser | null; const acctLower = `${usernameLower}@${host}`; diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index da6a00e58e..bbbc231b8c 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -67,7 +67,7 @@ router.get('/notes/:note', async (ctx, next) => { const note = await Notes.findOne({ id: ctx.params.note, - visibility: In(['public', 'home']), + visibility: In(['public' as const, 'home' as const]), localOnly: false, }); @@ -96,7 +96,7 @@ router.get('/notes/:note/activity', async ctx => { const note = await Notes.findOne({ id: ctx.params.note, userHost: null, - visibility: In(['public', 'home']), + visibility: In(['public' as const, 'home' as const]), localOnly: false, }); diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index d33e9e3753..9e2f3eb743 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -10,7 +10,7 @@ export class AuthenticationError extends Error { } } -export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => { +export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => { if (token == null) { return [null, null]; } diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 36aadb532b..399ee65bde 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,5 +1,5 @@ import { performance } from 'perf_hooks'; -import limiter from './limiter'; +import { limiter } from './limiter'; import { User } from '@/models/entities/user'; import endpoints from './endpoints'; import { ApiError } from './error'; diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts index b713260ac6..df986fc457 100644 --- a/packages/backend/src/server/api/common/signin.ts +++ b/packages/backend/src/server/api/common/signin.ts @@ -29,14 +29,14 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { (async () => { // Append signin history - const record = await Signins.save({ + const record = await Signins.insert({ id: genId(), createdAt: new Date(), userId: user.id, ip: ctx.ip, headers: ctx.headers, success: true, - }); + }).then(x => Signins.findOneOrFail(x.identifiers[0])); // Publish signin event publishMainStream(user.id, 'signin', await Signins.pack(record)); diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index a61b3f564c..bb4e972b8e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -3,7 +3,7 @@ import { dirname } from 'path'; import { Context } from 'cafy'; import * as path from 'path'; import * as glob from 'glob'; -import { SimpleSchema } from '@/misc/simple-schema'; +import { Schema } from '@/misc/schema'; //const _filename = fileURLToPath(import.meta.url); const _filename = __filename; @@ -18,87 +18,87 @@ export type Param = { }; export interface IEndpointMeta { - stability?: string; //'deprecated' | 'experimental' | 'stable'; + readonly stability?: 'deprecated' | 'experimental' | 'stable'; - tags?: string[]; + readonly tags?: ReadonlyArray<string>; - params?: { - [key: string]: Param; + readonly params?: { + readonly [key: string]: Param; }; - errors?: { - [key: string]: { - message: string; - code: string; - id: string; + readonly errors?: { + readonly [key: string]: { + readonly message: string; + readonly code: string; + readonly id: string; }; }; - res?: SimpleSchema; + readonly res?: Schema; /** * ã“ã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã™ã‚‹ã®ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼æƒ…å ±ãŒå¿…é ˆã‹å¦ã‹ * çœç•¥ã—ãŸå ´åˆã¯ false ã¨ã—ã¦è§£é‡ˆã•れã¾ã™ã€‚ */ - requireCredential?: boolean; + readonly requireCredential?: boolean; /** * 管ç†è€…ã®ã¿ä½¿ãˆã‚‹ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‹å¦ã‹ */ - requireAdmin?: boolean; + readonly requireAdmin?: boolean; /** * 管ç†è€…ã¾ãŸã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®ã¿ä½¿ãˆã‚‹ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‹å¦ã‹ */ - requireModerator?: boolean; + readonly requireModerator?: boolean; /** * エンドãƒã‚¤ãƒ³ãƒˆã®ãƒªãƒŸãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã«é–¢ã™ã‚‹ã‚„㤠* çœç•¥ã—ãŸå ´åˆã¯ãƒªãƒŸãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã¯ç„¡ã„ã‚‚ã®ã¨ã—ã¦è§£é‡ˆã•れã¾ã™ã€‚ * ã¾ãŸã€withCredential ㌠false ã®å ´åˆã¯ãƒªãƒŸãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã‚’行ã†ã“ã¨ã¯ã§ãã¾ã›ã‚“。 */ - limit?: { + readonly limit?: { /** * 複数ã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã§ãƒªãƒŸãƒƒãƒˆã‚’共有ã—ãŸã„å ´åˆã«æŒ‡å®šã™ã‚‹ã‚ー */ - key?: string; + readonly key?: string; /** * リミットをé©ç”¨ã™ã‚‹æœŸé–“(ms) * ã“ã®ãƒ—ãƒãƒ‘ティをè¨å®šã™ã‚‹å ´åˆã€max プãƒãƒ‘ティもè¨å®šã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ */ - duration?: number; + readonly duration?: number; /** * durationã§æŒ‡å®šã—ãŸæœŸé–“内ã«ã„ãã¤ã¾ã§ãƒªã‚¯ã‚¨ã‚¹ãƒˆã§ãã‚‹ã®ã‹ * ã“ã®ãƒ—ãƒãƒ‘ティをè¨å®šã™ã‚‹å ´åˆã€duration プãƒãƒ‘ティもè¨å®šã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ */ - max?: number; + readonly max?: number; /** * 最低ã§ã‚‚ã©ã‚Œãらã„ã®é–“隔を開ã‘ã¦ãƒªã‚¯ã‚¨ã‚¹ãƒˆã—ãªã‘れã°ãªã‚‰ãªã„ã‹(ms) */ - minInterval?: number; + readonly minInterval?: number; }; /** * ãƒ•ã‚¡ã‚¤ãƒ«ã®æ·»ä»˜ã‚’å¿…è¦ã¨ã™ã‚‹ã‹å¦ã‹ * çœç•¥ã—ãŸå ´åˆã¯ false ã¨ã—ã¦è§£é‡ˆã•れã¾ã™ã€‚ */ - requireFile?: boolean; + readonly requireFile?: boolean; /** * サードパーティアプリã‹ã‚‰ã¯ãƒªã‚¯ã‚¨ã‚¹ãƒˆã™ã‚‹ã“ã¨ãŒã§ããªã„ã‹å¦ã‹ * çœç•¥ã—ãŸå ´åˆã¯ false ã¨ã—ã¦è§£é‡ˆã•れã¾ã™ã€‚ */ - secure?: boolean; + readonly secure?: boolean; /** * エンドãƒã‚¤ãƒ³ãƒˆã®ç¨®é¡ž * パーミッションã®å®Ÿç¾ã«åˆ©ç”¨ã•れã¾ã™ã€‚ */ - kind?: string; + readonly kind?: string; } export interface IEndpoint { diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 774506bf0d..ed7b146d03 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -46,70 +46,76 @@ export const meta = { ]), default: 'combined', }, + + forwarded: { + validator: $.optional.bool, + default: false, + }, }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'date-time', }, comment: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, resolved: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, example: false, }, reporterId: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, targetUserId: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, assigneeId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'id', }, reporter: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, ref: 'User', }, targetUser: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, ref: 'User', }, assignee: { - type: 'object' as const, - nullable: true as const, optional: true as const, + type: 'object', + nullable: true, optional: true, ref: 'User', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index b1cdb29a56..20f1232959 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -16,18 +16,19 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, _me) => { const me = _me ? await Users.findOneOrFail(_me.id) : null; const noUsers = (await Users.count({ diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 3881e02bba..1701c1e3a7 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,8 +17,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index e41d015646..00ad2012fe 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,8 +32,9 @@ export const meta = { validator: $.str.min(1), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { await Ads.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index ab5cd96d0f..c0124e2484 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -23,8 +23,9 @@ export const meta = { id: 'ccac9863-3a03-416e-b899-8a64041118b1', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ad = await Ads.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index f22dd0e961..7a83637f3b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -24,8 +24,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId) .andWhere('ad.expiresAt > :now', { now: new Date() }); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 1d91cfd162..c2b09ab9cf 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -44,8 +44,9 @@ export const meta = { id: 'b7aa1727-1354-47bc-a182-3a9c3973d300', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ad = await Ads.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 886cacff73..24c4caa37d 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -22,50 +22,51 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { - const announcement = await Announcements.save({ + const announcement = await Announcements.insert({ id: genId(), createdAt: new Date(), updatedAt: null, title: ps.title, text: ps.text, imageUrl: ps.imageUrl, - }); + }).then(x => Announcements.findOneOrFail(x.identifiers[0])); return announcement; }); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index ade9ef581e..5548f99006 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -23,8 +23,9 @@ export const meta = { id: 'ecad8040-a276-4e85-bda9-015a708d291e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const announcement = await Announcements.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index f25b5e8efc..e5cc53ccdd 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -26,49 +26,50 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, reads: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); 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 3fa8440ebd..f66293bb18 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,8 +32,9 @@ export const meta = { id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const announcement = await Announcements.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 50979d53e8..249e63a0f8 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ userId: ps.userId, diff --git a/packages/backend/src/server/api/endpoints/admin/delete-logs.ts b/packages/backend/src/server/api/endpoints/admin/delete-logs.ts deleted file mode 100644 index 9d37ceb434..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/delete-logs.ts +++ /dev/null @@ -1,13 +0,0 @@ -import define from '../../define'; -import { Logs } from '@/models/index'; - -export const meta = { - tags: ['admin'], - - requireCredential: true as const, - requireModerator: true, -}; - -export default define(meta, async (ps) => { - await Logs.clear(); // TRUNCATE -}); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index 76a6acff59..acabbfef5c 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -4,10 +4,11 @@ import { createCleanRemoteFilesJob } from '@/queue/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { createCleanRemoteFilesJob(); }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index ae0e396b4f..452e7069a8 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -6,10 +6,11 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ userId: IsNull(), diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index ee07245db7..264f549867 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: false as const, + requireCredential: false, requireModerator: true, params: { @@ -44,16 +44,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index ab8e3d68e9..5d9a1f2703 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -7,7 +7,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -29,138 +29,139 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, userId: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'id', example: 'xxxxxxxxxx', }, userHost: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, md5: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'md5', example: '15eca7fba0480996e2245f5185bf39f2', }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'lenna.jpg', }, type: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'image/jpeg', }, size: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 51469, }, comment: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, blurhash: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, properties: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { width: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 1280, }, height: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 720, }, avgColor: { - type: 'string' as const, - optional: true as const, nullable: false as const, + type: 'string', + optional: true, nullable: false, example: 'rgb(40,65,87)', }, }, }, storedInternal: { - type: 'boolean' as const, - optional: false as const, nullable: true as const, + type: 'boolean', + optional: false, nullable: true, example: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, thumbnailUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, webpublicUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, accessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, thumbnailAccessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, webpublicAccessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, uri: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, src: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, folderId: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'id', example: 'xxxxxxxxxx', }, isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isLink: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const file = ps.fileId ? await DriveFiles.findOne(ps.fileId) : await DriveFiles.findOne({ where: [{ diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts new file mode 100644 index 0000000000..f0fd73c276 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: [...new Set(emoji.aliases.concat(ps.aliases))], + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); 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 407d9920d9..1dfeae262f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -12,7 +12,7 @@ import { publishBroadcastStream } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -28,8 +28,9 @@ export const meta = { id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const file = await DriveFiles.findOne(ps.fileId); @@ -37,16 +38,17 @@ export default define(meta, async (ps, me) => { const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; - const emoji = await Emojis.save({ + const emoji = await Emojis.insert({ id: genId(), updatedAt: new Date(), name: name, category: null, host: null, aliases: [], - url: file.url, - type: file.type, - }); + originalUrl: file.url, + publicUrl: file.webpublicUrl ?? file.url, + type: file.webpublicType ?? file.type, + }).then(x => Emojis.findOneOrFail(x.identifiers[0])); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index ae02df65e2..17cbf208aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -6,13 +6,13 @@ import { getConnection } from 'typeorm'; import { ApiError } from '../../../error'; import { DriveFile } from '@/models/entities/drive-file'; import { ID } from '@/misc/cafy-id'; -import uploadFromUrl from '@/services/drive/upload-from-url'; +import { uploadFromUrl } from '@/services/drive/upload-from-url'; import { publishBroadcastStream } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -30,18 +30,19 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const emoji = await Emojis.findOne(ps.emojiId); @@ -53,7 +54,7 @@ export default define(meta, async (ps, me) => { try { // Create file - driveFile = await uploadFromUrl(emoji.url, null, null, null, false, true); + driveFile = await uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); } catch (e) { throw new ApiError(); } @@ -64,9 +65,9 @@ export default define(meta, async (ps, me) => { name: emoji.name, host: null, aliases: [], - url: driveFile.url, - type: driveFile.type, - fileId: driveFile.id, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, }).then(x => Emojis.findOneOrFail(x.identifiers[0])); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts new file mode 100644 index 0000000000..797a5de672 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { insertModerationLog } from '@/services/insert-moderation-log'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, me) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.delete(emoji.id); + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + } +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 78085470a9..1580439024 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -25,8 +25,9 @@ export const meta = { id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const emoji = await Emojis.findOne(ps.id); @@ -36,7 +37,7 @@ export default define(meta, async (ps, me) => { await getConnection().queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'removeEmoji', { + insertModerationLog(me, 'deleteEmoji', { emoji: emoji, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts new file mode 100644 index 0000000000..8856a38f24 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -0,0 +1,21 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { createImportCustomEmojisJob } from '@/queue/index'; +import ms from 'ms'; +import { ID } from '@/misc/cafy-id'; + +export const meta = { + secure: true, + requireCredential: true, + requireModerator: true, + params: { + fileId: { + validator: $.type(ID), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, user) => { + createImportCustomEmojisJob(user, ps.fileId); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 090ba2b64a..6e502547f5 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -8,7 +8,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -37,46 +37,47 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 89cb60acaf..76ef190f94 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -8,7 +8,7 @@ import { Emoji } from '@/models/entities/emoji'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,46 +32,47 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) .andWhere(`emoji.host IS NULL`); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts new file mode 100644 index 0000000000..c49f84b7fb --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts new file mode 100644 index 0000000000..06197820f0 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + aliases: ps.aliases, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts new file mode 100644 index 0000000000..f0645f111b --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + category: { + validator: $.optional.nullable.str, + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + category: ps.category, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); 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 c61d8cb320..54a2cf9517 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -36,8 +36,9 @@ export const meta = { id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const emoji = await Emojis.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index d9e003d35d..db023c6f0b 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -6,7 +6,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ userHost: ps.host, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 9e7a8ec476..b68252ef2e 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -7,7 +7,7 @@ import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await Instances.findOne({ host: toPuny(ps.host) }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 8fc964fd2f..4de8ad1336 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -6,7 +6,7 @@ import { Followings, Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const followings = await Followings.find({ followerHost: ps.host, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 4ea1275f7c..6ac2f1f467 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -6,7 +6,7 @@ import { toPuny } from '@/misc/convert-host'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -18,8 +18,9 @@ export const meta = { validator: $.bool, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await Instances.findOne({ host: toPuny(ps.host) }); diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index f2b06d0ef2..9a2bccec77 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -2,15 +2,16 @@ import define from '../../define'; import { getConnection } from 'typeorm'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin'], params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const stats = await getConnection().query(`SELECT * FROM pg_indexes;`) diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index 64f0292943..1c5f250676 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -2,7 +2,7 @@ import define from '../../define'; import { getConnection } from 'typeorm'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin'], @@ -11,8 +11,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, example: { migrations: { count: 66, @@ -20,8 +20,9 @@ export const meta = { }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const sizes = await getConnection().query(` diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index b9452c83d2..3428709c04 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -6,26 +6,27 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { code: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: '2ERUA5VR', maxLength: 8, minLength: 8, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const code = rndstr({ length: 8, diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 8e3419bf66..0308cf2761 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 5a5a91e9ce..bdb976e9ec 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 245780c2d1..f2735ac9f8 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -8,7 +8,7 @@ import { PromoNotes } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -34,8 +34,9 @@ export const meta = { id: 'ae427aa2-7a41-484f-a18c-2c1104051604', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index 3a7ae6b4b0..3c8e7a27a2 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -5,12 +5,13 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { destroy(); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index cb2d1d0d79..4760e2c310 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -5,25 +5,25 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { anyOf: [ { - type: 'string' as const, + type: 'string', }, { - type: 'number' as const, + type: 'number', }, ], }, @@ -33,8 +33,9 @@ export const meta = { 12, ]], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const jobs = await deliverQueue.getJobs(['delayed']); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index d524002faf..a95aabc506 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -5,25 +5,25 @@ import { inboxQueue } from '@/queue/queues'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { anyOf: [ { - type: 'string' as const, + type: 'string', }, { - type: 'number' as const, + type: 'number', }, ], }, @@ -33,8 +33,9 @@ export const meta = { 12, ]], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const jobs = await inboxQueue.getJobs(['delayed']); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts index ece1d46f3f..df0b4a8f13 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts @@ -5,7 +5,7 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -24,38 +24,39 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, data: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, attempts: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, maxAttempts: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, timestamp: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const queue = ps.domain === 'deliver' ? deliverQueue : diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 1fc42ea0b5..dab0be5dbc 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -4,31 +4,36 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { deliver: { + optional: false, nullable: false, ref: 'QueueCount', }, inbox: { + optional: false, nullable: false, ref: 'QueueCount', }, db: { + optional: false, nullable: false, ref: 'QueueCount', }, objectStorage: { + optional: false, nullable: false, ref: 'QueueCount', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const deliverJobCounts = await deliverQueue.getJobCounts(); const inboxJobCounts = await inboxQueue.getJobCounts(); 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 80609aee94..65890a00f7 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -7,8 +7,8 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { inbox: { @@ -25,22 +25,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, inbox: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, status: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'requesting', enum: [ 'requesting', @@ -50,8 +50,9 @@ export const meta = { }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { try { if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts index ef103fbb31..bdddf13374 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -4,32 +4,32 @@ import { listRelay } from '@/services/relay'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, inbox: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, status: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'requesting', enum: [ 'requesting', @@ -40,8 +40,9 @@ export const meta = { }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { return await listRelay(); }); diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts index e7ddef30fc..4b04e620c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -5,16 +5,17 @@ import { removeRelay } from '@/services/relay'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { inbox: { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { return await removeRelay(ps.inbox); }); diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index bdd7ef27b8..b6cf1ee2d0 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -8,7 +8,7 @@ import { Users, UserProfiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -18,19 +18,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { password: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, minLength: 8, maxLength: 8, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 94158ecfdb..b00457f092 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,21 +1,32 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; -import { AbuseUserReports } from '@/models/index'; +import { AbuseUserReports, Users } from '@/models/index'; +import { getInstanceActor } from '@/services/instance-actor'; +import { deliver } from '@/queue/index'; +import { renderActivity } from '@/remote/activitypub/renderer/index'; +import { renderFlag } from '@/remote/activitypub/renderer/flag'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { reportId: { validator: $.type(ID), }, + + forward: { + validator: $.optional.boolean, + required: false, + default: false, + }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const report = await AbuseUserReports.findOne(ps.reportId); @@ -23,8 +34,16 @@ export default define(meta, async (ps, me) => { throw new Error('report not found'); } + if (ps.forward && report.targetUserHost != null) { + const actor = await getInstanceActor(); + const targetUser = await Users.findOne(report.targetUserId); + + deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri], report.comment)), targetUser.inbox); + } + await AbuseUserReports.update(report.id, { resolved: true, assigneeId: me.id, + forwarded: ps.forward && report.targetUserHost != null, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts index e01dfce1b6..d80d2b0426 100644 --- a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts +++ b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts @@ -5,10 +5,11 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { insertModerationLog(me, 'chartResync'); diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts index 4797aa7a2b..c2972c35fa 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -5,7 +5,7 @@ import { sendEmail } from '@/services/send-email'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -19,8 +19,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { await sendEmail(ps.to, ps.subject, ps.text, ps.text); }); diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 4ddc39deb0..cd282e364c 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -5,7 +5,7 @@ import define from '../../define'; import { redisClient } from '../../../../db/redis'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin', 'meta'], @@ -14,82 +14,83 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { machine: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, os: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'linux', }, node: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, psql: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, cpu: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { model: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, cores: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, mem: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { total: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, }, }, fs: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { total: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, used: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, }, }, net: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { interface: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'eth0', }, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 19d4be973f..84e2b84bb5 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -26,45 +26,46 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, type: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, info: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(ModerationLogs.createQueryBuilder('report'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 21cc9e5abc..c2a6a294b5 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -16,149 +16,150 @@ export const meta = { }, res: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, properties: { id: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, createdAt: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'date-time', }, lastFetchedAt: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, username: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, name: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, folowersCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: true, }, followingCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: false, }, notesCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: false, }, avatarId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, bannerId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, tags: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, }, avatarUrl: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'url', }, bannerUrl: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'url', }, avatarBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, + type: 'any', + nullable: true, optional: false, default: null, }, bannerBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, + type: 'any', + nullable: true, optional: false, default: null, }, isSuspended: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isSilenced: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isLocked: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isBot: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isCat: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isAdmin: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isModerator: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, emojis: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, }, host: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, inbox: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, sharedInbox: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, featured: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, uri: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, token: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: true, optional: false, default: '<MASKED>', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 9c57917cb2..d3dde99b72 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -5,7 +5,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -64,16 +64,17 @@ export const meta = { }, res: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - ref: 'User', + type: 'object', + nullable: false, optional: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user'); diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 9f5135b1a6..872bd2a6ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -7,7 +7,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index c062dcc93f..2bb1875fc0 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -11,7 +11,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -19,8 +19,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index bc081c8487..a4c6ff2ade 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -7,7 +7,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 73a5bc739b..5ab56d51c7 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -8,7 +8,7 @@ import { doPostUnsuspend } from '@/services/unsuspend-user'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -16,8 +16,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index e67088e0aa..aa2d1222f7 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -297,8 +297,9 @@ export const meta = { validator: $.optional.bool, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const set = {} as Partial<Meta>; diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts index d08dfdd9ea..4229ef0d29 100644 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts @@ -6,7 +6,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,8 +17,9 @@ export const meta = { validator: $.bool, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const params: string[] = []; diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 32ef49d889..0bd29607d6 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../common/make-pagination-query'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -30,49 +30,50 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, isRead: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 83c7d3e0a5..2092d177ba 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -9,7 +9,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -74,12 +74,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let userList; let userGroupJoining; diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 721932f311..b2793fc70d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -8,7 +8,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: 'b34dcf9d-348f-44bb-99d0-6c9314cfe2df', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const antenna = await Antennas.findOne({ id: ps.antennaId, diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index 4a9eed8563..bb58912612 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -4,21 +4,22 @@ import { Antennas } from '@/models/index'; export const meta = { tags: ['antennas', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const antennas = await Antennas.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 968924831c..eb7de901c5 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -12,7 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['antennas', 'account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -52,16 +52,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const antenna = await Antennas.findOne({ id: ps.antennaId, diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 79e988497d..a37d37d31c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -7,7 +7,7 @@ import { Antennas } from '@/models/index'; export const meta = { tags: ['antennas', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -26,12 +26,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the antenna const antenna = await Antennas.findOne({ diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index d5400a7926..900f725505 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -8,7 +8,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -83,12 +83,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the antenna const antenna = await Antennas.findOne({ diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 000ed2d2df..ff8c677b91 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -7,7 +7,7 @@ import ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -24,11 +24,12 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const resolver = new Resolver(); const object = await resolver.resolve(ps.uri); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 709349262a..7d17d8edce 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -12,11 +12,12 @@ import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; import ms from 'ms'; +import { SchemaType } from '@/misc/schema'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -38,22 +39,43 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['User', 'Note'], - }, - object: { - type: 'object' as const, - optional: false as const, nullable: false as const, + optional: false, nullable: false, + oneOf: [ + { + type: 'object', + properties: { + type: { + type: 'string', + optional: false, nullable: false, + enum: ['User'], + }, + object: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', + } + } }, - }, + { + type: 'object', + properties: { + type: { + type: 'string', + optional: false, nullable: false, + enum: ['Note'], + }, + object: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + } + } + } + ], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const object = await fetchAny(ps.uri); if (object) { @@ -66,7 +88,7 @@ export default define(meta, async (ps) => { /*** * URIã‹ã‚‰Userã‹Noteを解決ã™ã‚‹ */ -async function fetchAny(uri: string) { +async function fetchAny(uri: string): Promise<SchemaType<typeof meta['res']> | null> { // URIãŒã“ã®ã‚µãƒ¼ãƒãƒ¼ã‚’指ã—ã¦ã„ã‚‹ãªã‚‰ã€ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼IDã¨ã—ã¦DBã‹ã‚‰ãƒ•ェッムif (uri.startsWith(config.url + '/')) { const parts = uri.split('/'); @@ -95,8 +117,8 @@ async function fetchAny(uri: string) { } // ブãƒãƒƒã‚¯ã—ã¦ãŸã‚‰ä¸æ– - const meta = await fetchMeta(); - if (meta.blockedHosts.includes(extractDbHost(uri))) return null; + const fetchedMeta = await fetchMeta(); + if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null; // URI(AP Object id)ã¨ã—ã¦DB検索 { @@ -171,7 +193,7 @@ async function fetchAny(uri: string) { return null; } -async function mergePack(user: User | null | undefined, note: Note | null | undefined) { +async function mergePack(user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index 78586cbcf2..fbe6690f1d 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -8,7 +8,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['app'], - requireCredential: false as const, + requireCredential: false, params: { name: { @@ -31,12 +31,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Generate secret const secret = secureRndstr(32, true); @@ -45,7 +46,7 @@ export default define(meta, async (ps, user) => { const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); // Create account - const app = await Apps.save({ + const app = await Apps.insert({ id: genId(), createdAt: new Date(), userId: user ? user.id : null, @@ -54,7 +55,7 @@ export default define(meta, async (ps, user) => { permission, callbackUrl: ps.callbackUrl, secret: secret, - }); + }).then(x => Apps.findOneOrFail(x.identifiers[0])); return await Apps.pack(app, null, { detail: true, diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index c83d576b43..9f4777b383 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -13,12 +13,6 @@ export const meta = { }, }, - res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'App', - }, - errors: { noSuchApp: { message: 'No such app.', @@ -28,12 +22,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { const isSecure = user != null && token == null; diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 989ebf8f25..f028135ca5 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -9,7 +9,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['auth'], - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: '9c72d8de-391a-43c1-9d06-08d29efde8df', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch token const session = await AuthSessions diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 39ba5a972a..98987eba5b 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { appSecret: { @@ -18,16 +18,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, @@ -40,8 +40,9 @@ export const meta = { id: '92f93e63-428e-4f2f-a5a4-39e1407fe998', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { // Lookup app const app = await Apps.findOne({ @@ -56,12 +57,12 @@ export default define(meta, async (ps) => { const token = uuid(); // Create session token document - const doc = await AuthSessions.save({ + const doc = await AuthSessions.insert({ id: genId(), createdAt: new Date(), appId: app.id, token: token, - }); + }).then(x => AuthSessions.findOneOrFail(x.identifiers[0])); return { token: doc.token, diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index e890dd95a3..ae0d016cea 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -6,7 +6,7 @@ import { AuthSessions } from '@/models/index'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { token: { @@ -23,27 +23,28 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, app: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Lookup session const session = await AuthSessions.findOne({ diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 241b13d4da..fe0211ebe3 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -6,7 +6,7 @@ import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { appSecret: { @@ -19,18 +19,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { accessToken: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, }, }, @@ -54,8 +54,9 @@ export const meta = { id: '8c8a4145-02cc-4cca-8e66-29ba60445a8e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { // Lookup app const app = await Apps.findOne({ diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index cb92589d19..6d555ff569 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:blocks', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const blocker = await Users.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 7a99fe0f49..942cddaedf 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:blocks', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const blocker = await Users.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 901403d144..9a4f662140 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:blocks', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Blocking', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 0176f86e00..68cdf1143e 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -28,8 +28,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -40,8 +40,9 @@ export const meta = { id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let banner = null; if (ps.bannerId != null) { @@ -55,14 +56,14 @@ export default define(meta, async (ps, user) => { } } - const channel = await Channels.save({ + const channel = await Channels.insert({ id: genId(), createdAt: new Date(), userId: user.id, name: ps.name, description: ps.description || null, bannerId: banner ? banner.id : null, - } as Channel); + } as Channel).then(x => Channels.findOneOrFail(x.identifiers[0])); return await Channels.pack(channel, user); }); diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 8eab2402be..ceadde907c 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -4,19 +4,20 @@ import { Channels } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Channels.createQueryBuilder('channel') .where('channel.lastNotedAt IS NOT NULL') diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index d30593acd9..bf580eea60 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -9,7 +9,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -26,8 +26,9 @@ export const meta = { id: 'c0031718-d573-4e85-928e-10039f1fbb68', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index 28454c97fd..9e4c942af2 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['channels', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:channels', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(ChannelFollowings.createQueryBuilder(), ps.sinceId, ps.untilId) .andWhere({ followerId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 44024b158e..5473636a85 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['channels', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:channels', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId) .andWhere({ userId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index e7ce4f22eb..598a87ec4e 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -7,7 +7,7 @@ import { Channels } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: false as const, + requireCredential: false, params: { channelId: { @@ -16,8 +16,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -28,8 +28,9 @@ export const meta = { id: '6f6c314b-7486-4897-8966-c04a66a02923', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 56c205cae5..927ce7c741 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -9,7 +9,7 @@ import { activeUsersChart } from '@/services/chart/index'; export const meta = { tags: ['notes', 'channels'], - requireCredential: false as const, + requireCredential: false, params: { channelId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -55,8 +55,9 @@ export const meta = { id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 8ce9a7e644..ada0cb29fd 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -8,7 +8,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -25,8 +25,9 @@ export const meta = { id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 0e6863ac76..1f7108a1cb 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -7,7 +7,7 @@ import { Channels, DriveFiles } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -30,8 +30,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -54,8 +54,9 @@ export const meta = { id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts index c4878f7d61..f7eadc7089 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(activeUsersChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await activeUsersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts index 07bff82cf4..364279da95 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(driveChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await driveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts index 9575f9a7b7..6feb82b6d9 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(federationChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await federationChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/hashtag.ts b/packages/backend/src/server/api/endpoints/charts/hashtag.ts index 53dc61e51e..99dc77998e 100644 --- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts +++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts @@ -27,8 +27,9 @@ export const meta = { }, res: convertLog(hashtagChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.tag); }); diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts index 4e96304d8c..23e6fbf2b0 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -27,8 +27,9 @@ export const meta = { }, res: convertLog(instanceChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await instanceChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.host); }); diff --git a/packages/backend/src/server/api/endpoints/charts/network.ts b/packages/backend/src/server/api/endpoints/charts/network.ts index b524df93be..c5a39bbd76 100644 --- a/packages/backend/src/server/api/endpoints/charts/network.ts +++ b/packages/backend/src/server/api/endpoints/charts/network.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(networkChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await networkChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts index 676f302939..dcbd80c3e9 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(notesChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await notesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts index 4fc12b3306..94787b4a57 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserDriveChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index 9207760a3c..effe0c54b9 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserFollowingChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts index f543920572..df68a5fe52 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserNotesChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts index 4ceb79f7f8..dcd067305f 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserReactionsChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts index deac89b59d..d32e14ad61 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(usersChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await usersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 99312a71b9..4a740b6cfe 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -9,7 +9,7 @@ import { getNote } from '../../common/getters'; export const meta = { tags: ['account', 'notes', 'clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -42,8 +42,9 @@ export const meta = { id: '734806c4-542c-463a-9311-15c512803965', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.findOne({ id: ps.clipId, diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index cb4ff56abd..852e66c9e4 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -6,7 +6,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,12 +25,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index 9ec6bc7eac..85c64a115d 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -24,8 +24,9 @@ export const meta = { id: '70ca08ba-6865-4630-b6fb-8494759aa754', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.findOne({ id: ps.clipId, diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 3b32c02899..d88897d164 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -4,21 +4,22 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const clips = await Clips.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 90ddd66a1f..eeb20631c1 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -11,7 +11,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['account', 'notes', 'clips'], - requireCredential: false as const, + requireCredential: false, kind: 'read:account', @@ -43,16 +43,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.findOne({ id: ps.clipId, diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 8e9409fadd..0a45672019 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips', 'account'], - requireCredential: false as const, + requireCredential: false, kind: 'read:account', @@ -26,12 +26,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the clip const clip = await Clips.findOne({ diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 9cf12499af..795483d5b2 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -38,12 +38,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the clip const clip = await Clips.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index b9741ba875..d9ab9883ca 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -5,26 +5,27 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { capacity: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, usage: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const instance = await fetchMeta(true); diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 00ebb51e30..a5c0a626a1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -36,16 +36,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) .andWhere('file.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index c8317c1cc8..835dde8058 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -7,7 +7,7 @@ import { DriveFiles, Notes } from '@/models/index'; export const meta = { tags: ['drive', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -18,11 +18,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -34,8 +34,9 @@ export const meta = { id: 'c118ece3-2e4b-4296-99d1-51756e32d232', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch file const file = await DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index a6db160d2f..a45d357ee8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -5,7 +5,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -16,11 +16,12 @@ export const meta = { }, res: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne({ md5: ps.md5, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 5523ae1967..dd65ab0611 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,16 +1,17 @@ import ms from 'ms'; import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import create from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import define from '../../../define'; import { apiLogger } from '../../../logger'; import { ApiError } from '../../../error'; import { DriveFiles } from '@/models/index'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -32,6 +33,11 @@ export const meta = { default: null, }, + comment: { + validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH), + default: null, + }, + isSensitive: { validator: $.optional.either($.bool, $.str), default: false, @@ -46,8 +52,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, @@ -58,8 +64,9 @@ export const meta = { id: 'f449b209-0c60-4e51-84d5-29486263bfd4', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, _, file, cleanup) => { // Get 'name' parameter let name = ps.name || file.originalname; @@ -78,7 +85,7 @@ export default define(meta, async (ps, user, _, file, cleanup) => { try { // Create file - const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); + const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive }); return await DriveFiles.pack(driveFile, { self: true }); } catch (e) { apiLogger.error(e); diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 3a8e4b11f4..308beb58a4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -9,7 +9,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -32,8 +32,9 @@ export const meta = { id: '5eb8d909-2540-4970-90b8-dd6f86088121', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 16717149ac..dc74dcb7e6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -5,7 +5,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -16,16 +16,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = await DriveFiles.find({ md5: ps.md5, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 108e08593a..2244df13cd 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { DriveFiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, tags: ['drive'], @@ -22,16 +22,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = await DriveFiles.find({ name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 96f8e5c03a..18b17c4653 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -23,8 +23,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, @@ -47,8 +47,9 @@ export const meta = { id: '89674805-722c-440c-8d88-5641830dc3e4', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let file: DriveFile | undefined; diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 04b2db9cf4..b7ca80e83c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -9,7 +9,7 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -60,12 +60,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 8a2fbc36b5..40da1a4fb4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import ms from 'ms'; -import uploadFromUrl from '@/services/drive/upload-from-url'; +import { uploadFromUrl } from '@/services/drive/upload-from-url'; import define from '../../../define'; import { DriveFiles } from '@/models/index'; import { publishMainStream } from '@/services/stream'; @@ -15,7 +15,7 @@ export const meta = { max: 60, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -50,10 +50,11 @@ export const meta = { default: false, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { - uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force, false, ps.comment).then(file => { + uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { DriveFiles.pack(file, { self: true }).then(packedFile => { publishMainStream(user.id, 'urlUploadFinished', { marker: ps.marker, diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index cd2d1743c8..8f8d1d2c0a 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -32,16 +32,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) .andWhere('folder.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 9ae59d4b49..38ed17e0e5 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -37,8 +37,9 @@ export const meta = { optional: false as const, nullable: false as const, ref: 'DriveFolder', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // If the parent folder is specified let parent = null; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index bfd3361e76..13716fccea 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -8,7 +8,7 @@ import { DriveFolders, DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -31,8 +31,9 @@ export const meta = { id: 'b0fc8a17-963c-405d-bfbc-859a487295e1', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get folder const folder = await DriveFolders.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 872eabef98..911f51d78b 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -6,7 +6,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -22,16 +22,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const folders = await DriveFolders.find({ name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 63b5bc9e77..58a6dd3c06 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -7,7 +7,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, @@ -30,8 +30,9 @@ export const meta = { id: 'd74ab9eb-bb09-4bba-bf24-fb58f761e1e9', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get folder const folder = await DriveFolders.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index e547808866..5b0cccd1c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -8,7 +8,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -47,12 +47,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch folder const folder = await DriveFolders.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index e3031f75b2..9ba7804946 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -31,16 +31,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) .andWhere('file.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts index f2254e1481..19f9b7ccdc 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -5,7 +5,7 @@ import { validateEmailForAccount } from '@/services/validate-email-for-account'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { emailAddress: { @@ -14,21 +14,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { available: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, reason: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await validateEmailForAccount(ps.emailAddress); }); diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index eacee689dc..42fd468838 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -3,7 +3,7 @@ import define from '../define'; import endpoints from '../endpoints'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -12,8 +12,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const ep = endpoints.find(x => x.name === ps.endpoint); if (ep == null) return null; diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index 6d4588383d..ebb78de337 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -2,7 +2,7 @@ import define from '../define'; import endpoints from '../endpoints'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -10,11 +10,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, example: [ 'admin/abuse-user-reports', @@ -23,8 +23,9 @@ export const meta = { '...', ], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { return endpoints.map(x => x.name); }); diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 92738c8288..24c9f56aa6 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -5,13 +5,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportCustomEmojisJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/federation/dns.ts b/packages/backend/src/server/api/endpoints/federation/dns.ts deleted file mode 100644 index 19e3ef8055..0000000000 --- a/packages/backend/src/server/api/endpoints/federation/dns.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { promises as dns } from 'dns'; -import $ from 'cafy'; -import define from '../../define'; -import { Instances } from '@/models/index'; -import { toPuny } from '@/misc/convert-host'; - -const resolver = new dns.Resolver(); -resolver.setServers(['1.1.1.1']); - -export const meta = { - tags: ['federation'], - - requireCredential: false as const, - - params: { - host: { - validator: $.str, - }, - }, -}; - -export default define(meta, async (ps, me) => { - const instance = await Instances.findOneOrFail({ host: toPuny(ps.host) }); - - const [ - resolved4, - resolved6, - resolvedCname, - resolvedTxt, - ] = await Promise.all([ - resolver.resolve4(instance.host).catch(() => []), - resolver.resolve6(instance.host).catch(() => []), - resolver.resolveCname(instance.host).catch(() => []), - resolver.resolveTxt(instance.host).catch(() => []), - ]); - - return { - a: resolved4, - aaaa: resolved6, - cname: resolvedCname, - txt: resolvedTxt, - }; -}); diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index 9cb4082bbf..c0a85f166c 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,16 +29,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) .andWhere(`following.followeeHost = :host`, { host: ps.host }); diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index 4a42550f93..147f0aedb2 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,16 +29,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) .andWhere(`following.followerHost = :host`, { host: ps.host }); diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 50d9ff3fd7..11df7ed6b6 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -7,7 +7,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -54,16 +54,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'FederationInstance', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Instances.createQueryBuilder('instance'); diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 9d5d9bc9f8..6f13b28cae 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -6,7 +6,7 @@ import { toPuny } from '@/misc/convert-host'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -15,12 +15,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, ref: 'FederationInstance', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await Instances .findOne({ host: toPuny(ps.host) }); diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 2ba09b1361..092f805bc2 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -7,15 +7,16 @@ import { updatePerson } from '@/remote/activitypub/models/person'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, params: { userId: { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await getRemoteUser(ps.userId); await updatePerson(user.uri!); diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 730dbd74c3..9a8f749936 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,16 +29,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Users.createQueryBuilder('user'), ps.sinceId, ps.untilId) .andWhere(`user.host = :host`, { host: ps.host }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 096b1f6055..96aede4550 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -58,12 +58,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const follower = user; diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 5a0e44ad0b..4cd0c49452 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const follower = user; diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 050199bfaa..92e887e00b 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const followee = user; diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index 9c07248568..7e7c056f55 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -8,7 +8,7 @@ import { getUser } from '../../../common/getters'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -30,8 +30,9 @@ export const meta = { id: 'bcde4f8b-0913-4614-8881-614e522fb041', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch follower const follower = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index d65aa436a0..19ed02c152 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -9,7 +9,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -34,12 +34,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch followee const followee = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index 2dadd0d60c..ec0c76502c 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -4,37 +4,38 @@ import { FollowRequests } from '@/models/index'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:following', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, follower: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, followee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const reqs = await FollowRequests.find({ followeeId: user.id, diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index c385b32385..a5ce1e7c77 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -8,7 +8,7 @@ import { getUser } from '../../../common/getters'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -25,8 +25,9 @@ export const meta = { id: 'abc2ffa6-25b2-4380-ba99-321ff3a94555', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch follower const follower = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index dc86cf40ba..ff7c16889f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -4,19 +4,20 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = GalleryPosts.createQueryBuilder('post') .andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) }) diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index ee3fe51ebf..2c3368a19d 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -4,19 +4,20 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = GalleryPosts.createQueryBuilder('post') .andWhere('post.likedCount > 0') diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 90bd2d959f..9d2601c7e9 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -23,16 +23,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) .innerJoinAndSelect('post.user', 'user'); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index dae6e27ddb..e9d5df1ab6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -11,7 +11,7 @@ import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -40,16 +40,17 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index e43c12a4c1..2a13b9ed58 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -24,8 +24,9 @@ export const meta = { id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const post = await GalleryPosts.findOne({ id: ps.postId, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index d355d1e312..0fb408fa5f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -8,7 +8,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery-likes', @@ -37,8 +37,9 @@ export const meta = { id: '40e9ed56-a59c-473a-bf3f-f289c54fb5a7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const post = await GalleryPosts.findOne(ps.postId); if (post == null) { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 7e620b2c48..4325d2ad37 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -7,7 +7,7 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, params: { postId: { @@ -24,12 +24,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const post = await GalleryPosts.findOne({ id: ps.postId, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index 323e7c4828..9cca09bddc 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -7,7 +7,7 @@ import { GalleryPosts, GalleryLikes } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery-likes', @@ -30,8 +30,9 @@ export const meta = { id: 'e3e8e06e-be37-41f7-a5b4-87a8250288f0', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const post = await GalleryPosts.findOne(ps.postId); if (post == null) { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 7cd694e804..c35e1bbf58 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -10,7 +10,7 @@ import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -43,16 +43,17 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games.ts b/packages/backend/src/server/api/endpoints/games/reversi/games.ts deleted file mode 100644 index f77f11942c..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games.ts +++ /dev/null @@ -1,156 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import define from '../../../define'; -import { ReversiGames } from '@/models/index'; -import { makePaginationQuery } from '../../../common/make-pagination-query'; -import { Brackets } from 'typeorm'; - -export const meta = { - tags: ['games'], - - params: { - limit: { - validator: $.optional.num.range(1, 100), - default: 10, - }, - - sinceId: { - validator: $.optional.type(ID), - }, - - untilId: { - validator: $.optional.type(ID), - }, - - my: { - validator: $.optional.bool, - default: false, - }, - }, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User', - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, -}; - -export default define(meta, async (ps, user) => { - const query = makePaginationQuery(ReversiGames.createQueryBuilder('game'), ps.sinceId, ps.untilId) - .andWhere('game.isStarted = TRUE'); - - if (ps.my && user) { - query.andWhere(new Brackets(qb => { qb - .where('game.user1Id = :userId', { userId: user.id }) - .orWhere('game.user2Id = :userId', { userId: user.id }); - })); - } - - // Fetch games - const games = await query.take(ps.limit!).getMany(); - - return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { - detail: false, - }))); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts b/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts deleted file mode 100644 index 0476a4be9b..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts +++ /dev/null @@ -1,168 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import Reversi from '../../../../../../games/reversi/core'; -import define from '../../../../define'; -import { ApiError } from '../../../../error'; -import { ReversiGames } from '@/models/index'; - -export const meta = { - tags: ['games'], - - params: { - gameId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchGame: { - message: 'No such game.', - code: 'NO_SUCH_GAME', - id: 'f13a03db-fae1-46c9-87f3-43c8165419e1', - }, - }, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User', - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - board: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'any' as const, - optional: false as const, nullable: false as const, - }, - }, - turn: { - type: 'any' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, -}; - -export default define(meta, async (ps, user) => { - const game = await ReversiGames.findOne(ps.gameId); - - if (game == null) { - throw new ApiError(meta.errors.noSuchGame); - } - - const o = new Reversi(game.map, { - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - }); - - for (const log of game.logs) { - o.put(log.color, log.pos); - } - - const packed = await ReversiGames.pack(game, user); - - return Object.assign({ - board: o.board, - turn: o.turn, - }, packed); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts b/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts deleted file mode 100644 index 84b80c47a0..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts +++ /dev/null @@ -1,67 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import { publishReversiGameStream } from '@/services/stream'; -import define from '../../../../define'; -import { ApiError } from '../../../../error'; -import { ReversiGames } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - params: { - gameId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchGame: { - message: 'No such game.', - code: 'NO_SUCH_GAME', - id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df', - }, - - alreadyEnded: { - message: 'That game has already ended.', - code: 'ALREADY_ENDED', - id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d', - }, - - accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '6e04164b-a992-4c93-8489-2123069973e1', - }, - }, -}; - -export default define(meta, async (ps, user) => { - const game = await ReversiGames.findOne(ps.gameId); - - if (game == null) { - throw new ApiError(meta.errors.noSuchGame); - } - - if (game.isEnded) { - throw new ApiError(meta.errors.alreadyEnded); - } - - if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } - - const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id; - - await ReversiGames.update(game.id, { - surrendered: user.id, - isEnded: true, - winnerId: winnerId, - }); - - publishReversiGameStream(game.id, 'ended', { - winnerId: winnerId, - game: await ReversiGames.pack(game.id, user), - }); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts b/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts deleted file mode 100644 index b859a2fc75..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts +++ /dev/null @@ -1,58 +0,0 @@ -import define from '../../../define'; -import { ReversiMatchings } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - parent: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - childId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - child: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - }, - }, - }, -}; - -export default define(meta, async (ps, user) => { - // Find session - const invitations = await ReversiMatchings.find({ - childId: user.id, - }); - - return await Promise.all(invitations.map((i) => ReversiMatchings.pack(i, user))); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/match.ts b/packages/backend/src/server/api/endpoints/games/reversi/match.ts deleted file mode 100644 index c66c5a4f75..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/match.ts +++ /dev/null @@ -1,108 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import { publishMainStream, publishReversiStream } from '@/services/stream'; -import { eighteight } from '../../../../../games/reversi/maps'; -import define from '../../../define'; -import { ApiError } from '../../../error'; -import { getUser } from '../../../common/getters'; -import { genId } from '@/misc/gen-id'; -import { ReversiMatchings, ReversiGames } from '@/models/index'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - params: { - userId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '0b4f0559-b484-4e31-9581-3f73cee89b28', - }, - - isYourself: { - message: 'Target user is yourself.', - code: 'TARGET_IS_YOURSELF', - id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e', - }, - }, -}; - -export default define(meta, async (ps, user) => { - // Myself - if (ps.userId === user.id) { - throw new ApiError(meta.errors.isYourself); - } - - // Find session - const exist = await ReversiMatchings.findOne({ - parentId: ps.userId, - childId: user.id, - }); - - if (exist) { - // Destroy session - ReversiMatchings.delete(exist.id); - - // Create game - const game = await ReversiGames.save({ - id: genId(), - createdAt: new Date(), - user1Id: exist.parentId, - user2Id: user.id, - user1Accepted: false, - user2Accepted: false, - isStarted: false, - isEnded: false, - logs: [], - map: eighteight.data, - bw: 'random', - isLlotheo: false, - } as Partial<ReversiGame>); - - publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, { id: exist.parentId })); - - const other = await ReversiMatchings.count({ - childId: user.id, - }); - - if (other == 0) { - publishMainStream(user.id, 'reversiNoInvites'); - } - - return await ReversiGames.pack(game, user); - } else { - // Fetch child - const child = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // 以å‰ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯ã™ã¹ã¦å‰Šé™¤ã—ã¦ãŠã - await ReversiMatchings.delete({ - parentId: user.id, - }); - - // ã‚»ãƒƒã‚·ãƒ§ãƒ³ã‚’ä½œæˆ - const matching = await ReversiMatchings.save({ - id: genId(), - createdAt: new Date(), - parentId: user.id, - childId: child.id, - } as ReversiMatching); - - const packed = await ReversiMatchings.pack(matching, child); - publishReversiStream(child.id, 'invited', packed); - publishMainStream(child.id, 'reversiInvited', packed); - - return; - } -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts b/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts deleted file mode 100644 index 8076b7c5d8..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts +++ /dev/null @@ -1,14 +0,0 @@ -import define from '../../../../define'; -import { ReversiMatchings } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, -}; - -export default define(meta, async (ps, user) => { - await ReversiMatchings.delete({ - parentId: user.id, - }); -}); diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 0616431abc..5b13d5a3b8 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -6,12 +6,13 @@ import define from '../define'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const count = await Users.count({ lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 5f8bfee2df..9fa9b3edc6 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -5,7 +5,7 @@ import { Hashtags } from '@/models/index'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -47,16 +47,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Hashtag', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Hashtags.createQueryBuilder('tag'); diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index d7de1db557..0d646c64f5 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -5,7 +5,7 @@ import { Hashtags } from '@/models/index'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -24,15 +24,16 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const hashtags = await Hashtags.createQueryBuilder('tag') .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 9410aea389..242cef99d4 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -7,7 +7,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { tag: { @@ -16,8 +16,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Hashtag', }, @@ -28,8 +28,9 @@ export const meta = { id: '110ee688-193e-4a3a-9ecf-c167b2e6981e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const hashtag = await Hashtags.findOne({ name: normalizeForSearch(ps.tag) }); if (hashtag == null) { diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index deb8417ad6..be964ad639 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -23,36 +23,37 @@ const max = 5; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { tag: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, chart: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const instance = await fetchMeta(true); const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 69b17a19eb..2158dc4349 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -4,7 +4,7 @@ import { Users } from '@/models/index'; import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['hashtags', 'users'], @@ -48,16 +48,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user') .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 2063a55a81..d69c118cfc 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -4,22 +4,23 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { const isSecure = token == null; // ã“ã“ã§æ¸¡ã£ã¦ãã¦ã„ã‚‹ user ã¯ã‚ャッシュã•れã¦ã„ã¦å¤ã„å¯èƒ½æ€§ã‚‚ã‚ã‚‹ã®ã§ id ã ã‘æ¸¡ã™ - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 3b772386f3..4853908693 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const token = ps.token.replace(/\s/g, ''); 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 f0045fb997..26e9a60886 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 @@ -16,7 +16,7 @@ import { publishMainStream } from '@/services/stream'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -37,10 +37,11 @@ export const meta = { validator: $.str, }, }, -}; +} as const; const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); @@ -129,7 +130,7 @@ export default define(meta, async (ps, user) => { const credentialIdString = credentialId.toString('hex'); - await UserSecurityKeys.save({ + await UserSecurityKeys.insert({ userId: user.id, id: credentialIdString, lastUsed: new Date(), diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index dc2b66286b..854848a434 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -12,8 +12,9 @@ export const meta = { validator: $.boolean, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await UserProfiles.update(user.id, { usePasswordLessLogin: ps.value, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index aa6c8fb1d5..057e54c69b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -10,7 +10,7 @@ import { hash } from '../../../2fa'; const randomBytes = promisify(crypto.randomBytes); export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -19,8 +19,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); @@ -44,7 +45,7 @@ export default define(meta, async (ps, user) => { const challengeId = genId(); - await AttestationChallenges.save({ + await AttestationChallenges.insert({ userId: user.id, id: challengeId, challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 347dec0f43..c5cfb9dfad 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -7,7 +7,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -16,8 +16,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 05d63452f1..03e1d0434d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -5,7 +5,7 @@ import { UserProfiles, UserSecurityKeys, Users } from '@/models/index'; import { publishMainStream } from '@/services/stream'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,8 +17,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index a0d8b5906b..a19ad6810d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 64986865b2..63999b0981 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -3,7 +3,7 @@ import define from '../../define'; import { AccessTokens } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,8 +17,9 @@ export const meta = { ]), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = AccessTokens.createQueryBuilder('token') .where('token.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index bfe20eb984..52122b851b 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -3,7 +3,7 @@ import define from '../../define'; import { AccessTokens, Apps } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,8 +23,9 @@ export const meta = { default: 'desc', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get tokens const tokens = await AccessTokens.find({ diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 416eb6229f..7b6c137737 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -4,7 +4,7 @@ import define from '../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,8 +17,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 13a8f79dfa..e1eee949fc 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -7,7 +7,7 @@ import { publishUserEvent } from '@/services/stream'; import { createDeleteAccountJob } from '@/queue'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -16,8 +16,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); const userDetailed = await Users.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index e276ecf384..44d8a1cb38 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportBlockingJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index 15c09941e8..5d1617d57b 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -5,7 +5,7 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -20,8 +20,9 @@ export const meta = { default: false, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index b176c7ee8d..27ce8f0b29 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportMuteJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index 8cba04552e..25b1849e80 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1day'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportNotesJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index 44d43c0bea..d28b699c5a 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1min'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportUserListsJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 49b0bcd46c..92c767876b 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'read:favorites', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteFavorite', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) .andWhere(`favorite.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index 3ee7174f71..f1c5763593 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['account', 'gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'read:gallery-likes', @@ -27,23 +27,24 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, page: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) .andWhere(`like.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index c8aceb8bf3..d46d42f633 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['account', 'gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'read:gallery', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) .andWhere(`post.userId = :meId`, { meId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index 3eddc2746c..4e1a4d3db9 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -4,7 +4,7 @@ import { MutedNotes } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -12,17 +12,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { count: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { return { count: await MutedNotes.count({ diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index f0e3106c53..acc5797420 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,8 +46,9 @@ export const meta = { id: '6f3a4dcc-f060-a707-4950-806fbdbe60d6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 61e500599f..35006746fb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -45,8 +45,9 @@ export const meta = { id: '31a1b42c-06f7-42ae-8a38-a661c5c9f691', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index da26617d91..7bbb2e008e 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,8 +46,9 @@ export const meta = { id: 'd2f12af1-e7b4-feac-86a3-519548f2728e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 1b850d314f..759d41b6cd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -45,8 +45,9 @@ export const meta = { id: '99efe367-ce6e-4d44-93f8-5fae7b040356', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 9083aefacd..59efd32bb2 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -12,7 +12,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['account', 'notifications'], - requireCredential: true as const, + requireCredential: true, kind: 'read:notifications', @@ -55,16 +55,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Notification', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // includeTypes ãŒç©ºã®å ´åˆã¯ã‚¯ã‚¨ãƒªã—ãªã„ if (ps.includeTypes && ps.includeTypes.length === 0) { diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 92fc294850..59239c7446 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'pages'], - requireCredential: true as const, + requireCredential: true, kind: 'read:page-likes', @@ -27,23 +27,24 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, page: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) .andWhere(`like.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 5948712c30..bef775d063 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'pages'], - requireCredential: true as const, + requireCredential: true, kind: 'read:pages', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) .andWhere(`page.userId = :meId`, { meId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index 5fc49d6518..a940d1b99b 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -8,7 +8,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -39,12 +39,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await addPinned(user, ps.noteId).catch(e => { if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); @@ -53,7 +54,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, }); }); diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index a66d6bac7b..4e4fb3840f 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -5,14 +5,15 @@ import { MessagingMessages, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['account', 'messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Update documents await MessagingMessages.update({ diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index 90f555763e..99f17ddfc9 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -5,14 +5,15 @@ import { NoteUnreads } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Remove documents await NoteUnreads.delete({ diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index d948f3efdf..e9bb66264b 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -9,7 +9,7 @@ import { publishMainStream } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -26,8 +26,9 @@ export const meta = { id: '184663db-df88-4bc2-8b52-fb85f0681939', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Check if announcement exists const announcement = await Announcements.findOne(ps.announcementId); diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index f7e910154d..a20719363b 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -6,7 +6,7 @@ import define from '../../define'; import { Users, UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -15,8 +15,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 1599ccea6b..2941b441e2 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index 4edeae9e95..51371353c9 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index aa0695281a..ac617defb0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index 9cac503538..0445922188 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 215ccbd5b5..a3c9d0e5ee 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .select('item.key') diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index 17ce5851c0..08185f224b 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index 45aeb59771..9de68ac6e8 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -2,14 +2,15 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .select('item.scope') diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index 7c282064c3..27884046b4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -5,7 +5,7 @@ import { RegistryItems } from '@/models/index'; import { genId } from '@/misc/gen-id'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,8 +23,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 1b6b18aa80..51721c5b58 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -5,7 +5,7 @@ import { ID } from '@/misc/cafy-id'; import { publishUserEvent } from '@/services/stream'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -14,8 +14,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const token = await AccessTokens.findOne(ps.tokenId); diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 6f2f8fc8ca..796e2ec309 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -5,7 +5,7 @@ import { Signins } from '@/models/index'; import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,8 +23,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) .andWhere(`signin.userId = :meId`, { meId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index c1b753bfaf..9c82b74960 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -8,7 +8,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -27,19 +27,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await removePinned(user, ps.noteId).catch(e => { if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); throw e; }); - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, }); }); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index d99fa2474d..b4479aa50d 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -11,7 +11,7 @@ import { ApiError } from '../../error'; import { validateEmailForAccount } from '@/services/validate-email-for-account'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -43,8 +43,9 @@ export const meta = { id: 'a2defefb-f220-8849-0af6-17f816099323', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 5a62b39377..6b7e53aa1f 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -19,7 +19,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -162,12 +162,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, _user, token) => { const user = await Users.findOneOrFail(_user.id); const isSecure = token == null; @@ -278,7 +279,7 @@ export default define(meta, async (ps, _user, token) => { if (Object.keys(updates).length > 0) await Users.update(user.id, updates); if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); - const iObj = await Users.pack(user.id, user, { + const iObj = await Users.pack<true, true>(user.id, user, { detail: true, includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 6949e486ab..76a3131e6d 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'groups'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', @@ -27,27 +27,28 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, group: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) .andWhere(`invitation.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index 4ca3d6ebed..5ac49cf96b 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -7,7 +7,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'read:messaging', @@ -24,16 +24,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const mute = await Mutings.find({ muterId: user.id, diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 79e7764245..7dbddd80e2 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -11,7 +11,7 @@ import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivit export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'read:messaging', @@ -44,11 +44,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, }, @@ -72,8 +72,9 @@ export const meta = { id: 'a053a8dd-a491-4718-8f87-50775aad9284', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { if (ps.userId != null) { // Fetch recipient (user) diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index 02b22ead80..5ec16f5e5a 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -11,7 +11,7 @@ import { createMessage } from '@/services/messages/create'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -34,8 +34,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, @@ -82,8 +82,9 @@ export const meta = { id: 'c15a5199-7422-4968-941a-2a462c478f7d', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let recipientUser: User | undefined; let recipientGroup: UserGroup | undefined; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index dd1c2e8dee..2975419cef 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -9,7 +9,7 @@ import { deleteMessage } from '@/services/messages/delete'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -32,8 +32,9 @@ export const meta = { id: '54b5b326-7925-42cf-8019-130fda8b56af', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const message = await MessagingMessages.findOne({ id: ps.messageId, diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 96d68b2605..42c3f49f6f 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -8,7 +8,7 @@ import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../co export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -25,8 +25,9 @@ export const meta = { id: '86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const message = await MessagingMessages.findOne(ps.messageId); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index bced077c12..693a7a04ec 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -9,7 +9,7 @@ import { MoreThan } from 'typeorm'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { detail: { @@ -19,435 +19,436 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { maintainerName: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, maintainerEmail: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, version: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: config.version, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, uri: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', example: 'https://misskey.example.com', }, description: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, langs: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, tosUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, repositoryUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey', }, feedbackUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey/issues/new', }, secure: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, default: false, }, disableRegistration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, disableLocalTimeline: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, disableGlobalTimeline: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, driveCapacityPerLocalUserMb: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveCapacityPerRemoteUserMb: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, cacheRemoteFiles: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, proxyRemoteFiles: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, emailRequiredForSignup: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableHcaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hcaptchaSiteKey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, enableRecaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, recaptchaSiteKey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, swPublickey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, mascotImageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: '/assets/ai.png', }, bannerUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, errorImageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://xn--931a.moe/aiart/yubitun.png', }, iconUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, maxNoteTextLength: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, default: 500, }, emojis: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, }, }, ads: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { place: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, }, }, requireSetup: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, example: false, }, enableEmail: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableTwitterIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableGithubIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableDiscordIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableServiceWorker: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, translatorAvailable: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, proxyAccountName: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, features: { - type: 'object' as const, - optional: true as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, properties: { registration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, localTimeLine: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, globalTimeLine: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, elasticsearch: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hcaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, recaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, objectStorage: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, twitter: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, github: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, discord: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, serviceWorker: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, miauth: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, default: true, }, }, }, userStarForReactionFallback: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, pinnedUsers: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, hiddenTags: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, blockedHosts: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, hcaptchaSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, recaptchaSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, proxyAccountId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, format: 'id', }, twitterConsumerKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, twitterConsumerSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, githubClientId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, githubClientSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, discordClientId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, discordClientSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, summaryProxy: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, email: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpSecure: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, smtpHost: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpPort: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpUser: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpPass: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, swPrivateKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, useObjectStorage: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageBaseUrl: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageBucket: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStoragePrefix: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageEndpoint: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageRegion: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStoragePort: { - type: 'number' as const, - optional: true as const, nullable: true as const, + type: 'number', + optional: true, nullable: true, }, objectStorageAccessKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageUseSSL: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageUseProxy: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageSetPublicRead: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await fetchMeta(true); diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 29f109f369..158c8877e9 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -7,7 +7,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['auth'], - requireCredential: true as const, + requireCredential: true, secure: true, @@ -34,17 +34,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Generate access token const accessToken = secureRndstr(32, true); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 703611f67f..6ba5a453c3 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -11,7 +11,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:mutes', @@ -40,8 +40,9 @@ export const meta = { id: '7e7359cb-160c-4956-b08f-4d1c653cd007', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const muter = user; diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index aa8c33d046..21948dc3d1 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -9,7 +9,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:mutes', @@ -38,8 +38,9 @@ export const meta = { id: '5467d020-daa9-4553-81e1-135c0c35a96d', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const muter = user; diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 48b6ddb060..4c6a81b63c 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -7,7 +7,7 @@ import { Mutings } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:mutes', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Muting', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId) .andWhere(`muting.muterId = :meId`, { meId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 1164f5f6f3..42bd5c5f75 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -5,7 +5,7 @@ import { Apps } from '@/models/index'; export const meta = { tags: ['account', 'app'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -20,55 +20,56 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, callbackUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, permission: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, secret: { - type: 'string' as const, - optional: true as const, nullable: false as const, + type: 'string', + optional: true, nullable: false, }, isAuthorized: { - type: 'object' as const, - optional: true as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, properties: { appId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = { userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 37d1b03dce..9edc6cb11c 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -43,16 +43,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(`note.visibility = 'public'`) diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index acd9d6f7e4..088ef65e96 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -12,7 +12,7 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -34,16 +34,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(new Brackets(qb => { qb diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index deb14da16c..b89c6db4a8 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -9,7 +9,7 @@ import { In } from 'typeorm'; export const meta = { tags: ['clips', 'notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -18,12 +18,12 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'Note', + type: 'object', + optional: false, nullable: false, + ref: 'Clip', }, }, @@ -34,8 +34,9 @@ export const meta = { id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 8fdbb7fdeb..4bd89c32e7 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -9,7 +9,7 @@ import { Notes } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -28,11 +28,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -44,8 +44,9 @@ export const meta = { id: 'e1035875-9551-45ec-afa8-1ded1fcb53c8', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 9567374c63..4efa76b248 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -25,7 +25,7 @@ setInterval(() => { export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -48,7 +48,7 @@ export const meta = { validator: $.optional.nullable.str.pipe(text => text.trim() != '' && length(text.trim()) <= maxNoteTextLength - && Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH // DB limit + && Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH, // DB limit ), default: null, }, @@ -78,11 +78,11 @@ export const meta = { }, fileIds: { - validator: $.optional.arr($.type(ID)).unique().range(1, 4), + validator: $.optional.arr($.type(ID)).unique().range(1, 16), }, mediaIds: { - validator: $.optional.arr($.type(ID)).unique().range(1, 4), + validator: $.optional.arr($.type(ID)).unique().range(1, 16), deprecated: true, }, @@ -113,12 +113,12 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { createdNote: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -173,8 +173,9 @@ export const meta = { id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let visibleUsers: User[] = []; if (ps.visibleUserIds) { diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 532213c725..9e080d9e99 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -10,7 +10,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notes', @@ -39,8 +39,9 @@ export const meta = { id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 14191eefde..78da6a3b00 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'write:favorites', @@ -32,8 +32,9 @@ export const meta = { id: 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get favoritee const note = await getNote(ps.noteId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index f8d3b63026..3f3d50f0d5 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -8,7 +8,7 @@ import { NoteFavorites } from '@/models/index'; export const meta = { tags: ['notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'write:favorites', @@ -31,8 +31,9 @@ export const meta = { id: 'b625fc69-635e-45e9-86f4-dbefbef35af5', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get favoritee const note = await getNote(ps.noteId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 2a14c52abc..5a47fb9e08 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -7,7 +7,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -22,16 +22,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const max = 30; const day = 1000 * 60 * 60 * 24 * 3; // 3æ—¥å‰ã¾ã§ 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 c3be042bfb..cac8b7d8a9 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -43,11 +43,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -59,8 +59,9 @@ export const meta = { id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const m = await fetchMeta(); if (m.disableGlobalTimeline) { 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 4a0b9d49d7..9683df4611 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -18,7 +18,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -63,11 +63,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -79,8 +79,9 @@ export const meta = { id: '620763f4-f621-4533-ab33-0577a1a3c342', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const m = await fetchMeta(); if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { 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 113268982b..7776644124 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -54,11 +54,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -70,8 +70,9 @@ export const meta = { id: '45a6eb02-7695-4393-b023-dd3be9aaaefd', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const m = await fetchMeta(); if (m.disableLocalTimeline) { diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 916209ca71..81b3844365 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -13,7 +13,7 @@ import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-t export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { following: { @@ -40,16 +40,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const followingQuery = Followings.createQueryBuilder('following') .select('following.followeeId') diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 9f133c071e..79b558e65e 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -6,7 +6,7 @@ import { Brackets, In } from 'typeorm'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -21,16 +21,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = Polls.createQueryBuilder('poll') .where('poll.userHost IS NULL') diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 479034389a..77387cacb2 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -17,7 +17,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:votes', @@ -68,8 +68,9 @@ export const meta = { id: '85a5377e-b1e9-4617-b0b9-5bea73331e49', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const createdAt = new Date(); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index dca6deb06f..5205a78171 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -10,7 +10,7 @@ import { NoteReaction } from '@/models/entities/note-reaction'; export const meta = { tags: ['notes', 'reactions'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -41,11 +41,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteReaction', }, }, @@ -57,8 +57,9 @@ export const meta = { id: '263fff3d-d0e1-4af4-bea7-8408059b451a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 879b32cd07..1b42781ceb 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['reactions', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:reactions', @@ -41,8 +41,9 @@ export const meta = { id: '20ef5475-9f38-4e4c-bd33-de6d979498ec', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index eb9281f7a0..1d686b5971 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['reactions', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:reactions', @@ -38,8 +38,9 @@ export const meta = { id: '92f4426d-4196-4125-aa5b-02943e2ec8fc', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index d53d725165..f71d23146a 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -12,7 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -34,11 +34,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -50,8 +50,9 @@ export const meta = { id: '12908022-2e21-46cd-ba6a-3edaf6093f46', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index e39878f4f2..62c56534e1 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -10,7 +10,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -32,16 +32,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) 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 2275f7c1ae..87eaffe2f1 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 @@ -56,16 +56,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoinAndSelect('note.user', 'user') diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index b49ee87199..e75212b14b 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -13,7 +13,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { query: { @@ -50,19 +50,20 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { if (es == null) { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 1f7f84cbe4..feb94be1a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -8,7 +8,7 @@ import { Notes } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -17,8 +17,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, @@ -29,8 +29,9 @@ export const meta = { id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 9673b5a77c..c3e9090bbf 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -6,7 +6,7 @@ import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { noteId: { @@ -15,25 +15,26 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { isFavorited: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isWatching: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMutedThread: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await Notes.findOneOrFail(ps.noteId); diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index dd2f887f01..a8b50d90f6 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -10,7 +10,7 @@ import readNote from '@/services/note/read'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -27,8 +27,9 @@ export const meta = { id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index d34c99f901..f76b526ce1 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -8,7 +8,7 @@ import { NoteThreadMutings } from '@/models'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 211b8d4f40..8be2861aec 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -16,7 +16,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -61,16 +61,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const hasFollowing = (await Followings.count({ where: { diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 647ae4efe7..ed069cb75a 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -13,7 +13,7 @@ import { Notes } from '@/models'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -25,8 +25,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, errors: { @@ -36,8 +36,9 @@ export const meta = { id: 'bea9b03f-36e0-49c5-a4db-627a029f8971', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 3661db4d86..8db543d328 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -10,7 +10,7 @@ import { Notes, Users } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notes', @@ -33,8 +33,9 @@ export const meta = { id: 'efd4a259-2442-496b-8dd7-b255aa1a160f', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index d614ddf453..89de73fb9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -11,7 +11,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['notes', 'lists'], - requireCredential: true as const, + requireCredential: true, params: { listId: { @@ -60,11 +60,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -76,8 +76,9 @@ export const meta = { id: '8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const list = await UserLists.findOne({ id: ps.listId, diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index 7f724953df..6433c6bc2a 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 76a368c51d..3e9faa2b23 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: '09b3695c-f72c-4731-a428-7cff825fc82e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index e285eae460..bd8a7ba1b7 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -5,7 +5,7 @@ import { createNotification } from '@/services/create-notification'; export const meta = { tags: ['notifications'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', @@ -25,8 +25,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { createNotification(user.id, 'app', { appAccessTokenId: token ? token.id : null, diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index 963af6cb43..4cec38a95d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -5,11 +5,12 @@ import { Notifications } from '@/models/index'; export const meta = { tags: ['notifications', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Update documents await Notifications.update({ diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 9ff0bbeb83..7e23bc234d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['notifications', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', @@ -26,8 +26,9 @@ export const meta = { id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const notification = await Notifications.findOne({ notifieeId: user.id, diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 421eed5ea1..61c0160f83 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -6,7 +6,7 @@ import { Users, Pages } from '@/models/index'; import { ApiError } from '../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, params: { @@ -30,8 +30,9 @@ export const meta = { id: '4a13ad31-6729-46b4-b9af-e86b265c2e74', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 441ba54265..7ee50fbdfa 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -10,7 +10,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -65,8 +65,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, @@ -82,8 +82,9 @@ export const meta = { id: '4650348e-301c-499a-83c9-6aa988c66bc1', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let eyeCatchingImage = null; if (ps.eyeCatchingImageId != null) { @@ -106,7 +107,7 @@ export default define(meta, async (ps, user) => { } }); - const page = await Pages.save(new Page({ + const page = await Pages.insert(new Page({ id: genId(), createdAt: new Date(), updatedAt: new Date(), @@ -122,7 +123,7 @@ export default define(meta, async (ps, user) => { alignCenter: ps.alignCenter, hideTitleWhenPinned: ps.hideTitleWhenPinned, font: ps.font, - })); + })).then(x => Pages.findOneOrFail(x.identifiers[0])); return await Pages.pack(page); }); diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index 7a45237697..aeda823e52 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -30,8 +30,9 @@ export const meta = { id: '8b741b3e-2c22-44b3-a15f-29949aa1601e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 1dcfb8dd83..7f0d58b350 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -4,19 +4,20 @@ import { Pages } from '@/models/index'; export const meta = { tags: ['pages'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Pages.createQueryBuilder('page') .where('page.visibility = \'public\'') diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index f48359ab2d..c479f637a9 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -8,7 +8,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:page-likes', @@ -37,8 +37,9 @@ export const meta = { id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index d94c7457da..5cda5386d5 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -8,7 +8,7 @@ import { Page } from '@/models/entities/page'; export const meta = { tags: ['pages'], - requireCredential: false as const, + requireCredential: false, params: { pageId: { @@ -25,8 +25,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, @@ -37,8 +37,9 @@ export const meta = { id: '222120c0-3ead-4528-811b-b96f233388d7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let page: Page | undefined; diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 5a2b68e425..cca5e5b5a9 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -7,7 +7,7 @@ import { Pages, PageLikes } from '@/models/index'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:page-likes', @@ -30,8 +30,9 @@ export const meta = { id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index f980d9207b..991085ee09 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -9,7 +9,7 @@ import { Not } from 'typeorm'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -88,8 +88,9 @@ export const meta = { id: '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index c8f67981f5..3eab70ae2e 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -1,7 +1,7 @@ import define from '../define'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -9,17 +9,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { pong: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { return { pong: Date.now(), diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 9d8d3963b4..ff0e22555f 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -7,22 +7,23 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const meta = await fetchMeta(); diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index ac3cd9cd0b..8d8c60d755 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { noteId: { @@ -24,8 +24,9 @@ export const meta = { id: 'd785b897-fcd3-4fe9-8fc3-b85c26e6c932', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 6caf572222..af1aeb4311 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -11,7 +11,7 @@ import { genId } from '@/misc/gen-id'; import { IsNull } from 'typeorm'; export const meta = { - requireCredential: false as const, + requireCredential: false, limit: { duration: ms('1hour'), @@ -31,8 +31,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne({ usernameLower: ps.username.toLowerCase(), diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index f6fd5735d9..e99dc9db15 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -4,7 +4,7 @@ import { ApiError } from '../error'; import { resetDb } from '@/db/postgre'; export const meta = { - requireCredential: false as const, + requireCredential: false, params: { }, @@ -12,8 +12,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 706f0a32c0..a7366584b1 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -6,7 +6,7 @@ import { Users, UserProfiles, PasswordResetRequests } from '@/models/index'; import { ApiError } from '../error'; export const meta = { - requireCredential: false as const, + requireCredential: false, params: { token: { @@ -21,8 +21,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const req = await PasswordResetRequests.findOneOrFail({ token: ps.token, diff --git a/packages/backend/src/server/api/endpoints/room/show.ts b/packages/backend/src/server/api/endpoints/room/show.ts deleted file mode 100644 index ec53982ebb..0000000000 --- a/packages/backend/src/server/api/endpoints/room/show.ts +++ /dev/null @@ -1,159 +0,0 @@ -import $ from 'cafy'; -import define from '../../define'; -import { ApiError } from '../../error'; -import { Users, UserProfiles } from '@/models/index'; -import { ID } from '@/misc/cafy-id'; -import { toPunyNullable } from '@/misc/convert-host'; - -export const meta = { - tags: ['room'], - - requireCredential: false as const, - - params: { - userId: { - validator: $.optional.type(ID), - }, - - username: { - validator: $.optional.str, - }, - - host: { - validator: $.optional.nullable.str, - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '7ad3fa3e-5e12-42f0-b23a-f3d13f10ee4b', - }, - }, - - res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - roomType: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['default', 'washitsu'], - }, - furnitures: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - props: { - type: 'object' as const, - optional: true as const, nullable: false as const, - }, - position: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - x: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - y: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - z: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - rotation: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - x: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - y: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - z: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, - }, - }, - carpetColor: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'hex', - example: '#85CAF0', - }, - }, - }, -}; - -export default define(meta, async (ps, me) => { - const user = await Users.findOne(ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) }); - - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } - - const profile = await UserProfiles.findOneOrFail(user.id); - - if (profile.room.furnitures == null) { - await UserProfiles.update(user.id, { - room: { - furnitures: [], - ...profile.room, - }, - }); - - profile.room.furnitures = []; - } - - if (profile.room.roomType == null) { - const initialType = 'default'; - await UserProfiles.update(user.id, { - room: { - roomType: initialType as any, - ...profile.room, - }, - }); - - profile.room.roomType = initialType; - } - - if (profile.room.carpetColor == null) { - const initialColor = '#85CAF0'; - await UserProfiles.update(user.id, { - room: { - carpetColor: initialColor as any, - ...profile.room, - }, - }); - - profile.room.carpetColor = initialColor; - } - - return profile.room; -}); diff --git a/packages/backend/src/server/api/endpoints/room/update.ts b/packages/backend/src/server/api/endpoints/room/update.ts deleted file mode 100644 index f9fc2b278e..0000000000 --- a/packages/backend/src/server/api/endpoints/room/update.ts +++ /dev/null @@ -1,51 +0,0 @@ -import $ from 'cafy'; -import { publishMainStream } from '@/services/stream'; -import define from '../../define'; -import { Users, UserProfiles } from '@/models/index'; - -export const meta = { - tags: ['room'], - - requireCredential: true as const, - - params: { - room: { - validator: $.obj({ - furnitures: $.arr($.obj({ - id: $.str, - type: $.str, - position: $.obj({ - x: $.num, - y: $.num, - z: $.num, - }), - rotation: $.obj({ - x: $.num, - y: $.num, - z: $.num, - }), - props: $.optional.nullable.obj(), - })), - roomType: $.str, - carpetColor: $.str, - }), - }, - }, -}; - -export default define(meta, async (ps, user) => { - await UserProfiles.update(user.id, { - room: ps.room as any, - }); - - const iObj = await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - - // TODO: レスãƒãƒ³ã‚¹ãŒãŠã‹ã—ã„ã¨æ€ã† by YuzuRyo61 - return iObj; -}); diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index be502cf24a..1ad2c54ab5 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -3,7 +3,7 @@ import * as si from 'systeminformation'; import define from '../define'; export const meta = { - requireCredential: false as const, + requireCredential: false, desc: { }, @@ -12,8 +12,9 @@ export const meta = { params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index f47b0d0a2b..9879ef2adf 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -3,7 +3,7 @@ import { NoteReactions, Notes, Users } from '@/models/index'; import { federationChart, driveChart } from '@/services/chart/index'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -11,41 +11,42 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { notesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, originalNotesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, originalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, instances: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveUsageLocal: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveUsageRemote: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const [ notesCount, diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 9734746770..ae3e9ce77a 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -7,7 +7,7 @@ import { SwSubscriptions } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: { endpoint: { @@ -24,22 +24,23 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { state: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, enum: ['already-subscribed', 'subscribed'], }, key: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // if already subscribed const exist = await SwSubscriptions.findOne({ diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 24ee861f16..6f569e9417 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -5,15 +5,16 @@ import { SwSubscriptions } from '../../../../models'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: { endpoint: { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await SwSubscriptions.delete({ userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index f1b46a2b65..74120fc406 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -5,7 +5,7 @@ import { Users, UsedUsernames } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { username: { @@ -14,17 +14,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { available: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { // Get exist const exist = await Users.count({ diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 601578de27..6b11ec0f01 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -7,7 +7,7 @@ import { generateBlockQueryForUsers } from '../common/generate-block-query'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -53,16 +53,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user'); query.where('user.isExplorable = TRUE'); diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index f5964c54db..d4152fbf50 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -25,8 +25,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId) .andWhere(`clip.userId = :userId`, { userId: ps.userId }) diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 535b10412e..6214ab40ba 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -9,7 +9,7 @@ import { toPunyNullable } from '@/misc/convert-host'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, @@ -61,8 +61,9 @@ export const meta = { id: '3c6a84db-d619-26af-ca14-06232a21df8a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 58c72bb957..76112eab25 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -9,7 +9,7 @@ import { toPunyNullable } from '@/misc/convert-host'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, @@ -61,8 +61,9 @@ export const meta = { id: 'f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 6ef884deda..c5f08b4c94 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -25,8 +25,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) .andWhere(`post.userId = :userId`, { userId: ps.userId }); diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index a88de7ac83..d886d3355a 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -10,7 +10,7 @@ import { Notes, Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -24,12 +24,22 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + properties: { + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + weight: { + type: 'number', + optional: false, nullable: false, + }, + }, }, }, @@ -40,8 +50,9 @@ export const meta = { id: 'e6965129-7b2a-40a4-bae2-cd84cd434822', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Lookup user const user = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 12ee11ba55..25e29de01c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -8,7 +8,7 @@ import { UserGroupJoining } from '@/models/entities/user-group-joining'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -19,12 +19,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userGroup = await UserGroups.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index dbc77dd8fe..f30ab78ca0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -7,7 +7,7 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -24,8 +24,9 @@ export const meta = { id: '63dbd64c-cd77-413f-8e08-61781e210b38', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userGroup = await UserGroups.findOne({ id: ps.groupId, diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index fef94c306f..7061db538b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -9,7 +9,7 @@ import { UserGroupJoining } from '@/models/entities/user-group-joining'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -26,8 +26,9 @@ export const meta = { id: '98c11eca-c890-4f42-9806-c8c8303ebb5e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the invitation const invitation = await UserGroupInvitations.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 33a202f029..f5ca3dec8b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -7,7 +7,7 @@ import { UserGroupInvitations } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -24,8 +24,9 @@ export const meta = { id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the invitation const invitation = await UserGroupInvitations.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 4dee18fcb0..3b7a4edb81 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -11,7 +11,7 @@ import { createNotification } from '@/services/create-notification'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -50,8 +50,9 @@ export const meta = { id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 1bd065ca00..ab48b1910d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -5,21 +5,22 @@ import { Not, In } from 'typeorm'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ownedGroups = await UserGroups.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 9a41175d63..d2fcdab301 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -7,7 +7,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -30,8 +30,9 @@ export const meta = { id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index 69e4c85717..6193a71019 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -4,21 +4,22 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const userGroups = await UserGroups.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index 70c1457dcd..785bea140d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -8,7 +8,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -41,8 +41,9 @@ export const meta = { id: '1546eed5-4414-4dea-81c1-b0aec4f6d2af', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 0bb06f8df4..eb26eac2a8 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -7,7 +7,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -30,8 +30,9 @@ export const meta = { id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 54cf8197e7..4b1c8fbbdb 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -8,7 +8,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -23,8 +23,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -47,8 +47,9 @@ export const meta = { id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index d16f1ac42b..6caf903555 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -7,7 +7,7 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -22,8 +22,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -34,8 +34,9 @@ export const meta = { id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 8372139f84..945b511628 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -7,7 +7,7 @@ import { UserList } from '@/models/entities/user-list'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -18,12 +18,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userList = await UserLists.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index fac4e90dbf..3183d2a09c 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -24,8 +24,9 @@ export const meta = { id: '78436795-db79-42f5-b1e2-55ea2cf19166', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userList = await UserLists.findOne({ id: 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 222c930d0e..ae66b0aacc 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -4,21 +4,22 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const userLists = await UserLists.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 86daa9b2e1..4c74aefa8a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -9,7 +9,7 @@ import { UserLists, UserListJoinings, Users } from '@/models/index'; export const meta = { tags: ['lists', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -36,8 +36,9 @@ export const meta = { id: '588e7f72-c744-4a61-b180-d354e912bda2', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the list const userList = await UserLists.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 77ecb4a223..8b50c475b0 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -9,7 +9,7 @@ import { UserLists, UserListJoinings, Blockings } from '@/models/index'; export const meta = { tags: ['lists', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -48,8 +48,9 @@ export const meta = { id: '990232c5-3f9d-4d83-9f3f-ef27b6332a4b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the list const userList = await UserLists.findOne({ 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 9c985bb091..06555c1a88 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, @@ -30,8 +30,9 @@ export const meta = { id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the list const userList = await UserLists.findOne({ 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 8a0f96a5d9..02b0d5fe18 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -22,8 +22,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, @@ -34,8 +34,9 @@ export const meta = { id: '796666fe-3dff-4d39-becb-8a5932c1d5b7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the list const userList = await UserLists.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index da8e858119..99158fb0ae 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -66,11 +66,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -82,8 +82,9 @@ export const meta = { id: '27e494ba-2ac2-48e8-893b-10d4d8c2387b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Lookup user const user = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index 4763303a70..6e003dd1af 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -25,8 +25,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) .andWhere(`page.userId = :userId`, { userId: ps.userId }) diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 626487176b..312d4dbf23 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['users', 'reactions'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteReaction', }, }, @@ -55,8 +55,9 @@ export const meta = { id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const profile = await UserProfiles.findOneOrFail(ps.userId); diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 71c564c980..9ea39eb2dd 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -8,7 +8,7 @@ import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../comm export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -25,16 +25,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user') .where('user.isLocked = FALSE') diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index af1984fa2b..7e319ca105 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, params: { userId: { @@ -15,93 +15,93 @@ export const meta = { }, res: { + optional: false, nullable: false, oneOf: [ { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, isFollowing: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isFollowed: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocking: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocked: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMuted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, isFollowing: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isFollowed: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocking: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocked: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMuted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, }, ], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index a1d8376651..ed2aa7bb26 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -13,7 +13,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, params: { userId: { @@ -44,7 +44,7 @@ export const meta = { id: '35e166f5-05fb-4f87-a2d5-adb42676d48f', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { @@ -62,7 +62,7 @@ export default define(meta, async (ps, me) => { throw new ApiError(meta.errors.cannotReportAdmin); } - const report = await AbuseUserReports.save({ + const report = await AbuseUserReports.insert({ id: genId(), createdAt: new Date(), targetUserId: user.id, @@ -70,7 +70,7 @@ export default define(meta, async (ps, me) => { reporterId: me.id, reporterHost: null, comment: ps.comment, - }); + }).then(x => AbuseUserReports.findOneOrFail(x.identifiers[0])); // Publish event to moderators setTimeout(async () => { diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 58a8d929f7..d67625e624 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -8,7 +8,7 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { username: { @@ -31,16 +31,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30æ—¥ @@ -111,6 +112,6 @@ export default define(meta, async (ps, me) => { .getMany(); } - return await Users.packMany(users, me, { detail: ps.detail }); + return await Users.packMany(users, me, { detail: !!ps.detail }); } }); diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index f87088688c..26f818afcc 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -7,7 +7,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { query: { @@ -36,16 +36,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30æ—¥ diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index eacb2aee16..92910e9ed8 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -11,7 +11,7 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -32,9 +32,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + optional: false, nullable: false, + oneOf: [ + { + type: 'object', + ref: 'UserDetailed', + }, + { + type: 'array', + items: { + type: 'object', + ref: 'UserDetailed', + } + }, + ] }, errors: { @@ -42,7 +53,7 @@ export const meta = { message: 'Failed to resolve remote user.', code: 'FAILED_TO_RESOLVE_REMOTE_USER', id: 'ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c', - kind: 'server' as const, + kind: 'server', }, noSuchUser: { @@ -51,8 +62,9 @@ export const meta = { id: '4362f8dc-731f-4ad8-a694-be5a88922a24', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { let user; diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index b8564218ac..381e433479 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -2,12 +2,12 @@ import $ from 'cafy'; import define from '../../define'; import { ApiError } from '../../error'; import { ID } from '@/misc/cafy-id'; -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, ReversiGames, Users } from '@/models/index'; +import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -22,8 +22,9 @@ export const meta = { id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId); if (user == null) { @@ -49,7 +50,6 @@ export default define(meta, async (ps, me) => { pageLikedCount, driveFilesCount, driveUsage, - reversiCount, ] = await Promise.all([ Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) @@ -112,10 +112,6 @@ export default define(meta, async (ps, me) => { .where('file.userId = :userId', { userId: user.id }) .getCount(), DriveFiles.calcDriveUsageOf(user), - ReversiGames.createQueryBuilder('game') - .where('game.user1Id = :userId', { userId: user.id }) - .orWhere('game.user2Id = :userId', { userId: user.id }) - .getCount(), ]); return { @@ -139,6 +135,5 @@ export default define(meta, async (ps, me) => { pageLikedCount, driveFilesCount, driveUsage, - reversiCount, }; }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 5f617771e0..4721f6263a 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -7,8 +7,8 @@ import Logger from '@/services/logger'; const logger = new Logger('limiter'); -export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, reject) => { - const limitation = endpoint.meta.limit!; +export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: User) => new Promise<void>((ok, reject) => { + const limitation = endpoint.meta.limit; const key = Object.prototype.hasOwnProperty.call(limitation, 'key') ? limitation.key @@ -30,7 +30,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, rejec } // Short-term limit - function min() { + function min(): void { const minIntervalLimiter = new Limiter({ id: `${user.id}:${key}:min`, duration: limitation.minInterval, @@ -58,7 +58,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, rejec } // Long term limit - function max() { + function max(): void { const limiter = new Limiter({ id: `${user.id}:${key}`, duration: limitation.duration, diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 1c521f212f..1efef8d26d 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -118,7 +118,7 @@ export function genOpenapiSpec(lang = 'ja-JP') { description: desc, externalDocs: { description: 'Source code', - url: `https://github.com/misskey-dev/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`, + url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, }, ...(endpoint.meta.tags ? { tags: [endpoint.meta.tags[0]], diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index 723b3e884a..eb42667fd5 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -1,6 +1,6 @@ -import { refs, Schema } from '@/misc/schema'; +import { refs, MinimumSchema } from '@/misc/schema'; -export function convertSchemaToOpenApiSchema(schema: Schema) { +export function convertSchemaToOpenApiSchema(schema: MinimumSchema) { const res: any = schema; if (schema.type === 'object' && schema.properties) { @@ -15,6 +15,10 @@ export function convertSchemaToOpenApiSchema(schema: Schema) { res.items = convertSchemaToOpenApiSchema(schema.items); } + if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema); + if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema); + if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema); + if (schema.ref) { res.$ref = `#/components/schemas/${schema.ref}`; } diff --git a/packages/backend/src/server/api/stream/channels/games/reversi-game.ts b/packages/backend/src/server/api/stream/channels/games/reversi-game.ts deleted file mode 100644 index 314db48b5e..0000000000 --- a/packages/backend/src/server/api/stream/channels/games/reversi-game.ts +++ /dev/null @@ -1,372 +0,0 @@ -import autobind from 'autobind-decorator'; -import * as CRC32 from 'crc-32'; -import { publishReversiGameStream } from '@/services/stream'; -import Reversi from '../../../../../games/reversi/core'; -import * as maps from '../../../../../games/reversi/maps'; -import Channel from '../../channel'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiGames, Users } from '@/models/index'; -import { User } from '@/models/entities/user'; - -export default class extends Channel { - public readonly chName = 'gamesReversiGame'; - public static shouldShare = false; - public static requireCredential = false; - - private gameId: ReversiGame['id'] | null = null; - private watchers: Record<User['id'], Date> = {}; - private emitWatchersIntervalId: ReturnType<typeof setInterval>; - - @autobind - public async init(params: any) { - this.gameId = params.gameId; - - // Subscribe game stream - this.subscriber.on(`reversiGameStream:${this.gameId}`, this.onEvent); - this.emitWatchersIntervalId = setInterval(this.emitWatchers, 5000); - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - // 観戦者イベント - this.watch(game); - } - - @autobind - private onEvent(data: any) { - if (data.type === 'watching') { - const id = data.body; - this.watchers[id] = new Date(); - } else { - this.send(data); - } - } - - @autobind - private async emitWatchers() { - const now = new Date(); - - // Remove not watching users - for (const [userId, date] of Object.entries(this.watchers)) { - if (now.getTime() - date.getTime() > 5000) delete this.watchers[userId]; - } - - const users = await Users.packMany(Object.keys(this.watchers), null, { detail: false }); - - this.send({ - type: 'watchers', - body: users, - }); - } - - @autobind - public dispose() { - // Unsubscribe events - this.subscriber.off(`reversiGameStream:${this.gameId}`, this.onEvent); - clearInterval(this.emitWatchersIntervalId); - } - - @autobind - public onMessage(type: string, body: any) { - switch (type) { - case 'accept': this.accept(true); break; - case 'cancelAccept': this.accept(false); break; - case 'updateSettings': this.updateSettings(body.key, body.value); break; - case 'initForm': this.initForm(body); break; - case 'updateForm': this.updateForm(body.id, body.value); break; - case 'message': this.message(body); break; - case 'set': this.set(body.pos); break; - case 'check': this.check(body.crc32); break; - } - } - - @autobind - private async updateSettings(key: string, value: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - if ((game.user1Id === this.user.id) && game.user1Accepted) return; - if ((game.user2Id === this.user.id) && game.user2Accepted) return; - - if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; - - await ReversiGames.update(this.gameId!, { - [key]: value, - }); - - publishReversiGameStream(this.gameId!, 'updateSettings', { - key: key, - value: value, - }); - } - - @autobind - private async initForm(form: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const set = game.user1Id === this.user.id ? { - form1: form, - } : { - form2: form, - }; - - await ReversiGames.update(this.gameId!, set); - - publishReversiGameStream(this.gameId!, 'initForm', { - userId: this.user.id, - form, - }); - } - - @autobind - private async updateForm(id: string, value: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const form = game.user1Id === this.user.id ? game.form2 : game.form1; - - const item = form.find((i: any) => i.id == id); - - if (item == null) return; - - item.value = value; - - const set = game.user1Id === this.user.id ? { - form2: form, - } : { - form1: form, - }; - - await ReversiGames.update(this.gameId!, set); - - publishReversiGameStream(this.gameId!, 'updateForm', { - userId: this.user.id, - id, - value, - }); - } - - @autobind - private async message(message: any) { - if (this.user == null) return; - - message.id = Math.random(); - publishReversiGameStream(this.gameId!, 'message', { - userId: this.user.id, - message, - }); - } - - @autobind - private async accept(accept: boolean) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - - let bothAccepted = false; - - if (game.user1Id === this.user.id) { - await ReversiGames.update(this.gameId!, { - user1Accepted: accept, - }); - - publishReversiGameStream(this.gameId!, 'changeAccepts', { - user1: accept, - user2: game.user2Accepted, - }); - - if (accept && game.user2Accepted) bothAccepted = true; - } else if (game.user2Id === this.user.id) { - await ReversiGames.update(this.gameId!, { - user2Accepted: accept, - }); - - publishReversiGameStream(this.gameId!, 'changeAccepts', { - user1: game.user1Accepted, - user2: accept, - }); - - if (accept && game.user1Accepted) bothAccepted = true; - } else { - return; - } - - if (bothAccepted) { - // 3秒後ã€ã¾ã acceptã•れã¦ã„ãŸã‚‰ã‚²ãƒ¼ãƒ é–‹å§‹ - setTimeout(async () => { - const freshGame = await ReversiGames.findOne(this.gameId!); - if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; - if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; - - let bw: number; - if (freshGame.bw == 'random') { - bw = Math.random() > 0.5 ? 1 : 2; - } else { - bw = parseInt(freshGame.bw, 10); - } - - function getRandomMap() { - const mapCount = Object.entries(maps).length; - const rnd = Math.floor(Math.random() * mapCount); - return Object.values(maps)[rnd].data; - } - - const map = freshGame.map != null ? freshGame.map : getRandomMap(); - - await ReversiGames.update(this.gameId!, { - startedAt: new Date(), - isStarted: true, - black: bw, - map: map, - }); - - //#region 盤é¢ã«æœ€åˆã‹ã‚‰çŸ³ãŒãªã„ãªã©ã—ã¦å§‹ã¾ã£ãŸçž¬é–“ã«å‹æ•—ãŒæ±ºå®šã™ã‚‹å ´åˆãŒã‚ã‚‹ã®ã§ãã®å‡¦ç† - const o = new Reversi(map, { - isLlotheo: freshGame.isLlotheo, - canPutEverywhere: freshGame.canPutEverywhere, - loopedBoard: freshGame.loopedBoard, - }); - - if (o.isEnded) { - let winner; - if (o.winner === true) { - winner = freshGame.black == 1 ? freshGame.user1Id : freshGame.user2Id; - } else if (o.winner === false) { - winner = freshGame.black == 1 ? freshGame.user2Id : freshGame.user1Id; - } else { - winner = null; - } - - await ReversiGames.update(this.gameId!, { - isEnded: true, - winnerId: winner, - }); - - publishReversiGameStream(this.gameId!, 'ended', { - winnerId: winner, - game: await ReversiGames.pack(this.gameId!, this.user), - }); - } - //#endregion - - publishReversiGameStream(this.gameId!, 'started', - await ReversiGames.pack(this.gameId!, this.user)); - }, 3000); - } - } - - // 石を打㤠- @autobind - private async set(pos: number) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (!game.isStarted) return; - if (game.isEnded) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const myColor = - ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) - ? true - : false; - - const o = new Reversi(game.map, { - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - }); - - // 盤é¢ã®çŠ¶æ…‹ã‚’å†ç”Ÿ - for (const log of game.logs) { - o.put(log.color, log.pos); - } - - if (o.turn !== myColor) return; - - if (!o.canPut(myColor, pos)) return; - o.put(myColor, pos); - - let winner; - if (o.isEnded) { - if (o.winner === true) { - winner = game.black == 1 ? game.user1Id : game.user2Id; - } else if (o.winner === false) { - winner = game.black == 1 ? game.user2Id : game.user1Id; - } else { - winner = null; - } - } - - const log = { - at: new Date(), - color: myColor, - pos, - }; - - const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); - - game.logs.push(log); - - await ReversiGames.update(this.gameId!, { - crc32, - isEnded: o.isEnded, - winnerId: winner, - logs: game.logs, - }); - - publishReversiGameStream(this.gameId!, 'set', Object.assign(log, { - next: o.turn, - })); - - if (o.isEnded) { - publishReversiGameStream(this.gameId!, 'ended', { - winnerId: winner, - game: await ReversiGames.pack(this.gameId!, this.user), - }); - } - } - - @autobind - private async check(crc32: string | number) { - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (!game.isStarted) return; - - if (crc32.toString() !== game.crc32) { - this.send('rescue', await ReversiGames.pack(game, this.user)); - } - - // ã¤ã„ã§ã«è¦³æˆ¦è€…イベントを発行 - this.watch(game); - } - - @autobind - private watch(game: ReversiGame) { - if (this.user != null) { - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) { - publishReversiGameStream(this.gameId!, 'watching', this.user.id); - } - } - } -} diff --git a/packages/backend/src/server/api/stream/channels/games/reversi.ts b/packages/backend/src/server/api/stream/channels/games/reversi.ts deleted file mode 100644 index 121560ff87..0000000000 --- a/packages/backend/src/server/api/stream/channels/games/reversi.ts +++ /dev/null @@ -1,34 +0,0 @@ -import autobind from 'autobind-decorator'; -import { publishMainStream } from '@/services/stream'; -import Channel from '../../channel'; -import { ReversiMatchings } from '@/models/index'; - -export default class extends Channel { - public readonly chName = 'gamesReversi'; - public static shouldShare = true; - public static requireCredential = true; - - @autobind - public async init(params: any) { - // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user!.id}`, data => { - this.send(data); - }); - } - - @autobind - public async onMessage(type: string, body: any) { - switch (type) { - case 'ping': { - if (body.id == null) return; - const matching = await ReversiMatchings.findOne({ - parentId: this.user!.id, - childId: body.id, - }); - if (matching == null) return; - publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, { id: matching.childId })); - break; - } - } - } -} diff --git a/packages/backend/src/server/api/stream/channels/index.ts b/packages/backend/src/server/api/stream/channels/index.ts index 89d93f2da3..f3826c4cf7 100644 --- a/packages/backend/src/server/api/stream/channels/index.ts +++ b/packages/backend/src/server/api/stream/channels/index.ts @@ -13,8 +13,6 @@ import drive from './drive'; import hashtag from './hashtag'; import channel from './channel'; import admin from './admin'; -import gamesReversi from './games/reversi'; -import gamesReversiGame from './games/reversi-game'; export default { main, @@ -32,6 +30,4 @@ export default { hashtag, channel, admin, - gamesReversi, - gamesReversiGame, }; diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index f4302f64a0..e70c26f5e5 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -11,7 +11,6 @@ import { Emoji } from '@/models/entities/emoji'; import { UserList } from '@/models/entities/user-list'; import { MessagingMessage } from '@/models/entities/messaging-message'; import { UserGroup } from '@/models/entities/user-group'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; import { AbuseUserReport } from '@/models/entities/abuse-user-report'; import { Signin } from '@/models/entities/signin'; import { Page } from '@/models/entities/page'; @@ -37,7 +36,7 @@ export interface UserStreamTypes { updateUserProfile: UserProfile; mute: User; unmute: User; - follow: Packed<'User'>; + follow: Packed<'UserDetailedNotMe'>; unfollow: Packed<'User'>; userAdded: Packed<'User'>; } @@ -47,7 +46,7 @@ export interface MainStreamTypes { mention: Packed<'Note'>; reply: Packed<'Note'>; renote: Packed<'Note'>; - follow: Packed<'User'>; + follow: Packed<'UserDetailedNotMe'>; followed: Packed<'User'>; unfollow: Packed<'User'>; meUpdated: Packed<'User'>; @@ -77,8 +76,6 @@ export interface MainStreamTypes { readAllChannels: undefined; unreadChannel: Note['id']; myTokenRegenerated: undefined; - reversiNoInvites: undefined; - reversiInvited: Packed<'ReversiMatching'>; signin: Signin; registryUpdated: { scope?: string[]; @@ -158,47 +155,6 @@ export interface MessagingIndexStreamTypes { message: Packed<'MessagingMessage'>; } -export interface ReversiStreamTypes { - matched: Packed<'ReversiGame'>; - invited: Packed<'ReversiMatching'>; -} - -export interface ReversiGameStreamTypes { - started: Packed<'ReversiGame'>; - ended: { - winnerId?: User['id'] | null, - game: Packed<'ReversiGame'>; - }; - updateSettings: { - key: string; - value: FIXME; - }; - initForm: { - userId: User['id']; - form: FIXME; - }; - updateForm: { - userId: User['id']; - id: string; - value: FIXME; - }; - message: { - userId: User['id']; - message: FIXME; - }; - changeAccepts: { - user1: boolean; - user2: boolean; - }; - set: { - at: Date; - color: boolean; - pos: number; - next: boolean; - }; - watching: User['id']; -} - export interface AdminStreamTypes { newAbuseUserReport: { id: AbuseUserReport['id']; @@ -268,14 +224,6 @@ export type StreamMessages = { name: `messagingIndexStream:${User['id']}`; payload: EventUnionFromDictionary<MessagingIndexStreamTypes>; }; - reversi: { - name: `reversiStream:${User['id']}`; - payload: EventUnionFromDictionary<ReversiStreamTypes>; - }; - reversiGame: { - name: `reversiGameStream:${ReversiGame['id']}`; - payload: EventUnionFromDictionary<ReversiGameStreamTypes>; - }; admin: { name: `adminStream:${User['id']}`; payload: EventUnionFromDictionary<AdminStreamTypes>; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 8bb5655b4f..f3c6c518fa 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -11,9 +11,10 @@ import { DriveFiles } from '@/models/index'; import { InternalStorage } from '@/services/drive/internal-storage'; import { downloadUrl } from '@/misc/download-url'; import { detectType } from '@/misc/get-file-info'; -import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor'; +import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor'; import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail'; import { StatusError } from '@/misc/fetch'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; //const _filename = fileURLToPath(import.meta.url); const _filename = __filename; @@ -27,6 +28,7 @@ const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => ctx.set('Cache-Control', 'max-age=300'); }; +// eslint-disable-next-line import/no-default-export export default async function(ctx: Koa.Context) { const key = ctx.params.key; @@ -65,13 +67,19 @@ export default async function(ctx: Koa.Context) { if (isThumbnail) { if (['image/jpeg', 'image/webp'].includes(mime)) { return await convertToJpeg(path, 498, 280); - } else if (['image/png'].includes(mime)) { + } else if (['image/png', 'image/svg+xml'].includes(mime)) { return await convertToPngOrJpeg(path, 498, 280); } else if (mime.startsWith('video/')) { return await GenerateVideoThumbnail(path); } } + if (isWebpublic) { + if (['image/svg+xml'].includes(mime)) { + return await convertToPng(path, 2048, 2048); + } + } + return { data: fs.readFileSync(path), ext, @@ -81,7 +89,7 @@ export default async function(ctx: Koa.Context) { const image = await convertFile(); ctx.body = image.data; - ctx.set('Content-Type', image.type); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); } catch (e) { serverLogger.error(`${e}`); @@ -112,14 +120,14 @@ export default async function(ctx: Koa.Context) { }).toString(); ctx.body = InternalStorage.read(key); - ctx.set('Content-Type', mime); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Content-Disposition', contentDisposition('inline', filename)); } else { const readable = InternalStorage.read(file.accessKey!); readable.on('error', commonReadableHandlerGenerator(ctx)); ctx.body = readable; - ctx.set('Content-Type', file.type); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Content-Disposition', contentDisposition('inline', file.name)); } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 85fe21accb..764306c7d8 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -23,7 +23,7 @@ import Logger from '@/services/logger'; import { envOption } from '../env'; import { UserProfiles, Users } from '@/models/index'; import { networkChart } from '@/services/chart/index'; -import { genAvatar } from '@/misc/gen-avatar'; +import { genIdenticon } from '@/misc/gen-identicon'; import { createTemp } from '@/misc/create-temp'; import { publishMainStream } from '@/services/stream'; import * as Acct from 'misskey-js/built/acct'; @@ -84,9 +84,9 @@ router.get('/avatar/@:acct', async ctx => { } }); -router.get('/random-avatar/:x', async ctx => { +router.get('/identicon/:x', async ctx => { const [temp] = await createTemp(); - await genAvatar(ctx.params.x, fs.createWriteStream(temp)); + await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); ctx.set('Content-Type', 'image/png'); ctx.body = fs.createReadStream(temp); }); diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index 097c6c664d..44f32bf882 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -1,9 +1,8 @@ import * as Router from '@koa/router'; import config from '@/config/index'; import { fetchMeta } from '@/misc/fetch-meta'; -import { Users } from '@/models/index'; -// import User from '../models/user'; -// import Note from '../models/note'; +import { Users, Notes } from '@/models/index'; +import { Not, IsNull, MoreThan } from 'typeorm'; const router = new Router(); @@ -19,20 +18,21 @@ export const links = [/* (awaiting release) { }]; const nodeinfo2 = async () => { + const now = Date.now(); const [ meta, - // total, - // activeHalfyear, - // activeMonth, - // localPosts, - // localComments + total, + activeHalfyear, + activeMonth, + localPosts, + localComments, ] = await Promise.all([ fetchMeta(true), - // User.count({ host: null }), - // User.count({ host: null, updatedAt: { $gt: new Date(Date.now() - 15552000000) } }), - // User.count({ host: null, updatedAt: { $gt: new Date(Date.now() - 2592000000) } }), - // Note.count({ '_user.host': null, replyId: null }), - // Note.count({ '_user.host': null, replyId: { $ne: null } }) + Users.count({ where: { host: null } }), + Users.count({ where: { host: null, updatedAt: MoreThan(new Date(now - 15552000000)) } }), + Users.count({ where: { host: null, updatedAt: MoreThan(new Date(now - 2592000000)) } }), + Notes.count({ where: { userHost: null, replyId: null } }), + Notes.count({ where: { userHost: null, replyId: Not(IsNull()) } }), ]); const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; @@ -50,9 +50,9 @@ const nodeinfo2 = async () => { }, openRegistrations: !meta.disableRegistration, usage: { - users: {}, // { total, activeHalfyear, activeMonth }, - // localPosts, - // localComments + users: { total, activeHalfyear, activeMonth }, + localPosts, + localComments, }, metadata: { nodeName: meta.name, diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index 9e13c0877f..c234b70c55 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -6,6 +6,7 @@ import { createTemp } from '@/misc/create-temp'; import { downloadUrl } from '@/misc/download-url'; import { detectType } from '@/misc/get-file-info'; import { StatusError } from '@/misc/fetch'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; export async function proxyMedia(ctx: Koa.Context) { const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; @@ -18,14 +19,16 @@ export async function proxyMedia(ctx: Koa.Context) { const { mime, ext } = await detectType(path); - if (!mime.startsWith('image/')) throw 403; - let image: IImage; - if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp'].includes(mime)) { + if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) { image = await convertToPng(path, 498, 280); - } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng'].includes(mime)) { + } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) { image = await convertToJpeg(path, 200, 200); + } else if (['image/svg+xml'].includes(mime)) { + image = await convertToPng(path, 2048, 2048); + } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { + throw new StatusError('Rejected type', 403, 'Rejected type'); } else { image = { data: fs.readFileSync(path), diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 5623f7db83..e95a115aec 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -390,9 +390,6 @@ router.get('/cli', async ctx => { const override = (source: string, target: string, depth: number = 0) => [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); -router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1))); -router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games'))); - router.get('/flush', async ctx => { await ctx.render('flush'); }); diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index f97fa521d3..e406449f4f 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -7,7 +7,7 @@ import * as nestedProperty from 'nested-property'; import autobind from 'autobind-decorator'; import Logger from '../logger'; -import { SimpleSchema } from '@/misc/simple-schema'; +import { Schema } from '@/misc/schema'; import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time'; import { getChartInsertLock } from '@/misc/app-lock'; @@ -57,7 +57,7 @@ export default abstract class Chart<T extends Record<string, any>> { diff: DeepPartial<T>; group: string | null; }[] = []; - public schema: SimpleSchema; + public schema: Schema; protected repositoryForHour: Repository<Log>; protected repositoryForDay: Repository<Log>; @@ -71,7 +71,7 @@ export default abstract class Chart<T extends Record<string, any>> { protected abstract fetchActual(group: string | null): Promise<DeepPartial<T>>; @autobind - private static convertSchemaToFlatColumnDefinitions(schema: SimpleSchema) { + private static convertSchemaToFlatColumnDefinitions(schema: Schema) { const columns = {} as Record<string, unknown>; const flatColumns = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { @@ -183,7 +183,7 @@ export default abstract class Chart<T extends Record<string, any>> { } @autobind - public static schemaToEntity(name: string, schema: SimpleSchema, grouped = false): { + public static schemaToEntity(name: string, schema: Schema, grouped = false): { hour: EntitySchema, day: EntitySchema, } { @@ -233,7 +233,7 @@ export default abstract class Chart<T extends Record<string, any>> { }; } - constructor(name: string, schema: SimpleSchema, grouped = false) { + constructor(name: string, schema: Schema, grouped = false) { this.name = name; this.schema = schema; @@ -573,8 +573,8 @@ export default abstract class Chart<T extends Record<string, any>> { } } -export function convertLog(logSchema: SimpleSchema): SimpleSchema { - const v: SimpleSchema = JSON.parse(JSON.stringify(logSchema)); // copy +export function convertLog(logSchema: Schema): Schema { + const v: Schema = JSON.parse(JSON.stringify(logSchema)); // copy if (v.type === 'number') { v.type = 'array'; v.items = { diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index fc43ab29d7..1c1c1fcdff 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -20,7 +20,7 @@ export async function createNotification( const isMuted = profile?.mutingNotificationTypes.includes(type); // Create notification - const notification = await Notifications.save({ + const notification = await Notifications.insert({ id: genId(), createdAt: new Date(), notifieeId: notifieeId, @@ -28,7 +28,8 @@ export async function createNotification( // 相手ãŒã“ã®é€šçŸ¥ã‚’ミュートã—ã¦ã„るよã†ãªã‚‰ã€æ—¢èªã‚’予ã‚ã¤ã‘ã¦ãŠã isRead: isMuted, ...data, - } as Partial<Notification>); + } as Partial<Notification>) + .then(x => Notifications.findOneOrFail(x.identifiers[0])); const packed = await Notifications.pack(notification, {}); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index ee4d51a96d..a89e068f45 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -20,6 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; import * as S3 from 'aws-sdk/clients/s3'; import { getS3 } from './s3'; import * as sharp from 'sharp'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; const logger = driveLogger.createSubLogger('register', 'yellow'); @@ -49,6 +50,12 @@ async function save(file: DriveFile, path: string, name: string, type: string, h if (type === 'image/vnd.mozilla.apng') ext = '.apng'; } + // æ‹¡å¼µåã‹ã‚‰Content-Typeã‚’è¨å®šã—ã¦ãã†ãªæŒ™å‹•を示ã™ã‚ªãƒ–ジェクトストレージ (upcloud?) ã‚‚å˜åœ¨ã™ã‚‹ã®ã§ã€ + // 許å¯ã•れã¦ã„るファイル形å¼ã§ã—ã‹æ‹¡å¼µåã‚’ã¤ã‘ãªã„ + if (!FILE_TYPE_BROWSERSAFE.includes(type)) { + ext = ''; + } + const baseUrl = meta.objectStorageBaseUrl || `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; @@ -94,13 +101,14 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.accessKey = key; file.thumbnailAccessKey = thumbnailKey; file.webpublicAccessKey = webpublicKey; + file.webpublicType = alts.webpublic?.type ?? null; file.name = name; file.type = type; file.md5 = hash; file.size = size; file.storedInternal = false; - return await DriveFiles.save(file); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } else { // use internal storage const accessKey = uuid(); const thumbnailAccessKey = 'thumbnail-' + uuid(); @@ -128,12 +136,13 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.accessKey = accessKey; file.thumbnailAccessKey = thumbnailAccessKey; file.webpublicAccessKey = webpublicAccessKey; + file.webpublicType = alts.webpublic?.type ?? null; file.name = name; file.type = type; file.md5 = hash; file.size = size; - return await DriveFiles.save(file); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } } @@ -160,7 +169,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool } } - if (!['image/jpeg', 'image/png', 'image/webp'].includes(type)) { + if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) { logger.debug(`web image and thumbnail not created (not an required file)`); return { webpublic: null, @@ -201,7 +210,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool webpublic = await convertSharpToJpeg(img, 2048, 2048); } else if (['image/webp'].includes(type)) { webpublic = await convertSharpToWebp(img, 2048, 2048); - } else if (['image/png'].includes(type)) { + } else if (['image/png', 'image/svg+xml'].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); } else { logger.debug(`web image not created (not an required image)`); @@ -220,7 +229,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool try { if (['image/jpeg', 'image/webp'].includes(type)) { thumbnail = await convertSharpToJpeg(img, 498, 280); - } else if (['image/png'].includes(type)) { + } else if (['image/png', 'image/svg+xml'].includes(type)) { thumbnail = await convertSharpToPngOrJpeg(img, 498, 280); } else { logger.debug(`thumbnail not created (not an required file)`); @@ -241,6 +250,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool */ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { if (type === 'image/apng') type = 'image/png'; + if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; const meta = await fetchMeta(); @@ -287,33 +297,45 @@ async function deleteOldFile(user: IRemoteUser) { } } +type AddFileArgs = { + /** User who wish to add file */ + user: { id: User['id']; host: User['host'] } | null; + /** File path */ + path: string; + /** Name */ + name?: string | null; + /** Comment */ + comment?: string | null; + /** Folder ID */ + folderId?: any; + /** If set to true, forcibly upload the file even if there is a file with the same hash. */ + force?: boolean; + /** Do not save file to local */ + isLink?: boolean; + /** URL of source (URLã‹ã‚‰ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã•れãŸå ´åˆ(ãƒãƒ¼ã‚«ãƒ«/リモート)ã®å…ƒURL) */ + url?: string | null; + /** URL of source (リモートインスタンスã®URLã‹ã‚‰ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã•れãŸå ´åˆã®å…ƒURL) */ + uri?: string | null; + /** Mark file as sensitive */ + sensitive?: boolean | null; +}; + /** * Add file to drive * - * @param user User who wish to add file - * @param path File path - * @param name Name - * @param comment Comment - * @param folderId Folder ID - * @param force If set to true, forcibly upload the file even if there is a file with the same hash. - * @param isLink Do not save file to local - * @param url URL of source (URLã‹ã‚‰ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã•れãŸå ´åˆ(ãƒãƒ¼ã‚«ãƒ«/リモート)ã®å…ƒURL) - * @param uri URL of source (リモートインスタンスã®URLã‹ã‚‰ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã•れãŸå ´åˆã®å…ƒURL) - * @param sensitive Mark file as sensitive - * @return Created drive file */ -export default async function( - user: { id: User['id']; host: User['host'] } | null, - path: string, - name: string | null = null, - comment: string | null = null, - folderId: any = null, - force: boolean = false, - isLink: boolean = false, - url: string | null = null, - uri: string | null = null, - sensitive: boolean | null = null -): Promise<DriveFile> { +export async function addFile({ + user, + path, + name = null, + comment = null, + folderId = null, + force = false, + isLink = false, + url = null, + uri = null, + sensitive = null +}: AddFileArgs): Promise<DriveFile> { const info = await getFileInfo(path); logger.info(`${JSON.stringify(info)}`); @@ -428,7 +450,7 @@ export default async function( file.type = info.type.mime; file.storedInternal = false; - file = await DriveFiles.save(file); + file = await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } catch (e) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(e)) { diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 28f42bc344..7c5fa5ce3f 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -1,5 +1,5 @@ import { URL } from 'url'; -import create from './add-file'; +import { addFile } from './add-file'; import { User } from '@/models/entities/user'; import { driveLogger } from './logger'; import { createTemp } from '@/misc/create-temp'; @@ -10,16 +10,27 @@ import { DriveFiles } from '@/models/index'; const logger = driveLogger.createSubLogger('downloader'); -export default async ( - url: string, - user: { id: User['id']; host: User['host'] } | null, - folderId: DriveFolder['id'] | null = null, - uri: string | null = null, +type Args = { + url: string; + user: { id: User['id']; host: User['host'] } | null; + folderId?: DriveFolder['id'] | null; + uri?: string | null; + sensitive?: boolean; + force?: boolean; + isLink?: boolean; + comment?: string | null; +}; + +export async function uploadFromUrl({ + url, + user, + folderId = null, + uri = null, sensitive = false, force = false, - link = false, + isLink = false, comment = null -): Promise<DriveFile> => { +}: Args): Promise<DriveFile> { let name = new URL(url).pathname.split('/').pop() || null; if (name == null || !DriveFiles.validateFileName(name)) { name = null; @@ -41,7 +52,7 @@ export default async ( let error; try { - driveFile = await create(user, path, name, comment, folderId, force, link, url, uri, sensitive); + driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); logger.succ(`Got: ${driveFile.id}`); } catch (e) { error = e; @@ -59,4 +70,4 @@ export default async ( } else { return driveFile!; } -}; +} diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 45bd226588..bc5ac275b5 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -14,6 +14,7 @@ import { instanceChart, perUserFollowingChart } from '@/services/chart/index'; import { genId } from '@/misc/gen-id'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; +import { Packed } from '@/misc/schema'; const logger = new Logger('following/create'); @@ -89,8 +90,8 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ Users.pack(followee.id, follower, { detail: true, }).then(packed => { - publishUserEvent(follower.id, 'follow', packed); - publishMainStream(follower.id, 'follow', packed); + publishUserEvent(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); + publishMainStream(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); }); } diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index cc1abcce19..e45023015d 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -25,7 +25,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur if (blocking != null) throw new Error('blocking'); if (blocked != null) throw new Error('blocked'); - const followRequest = await FollowRequests.save({ + const followRequest = await FollowRequests.insert({ id: genId(), createdAt: new Date(), followerId: follower.id, @@ -39,7 +39,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur followeeHost: followee.host, followeeInbox: Users.isRemoteUser(followee) ? followee.inbox : undefined, followeeSharedInbox: Users.isRemoteUser(followee) ? followee.sharedInbox : undefined, - }); + }).then(x => FollowRequests.findOneOrFail(x.identifiers[0])); // Publish receiveRequest event if (Users.isLocalUser(followee)) { diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index e0baa6a096..47f46419dd 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -81,19 +81,15 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, name: decodedReaction.name, host: decodedReaction.host, }, - select: ['name', 'host', 'url'], + select: ['name', 'host', 'originalUrl', 'publicUrl'], }); - if (emoji) { - emoji = { - name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, - url: emoji.url, - } as any; - } - publishNoteStream(note.id, 'reacted', { reaction: decodedReaction.reaction, - emoji: emoji, + emoji: emoji != null ? { + name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ + } : null, userId: user.id, }); diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index a548ab0497..18b42ed15b 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -16,12 +16,12 @@ export async function registerOrFetchInstanceDoc(host: string): Promise<Instance const index = await Instances.findOne({ host }); if (index == null) { - const i = await Instances.save({ + const i = await Instances.insert({ id: genId(), host, caughtAt: new Date(), lastCommunicatedAt: new Date(), - }); + }).then(x => Instances.findOneOrFail(x.identifiers[0])); federationChart.update(true); diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 422da88846..33a5ef7f9b 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -22,11 +22,11 @@ export async function getRelayActor(): Promise<ILocalUser> { } export async function addRelay(inbox: string) { - const relay = await Relays.save({ + const relay = await Relays.insert({ id: genId(), inbox, status: 'requesting', - }); + }).then(x => Relays.findOneOrFail(x.identifiers[0])); const relayActor = await getRelayActor(); const follow = await renderFollowRelay(relay, relayActor); diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index 7d11ff3f45..c0cefe9af4 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -2,7 +2,6 @@ import { redisClient } from '../db/redis'; import { User } from '@/models/entities/user'; import { Note } from '@/models/entities/note'; import { UserList } from '@/models/entities/user-list'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; import { UserGroup } from '@/models/entities/user-group'; import config from '@/config/index'; import { Antenna } from '@/models/entities/antenna'; @@ -20,8 +19,6 @@ import { MessagingIndexStreamTypes, MessagingStreamTypes, NoteStreamTypes, - ReversiGameStreamTypes, - ReversiStreamTypes, UserListStreamTypes, UserStreamTypes, } from '@/server/api/stream/types'; @@ -90,14 +87,6 @@ class Publisher { this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); }; - public publishReversiStream = <K extends keyof ReversiStreamTypes>(userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => { - this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); - }; - - public publishReversiGameStream = <K extends keyof ReversiGameStreamTypes>(gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => { - this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); - }; - public publishNotesStream = (note: Packed<'Note'>): void => { this.publish('notesStream', null, note); }; @@ -124,6 +113,4 @@ export const publishAntennaStream = publisher.publishAntennaStream; export const publishMessagingStream = publisher.publishMessagingStream; export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; export const publishMessagingIndexStream = publisher.publishMessagingIndexStream; -export const publishReversiStream = publisher.publishReversiStream; -export const publishReversiGameStream = publisher.publishReversiGameStream; export const publishAdminStream = publisher.publishAdminStream; diff --git a/packages/backend/src/tools/add-emoji.ts b/packages/backend/src/tools/add-emoji.ts deleted file mode 100644 index a3f4b54c7e..0000000000 --- a/packages/backend/src/tools/add-emoji.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { initDb } from '@/db/postgre'; -import { genId } from '@/misc/gen-id'; - -async function main(name: string, url: string, alias?: string): Promise<any> { - await initDb(); - const { Emojis } = await import('@/models/index'); - - const aliases = alias != null ? [ alias ] : []; - - await Emojis.save({ - id: genId(), - host: null, - name, - url, - aliases, - updatedAt: new Date(), - }); -} - -const args = process.argv.slice(2); -const name = args[0]; -const url = args[1]; - -if (!name) throw new Error('require name'); -if (!url) throw new Error('require url'); - -main(name, url).then(() => { - console.log('success'); - process.exit(0); -}).catch(e => { - console.warn(e); - process.exit(1); -}); diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml index c045e7c6c4..5f95bec4c0 100644 --- a/packages/backend/test/docker-compose.yml +++ b/packages/backend/test/docker-compose.yml @@ -2,12 +2,12 @@ version: "3" services: redistest: - image: redis:4.0-alpine + image: redis:6 ports: - "127.0.0.1:56312:6379" dbtest: - image: postgres:12.2-alpine + image: postgres:13 ports: - "127.0.0.1:54312:5432" environment: diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 0652d1df3f..99e2e2306e 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -82,14 +82,14 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@eslint/eslintrc@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.4.tgz#dfe0ff7ba270848d10c5add0715e04964c034b31" - integrity sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q== +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.0.0" + espree "^9.2.0" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" @@ -97,19 +97,24 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" - integrity sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A== +"@gar/promisify@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" + integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@koa/cors@3.1.0": version "3.1.0" @@ -134,6 +139,36 @@ methods "^1.1.2" path-to-regexp "^6.1.0" +"@node-redis/bloom@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@node-redis/bloom/-/bloom-1.0.1.tgz#144474a0b7dc4a4b91badea2cfa9538ce0a1854e" + integrity sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw== + +"@node-redis/client@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/client/-/client-1.0.2.tgz#7f09fb739675728fbc6e73536f7cd1be99bf7b8f" + integrity sha512-C+gkx68pmTnxfV+y4pzasvCH3s4UGHNOAUNhdJxGI27aMdnXNDZct7ffDHBL7bAZSGv9FSwCP5PeYvEIEKGbiA== + dependencies: + cluster-key-slot "1.1.0" + generic-pool "3.8.2" + redis-parser "3.0.0" + yallist "4.0.0" + +"@node-redis/json@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/json/-/json-1.0.2.tgz#8ad2d0f026698dc1a4238cc3d1eb099a3bee5ab8" + integrity sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g== + +"@node-redis/search@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/search/-/search-1.0.2.tgz#8cfc91006ea787df801d41410283e1f59027f818" + integrity sha512-gWhEeji+kTAvzZeguUNJdMSZNH2c5dv3Bci8Nn2f7VGuf6IvvwuZDSBOuOlirLVgayVuWzAG7EhwaZWK1VDnWQ== + +"@node-redis/time-series@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@node-redis/time-series/-/time-series-1.0.1.tgz#703149f8fa4f6fff377c61a0873911e7c1ba5cc3" + integrity sha512-+nTn6EewVj3GlUXPuD3dgheWqo219jTxlo6R+pg24OeVvFHx9aFGGiyOgj3vBPhWUdRZ0xMcujXV5ki4fbLyMw== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -155,6 +190,14 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.0.tgz#bec1d1b89c170d40e1b73ad6c943b0b75e7d2951" + integrity sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" @@ -163,29 +206,30 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" "@sindresorhus/is@^3.0.0": @@ -245,11 +289,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - "@tsconfig/node10@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.7.tgz#1eb1de36c73478a2479cc661ef5af1c16d86d606" @@ -295,12 +334,13 @@ "@types/connect" "*" "@types/node" "*" -"@types/bull@3.15.5": - version "3.15.5" - resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.5.tgz#a4459c127c5b10fb847531579a2cd5db35751366" - integrity sha512-XgJQWJ03jyKMfdoL8IAIoHIo7JkkL74kcxuujTONkSJswm0giIJ9kuVgDNHS0OvD+OiPNcFmbBl0H3scj2+A8A== +"@types/bull@3.15.7": + version "3.15.7" + resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.7.tgz#a9d7fb332cc02dc021d0eb234b9604b356e9e6de" + integrity sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g== dependencies: "@types/ioredis" "*" + "@types/redis" "^2.8.0" "@types/cacheable-request@^6.0.1": version "6.0.1" @@ -356,10 +396,10 @@ resolved "https://registry.yarnpkg.com/@types/disposable-email-domains/-/disposable-email-domains-1.0.2.tgz#0280f6b38fa7f14e54b056a434135ecd254483b1" integrity sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw== -"@types/escape-regexp@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3" - integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg== +"@types/escape-regexp@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" + integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -400,10 +440,10 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -442,15 +482,15 @@ resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a" integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw== -"@types/js-yaml@4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.4.tgz#cc38781257612581a1a0eb25f1709d2b06812fce" - integrity sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ== +"@types/js-yaml@4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== -"@types/jsdom@16.2.13": - version "16.2.13" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.13.tgz#126c8b7441b159d6234610a48de77b6066f1823f" - integrity sha512-8JQCjdeAidptSsOcRWk2iTm9wCcwn9l+kRG6k5bzUacrnm1ezV4forq0kWjUih/tumAeoG+OspOvQEbbRucBTw== +"@types/jsdom@16.2.14": + version "16.2.14" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.14.tgz#26fe9da6a8870715b154bb84cd3b2e53433d8720" + integrity sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w== dependencies: "@types/node" "*" "@types/parse5" "*" @@ -493,10 +533,10 @@ dependencies: "@types/node" "*" -"@types/koa-bodyparser@4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.3.tgz#9c7d4295576bc863d550002f732f1c57dd88cc58" - integrity sha512-/ileIpXsy1fFEzgZhZ07eZH8rAVL7jwuk/kaoVEfauO6s80g2LIDIJKEyDbuAL9S/BWflKzEC0PHD6aXkmaSbw== +"@types/koa-bodyparser@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.5.tgz#0c5fa44d7150202ffc16b89bd730ce1b6c7bc250" + integrity sha512-NRqqoTtt7cfdDk/KNo+EwCIKRuzPAu/wsaZ7tgIvSIBtNfxuZHYueaLoWdxX3ZftWavQv07NE46TcpyoZGqpgQ== dependencies: "@types/koa" "*" @@ -577,10 +617,10 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/koa__cors@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.0.3.tgz#49d75813b443ba3d4da28ea6cf6244b7e99a3b23" - integrity sha512-74Xb4hJOPGKlrQ4PRBk1A/p0gfLpgbnpT0o67OMVbwyeMXvlBN+ZCRztAAmkKZs+8hKbgMutUlZVbA52Hr/0IA== +"@types/koa__cors@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.1.1.tgz#198b5abbc425a672ae57c311b420bc270e65bdef" + integrity sha512-O7MBkCocnLrpEvkMrYAp17arUDS+KuS5bXMG/Z4aPSbrO7vrYB6YrqcsTD3Dp2OnAL3j4WME2k/x2kOcyzwNUw== dependencies: "@types/koa" "*" @@ -591,10 +631,10 @@ dependencies: "@types/koa" "*" -"@types/koa__router@8.0.8": - version "8.0.8" - resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.8.tgz#b1e0e9a512498777d3366bbdf0e853df27ec831c" - integrity sha512-9pGCaDtzCsj4HJ8HmGuqzk8+s57sPj4njWd08GG5o92n5Xp9io2snc40CPpXFhoKcZ8OKhuu6ht4gNou9e1C2w== +"@types/koa__router@8.0.11": + version "8.0.11" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e" + integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ== dependencies: "@types/koa" "*" @@ -613,23 +653,22 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== -"@types/node-fetch@2.5.12": - version "2.5.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" - integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== +"@types/node-fetch@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-3.0.3.tgz#9d969c9a748e841554a40ee435d26e53fa3ee899" + integrity sha512-HhggYPH5N+AQe/OmN6fmhKmRRt2XuNJow+R3pQwJxOOF9GuwM7O2mheyGeIrs5MOIeNjDEdgdoyHBOrFeJBR3g== dependencies: - "@types/node" "*" - form-data "^3.0.0" + node-fetch "*" "@types/node@*": version "16.6.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@16.11.7": - version "16.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" - integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== +"@types/node@17.0.10": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.10.tgz#616f16e9d3a2a3d618136b1be244315d95bd7cab" + integrity sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog== "@types/node@^14.11.8": version "14.17.9" @@ -665,20 +704,20 @@ resolved "https://registry.yarnpkg.com/@types/portscanner/-/portscanner-2.1.1.tgz#89d5094e16f3d941f20f3889dfa5d3a164b3dd3b" integrity sha512-1NsVIbgBKvrqxwtMN0V6CLji1ERwKSI/RWz0J3y++CzSwYNGBStCfpIFgxV3ZwxsDR5PoZqoUWhwraDm+Ztn0Q== -"@types/pug@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.5.tgz#69bc700934dd473c7ab97270bd2dbacefe562231" - integrity sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA== +"@types/pug@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6" + integrity sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg== "@types/punycode@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== -"@types/qrcode@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.1.tgz#0689f400c3a95d2db040c99c99834faa09ee9dc1" - integrity sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA== +"@types/qrcode@1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" + integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ== dependencies: "@types/node" "*" @@ -697,10 +736,12 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== -"@types/ratelimiter@3.4.2": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@types/ratelimiter/-/ratelimiter-3.4.2.tgz#adf1a6d0cbe72d42207efc510a9170602e23456c" - integrity sha512-iz+yyY+ViphaM8ZwrX1mUQzelIeC59zyaaLKTJ0YVOOCkCpIYpaysiIM4z5Xv9HdXYqIb80S+DhH7J22A0rW2w== +"@types/ratelimiter@3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@types/ratelimiter/-/ratelimiter-3.4.3.tgz#2159c234b9d75bcc2be39379f05c6af0a5e4a3b7" + integrity sha512-B/IRdHGcttRsDeDJ4+VFjzRA1mzqTxsYlg2X8GLQtTgRUMhQQc+bL8zFmuHhZkK4oA+Ldb4K1NogspNDxevWBA== + dependencies: + "@types/redis" "^2.8.0" "@types/readable-stream@^2.3.9": version "2.3.9" @@ -710,7 +751,14 @@ "@types/node" "*" safe-buffer "*" -"@types/redis@2.8.32": +"@types/redis@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1" + integrity sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg== + dependencies: + redis "*" + +"@types/redis@^2.8.0": version "2.8.32" resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11" integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w== @@ -736,10 +784,10 @@ dependencies: "@types/node" "*" -"@types/sanitize-html@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.5.0.tgz#bfef58fbcf2674b20ffcc23c3506faa68c3a13e3" - integrity sha512-PeFIEZsO9m1+ACJlXUaimgrR+5DEDiIXhz7Hso307jmq5Yz0lb5kDp8LiTr5dMMMliC/jNNx/qds7Zoxa4zexw== +"@types/sanitize-html@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.6.2.tgz#9c47960841b9def1e4c9dfebaaab010a3f6e97b9" + integrity sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ== dependencies: htmlparser2 "^6.0.0" @@ -756,10 +804,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/sharp@0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.3.tgz#54ceb81b68bb99a0e62db2b52f4fb0bea84878ac" - integrity sha512-83Xp05eK2hvfNnmKLr2Fz0C2A0jrr2TnSLqKRbkLTYuAu+Erj6mKQLoEMGafE73Om8p3q3ryZxtHFM/7hy4Adg== +"@types/sharp@0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3" + integrity sha512-3TC+S3H5RwnJmLYMHrcdfNjz/CaApKmujjY9b6PU/pE6n0qfooi99YqXGWoW8frU9EWYj/XTI35Pzxa+ThAZ5Q== dependencies: "@types/node" "*" @@ -773,10 +821,10 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== -"@types/speakeasy@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/speakeasy/-/speakeasy-2.0.6.tgz#12540f7b64d08180393ae2c5c8c280866a85da61" - integrity sha512-2wIXZp5yJUddhsSZarYCZIakCvzwQgTVdtT29DYVdFzc0cHttanaQx9THRhtjY4kDqVaF2jhyFOEofozOioFdQ== +"@types/speakeasy@2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/speakeasy/-/speakeasy-2.0.7.tgz#cb087c501b3eef744a1ae620c19812dd1c3b2f3f" + integrity sha512-JEcOhN2SQCoX86ZfiZEe8px84sVJtivBXMZfOVyARTYEj0hrwwbj1nF0FwEL3nJSoEV6uTbcdLllMKBgAYHWCQ== dependencies: "@types/node" "*" @@ -795,10 +843,10 @@ resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== -"@types/tmp@0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.2.tgz#424537a3b91828cb26aaf697f21ae3cd1b69f7e7" - integrity sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A== +"@types/tmp@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" + integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA== "@types/tough-cookie@*": version "4.0.0" @@ -812,10 +860,10 @@ dependencies: source-map "^0.6.1" -"@types/uuid@8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" - integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/web-push@3.3.2": version "3.3.2" @@ -869,10 +917,10 @@ dependencies: "@types/node" "*" -"@types/ws@8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.0.tgz#75faefbe2328f3b833cb8dc640658328990d04f3" - integrity sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg== +"@types/ws@8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" + integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== dependencies: "@types/node" "*" @@ -881,13 +929,14 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== -"@typescript-eslint/eslint-plugin@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz#d8ff412f10f54f6364e7fd7c1e70eb6767f434c3" - integrity sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw== +"@typescript-eslint/eslint-plugin@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz#e90afea96dff8620892ad216b0e4ccdf8ee32d3a" + integrity sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ== dependencies: - "@typescript-eslint/experimental-utils" "5.3.1" - "@typescript-eslint/scope-manager" "5.3.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/type-utils" "5.10.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -895,94 +944,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz#bbd8f9b67b4d5fdcb9d2f90297d8fcda22561e05" - integrity sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.3.1" - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/typescript-estree" "5.3.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.1.0.tgz#6c7f837d210d2bc0a811e7ea742af414f4e00908" - integrity sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@typescript-eslint/scope-manager" "5.1.0" - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/typescript-estree" "5.1.0" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz#6f1f26ad66a8f71bbb33b635e74fec43f76b44df" - integrity sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/visitor-keys" "5.1.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/scope-manager@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz#3cfbfbcf5488fb2a9a6fbbe97963ee1e8d419269" - integrity sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg== +"@typescript-eslint/type-utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz#8524b9479c19c478347a7df216827e749e4a51e5" + integrity sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ== dependencies: - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/visitor-keys" "5.3.1" - -"@typescript-eslint/types@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.1.0.tgz#a8a75ddfc611660de6be17d3ad950302385607a9" - integrity sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA== - -"@typescript-eslint/types@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.3.1.tgz#afaa715b69ebfcfde3af8b0403bf27527912f9b7" - integrity sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ== - -"@typescript-eslint/typescript-estree@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz#132aea34372df09decda961cb42457433aa6e83d" - integrity sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ== - dependencies: - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/visitor-keys" "5.1.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz#50cc4bfb93dc31bc75e08ae52e29fcb786d606ec" - integrity sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ== +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== + +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== dependencies: - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/visitor-keys" "5.3.1" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz#e01a01b27eb173092705ae983aa1451bd1842630" - integrity sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w== +"@typescript-eslint/utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.0.tgz#c3d152a85da77c400e37281355561c72fb1b5a65" + integrity sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg== dependencies: - "@typescript-eslint/types" "5.1.0" - eslint-visitor-keys "^3.0.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz#c2860ff22939352db4f3806f34b21d8ad00588ba" - integrity sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ== +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/types" "5.3.1" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -1189,12 +1213,12 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== -acorn@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1233,12 +1257,7 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-colors@4.1.1, ansi-colors@^4.1.1: +ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== @@ -1253,11 +1272,6 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -1268,7 +1282,7 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -1311,6 +1325,11 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + archiver-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" @@ -1340,6 +1359,14 @@ archiver@5.3.0: tar-stream "^2.2.0" zip-stream "^4.1.0" +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -1353,13 +1380,6 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1449,11 +1469,6 @@ autobind-decorator@2.4.0, autobind-decorator@^2.4.0: resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== -autosize@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.4.tgz#924f13853a466b633b9309330833936d8bccce03" - integrity sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ== - autwh@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/autwh/-/autwh-0.1.0.tgz#24a5300923309d105133401a2568f9c8ab7d7e03" @@ -1461,27 +1476,27 @@ autwh@0.1.0: dependencies: oauth "0.9.15" -aws-sdk@2.1013.0: - version "2.1013.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1013.0.tgz#85babc473b0bc90cc1160eb48baf616ddb86e346" - integrity sha512-TXxkp/meAdofpC15goFpNuur7fvh/mcMRfHJoP1jYzTtD0wcoB4FK16GLcny0uDYgkQgZuiO9QYv3Rq5bhGCqQ== +aws-sdk@2.1061.0: + version "2.1061.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1061.0.tgz#79c75e6856e5a59e0857d0d066a8ff5ff5e0d752" + integrity sha512-T29yV+EPo4Fis9hAArxAXS/u6utKnlBq3DEu85LTSIA8i6e6Xg7e9u7Rveo8DmrlVrf7EGCNThaeF9WERHnwLg== dependencies: buffer "4.9.2" events "1.1.1" ieee754 "1.1.13" - jmespath "0.15.0" + jmespath "0.16.0" querystring "0.2.0" sax "1.2.1" url "0.10.3" uuid "3.3.2" xml2js "0.4.19" -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.14.4" babel-walk@3.0.0-canary-5: version "3.0.0-canary-5" @@ -1527,6 +1542,11 @@ big-integer@^1.6.16: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== +big-integer@^1.6.17: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1537,6 +1557,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bl@^4.0.1, bl@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" @@ -1551,6 +1579,11 @@ bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + blurhash@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.4.tgz#a7010ceb3019cd2c9809b17c910ebf6175d29244" @@ -1561,7 +1594,7 @@ bn.js@^4.0.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -1581,16 +1614,17 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -broadcast-channel@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.5.0.tgz#d4717c493e219908fcb7f2f9078fe0baf95b77c1" - integrity sha512-jp+VPlQ1HyR0CM3uIYUrdpXupBvhTMFRkjR6mEmt5W4HaGDPFEzrO2Jqvi2PZ6zCC4zwLeco7CC5EUJPrVH8Tw== +broadcast-channel@4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.9.0.tgz#8af337d4ea19aeb6b819ec2eb3dda942b28c724c" + integrity sha512-xWzFb3wrOZGJF2kOSs2D3KvHXdLDMVb+WypEIoNvwblcHgUBydVy65pDJ9RS4WN9Kyvs0UVQuCCzfKme0G6Qjw== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" microseconds "0.2.0" nano-time "1.0.0" oblivious-set "1.0.0" + p-queue "6.6.2" rimraf "3.0.2" unload "2.3.1" @@ -1604,7 +1638,7 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.0.0, browserslist@^4.14.5: +browserslist@^4.14.5: version "4.16.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== @@ -1615,48 +1649,6 @@ browserslist@^4.0.0, browserslist@^4.14.5: escalade "^3.1.1" node-releases "^1.1.70" -browserslist@^4.16.0: - version "4.16.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.4.tgz#7ebf913487f40caf4637b892b268069951c35d58" - integrity sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ== - dependencies: - caniuse-lite "^1.0.30001208" - colorette "^1.2.2" - electron-to-chromium "^1.3.712" - escalade "^3.1.1" - node-releases "^1.1.71" - -browserslist@^4.16.6: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== - dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" - escalade "^3.1.1" - node-releases "^1.1.71" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1667,21 +1659,16 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - -buffer-from@1.x: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-from@^1.0.0, buffer-from@^1.1.1: +buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -1696,7 +1683,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.4.3, buffer@^5.5.0: +buffer@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -1712,6 +1699,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1719,10 +1711,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.1.0.tgz#ff8f628694e7dbbdf89b6a6860a1f33e990527fd" - integrity sha512-rQcLuAmzZIv1dHJO/yKrWu497xcTxMpYeTEBfpStrJFZ1UZpBSGHSx+defbtFVeGEeY8Pn0aMGRvRtldUBVUyQ== +bull@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.2.1.tgz#c5a7e1496c7903274ce90192e4e5cb18f6c866c0" + integrity sha512-YkCQZMOub++siHw3SbYYXZ5xGEn6Tt3BPoCVq/irPNCxUqUYzta8yDlXyyAsfMKMVj0M7PcnynUabfMf9PFpOA== dependencies: cron-parser "^2.13.0" debuglog "^1.0.0" @@ -1730,9 +1722,7 @@ bull@4.1.0: ioredis "^4.27.0" lodash "^4.17.21" p-timeout "^3.2.0" - promise.prototype.finally "^3.1.2" semver "^7.3.2" - util.promisify "^1.0.1" uuid "^8.3.0" busboy@^0.2.11: @@ -1748,11 +1738,12 @@ bytes@3.1.0, bytes@^3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^15.0.5: - version "15.1.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.1.0.tgz#164c2f857ee606e4cc793c63018fefd0ea5eba7b" - integrity sha512-mfx0C+mCfWjD1PnwQ9yaOrwG1ou9FkKnx0SvzUHWdFt7r7GaRtzT+9M8HAvLu62zIHtnpQ/1m93nWNDCckJGXQ== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== dependencies: + "@npmcli/fs" "^1.0.0" "@npmcli/move-file" "^1.0.1" chownr "^2.0.0" fs-minipass "^2.0.0" @@ -1830,36 +1821,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0: - version "1.0.30001048" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" - integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== - caniuse-lite@^1.0.30001181: version "1.0.30001191" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9" integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw== -caniuse-lite@^1.0.30001208: - version "1.0.30001208" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9" - integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== - -caniuse-lite@^1.0.30001219: - version "1.0.30001230" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" - integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== - canonicalize@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.1.tgz#657b4f3fa38a6ecb97a9e5b7b26d7a19cc6e0da9" @@ -1880,6 +1846,13 @@ cbor@8.1.0: dependencies: nofilter "^3.1.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" @@ -1925,23 +1898,6 @@ character-parser@^2.2.0: dependencies: is-regex "^1.0.3" -chart.js@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.6.0.tgz#a87fce8431d4e7c5523d721f487f53aada1e42fe" - integrity sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ== - -chartjs-adapter-date-fns@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b" - integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw== - -chartjs-plugin-zoom@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.1.1.tgz#8a28923a17fcb5eb57a0dc94c5113bf402677647" - integrity sha512-1q54WOzK7FtAjkbemQeqvmFUV0btNYIQny2HbQ6Awq9wUtCz7Zmj6vIgp3C1DYMQwN0nqgpC3vnApqiwI7cSdQ== - dependencies: - hammerjs "^2.0.8" - cheerio@0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -2013,14 +1969,14 @@ cli-highlight@2.1.11, cli-highlight@^2.1.11: parse5-htmlparser2-tree-adapter "^6.0.0" yargs "^16.0.0" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" cliui@^7.0.2: version "7.0.4" @@ -2038,7 +1994,7 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -cluster-key-slot@^1.1.0: +cluster-key-slot@1.1.0, cluster-key-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== @@ -2105,6 +2061,11 @@ color-string@^1.6.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/color/-/color-4.0.1.tgz#21df44cd10245a91b1ccf5ba031609b0e10e7d67" @@ -2113,12 +2074,7 @@ color@^4.0.1: color-convert "^2.0.1" color-string "^1.6.0" -colord@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e" - integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw== - -colorette@^1.2.0, colorette@^1.2.1, colorette@^1.2.2: +colorette@^1.2.0, colorette@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -2135,21 +2091,11 @@ commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.2.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -compare-versions@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - compress-commons@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" @@ -2192,7 +2138,7 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -2212,7 +2158,14 @@ constantinople@^4.0.1: "@babel/parser" "^7.6.0" "@babel/types" "^7.6.1" -content-disposition@0.5.3, content-disposition@~0.5.2: +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -2287,43 +2240,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-color-names@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67" - integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA== - -css-declaration-sorter@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.0.3.tgz#9dfd8ea0df4cc7846827876fafb52314890c21a9" - integrity sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw== - dependencies: - timsort "^0.3.0" - -css-loader@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1" - integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ== - dependencies: - icss-utils "^5.1.0" - postcss "^8.2.15" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - semver "^7.3.5" - -css-select@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" - integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== - dependencies: - boolbase "^1.0.0" - css-what "^5.0.0" - domhandler "^4.2.0" - domutils "^2.6.0" - nth-check "^2.0.0" - css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -2334,86 +2250,11 @@ css-select@~1.2.0: domutils "1.5.1" nth-check "~1.0.1" -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-what@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.6.tgz#1bdb83be6a6b1fee6dc5e9ec2e61286bcadcc7a6" - integrity sha512-X2nDeNGBXc0486oHjT2vSj+TdeyVsxRvJUxaOH50hOM6vSDLkKd0+59YXpSZRInJ4sNtBOykS4KsPfhdrU/35w== - dependencies: - css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.1" - postcss-convert-values "^5.0.2" - postcss-discard-comments "^5.0.1" - postcss-discard-duplicates "^5.0.1" - postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" - postcss-merge-longhand "^5.0.3" - postcss-merge-rules "^5.0.2" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.1" - postcss-minify-selectors "^5.1.0" - postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" - postcss-normalize-url "^5.0.2" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.1" - postcss-reduce-transforms "^5.0.1" - postcss-svgo "^5.0.3" - postcss-unique-selectors "^5.0.1" - -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== - -cssnano@5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.10.tgz#92207eb7c9c6dc08d318050726f9fad0adf7220b" - integrity sha512-YfNhVJJ04imffOpbPbXP2zjIoByf0m8E2c/s/HnvSvjXgzXMfgopVjAEGvxYOjkOpWuRQDg/OZFjO7WW94Ri8w== - dependencies: - cssnano-preset-default "^5.1.6" - is-resolvable "^1.1.0" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -2451,6 +2292,11 @@ data-uri-to-buffer@^3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -2460,11 +2306,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@2.25.0: - version "2.25.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" - integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w== - dateformat@4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.5.1.tgz#c20e7a9ca77d147906b6dc2261a8be0a5bd2173c" @@ -2491,13 +2332,6 @@ debug@4.3.3: dependencies: ms "2.1.2" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2539,13 +2373,6 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -2553,17 +2380,15 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -deep-email-validator@0.1.18: - version "0.1.18" - resolved "https://registry.yarnpkg.com/deep-email-validator/-/deep-email-validator-0.1.18.tgz#a072a93f28e11863cc6b9ca3ae964e0e45b3ece8" - integrity sha512-eo2WEUidQvppg6Qdek8iwOqmXvaxRJ2D2VJKbIOwUgLZNFveDDdJMBsFc+yq0S+lILEUcmzrJRrCWbyoe7QUzQ== +deep-email-validator@0.1.21: + version "0.1.21" + resolved "https://registry.yarnpkg.com/deep-email-validator/-/deep-email-validator-0.1.21.tgz#5d0120fe1aeae83ab7cb39378a40a381b681219f" + integrity sha512-DBAmMzbr+MAubXQ+TS9tZuPwLcdKscb8YzKZiwoLqF3NmaeEgXvSSHhZ0EXOFeKFE2FNWC4mNXCyiQ/JdFXUwg== dependencies: "@types/disposable-email-domains" "^1.0.1" - axios "^0.19.2" - disposable-email-domains "^1.0.53" - lodash "^4.17.15" + axios "^0.24.0" + disposable-email-domains "^1.0.59" mailcheck "^1.1.1" - ts-jest "^25.2.1" deep-equal@~1.0.1: version "1.0.1" @@ -2590,7 +2415,7 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2677,10 +2502,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -disposable-email-domains@^1.0.53: - version "1.0.58" - resolved "https://registry.yarnpkg.com/disposable-email-domains/-/disposable-email-domains-1.0.58.tgz#ac9c879c02c4f0898bfb6c0c80b959c0b0b7bc51" - integrity sha512-frnNCPqTjk6t/sosPoco6EIFHbP9SazHQkeltJNfZeUyNgewaVf+kFjEfVkVDVd436Vln43YElJPb8JozhBs7Q== +disposable-email-domains@^1.0.59: + version "1.0.59" + resolved "https://registry.yarnpkg.com/disposable-email-domains/-/disposable-email-domains-1.0.59.tgz#8b3670667dcef9d0d21b224de283d56d468913c2" + integrity sha512-45NbOP1Oboaddf0pD5mGnT+1msEifY6VUcR9Msq4zBHk2EeGv9PxiwuoynIfdGID1BSFR3U3egPfMbERkqXxUQ== doctrine@^2.1.0: version "2.1.0" @@ -2785,7 +2610,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.6.0: +domutils@^2.5.2: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -2799,6 +2624,13 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -2834,21 +2666,6 @@ electron-to-chromium@^1.3.649: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.672.tgz#3a6e335016dab4bc584d5292adc4f98f54541f6a" integrity sha512-gFQe7HBb0lbOMqK2GAS5/1F+B0IMdYiAgB9OT/w1F4M7lgJK2aNOMNOM622aEax+nS1cTMytkiT0uMOkbtFmHw== -electron-to-chromium@^1.3.712: - version "1.3.717" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz#78d4c857070755fb58ab64bcc173db1d51cbc25f" - integrity sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ== - -electron-to-chromium@^1.3.723: - version "1.3.742" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.742.tgz#7223215acbbd3a5284962ebcb6df85d88b95f200" - integrity sha512-ihL14knI9FikJmH2XUIDdZFWJxvr14rPSdOhJ7PpS27xbz8qmaRwCwyg/bmFwjWKmWK9QyamiCZVCvXm5CH//Q== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2859,6 +2676,11 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2894,13 +2716,6 @@ enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -2921,23 +2736,6 @@ err-code@^2.0.2: resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== -es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -3049,43 +2847,32 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" -eslint-plugin-import@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" - -eslint-plugin-vue@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.0.3.tgz#791cc4543940319e612ea61a1d779e8c87cf749a" - integrity sha512-Rlhhy5ltzde0sRwSkqHuNePTXLMMaJ5+qsQubM4RYloYsQ8cXlnJT5MDaCzSirkGADipOHtmQXIbbPFAzUrADg== - dependencies: - eslint-utils "^3.0.0" - natural-compare "^1.4.0" - semver "^7.3.5" - vue-eslint-parser "^8.0.1" + tsconfig-paths "^3.12.0" eslint-scope@^5.1.1: version "5.1.1" @@ -3095,10 +2882,10 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" - integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -3120,24 +2907,28 @@ eslint-visitor-keys@^3.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== -eslint@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.2.0.tgz#44d3fb506d0f866a506d97a0fc0e90ee6d06a815" - integrity sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw== +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" + integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== dependencies: - "@eslint/eslintrc" "^1.0.4" - "@humanwhocodes/config-array" "^0.6.0" + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^6.0.0" + eslint-scope "^7.1.0" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3145,7 +2936,7 @@ eslint@8.2.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -3156,9 +2947,7 @@ eslint@8.2.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" @@ -3169,16 +2958,16 @@ esm@^3.2.22: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" - integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== dependencies: - acorn "^8.5.0" + acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.0.0" + eslint-visitor-keys "^3.1.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3222,7 +3011,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@4.0.7, eventemitter3@^4.0.7: +eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -3315,7 +3104,7 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3349,6 +3138,14 @@ fetch-blob@^2.1.1: resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" + integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3395,12 +3192,13 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - locate-path "^3.0.0" + locate-path "^5.0.0" + path-exists "^4.0.0" findup-sync@^4.0.0: version "4.0.0" @@ -3438,12 +3236,10 @@ fluent-ffmpeg@2.1.2: async ">=0.2.9" which "^1.1.1" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" +follow-redirects@^1.14.4: + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== form-data@^3.0.0: version "3.0.0" @@ -3454,6 +3250,13 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3490,6 +3293,16 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3500,6 +3313,21 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gauge@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.0.tgz#afba07aa0374a93c6219603b1fb83eaa2264d8f8" + integrity sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw== + dependencies: + ansi-regex "^5.0.1" + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -3514,6 +3342,11 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +generic-pool@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9" + integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg== + get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -3700,7 +3533,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.0: +graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -3715,11 +3548,6 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -hammerjs@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" - integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -3735,7 +3563,7 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -3752,7 +3580,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -3875,13 +3703,13 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.5.tgz#9f19496ffbf3227298d7b5f156e0e1a948678683" - integrity sha512-NwoTQYSJoFt34jSBbwzDHDofoA61NGXzu6wXh95o1Ry62EnmKjXb/nR/RknLeZ3G/uGwrlKNY2z7uPt+Cdl7Tw== +http-signature@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== dependencies: assert-plus "^1.0.0" - jsprim "^1.2.2" + jsprim "^2.0.2" sshpk "^1.14.1" http2-wrapper@^1.0.0-beta.5.0: @@ -3946,18 +3774,6 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -idb-keyval@5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.3.tgz#6ef5dff371897c23f144322dc6374eadd6a345d9" - integrity sha512-N9HbCK/FaXSRVI+k6Xq4QgWxbcZRUv+SfG1y7HJ28JdV8yEJu6k+C/YLea7npGckX2DQJeEVuMc4bKOBeU/2LQ== - dependencies: - safari-14-idb-fix "^1.0.4" - ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -3983,6 +3799,11 @@ ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -4001,11 +3822,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -4024,7 +3840,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4039,15 +3855,10 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== -insert-text-at-cursor@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da" - integrity sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ== - -install-artifact-from-github@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz#adcbd123c16a4337ec44ea76d0ebf253cc16b074" - integrity sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA== +install-artifact-from-github@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.0.tgz#cab6ff821976b8a35b0c079da19a727c90381a40" + integrity sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA== internal-slot@^1.0.3: version "1.0.3" @@ -4105,11 +3916,6 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" @@ -4142,7 +3948,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.1.5: +is-callable@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== @@ -4288,7 +4094,7 @@ is-promise@^2.0.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3, is-regex@^1.0.5: +is-regex@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -4303,11 +4109,6 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-resolvable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-shared-array-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" @@ -4325,10 +4126,10 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-svg@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.1.tgz#8c63ec8c67c8c7f0a8de0a71c8c7d58eccf4406b" - integrity sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA== +is-svg@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.2.tgz#a119e9932e1af53f6be1969d1790d6cc5fd947d3" + integrity sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw== dependencies: fast-xml-parser "^3.19.0" @@ -4378,11 +4179,6 @@ isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -4397,10 +4193,10 @@ jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jmespath@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" - integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== jpeg-js@^0.4.1: version "0.4.1" @@ -4442,14 +4238,6 @@ js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsbn@1.1.0, jsbn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -4518,10 +4306,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -4542,7 +4330,7 @@ json5-loader@4.0.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -json5@2.2.0, json5@2.x: +json5@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -4589,14 +4377,14 @@ jsonld@5.2.0: lru-cache "^6.0.0" rdf-canonize "^3.0.0" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" jsrsasign@8.0.20: @@ -4817,10 +4605,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lilconfig@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" - integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= loader-runner@^4.2.0: version "4.2.0" @@ -4844,13 +4632,12 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" @@ -4919,11 +4706,6 @@ lodash.map@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= -lodash.memoize@4.x, lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.merge@^4.4.0, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -4959,12 +4741,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5001,18 +4778,18 @@ mailcheck@^1.1.1: resolved "https://registry.yarnpkg.com/mailcheck/-/mailcheck-1.1.1.tgz#d87cf6ba0b64ba512199dbf93f1489f479591e34" integrity sha1-2Hz2ugtkulEhmdv5PxSJ9HlZHjQ= -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-fetch-happen@^8.0.14: - version "8.0.14" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" - integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== dependencies: agentkeepalive "^4.1.3" - cacache "^15.0.5" + cacache "^15.2.0" http-cache-semantics "^4.1.0" http-proxy-agent "^4.0.1" https-proxy-agent "^5.0.0" @@ -5023,15 +4800,11 @@ make-fetch-happen@^8.0.14: minipass-fetch "^1.3.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" + negotiator "^0.6.2" promise-retry "^2.0.1" - socks-proxy-agent "^5.0.0" + socks-proxy-agent "^6.0.0" ssri "^8.0.0" -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5057,21 +4830,13 @@ methods@^1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mfm-js@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.20.0.tgz#3afdcd7959461fd825aa8af9b9e8a57cdbddc290" - integrity sha512-1+3tV3nWUKQNh/ztX3wXu5iLBtdsg6q3wUhl+XyOhc2H3sQdG+sih/w2c0nR9TIawjN+Z1/pvgGzxMJHfmKQmA== +mfm-js@0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" + integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== dependencies: twemoji-parser "13.1.x" -micromatch@4.x: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -5119,11 +4884,6 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -5207,10 +4967,10 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -misskey-js@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.8.tgz#b47a1ec54bff96b23cc2a6b307a3895f99a94424" - integrity sha512-Q1L6FaroVz8kpW7T4xQyJmJKSwjOYPbNY3TspOUWmbIBDf2JP0HVeKEFLI9dvdSL0kSkdQNz3MSVLjlHiyPqLQ== +misskey-js@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.13.tgz#03a4e469186e28752d599dc4093519eb64647970" + integrity sha512-kBdJdfe281gtykzzsrN3IAxWUQIimzPiJGyKWf863ggWJlWYVPmP9hTFlX2z8oPOaypgVBPEPHyw/jNUdc2DbQ== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" @@ -5226,7 +4986,7 @@ mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@0.x, mkdirp@^0.5.4: +"mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5301,10 +5061,10 @@ ms@3.0.0-canary.1: resolved "https://registry.yarnpkg.com/ms/-/ms-3.0.0-canary.1.tgz#c7b34fbce381492fd0b345d1cf56e14d67b77b80" integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== -multer@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.3.tgz#4db352d6992e028ac0eacf7be45c6efd0264297b" - integrity sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg== +multer@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c" + integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw== dependencies: append-field "^1.0.0" busboy "^0.2.11" @@ -5329,10 +5089,10 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.14.2: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nan@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nano-time@1.0.0: version "1.0.0" @@ -5346,11 +5106,6 @@ nanoid@3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== -nanoid@^3.1.23: - version "3.1.23" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== - nanoid@^3.1.30: version "3.1.30" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" @@ -5375,7 +5130,7 @@ needle@^2.5.2: iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.2: +negotiator@0.6.2, negotiator@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== @@ -5405,18 +5160,32 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -node-abi@^2.21.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" - integrity sha512-smhrivuPqEM3H5LmnY3KU6HfYv0u4QklgAxfFyRNujKUzbUcYZ+Jc2EhukB9SRcD2VpqhxM7n/MIcp1Ua1/JMg== +node-abi@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.5.0.tgz#26e8b7b251c3260a5ac5ba5aef3b4345a0229248" + integrity sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw== dependencies: - semver "^5.4.1" + semver "^7.3.5" node-addon-api@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@*: + version "3.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.0.tgz#59390db4e489184fa35d4b74caf5510e8dfbaf3b" + integrity sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -5435,31 +5204,31 @@ node-gyp-build@~3.7.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== -node-gyp@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.0.0.tgz#225af2b06b8419ae81f924bf25ae4c167f6378a5" - integrity sha512-Jod6NxyWtcwrpAQe0O/aXOpC5QfncotgtG73dg65z6VW/C6g/G4jiajXQUBIJ8pk/VfM6mBYE9BN/HvudTunUQ== +node-gyp@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.6" - make-fetch-happen "^8.0.14" + make-fetch-happen "^9.1.0" nopt "^5.0.0" - npmlog "^4.1.2" + npmlog "^6.0.0" rimraf "^3.0.2" semver "^7.3.5" - tar "^6.1.0" + tar "^6.1.2" which "^2.0.2" -node-releases@^1.1.70, node-releases@^1.1.71: +node-releases@^1.1.70: version "1.1.71" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== -nodemailer@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.0.tgz#86614722c4e0c33d1b5b02aecb90d6d629932b0d" - integrity sha512-AtiTVUFHLiiDnMQ43zi0YgkzHOEWUkhDgPlBXrsDzJiJvB29Alo4OKxHQ0ugF3gRqRQIneCLtZU3yiUo7pItZw== +nodemailer@6.7.2: + version "6.7.2" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" + integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== nofilter@^2.0.3: version "2.0.3" @@ -5498,11 +5267,6 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.0.1.tgz#748dd68ed7de377bb1f7132c7dafe657be5ab400" @@ -5510,7 +5274,7 @@ npm-run-path@^5.0.1: dependencies: path-key "^4.0.0" -npmlog@^4.0.1, npmlog@^4.1.2: +npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -5520,12 +5284,15 @@ npmlog@^4.0.1, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== +npmlog@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.0.tgz#ba9ef39413c3d936ea91553db7be49c34ad0520c" + integrity sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q== dependencies: - boolbase "^1.0.0" + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.0" + set-blocking "^2.0.0" nth-check@~1.0.1: version "1.0.2" @@ -5559,26 +5326,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -5589,14 +5341,6 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -5706,7 +5450,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -5734,12 +5478,12 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: - p-limit "^2.0.0" + p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" @@ -5760,6 +5504,14 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-queue@6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -5932,28 +5684,26 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== -pngjs@^3.3.0, pngjs@^3.3.1: +pngjs@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + portscanner@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" @@ -5962,274 +5712,6 @@ portscanner@2.2.0: async "^2.6.0" is-number-like "^1.0.3" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== - dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-colormin@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.1.tgz#6e444a806fd3c578827dbad022762df19334414d" - integrity sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.1.0" - -postcss-convert-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" - integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-discard-comments@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" - integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== - -postcss-discard-duplicates@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" - integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== - -postcss-discard-empty@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" - integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== - -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== - -postcss-merge-longhand@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.3.tgz#42194a5ffbaa5513edbf606ef79c44958564658b" - integrity sha512-kmB+1TjMTj/bPw6MCDUiqSA5e/x4fvLffiAdthra3a0m2/IjTrWsTmD3FdSskzUjEwkj5ZHBDEbv5dOcqD7CMQ== - dependencies: - css-color-names "^1.0.1" - postcss-value-parser "^4.1.0" - stylehacks "^5.0.1" - -postcss-merge-rules@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz#d6e4d65018badbdb7dcc789c4f39b941305d410a" - integrity sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" - postcss-selector-parser "^6.0.5" - vendors "^1.0.3" - -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== - dependencies: - colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-minify-params@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz#371153ba164b9d8562842fdcd929c98abd9e5b6c" - integrity sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw== - dependencies: - alphanum-sort "^1.0.2" - browserslist "^4.16.0" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" - integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== - -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== - dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-url@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.2.tgz#ddcdfb7cede1270740cf3e4dfc6008bd96abc763" - integrity sha512-k4jLTPUxREQ5bpajFQZpx8bCF2UrlqOTzP9kEqcEnOfwsRshWs2+oAFIHfDQB8GO2PaUaSE0NlTAYtbluZTlHQ== - dependencies: - is-absolute-url "^3.0.3" - normalize-url "^6.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-reduce-initial@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz#9d6369865b0f6f6f6b165a0ef5dc1a4856c7e946" - integrity sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw== - dependencies: - browserslist "^4.16.0" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.5: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" - integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" - integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== - dependencies: - postcss-value-parser "^4.1.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz#3be5c1d7363352eff838bd62b0b07a0abad43bfc" - integrity sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.5" - uniqs "^2.0.0" - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@^8.2.15: - version "8.3.0" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f" - integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" - postcss@^8.3.11: version "8.3.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" @@ -6261,10 +5743,10 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f" - integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ== +prebuild-install@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" + integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -6272,11 +5754,11 @@ prebuild-install@^6.1.4: minimist "^1.2.3" mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" - node-abi "^2.21.0" + node-abi "^3.3.0" npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" - simple-get "^3.0.3" + simple-get "^4.0.0" tar-fs "^2.0.0" tunnel-agent "^0.6.0" @@ -6304,11 +5786,6 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== - private-ip@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/private-ip/-/private-ip-2.3.3.tgz#1e80ff8443e5ac78f555631aec3ea6ff027fa6aa" @@ -6319,10 +5796,10 @@ private-ip@2.3.3: is-ip "^3.1.0" netmask "^2.0.2" -probe-image-size@7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.1.tgz#df0c924e67e247bc94f8fcb0fad7f0081061fc44" - integrity sha512-d+6L3NvQBCNt4peRDoEfA7r9bPm6/qy18FnLKwg4NWBC5JrJm0pMLRg1kF4XNsPe1bUdt3WIMonPJzQWN2HXjQ== +probe-image-size@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.2.tgz#e5851b9be7864f21e3bac5e6e4fac9da9055b412" + integrity sha512-QUm+w1S9WTsT5GZB830u0BHExrUmF0J4fyRm5kbLUMEP3fl9UVYXc3xOBVqZNnH9tnvVEJO8vDk3PMtsLqjxug== dependencies: lodash.merge "^4.6.2" needle "^2.5.2" @@ -6333,11 +5810,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -6356,15 +5828,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -promise.prototype.finally@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" - integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.0" - function-bind "^1.1.1" - promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -6508,10 +5971,10 @@ punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pureimage@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/pureimage/-/pureimage-0.3.5.tgz#cd5e91f7b6409fcf4880297aaa3e7fc0afc24d5e" - integrity sha512-+CFUEpoX6GemlKlHihI7Ii4IqKqF5KZjd682sAxwzbc4t4zU4Gwhxd4W3UMZW94nJzf0n4nA9zJrwTR4jZB4TA== +pureimage@0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/pureimage/-/pureimage-0.3.8.tgz#b9c2a127f3182ab94fb4520e83f4fbcbdd9b38f1" + integrity sha512-+CuR0HM0VmBfKKQTM56myBonDZAhZkS6ymJ8W5oYYDXG7y7X34B/dEH3UesbJI497Vc2OkA+g8T1/Xj/FTyQ8A== dependencies: jpeg-js "^0.4.1" opentype.js "^0.4.3" @@ -6522,18 +5985,15 @@ q@1.4.1: resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= -qrcode@1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" - integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q== +qrcode@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" + integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ== dependencies: - buffer "^5.4.3" - buffer-alloc "^1.2.0" - buffer-from "^1.1.1" dijkstrajs "^1.0.1" - isarray "^2.0.1" - pngjs "^3.3.0" - yargs "^13.2.4" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" qs@^6.4.0, qs@^6.5.2: version "6.9.3" @@ -6601,14 +6061,14 @@ rdf-canonize@^3.0.0: dependencies: setimmediate "^1.0.5" -re2@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/re2/-/re2-1.16.0.tgz#f311eb4865b1296123800ea8e013cec8dab25590" - integrity sha512-eizTZL2ZO0ZseLqfD4t3Qd0M3b3Nr0MBWpX81EbPMIud/1d/CSfUIx2GQK8fWiAeHoSekO5EOeFib2udTZLwYw== +re2@1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/re2/-/re2-1.17.3.tgz#8cceb48f52c45b860b1f67cee8a44726f7d05e9a" + integrity sha512-Dp5iWVR8W3C7Nm9DziMY4BleMPRb/pe6kvfbzLv80dVYaXRc9jRnwwNqU0oE/taRm0qYR1+Qrtzk9rPjS9ecaQ== dependencies: - install-artifact-from-github "^1.2.0" - nan "^2.14.2" - node-gyp "^8.0.0" + install-artifact-from-github "^1.3.0" + nan "^2.15.0" + node-gyp "^8.4.1" readable-stream@1.1.x: version "1.1.14" @@ -6620,7 +6080,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6684,13 +6144,24 @@ redis-lock@0.1.4: resolved "https://registry.yarnpkg.com/redis-lock/-/redis-lock-0.1.4.tgz#e83590bee22b5f01cdb65bfbd88d988045356272" integrity sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA== -redis-parser@^3.0.0: +redis-parser@3.0.0, redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= dependencies: redis-errors "^1.0.0" +redis@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/redis/-/redis-4.0.2.tgz#096cf716842731a24f34c7c3a996c143e2b133bb" + integrity sha512-Ip1DJ/lwuvtJz9AZ6pl1Bv33fWzk5d3iQpGzsXpi04ErkT4fq0pfGOm4k/p9DHmPGieEIOWvJ9xmIeQMooLybg== + dependencies: + "@node-redis/bloom" "^1.0.0" + "@node-redis/client" "^1.0.2" + "@node-redis/json" "^1.0.2" + "@node-redis/search" "^1.0.2" + "@node-redis/time-series" "^1.0.1" + redis@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c" @@ -6802,6 +6273,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -6827,12 +6305,7 @@ s-age@1.1.2: resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== -safari-14-idb-fix@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19" - integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA== - -safe-buffer@*: +safe-buffer@*, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6852,10 +6325,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.3.tgz#91aa3dc760b072cdf92f9c6973747569b1ba1cd8" - integrity sha512-DGATXd1fs/Rm287/i5FBKVYSBBUL0iAaztOA1/RFhEs4yqo39/X52i/q/CwsfCUG5cilmXSBmnQmyWfnKhBlOg== +sanitize-html@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.6.1.tgz#5d37c08e189c61c0631560a889b10d9d155d000e" + integrity sha512-DzjSz3H5qDntD7s1TcWCSoRPmNR8UmA+y+xZQOvWgjATe2Br9ZW73+vD3Pj6Snrg0RuEuJdXgrKvnYuiuixRkA== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" @@ -6905,17 +6378,12 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@6.x: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^5.4.1, semver@^5.6.0: +semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -6941,7 +6409,7 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -setimmediate@^1.0.5: +setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -6964,17 +6432,17 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.29.2: - version "0.29.2" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.2.tgz#e8c003cd9cb321585b32dbda6eed3baa7d6f2308" - integrity sha512-XWRdiYLIJ3tDUejRyG24KERnJzMfIoyiJBntd2S6/uj3NEeNgRFRLgiBlvPxMa8aml14dKKD98yHinSNKp1xzQ== +sharp@0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== dependencies: color "^4.0.1" detect-libc "^1.0.3" node-addon-api "^4.2.0" - prebuild-install "^6.1.4" + prebuild-install "^7.0.0" semver "^7.3.5" - simple-get "^3.1.0" + simple-get "^4.0.0" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -7019,12 +6487,12 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^3.0.3, simple-get@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== dependencies: - decompress-response "^4.2.0" + decompress-response "^6.0.0" once "^1.3.1" simple-concat "^1.0.0" @@ -7045,16 +6513,16 @@ smart-buffer@^4.1.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== -socks-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" - integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== +socks-proxy-agent@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" + integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== dependencies: - agent-base "6" - debug "4" - socks "^2.3.3" + agent-base "^6.0.2" + debug "^4.3.1" + socks "^2.6.1" -socks@^2.3.3: +socks@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== @@ -7109,11 +6577,6 @@ sprintf-js@1.1.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - sshpk@^1.14.1: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -7136,11 +6599,6 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" @@ -7185,14 +6643,14 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" @@ -7203,14 +6661,6 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -7219,32 +6669,6 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" @@ -7293,13 +6717,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -7347,14 +6764,6 @@ style-loader@3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== -stylehacks@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" - integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== - dependencies: - browserslist "^4.16.0" - postcss-selector-parser "^6.0.4" - summaly@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea" @@ -7392,19 +6801,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -7481,10 +6877,10 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== +tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -7543,11 +6939,6 @@ through@2: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tinycolor2@1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -7611,21 +7002,10 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504" integrity sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg== -ts-jest@^25.2.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" - integrity sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - micromatch "4.x" - mkdirp "0.x" - semver "6.x" - yargs-parser "18.x" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= ts-loader@9.2.6: version "9.2.6" @@ -7667,10 +7047,10 @@ tsc-alias@1.4.1: mylas "^2.1.4" normalize-path "^3.0.0" -tsconfig-paths@3.11.0, tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== +tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" @@ -7770,10 +7150,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.2.39: - version "0.2.39" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.39.tgz#4d22fc68d114b2ca88a8d7b064f31af15e836ade" - integrity sha512-yQdvDpmmmn8wp1We25V76KIBPYR/lDbymNbGC++Uq8mSRhpHIPnlg26VAT4CF6Ypqx72zn8eqr+/72uSo7HdJQ== +typeorm@0.2.41: + version "0.2.41" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.41.tgz#88758101ac158dc0a0a903d70eaacea2974281cc" + integrity sha512-/d8CLJJxKPgsnrZWiMyPI0rz2MFZnBQrnQ5XP3Vu3mswv2WPexb58QM6BEtmRmlTMYN5KFWUz8SKluze+wS9xw== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" @@ -7792,10 +7172,10 @@ typeorm@0.2.39: yargs "^17.0.1" zen-observable-ts "^1.0.0" -typescript@4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== ulid@2.3.0: version "2.3.0" @@ -7812,16 +7192,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -7854,6 +7224,22 @@ unpipe@1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +unzipper@0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -7881,21 +7267,11 @@ utf-8-validate@^5.0.2: dependencies: node-gyp-build "~3.7.0" -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -7921,11 +7297,6 @@ vary@^1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vendors@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -7940,19 +7311,6 @@ void-elements@^3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= -vue-eslint-parser@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz#25e08b20a414551531f3e19f999902e1ecf45f13" - integrity sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A== - dependencies: - debug "^4.3.2" - eslint-scope "^6.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" - esquery "^1.4.0" - lodash "^4.17.21" - semver "^7.3.5" - w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -7987,6 +7345,11 @@ web-push@3.4.5: minimist "^1.2.5" urlsafe-base64 "^1.0.0" +web-streams-polyfill@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" + integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -8113,6 +7476,13 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + with@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" @@ -8133,14 +7503,14 @@ workerpool@6.1.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^7.0.0: version "7.0.0" @@ -8156,10 +7526,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== ws@^7.4.6: version "7.5.3" @@ -8234,43 +7604,30 @@ yaeti@^0.0.6: resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= +yallist@4.0.0, yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yaml-ast-parser@0.0.43: version "0.0.43" resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@18.x: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -8298,21 +7655,22 @@ yargs@16.2.0, yargs@^16.0.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^13.2.4: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^3.0.0" + string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.2" + yargs-parser "^18.1.2" yargs@^17.0.1: version "17.1.1" diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 8e4ff6e455..d414f86ed3 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -14,6 +14,10 @@ module.exports = { "plugin:vue/vue3-recommended" ], rules: { + // window ã®ç¦æ¢ç†ç”±: ã‚°ãƒãƒ¼ãƒãƒ«ã‚¹ã‚³ãƒ¼ãƒ—ã¨è¡çªã—ã€äºˆæœŸã›ã¬çµæžœã‚’æ‹›ããŸã‚ + // data ã®ç¦æ¢ç†ç”±: 抽象的ã™ãŽã‚‹ãŸã‚ + // e ã®ç¦æ¢ç†ç”±: error ã‚„ event ãªã©ã€è¤‡æ•°ã®ã‚ーワードã®é æ–‡å—ã§ã‚り分ã‹ã‚Šã«ãã„ãŸã‚ + "id-denylist": ["error", "window", "data", "e"], "vue/attributes-order": ["error", { "alphabetical": false }], @@ -43,6 +47,7 @@ module.exports = { "vue/no-unused-components": "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", diff --git a/packages/client/@types/vue.d.ts b/packages/client/@types/vue.d.ts index 8cb6130629..f6b66228f6 100644 --- a/packages/client/@types/vue.d.ts +++ b/packages/client/@types/vue.d.ts @@ -1,3 +1,5 @@ +/// <reference types="vue/macros-global" /> + declare module '*.vue' { import type { DefineComponent } from 'vue'; const component: DefineComponent<{}, {}, any>; diff --git a/packages/client/assets/room/furnitures/banknote/banknote.blend b/packages/client/assets/room/furnitures/banknote/banknote.blend Binary files differdeleted file mode 100644 index 60b1968a29..0000000000 --- a/packages/client/assets/room/furnitures/banknote/banknote.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/banknote/banknote.glb b/packages/client/assets/room/furnitures/banknote/banknote.glb Binary files differdeleted file mode 100644 index f4ef0b91e7..0000000000 --- a/packages/client/assets/room/furnitures/banknote/banknote.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/banknote/tex.png b/packages/client/assets/room/furnitures/banknote/tex.png Binary files differdeleted file mode 100644 index 9106dc1457..0000000000 --- a/packages/client/assets/room/furnitures/banknote/tex.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bed/bed.blend b/packages/client/assets/room/furnitures/bed/bed.blend Binary files differdeleted file mode 100644 index 731df76d0c..0000000000 --- a/packages/client/assets/room/furnitures/bed/bed.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bed/bed.glb b/packages/client/assets/room/furnitures/bed/bed.glb Binary files differdeleted file mode 100644 index f35ecb9ef4..0000000000 --- a/packages/client/assets/room/furnitures/bed/bed.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bin/bin.blend b/packages/client/assets/room/furnitures/bin/bin.blend Binary files differdeleted file mode 100644 index 8d459a0869..0000000000 --- a/packages/client/assets/room/furnitures/bin/bin.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bin/bin.glb b/packages/client/assets/room/furnitures/bin/bin.glb Binary files differdeleted file mode 100644 index b45f203802..0000000000 --- a/packages/client/assets/room/furnitures/bin/bin.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book/book.blend b/packages/client/assets/room/furnitures/book/book.blend Binary files differdeleted file mode 100644 index 0d4899d4ae..0000000000 --- a/packages/client/assets/room/furnitures/book/book.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book/book.glb b/packages/client/assets/room/furnitures/book/book.glb Binary files differdeleted file mode 100644 index 546893da06..0000000000 --- a/packages/client/assets/room/furnitures/book/book.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/barcode.png b/packages/client/assets/room/furnitures/book2/barcode.png Binary files differdeleted file mode 100644 index 37cfe5add3..0000000000 --- a/packages/client/assets/room/furnitures/book2/barcode.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/book2.blend b/packages/client/assets/room/furnitures/book2/book2.blend Binary files differdeleted file mode 100644 index e0fdb48101..0000000000 --- a/packages/client/assets/room/furnitures/book2/book2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/book2.glb b/packages/client/assets/room/furnitures/book2/book2.glb Binary files differdeleted file mode 100644 index 2b26402f8c..0000000000 --- a/packages/client/assets/room/furnitures/book2/book2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/texture.afdesign b/packages/client/assets/room/furnitures/book2/texture.afdesign Binary files differdeleted file mode 100644 index b63771607a..0000000000 --- a/packages/client/assets/room/furnitures/book2/texture.afdesign +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/texture.png b/packages/client/assets/room/furnitures/book2/texture.png Binary files differdeleted file mode 100644 index 5aa84f0340..0000000000 --- a/packages/client/assets/room/furnitures/book2/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/uv.png b/packages/client/assets/room/furnitures/book2/uv.png Binary files differdeleted file mode 100644 index 61c4fb0400..0000000000 --- a/packages/client/assets/room/furnitures/book2/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.blend b/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.blend Binary files differdeleted file mode 100644 index 3a528de32a..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.glb b/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.glb Binary files differdeleted file mode 100644 index bed372e94f..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend b/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend Binary files differdeleted file mode 100644 index 5f146267ac..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb b/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb Binary files differdeleted file mode 100644 index 85fcb5c0b6..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/texture.png b/packages/client/assets/room/furnitures/cardboard-box2/texture.png Binary files differdeleted file mode 100644 index e498d8f65b..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/uv.png b/packages/client/assets/room/furnitures/cardboard-box2/uv.png Binary files differdeleted file mode 100644 index d547843ee0..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend b/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend Binary files differdeleted file mode 100644 index 00681a3cfd..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb b/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb Binary files differdeleted file mode 100644 index 1ef0427689..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/texture.png b/packages/client/assets/room/furnitures/cardboard-box3/texture.png Binary files differdeleted file mode 100644 index 56c914cb9d..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/texture.xcf b/packages/client/assets/room/furnitures/cardboard-box3/texture.xcf Binary files differdeleted file mode 100644 index 7ffb3e3439..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/texture.xcf +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/uv.png b/packages/client/assets/room/furnitures/cardboard-box3/uv.png Binary files differdeleted file mode 100644 index 797ac509db..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend b/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend Binary files differdeleted file mode 100644 index 750343d4f0..0000000000 --- a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb b/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb Binary files differdeleted file mode 100644 index 3066a69e35..0000000000 --- a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair/chair.blend b/packages/client/assets/room/furnitures/chair/chair.blend Binary files differdeleted file mode 100644 index 79c29a8401..0000000000 --- a/packages/client/assets/room/furnitures/chair/chair.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair/chair.glb b/packages/client/assets/room/furnitures/chair/chair.glb Binary files differdeleted file mode 100644 index 08ee1a0bb0..0000000000 --- a/packages/client/assets/room/furnitures/chair/chair.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair2/chair2.blend b/packages/client/assets/room/furnitures/chair2/chair2.blend Binary files differdeleted file mode 100644 index c6a1acd96f..0000000000 --- a/packages/client/assets/room/furnitures/chair2/chair2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair2/chair2.glb b/packages/client/assets/room/furnitures/chair2/chair2.glb Binary files differdeleted file mode 100644 index 5ea2f3518b..0000000000 --- a/packages/client/assets/room/furnitures/chair2/chair2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/color-box/color-box.blend b/packages/client/assets/room/furnitures/color-box/color-box.blend Binary files differdeleted file mode 100644 index f96a4ff766..0000000000 --- a/packages/client/assets/room/furnitures/color-box/color-box.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/color-box/color-box.glb b/packages/client/assets/room/furnitures/color-box/color-box.glb Binary files differdeleted file mode 100644 index 43f2abcae8..0000000000 --- a/packages/client/assets/room/furnitures/color-box/color-box.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/corkboard/corkboard.blend b/packages/client/assets/room/furnitures/corkboard/corkboard.blend Binary files differdeleted file mode 100644 index 9a7e1878cd..0000000000 --- a/packages/client/assets/room/furnitures/corkboard/corkboard.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/corkboard/corkboard.glb b/packages/client/assets/room/furnitures/corkboard/corkboard.glb Binary files differdeleted file mode 100644 index fee108fb91..0000000000 --- a/packages/client/assets/room/furnitures/corkboard/corkboard.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cube/cube.blend b/packages/client/assets/room/furnitures/cube/cube.blend Binary files differdeleted file mode 100644 index 1af5bf40a9..0000000000 --- a/packages/client/assets/room/furnitures/cube/cube.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cube/cube.glb b/packages/client/assets/room/furnitures/cube/cube.glb Binary files differdeleted file mode 100644 index 4ac8b6036d..0000000000 --- a/packages/client/assets/room/furnitures/cube/cube.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.blend b/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.blend Binary files differdeleted file mode 100644 index 37ca8868c7..0000000000 --- a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.glb b/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.glb Binary files differdeleted file mode 100644 index 58efb1b3b4..0000000000 --- a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cup-noodle/noodle.png b/packages/client/assets/room/furnitures/cup-noodle/noodle.png Binary files differdeleted file mode 100644 index 1d74e0bbe7..0000000000 --- a/packages/client/assets/room/furnitures/cup-noodle/noodle.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/desk/desk.blend b/packages/client/assets/room/furnitures/desk/desk.blend Binary files differdeleted file mode 100644 index c88d01f0b2..0000000000 --- a/packages/client/assets/room/furnitures/desk/desk.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/desk/desk.glb b/packages/client/assets/room/furnitures/desk/desk.glb Binary files differdeleted file mode 100644 index 4a58513095..0000000000 --- a/packages/client/assets/room/furnitures/desk/desk.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/doll-ai/doll-ai.blend b/packages/client/assets/room/furnitures/doll-ai/doll-ai.blend Binary files differdeleted file mode 100644 index a912231ecb..0000000000 --- a/packages/client/assets/room/furnitures/doll-ai/doll-ai.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/doll-ai/doll-ai.glb b/packages/client/assets/room/furnitures/doll-ai/doll-ai.glb Binary files differdeleted file mode 100644 index ec55a7bd7b..0000000000 --- a/packages/client/assets/room/furnitures/doll-ai/doll-ai.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/doll-ai/doll_ai_tex.png b/packages/client/assets/room/furnitures/doll-ai/doll_ai_tex.png Binary files differdeleted file mode 100644 index 370ca5f75b..0000000000 --- a/packages/client/assets/room/furnitures/doll-ai/doll_ai_tex.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/energy-drink.blend b/packages/client/assets/room/furnitures/energy-drink/energy-drink.blend Binary files differdeleted file mode 100644 index 65fc41273e..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/energy-drink.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/energy-drink.glb b/packages/client/assets/room/furnitures/energy-drink/energy-drink.glb Binary files differdeleted file mode 100644 index 7fb1c27836..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/energy-drink.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/texture.afdesign b/packages/client/assets/room/furnitures/energy-drink/texture.afdesign Binary files differdeleted file mode 100644 index 8c117a49b1..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/texture.afdesign +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/texture.png b/packages/client/assets/room/furnitures/energy-drink/texture.png Binary files differdeleted file mode 100644 index 484ca0f96f..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/uv.png b/packages/client/assets/room/furnitures/energy-drink/uv.png Binary files differdeleted file mode 100644 index 2a3f20c999..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/cover.png b/packages/client/assets/room/furnitures/eraser/cover.png Binary files differdeleted file mode 100644 index 932a3fc62e..0000000000 --- a/packages/client/assets/room/furnitures/eraser/cover.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/cover.psd b/packages/client/assets/room/furnitures/eraser/cover.psd Binary files differdeleted file mode 100644 index c393337833..0000000000 --- a/packages/client/assets/room/furnitures/eraser/cover.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/eraser-uv.png b/packages/client/assets/room/furnitures/eraser/eraser-uv.png Binary files differdeleted file mode 100644 index 89e4ea4c45..0000000000 --- a/packages/client/assets/room/furnitures/eraser/eraser-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/eraser.blend b/packages/client/assets/room/furnitures/eraser/eraser.blend Binary files differdeleted file mode 100644 index 103c54fbae..0000000000 --- a/packages/client/assets/room/furnitures/eraser/eraser.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/eraser.glb b/packages/client/assets/room/furnitures/eraser/eraser.glb Binary files differdeleted file mode 100644 index 016b60df20..0000000000 --- a/packages/client/assets/room/furnitures/eraser/eraser.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png Binary files differdeleted file mode 100644 index e3865ad15e..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.blend b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.blend Binary files differdeleted file mode 100644 index d59f87c1ee..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.glb b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.glb Binary files differdeleted file mode 100644 index 48b36ef347..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.png b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.png Binary files differdeleted file mode 100644 index 7cee4b1859..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.psd b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.psd Binary files differdeleted file mode 100644 index cd59fc007b..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/fan/fan.blend b/packages/client/assets/room/furnitures/fan/fan.blend Binary files differdeleted file mode 100644 index 8c8106e5fe..0000000000 --- a/packages/client/assets/room/furnitures/fan/fan.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/fan/fan.glb b/packages/client/assets/room/furnitures/fan/fan.glb Binary files differdeleted file mode 100644 index d9367f3534..0000000000 --- a/packages/client/assets/room/furnitures/fan/fan.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/holo-display.blend b/packages/client/assets/room/furnitures/holo-display/holo-display.blend Binary files differdeleted file mode 100644 index 56d2e1f819..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/holo-display.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/holo-display.glb b/packages/client/assets/room/furnitures/holo-display/holo-display.glb Binary files differdeleted file mode 100644 index 4d042a59b3..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/holo-display.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/ray-uv.png b/packages/client/assets/room/furnitures/holo-display/ray-uv.png Binary files differdeleted file mode 100644 index aa7e817e0f..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/ray-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/ray.png b/packages/client/assets/room/furnitures/holo-display/ray.png Binary files differdeleted file mode 100644 index 6a5d24e143..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/ray.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/keyboard/keyboard.blend b/packages/client/assets/room/furnitures/keyboard/keyboard.blend Binary files differdeleted file mode 100644 index ab33d134b3..0000000000 --- a/packages/client/assets/room/furnitures/keyboard/keyboard.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/keyboard/keyboard.glb b/packages/client/assets/room/furnitures/keyboard/keyboard.glb Binary files differdeleted file mode 100644 index 15dc69f47a..0000000000 --- a/packages/client/assets/room/furnitures/keyboard/keyboard.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/low-table/low-table.blend b/packages/client/assets/room/furnitures/low-table/low-table.blend Binary files differdeleted file mode 100644 index e1592174d9..0000000000 --- a/packages/client/assets/room/furnitures/low-table/low-table.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/low-table/low-table.glb b/packages/client/assets/room/furnitures/low-table/low-table.glb Binary files differdeleted file mode 100644 index c69bf35d7b..0000000000 --- a/packages/client/assets/room/furnitures/low-table/low-table.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mat/mat.blend b/packages/client/assets/room/furnitures/mat/mat.blend Binary files differdeleted file mode 100644 index a1e1a68c55..0000000000 --- a/packages/client/assets/room/furnitures/mat/mat.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mat/mat.glb b/packages/client/assets/room/furnitures/mat/mat.glb Binary files differdeleted file mode 100644 index 87ccd44e1a..0000000000 --- a/packages/client/assets/room/furnitures/mat/mat.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk-uv.png b/packages/client/assets/room/furnitures/milk/milk-uv.png Binary files differdeleted file mode 100644 index 258fd54638..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.blend b/packages/client/assets/room/furnitures/milk/milk.blend Binary files differdeleted file mode 100644 index 2df508d5b9..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.glb b/packages/client/assets/room/furnitures/milk/milk.glb Binary files differdeleted file mode 100644 index b335fe3d02..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.png b/packages/client/assets/room/furnitures/milk/milk.png Binary files differdeleted file mode 100644 index 35181c8c8c..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.psd b/packages/client/assets/room/furnitures/milk/milk.psd Binary files differdeleted file mode 100644 index f31e439277..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/monitor.blend b/packages/client/assets/room/furnitures/monitor/monitor.blend Binary files differdeleted file mode 100644 index 6c042ccdd8..0000000000 --- a/packages/client/assets/room/furnitures/monitor/monitor.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/monitor.glb b/packages/client/assets/room/furnitures/monitor/monitor.glb Binary files differdeleted file mode 100644 index fc33286a15..0000000000 --- a/packages/client/assets/room/furnitures/monitor/monitor.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/monitor.psd b/packages/client/assets/room/furnitures/monitor/monitor.psd Binary files differdeleted file mode 100644 index 57afff9cd9..0000000000 --- a/packages/client/assets/room/furnitures/monitor/monitor.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/screen-uv.png b/packages/client/assets/room/furnitures/monitor/screen-uv.png Binary files differdeleted file mode 100644 index 35f74de8aa..0000000000 --- a/packages/client/assets/room/furnitures/monitor/screen-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/screen.jpg b/packages/client/assets/room/furnitures/monitor/screen.jpg Binary files differdeleted file mode 100644 index 4004a1ede9..0000000000 --- a/packages/client/assets/room/furnitures/monitor/screen.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/moon/moon.blend b/packages/client/assets/room/furnitures/moon/moon.blend Binary files differdeleted file mode 100644 index 4ff3deab8e..0000000000 --- a/packages/client/assets/room/furnitures/moon/moon.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/moon/moon.glb b/packages/client/assets/room/furnitures/moon/moon.glb Binary files differdeleted file mode 100644 index 07fa7e4c02..0000000000 --- a/packages/client/assets/room/furnitures/moon/moon.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/moon/moon.jpg b/packages/client/assets/room/furnitures/moon/moon.jpg Binary files differdeleted file mode 100644 index 8988ac64b9..0000000000 --- a/packages/client/assets/room/furnitures/moon/moon.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mousepad/mousepad.blend b/packages/client/assets/room/furnitures/mousepad/mousepad.blend Binary files differdeleted file mode 100644 index 14bd139c94..0000000000 --- a/packages/client/assets/room/furnitures/mousepad/mousepad.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mousepad/mousepad.glb b/packages/client/assets/room/furnitures/mousepad/mousepad.glb Binary files differdeleted file mode 100644 index 681ada49cd..0000000000 --- a/packages/client/assets/room/furnitures/mousepad/mousepad.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/motherboard-uv.png b/packages/client/assets/room/furnitures/pc/motherboard-uv.png Binary files differdeleted file mode 100644 index 355009fe7c..0000000000 --- a/packages/client/assets/room/furnitures/pc/motherboard-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/motherboard-uv.psd b/packages/client/assets/room/furnitures/pc/motherboard-uv.psd Binary files differdeleted file mode 100644 index 971f33f79e..0000000000 --- a/packages/client/assets/room/furnitures/pc/motherboard-uv.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/motherboard.jpg b/packages/client/assets/room/furnitures/pc/motherboard.jpg Binary files differdeleted file mode 100644 index d894e4efcf..0000000000 --- a/packages/client/assets/room/furnitures/pc/motherboard.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/pc.blend b/packages/client/assets/room/furnitures/pc/pc.blend Binary files differdeleted file mode 100644 index 13dfec6ccc..0000000000 --- a/packages/client/assets/room/furnitures/pc/pc.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/pc.glb b/packages/client/assets/room/furnitures/pc/pc.glb Binary files differdeleted file mode 100644 index 44a48b18ae..0000000000 --- a/packages/client/assets/room/furnitures/pc/pc.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pencil/pencil.blend b/packages/client/assets/room/furnitures/pencil/pencil.blend Binary files differdeleted file mode 100644 index 0fc6bdd776..0000000000 --- a/packages/client/assets/room/furnitures/pencil/pencil.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pencil/pencil.glb b/packages/client/assets/room/furnitures/pencil/pencil.glb Binary files differdeleted file mode 100644 index a938b5cdcc..0000000000 --- a/packages/client/assets/room/furnitures/pencil/pencil.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photo-uv.png b/packages/client/assets/room/furnitures/photoframe/photo-uv.png Binary files differdeleted file mode 100644 index 9b94906413..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photo-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photo.jpg b/packages/client/assets/room/furnitures/photoframe/photo.jpg Binary files differdeleted file mode 100644 index af14f0f36a..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photo.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photoframe.blend b/packages/client/assets/room/furnitures/photoframe/photoframe.blend Binary files differdeleted file mode 100644 index 4224cde45b..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photoframe.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photoframe.glb b/packages/client/assets/room/furnitures/photoframe/photoframe.glb Binary files differdeleted file mode 100644 index 4255a77de6..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photoframe.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/piano/piano.blend b/packages/client/assets/room/furnitures/piano/piano.blend Binary files differdeleted file mode 100644 index 7653cdf672..0000000000 --- a/packages/client/assets/room/furnitures/piano/piano.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/piano/piano.glb b/packages/client/assets/room/furnitures/piano/piano.glb Binary files differdeleted file mode 100644 index 7242e78ceb..0000000000 --- a/packages/client/assets/room/furnitures/piano/piano.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pinguin/pinguin.blend b/packages/client/assets/room/furnitures/pinguin/pinguin.blend Binary files differdeleted file mode 100644 index 514c713e4c..0000000000 --- a/packages/client/assets/room/furnitures/pinguin/pinguin.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pinguin/pinguin.glb b/packages/client/assets/room/furnitures/pinguin/pinguin.glb Binary files differdeleted file mode 100644 index 6df34c06e9..0000000000 --- a/packages/client/assets/room/furnitures/pinguin/pinguin.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant-soil-uv.png b/packages/client/assets/room/furnitures/plant/plant-soil-uv.png Binary files differdeleted file mode 100644 index d4971a896c..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant-soil-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant-soil.png b/packages/client/assets/room/furnitures/plant/plant-soil.png Binary files differdeleted file mode 100644 index e79ccd240e..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant-soil.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant-soil.psd b/packages/client/assets/room/furnitures/plant/plant-soil.psd Binary files differdeleted file mode 100644 index 1457b7ea5b..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant-soil.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant.blend b/packages/client/assets/room/furnitures/plant/plant.blend Binary files differdeleted file mode 100644 index aa38c7b54e..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant.glb b/packages/client/assets/room/furnitures/plant/plant.glb Binary files differdeleted file mode 100644 index 38422b4a9b..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant2/plant2.blend b/packages/client/assets/room/furnitures/plant2/plant2.blend Binary files differdeleted file mode 100644 index 6592c5d98d..0000000000 --- a/packages/client/assets/room/furnitures/plant2/plant2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant2/plant2.glb b/packages/client/assets/room/furnitures/plant2/plant2.glb Binary files differdeleted file mode 100644 index 223e6f5834..0000000000 --- a/packages/client/assets/room/furnitures/plant2/plant2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant2/soil.png b/packages/client/assets/room/furnitures/plant2/soil.png Binary files differdeleted file mode 100644 index e79ccd240e..0000000000 --- a/packages/client/assets/room/furnitures/plant2/soil.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-h/poster-h.blend b/packages/client/assets/room/furnitures/poster-h/poster-h.blend Binary files differdeleted file mode 100644 index 40f944f3c1..0000000000 --- a/packages/client/assets/room/furnitures/poster-h/poster-h.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-h/poster-h.glb b/packages/client/assets/room/furnitures/poster-h/poster-h.glb Binary files differdeleted file mode 100644 index c6032c1009..0000000000 --- a/packages/client/assets/room/furnitures/poster-h/poster-h.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-h/uv.png b/packages/client/assets/room/furnitures/poster-h/uv.png Binary files differdeleted file mode 100644 index f854231e0b..0000000000 --- a/packages/client/assets/room/furnitures/poster-h/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-v/poster-v.blend b/packages/client/assets/room/furnitures/poster-v/poster-v.blend Binary files differdeleted file mode 100644 index 07fe971634..0000000000 --- a/packages/client/assets/room/furnitures/poster-v/poster-v.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-v/poster-v.glb b/packages/client/assets/room/furnitures/poster-v/poster-v.glb Binary files differdeleted file mode 100644 index 6e3782f193..0000000000 --- a/packages/client/assets/room/furnitures/poster-v/poster-v.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-v/uv.png b/packages/client/assets/room/furnitures/poster-v/uv.png Binary files differdeleted file mode 100644 index 7bb2bf809e..0000000000 --- a/packages/client/assets/room/furnitures/poster-v/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pudding/pudding.blend b/packages/client/assets/room/furnitures/pudding/pudding.blend Binary files differdeleted file mode 100644 index bba40ce161..0000000000 --- a/packages/client/assets/room/furnitures/pudding/pudding.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pudding/pudding.glb b/packages/client/assets/room/furnitures/pudding/pudding.glb Binary files differdeleted file mode 100644 index 06c9ed80cc..0000000000 --- a/packages/client/assets/room/furnitures/pudding/pudding.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.blend b/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.blend Binary files differdeleted file mode 100644 index 6c09067e78..0000000000 --- a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.glb b/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.glb Binary files differdeleted file mode 100644 index d640df9b06..0000000000 --- a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/rack-uv.png b/packages/client/assets/room/furnitures/server/rack-uv.png Binary files differdeleted file mode 100644 index 65bdb0ffd9..0000000000 --- a/packages/client/assets/room/furnitures/server/rack-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/rack.png b/packages/client/assets/room/furnitures/server/rack.png Binary files differdeleted file mode 100644 index b851295cfa..0000000000 --- a/packages/client/assets/room/furnitures/server/rack.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/server.blend b/packages/client/assets/room/furnitures/server/server.blend Binary files differdeleted file mode 100644 index 6675dfbdc2..0000000000 --- a/packages/client/assets/room/furnitures/server/server.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/server.glb b/packages/client/assets/room/furnitures/server/server.glb Binary files differdeleted file mode 100644 index a8b530a2d2..0000000000 --- a/packages/client/assets/room/furnitures/server/server.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/server.png b/packages/client/assets/room/furnitures/server/server.png Binary files differdeleted file mode 100644 index 8e9a0d716c..0000000000 --- a/packages/client/assets/room/furnitures/server/server.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/uv.png b/packages/client/assets/room/furnitures/server/uv.png Binary files differdeleted file mode 100644 index ca2e747d16..0000000000 --- a/packages/client/assets/room/furnitures/server/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/sofa/sofa.blend b/packages/client/assets/room/furnitures/sofa/sofa.blend Binary files differdeleted file mode 100644 index fb5aa51a2c..0000000000 --- a/packages/client/assets/room/furnitures/sofa/sofa.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/sofa/sofa.glb b/packages/client/assets/room/furnitures/sofa/sofa.glb Binary files differdeleted file mode 100644 index 6ce77d94ac..0000000000 --- a/packages/client/assets/room/furnitures/sofa/sofa.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/spiral/spiral.blend b/packages/client/assets/room/furnitures/spiral/spiral.blend Binary files differdeleted file mode 100644 index 9d3be77bce..0000000000 --- a/packages/client/assets/room/furnitures/spiral/spiral.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/spiral/spiral.glb b/packages/client/assets/room/furnitures/spiral/spiral.glb Binary files differdeleted file mode 100644 index ee8e3c23b1..0000000000 --- a/packages/client/assets/room/furnitures/spiral/spiral.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/tv/screen-uv.png b/packages/client/assets/room/furnitures/tv/screen-uv.png Binary files differdeleted file mode 100644 index 4bb74f031f..0000000000 --- a/packages/client/assets/room/furnitures/tv/screen-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/tv/tv.blend b/packages/client/assets/room/furnitures/tv/tv.blend Binary files differdeleted file mode 100644 index 490e298e7b..0000000000 --- a/packages/client/assets/room/furnitures/tv/tv.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/tv/tv.glb b/packages/client/assets/room/furnitures/tv/tv.glb Binary files differdeleted file mode 100644 index b9bd23896b..0000000000 --- a/packages/client/assets/room/furnitures/tv/tv.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/wall-clock/wall-clock.blend b/packages/client/assets/room/furnitures/wall-clock/wall-clock.blend Binary files differdeleted file mode 100644 index 0a61c8f01e..0000000000 --- a/packages/client/assets/room/furnitures/wall-clock/wall-clock.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/wall-clock/wall-clock.glb b/packages/client/assets/room/furnitures/wall-clock/wall-clock.glb Binary files differdeleted file mode 100644 index b9f0093a8d..0000000000 --- a/packages/client/assets/room/furnitures/wall-clock/wall-clock.glb +++ /dev/null diff --git a/packages/client/assets/room/rooms/default/default.blend b/packages/client/assets/room/rooms/default/default.blend Binary files differdeleted file mode 100644 index 661154724a..0000000000 --- a/packages/client/assets/room/rooms/default/default.blend +++ /dev/null diff --git a/packages/client/assets/room/rooms/default/default.glb b/packages/client/assets/room/rooms/default/default.glb Binary files differdeleted file mode 100644 index 3d378deee2..0000000000 --- a/packages/client/assets/room/rooms/default/default.glb +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/husuma-uv.png b/packages/client/assets/room/rooms/washitsu/husuma-uv.png Binary files differdeleted file mode 100644 index ae2fca3911..0000000000 --- a/packages/client/assets/room/rooms/washitsu/husuma-uv.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/husuma.png b/packages/client/assets/room/rooms/washitsu/husuma.png Binary files differdeleted file mode 100644 index 084cbed67c..0000000000 --- a/packages/client/assets/room/rooms/washitsu/husuma.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami-single1600.png b/packages/client/assets/room/rooms/washitsu/tatami-single1600.png Binary files differdeleted file mode 100644 index c0e684d743..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami-single1600.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami-uv.png b/packages/client/assets/room/rooms/washitsu/tatami-uv.png Binary files differdeleted file mode 100644 index 5b16c66091..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami-uv.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami.afdesign b/packages/client/assets/room/rooms/washitsu/tatami.afdesign Binary files differdeleted file mode 100644 index 9300a26950..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami.afdesign +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami.png b/packages/client/assets/room/rooms/washitsu/tatami.png Binary files differdeleted file mode 100644 index 8894d040ae..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/washitsu.blend b/packages/client/assets/room/rooms/washitsu/washitsu.blend Binary files differdeleted file mode 100644 index 84dc11374d..0000000000 --- a/packages/client/assets/room/rooms/washitsu/washitsu.blend +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/washitsu.glb b/packages/client/assets/room/rooms/washitsu/washitsu.glb Binary files differdeleted file mode 100644 index 5b4767bc73..0000000000 --- a/packages/client/assets/room/rooms/washitsu/washitsu.glb +++ /dev/null diff --git a/packages/client/package.json b/packages/client/package.json index 198bcbcef6..71dd89bea4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -11,11 +11,9 @@ }, "dependencies": { "@discordapp/twemoji": "13.1.0", - "@sentry/browser": "5.29.2", - "@sentry/tracing": "5.29.2", "@syuilo/aiscript": "0.11.1", "@types/dateformat": "3.0.1", - "@types/escape-regexp": "0.0.0", + "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", "@types/gulp": "4.0.9", "@types/gulp-rename": "2.0.1", @@ -23,7 +21,6 @@ "@types/katex": "0.11.1", "@types/matter-js": "0.17.6", "@types/mocha": "8.2.3", - "@types/node": "16.11.12", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/punycode": "2.1.0", @@ -34,55 +31,54 @@ "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", - "@types/uuid": "8.3.3", + "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.4", "@types/ws": "8.2.2", - "@typescript-eslint/parser": "5.8.1", - "@vue/compiler-sfc": "3.2.26", + "@typescript-eslint/parser": "5.10.0", + "@vue/compiler-sfc": "3.2.29", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", - "autosize": "4.0.4", + "autosize": "5.0.1", "autwh": "0.1.0", "blurhash": "1.1.4", "broadcast-channel": "4.9.0", "chart.js": "3.7.0", "chartjs-adapter-date-fns": "2.0.0", "chartjs-plugin-zoom": "1.2.0", - "compare-versions": "3.6.0", + "compare-versions": "4.1.3", "content-disposition": "0.5.4", "crc-32": "1.2.0", "css-loader": "6.5.1", - "cssnano": "5.0.14", + "cssnano": "5.0.15", "date-fns": "2.28.0", - "dateformat": "4.5.1", "escape-regexp": "0.0.1", - "eslint": "8.5.0", - "eslint-plugin-vue": "8.2.0", + "eslint": "8.7.0", + "eslint-plugin-vue": "8.3.0", "eventemitter3": "4.0.7", "feed": "4.2.2", "glob": "7.2.0", - "idb-keyval": "5.1.3", + "idb-keyval": "6.1.0", "insert-text-at-cursor": "0.3.0", "ip-cidr": "3.0.4", "json5": "2.2.0", "json5-loader": "4.0.1", - "katex": "0.15.1", + "katex": "0.15.2", "langmap": "0.0.16", - "matter-js": "0.17.1", - "mfm-js": "0.20.0", - "misskey-js": "0.0.10", + "matter-js": "0.18.0", + "mfm-js": "0.21.0", + "misskey-js": "0.0.13", "mocha": "8.4.0", "ms": "2.1.3", "nested-property": "4.0.0", "parse5": "6.0.1", - "photoswipe": "git://github.com/dimsemenov/photoswipe#v5-beta", + "photoswipe": "git+https://github.com/dimsemenov/photoswipe#v5-beta", "portscanner": "2.2.0", "postcss": "8.4.5", "postcss-loader": "6.2.1", - "prismjs": "1.25.0", + "prismjs": "1.26.0", "private-ip": "2.3.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -94,7 +90,7 @@ "request-stats": "3.0.0", "rndstr": "1.0.0", "s-age": "1.1.2", - "sass": "1.45.1", + "sass": "1.49.0", "sass-loader": "12.4.0", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", @@ -102,39 +98,39 @@ "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.117.1", + "three": "0.136.0", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", "tmp": "0.2.1", "ts-loader": "9.2.6", "ts-node": "10.4.0", - "tsc-alias": "1.4.2", + "tsc-alias": "1.5.0", "tsconfig-paths": "3.12.0", "twemoji-parser": "13.1.0", - "typescript": "4.5.4", + "typescript": "4.5.5", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vue": "3.2.26", - "vue-loader": "16.8.3", + "vue": "3.2.29", + "vue-loader": "17.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vue-router": "4.0.5", "vue-style-loader": "4.1.3", "vue-svg-loader": "0.17.0-beta.2", "vuedraggable": "4.0.1", "web-push": "3.4.5", - "webpack": "5.65.0", + "webpack": "5.66.0", "webpack-cli": "4.9.1", "websocket": "1.0.34", - "ws": "8.4.0" + "ws": "8.4.2" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", - "@typescript-eslint/eslint-plugin": "5.8.1", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", + "@typescript-eslint/eslint-plugin": "5.10.0", "cross-env": "7.0.3", - "cypress": "9.2.0", - "eslint-plugin-import": "2.25.3", + "cypress": "9.3.1", + "eslint-plugin-import": "2.25.4", "start-server-and-test": "1.14.0" } } diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 4c83b78c91..5a935e1dc7 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -16,6 +16,8 @@ const data = localStorage.getItem('account'); // TODO: 外部ã‹ã‚‰ã¯readonlyã« export const $i = data ? reactive(JSON.parse(data) as Account) : null; +export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator); + export async function signout() { waiting(); localStorage.removeItem('account'); @@ -127,7 +129,12 @@ export async function login(token: Account['token'], redirect?: string) { unisonReload(); } -export async function openAccountMenu(ev: MouseEvent) { +export async function openAccountMenu(opts: { + includeCurrentAccount?: boolean; + withExtraOperation: boolean; + active?: misskey.entities.UserDetailed['id']; + onChoose?: (account: misskey.entities.UserDetailed) => void; +}, ev: MouseEvent) { function showSigninDialog() { popup(import('@/components/signin-dialog.vue'), {}, { done: res => { @@ -146,7 +153,7 @@ export async function openAccountMenu(ev: MouseEvent) { }, 'closed'); } - async function switchAccount(account: any) { + async function switchAccount(account: misskey.entities.UserDetailed) { const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; switchAccountWithToken(token); @@ -159,48 +166,58 @@ export async function openAccountMenu(ev: MouseEvent) { const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) }); + function createItem(account: misskey.entities.UserDetailed) { + return { + type: 'user', + user: account, + active: opts.active != null ? opts.active === account.id : false, + action: () => { + if (opts.onChoose) { + opts.onChoose(account); + } else { + switchAccount(account); + } + }, + }; + } + const accountItemPromises = storedAccounts.map(a => new Promise(res => { accountsPromise.then(accounts => { const account = accounts.find(x => x.id === a.id); if (account == null) return res(null); - res({ - type: 'user', - user: account, - action: () => { switchAccount(account); } - }); + res(createItem(account)); }); })); - popupMenu([...[{ - type: 'link', - text: i18n.locale.profile, - to: `/@${ $i.username }`, - avatar: $i, - }, null, ...accountItemPromises, { - icon: 'fas fa-plus', - text: i18n.locale.addAccount, - action: () => { - popupMenu([{ - text: i18n.locale.existingAccount, - action: () => { showSigninDialog(); }, - }, { - text: i18n.locale.createAccount, - action: () => { createAccount(); }, - }], ev.currentTarget || ev.target); - }, - }, { - type: 'link', - icon: 'fas fa-users', - text: i18n.locale.manageAccounts, - to: `/settings/accounts`, - }]], ev.currentTarget || ev.target, { - align: 'left' - }); -} - -// ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«æ›¸ããŸããªã„ã‘ã©ã“ã“ã«æ›¸ã‹ãªã„ã¨ä½•æ•…ã‹VeturãŒèªè˜ã—ãªã„ -declare module '@vue/runtime-core' { - interface ComponentCustomProperties { - $i: typeof $i; + if (opts.withExtraOperation) { + popupMenu([...[{ + type: 'link', + text: i18n.locale.profile, + to: `/@${ $i.username }`, + avatar: $i, + }, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { + icon: 'fas fa-plus', + text: i18n.locale.addAccount, + action: () => { + popupMenu([{ + text: i18n.locale.existingAccount, + action: () => { showSigninDialog(); }, + }, { + text: i18n.locale.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget || ev.target); + }, + }, { + type: 'link', + icon: 'fas fa-users', + text: i18n.locale.manageAccounts, + to: `/settings/accounts`, + }]], ev.currentTarget || ev.target, { + align: 'left' + }); + } else { + popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget || ev.target, { + align: 'left' + }); } } diff --git a/packages/client/src/components/note.sub.vue b/packages/client/src/components/MkNoteSub.vue index de4218e535..30c27e6235 100644 --- a/packages/client/src/components/note.sub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -10,13 +10,13 @@ <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> + <MkNoteSubNoteContent class="text" :note="note"/> </div> </div> </div> </div> <template v-if="depth < 5"> - <XSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> + <MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> </template> <div v-else class="more"> <MkA class="text _link" :to="notePage(note)">{{ $ts.continueThread }} <i class="fas fa-angle-double-right"></i></MkA> @@ -24,63 +24,36 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import { notePage } from '@/filters/note'; import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; +import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; import * as os from '@/os'; -export default defineComponent({ - name: 'XSub', +const props = withDefaults(defineProps<{ + note: misskey.entities.Note; + detail?: boolean; - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - }, - detail: { - type: Boolean, - required: false, - default: false - }, - // how many notes are in between this one and the note being viewed in detail - depth: { - type: Number, - required: false, - default: 1 - }, - }, - - data() { - return { - showContent: false, - replies: [], - }; - }, + // how many notes are in between this one and the note being viewed in detail + depth?: number; +}>(), { + depth: 1, +}); - created() { - if (this.detail) { - os.api('notes/children', { - noteId: this.note.id, - limit: 5 - }).then(replies => { - this.replies = replies; - }); - } - }, +let showContent = $ref(false); +let replies: misskey.entities.Note[] = $ref([]); - methods: { - notePage, - } -}); +if (props.detail) { + os.api('notes/children', { + noteId: props.note.id, + limit: 5 + }).then(res => { + replies = res; + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue index 6b07639f6d..cd04f62bca 100644 --- a/packages/client/src/components/abuse-report-window.vue +++ b/packages/client/src/components/abuse-report-window.vue @@ -1,8 +1,8 @@ <template> -<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> +<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> <template #header> <i class="fas fa-exclamation-circle" style="margin-right: 0.5em;"></i> - <I18n :src="$ts.reportAbuseOf" tag="span"> + <I18n :src="i18n.locale.reportAbuseOf" tag="span"> <template #name> <b><MkAcct :user="user"/></b> </template> @@ -11,65 +11,51 @@ <div class="dpvffvvy _monolithic_"> <div class="_section"> <MkTextarea v-model="comment"> - <template #label>{{ $ts.details }}</template> - <template #caption>{{ $ts.fillAbuseReportDescription }}</template> + <template #label>{{ i18n.locale.details }}</template> + <template #caption>{{ i18n.locale.fillAbuseReportDescription }}</template> </MkTextarea> </div> <div class="_section"> - <MkButton primary full :disabled="comment.length === 0" @click="send">{{ $ts.send }}</MkButton> + <MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.locale.send }}</MkButton> </div> </div> </XWindow> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script setup lang="ts"> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XWindow from '@/components/ui/window.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XWindow, - MkTextarea, - MkButton, - }, +const props = defineProps<{ + user: Misskey.entities.User; + initialComment?: string; +}>(); - props: { - user: { - type: Object, - required: true, - }, - initialComment: { - type: String, - required: false, - }, - }, +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); - emits: ['closed'], +const window = ref<InstanceType<typeof XWindow>>(); +const comment = ref(props.initialComment || ''); - data() { - return { - comment: this.initialComment || '', - }; - }, - - methods: { - send() { - os.apiWithDialog('users/report-abuse', { - userId: this.user.id, - comment: this.comment, - }, undefined, res => { - os.alert({ - type: 'success', - text: this.$ts.abuseReported - }); - this.$refs.window.close(); - }); - } - }, -}); +function send() { + os.apiWithDialog('users/report-abuse', { + userId: props.user.id, + comment: comment.value, + }, undefined).then(res => { + os.alert({ + type: 'success', + text: i18n.locale.abuseReported + }); + window.value?.close(); + emit('closed'); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue new file mode 100644 index 0000000000..b67cef209b --- /dev/null +++ b/packages/client/src/components/abuse-report.vue @@ -0,0 +1,102 @@ +<template> +<div class="bcekxzvu _card _gap"> + <div class="_content target"> + <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> + <MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId"> + <MkUserName class="name" :user="report.targetUser"/> + <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> + </MkA> + </div> + <div class="_content"> + <div> + <Mfm :text="report.comment"/> + </div> + <hr/> + <div>{{ $ts.reporter }}: <MkAcct :user="report.reporter"/></div> + <div v-if="report.assignee"> + {{ $ts.moderator }}: + <MkAcct :user="report.assignee"/> + </div> + <div><MkTime :time="report.createdAt"/></div> + </div> + <div class="_footer"> + <MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved"> + {{ $ts.forwardReport }} + <template #caption>{{ $ts.forwardReportIsAnonymous }}</template> + </MkSwitch> + <MkButton v-if="!report.resolved" primary @click="resolve">{{ $ts.abuseMarkAsResolved }}</MkButton> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; + +import MkButton from '@/components/ui/button.vue'; +import MkSwitch from '@/components/form/switch.vue'; +import { acct, userPage } from '@/filters/user'; +import * as os from '@/os'; + +export default defineComponent({ + components: { + MkButton, + MkSwitch, + }, + + emits: ['resolved'], + + props: { + report: { + type: Object, + required: true, + } + } + + data() { + return { + forward: this.report.forwarded, + }; + } + + methods: { + acct, + userPage, + + resolve() { + os.apiWithDialog('admin/resolve-abuse-user-report', { + forward: this.forward, + reportId: this.report.id, + }).then(() => { + this.$emit('resolved', this.report.id); + }); + } + } +}); +</script> + +<style lang="scss" scoped> +.bcekxzvu { + > .target { + display: flex; + width: 100%; + box-sizing: border-box; + text-align: left; + align-items: center; + + > .avatar { + width: 42px; + height: 42px; + } + + > .info { + margin-left: 0.3em; + padding: 0 8px; + flex: 1; + + > .name { + font-weight: bold; + } + } + } +} +</style> diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue index 450488b198..59b8e97304 100644 --- a/packages/client/src/components/analog-clock.vue +++ b/packages/client/src/components/analog-clock.vue @@ -40,106 +40,64 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; import * as tinycolor from 'tinycolor2'; -export default defineComponent({ - props: { - thickness: { - type: Number, - default: 0.1 - } - }, - - data() { - return { - now: new Date(), - enabled: true, - - graduationsPadding: 0.5, - handsPadding: 1, - handsTailLength: 0.7, - hHandLengthRatio: 0.75, - mHandLengthRatio: 1, - sHandLengthRatio: 1, - - computedStyle: getComputedStyle(document.documentElement) - }; - }, - - computed: { - dark(): boolean { - return tinycolor(this.computedStyle.getPropertyValue('--bg')).isDark(); - }, - - majorGraduationColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; - }, - minorGraduationColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - }, +withDefaults(defineProps<{ + thickness: number; +}>(), { + thickness: 0.1, +}); - sHandColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'; - }, - mHandColor(): string { - return tinycolor(this.computedStyle.getPropertyValue('--fg')).toHexString(); - }, - hHandColor(): string { - return tinycolor(this.computedStyle.getPropertyValue('--accent')).toHexString(); - }, +const now = ref(new Date()); +const enabled = ref(true); +const graduationsPadding = ref(0.5); +const handsPadding = ref(1); +const handsTailLength = ref(0.7); +const hHandLengthRatio = ref(0.75); +const mHandLengthRatio = ref(1); +const sHandLengthRatio = ref(1); +const computedStyle = getComputedStyle(document.documentElement); - s(): number { - return this.now.getSeconds(); - }, - m(): number { - return this.now.getMinutes(); - }, - h(): number { - return this.now.getHours(); - }, +const dark = computed(() => tinycolor(computedStyle.getPropertyValue('--bg')).isDark()); +const majorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'); +const minorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'); +const sHandColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'); +const mHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--fg')).toHexString()); +const hHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--accent')).toHexString()); +const s = computed(() => now.value.getSeconds()); +const m = computed(() => now.value.getMinutes()); +const h = computed(() => now.value.getHours()); +const hAngle = computed(() => Math.PI * (h.value % 12 + (m.value + s.value / 60) / 60) / 6); +const mAngle = computed(() => Math.PI * (m.value + s.value / 60) / 30); +const sAngle = computed(() => Math.PI * s.value / 30); +const graduations = computed(() => { + const angles: number[] = []; + for (let i = 0; i < 60; i++) { + const angle = Math.PI * i / 30; + angles.push(angle); + } - hAngle(): number { - return Math.PI * (this.h % 12 + (this.m + this.s / 60) / 60) / 6; - }, - mAngle(): number { - return Math.PI * (this.m + this.s / 60) / 30; - }, - sAngle(): number { - return Math.PI * this.s / 30; - }, + return angles; +}); - graduations(): any { - const angles = []; - for (let i = 0; i < 60; i++) { - const angle = Math.PI * i / 30; - angles.push(angle); - } +function tick() { + now.value = new Date(); +} - return angles; +onMounted(() => { + const update = () => { + if (enabled.value) { + tick(); + window.setTimeout(update, 1000); } - }, - - mounted() { - const update = () => { - if (this.enabled) { - this.tick(); - setTimeout(update, 1000); - } - }; - update(); - }, - - beforeUnmount() { - this.enabled = false; - }, + }; + update(); +}); - methods: { - tick() { - this.now = new Date(); - } - } +onBeforeUnmount(() => { + enabled.value = false; }); </script> diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue index 30be2ac741..7ba83b7cb1 100644 --- a/packages/client/src/components/autocomplete.vue +++ b/packages/client/src/components/autocomplete.vue @@ -1,5 +1,5 @@ <template> -<div class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> +<div ref="rootEl" class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> <ol v-if="type === 'user'" ref="suggests" class="users"> <li v-for="user in users" tabindex="-1" class="user" @click="complete(type, user)" @keydown="onKeydown"> <img class="avatar" :src="user.avatarUrl"/> @@ -8,7 +8,7 @@ </span> <span class="username">@{{ acct(user) }}</span> </li> - <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ $ts.selectUser }}</li> + <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ i18n.locale.selectUser }}</li> </ol> <ol v-else-if="hashtags.length > 0" ref="suggests" class="hashtags"> <li v-for="hashtag in hashtags" tabindex="-1" @click="complete(type, hashtag)" @keydown="onKeydown"> @@ -17,8 +17,8 @@ </ol> <ol v-else-if="emojis.length > 0" ref="suggests" class="emojis"> <li v-for="emoji in emojis" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown"> - <span v-if="emoji.isCustomEmoji" class="emoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> - <span v-else-if="!$store.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> + <span v-if="emoji.isCustomEmoji" class="emoji"><img :src="defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> + <span v-else-if="!defaultStore.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> <span v-else class="emoji">{{ emoji.emoji }}</span> <span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> <span v-if="emoji.aliasOf" class="alias">({{ emoji.aliasOf }})</span> @@ -33,15 +33,17 @@ </template> <script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { emojilist } from '@/scripts/emojilist'; +import { markRaw, ref, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import contains from '@/scripts/contains'; -import { twemojiSvgBase } from '@/scripts/twemoji-base'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { acct } from '@/filters/user'; import * as os from '@/os'; -import { instance } from '@/instance'; import { MFM_TAGS } from '@/scripts/mfm-tags'; +import { defaultStore } from '@/store'; +import { emojilist } from '@/scripts/emojilist'; +import { instance } from '@/instance'; +import { twemojiSvgBase } from '@/scripts/twemoji-base'; +import { i18n } from '@/i18n'; type EmojiDef = { emoji: string; @@ -54,16 +56,14 @@ type EmojiDef = { const lib = emojilist.filter(x => x.category !== 'flags'); const char2file = (char: string) => { - let codes = Array.from(char).map(x => x.codePointAt(0).toString(16)); + let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16)); if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); - codes = codes.filter(x => x && x.length); - return codes.join('-'); + return codes.filter(x => x && x.length).join('-'); }; const emjdb: EmojiDef[] = lib.map(x => ({ emoji: x.char, name: x.name, - aliasOf: null, url: `${twemojiSvgBase}/${char2file(x.char)}.svg` })); @@ -112,291 +112,270 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length); const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); //#endregion -export default defineComponent({ - props: { - type: { - type: String, - required: true, - }, +export default { + emojiDb, + emojiDefinitions, + emojilist, + customEmojis, +}; +</script> - q: { - type: String, - required: false, - }, +<script lang="ts" setup> +const props = defineProps<{ + type: string; + q: string | null; + textarea: HTMLTextAreaElement; + close: () => void; + x: number; + y: number; +}>(); - textarea: { - type: HTMLTextAreaElement, - required: true, - }, +const emit = defineEmits<{ + (e: 'done', v: { type: string; value: any }): void; + (e: 'closed'): void; +}>(); - close: { - type: Function, - required: true, - }, +const suggests = ref<Element>(); +const rootEl = ref<HTMLDivElement>(); - x: { - type: Number, - required: true, - }, +const fetching = ref(true); +const users = ref<any[]>([]); +const hashtags = ref<any[]>([]); +const emojis = ref<(EmojiDef)[]>([]); +const items = ref<Element[] | HTMLCollection>([]); +const mfmTags = ref<string[]>([]); +const select = ref(-1); +const zIndex = os.claimZIndex('high'); - y: { - type: Number, - required: true, - }, - }, +function complete(type: string, value: any) { + emit('done', { type, value }); + emit('closed'); + if (type === 'emoji') { + let recents = defaultStore.state.recentlyUsedEmojis; + recents = recents.filter((e: any) => e !== value); + recents.unshift(value); + defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); + } +} - emits: ['done', 'closed'], +function setPosition() { + if (!rootEl.value) return; + if (props.x + rootEl.value.offsetWidth > window.innerWidth) { + rootEl.value.style.left = (window.innerWidth - rootEl.value.offsetWidth) + 'px'; + } else { + rootEl.value.style.left = `${props.x}px`; + } + if (props.y + rootEl.value.offsetHeight > window.innerHeight) { + rootEl.value.style.top = (props.y - rootEl.value.offsetHeight) + 'px'; + rootEl.value.style.marginTop = '0'; + } else { + rootEl.value.style.top = props.y + 'px'; + rootEl.value.style.marginTop = 'calc(1em + 8px)'; + } +} - data() { - return { - getStaticImageUrl, - fetching: true, - users: [], - hashtags: [], - emojis: [], - items: [], - mfmTags: [], - select: -1, - zIndex: os.claimZIndex('high'), +function exec() { + select.value = -1; + if (suggests.value) { + for (const el of Array.from(items.value)) { + el.removeAttribute('data-selected'); } - }, - - updated() { - this.setPosition(); - this.items = (this.$refs.suggests as Element | undefined)?.children || []; - }, - - mounted() { - this.setPosition(); - - this.textarea.addEventListener('keydown', this.onKeydown); - - for (const el of Array.from(document.querySelectorAll('body *'))) { - el.addEventListener('mousedown', this.onMousedown); + } + if (props.type === 'user') { + if (!props.q) { + users.value = []; + fetching.value = false; + return; } - this.$nextTick(() => { - this.exec(); + const cacheKey = `autocomplete:user:${props.q}`; + const cache = sessionStorage.getItem(cacheKey); - this.$watch('q', () => { - this.$nextTick(() => { - this.exec(); - }); + if (cache) { + const users = JSON.parse(cache); + users.value = users; + fetching.value = false; + } else { + os.api('users/search-by-username-and-host', { + username: props.q, + limit: 10, + detail: false + }).then(searchedUsers => { + users.value = searchedUsers as any[]; + fetching.value = false; + // ã‚ャッシュ + sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers)); }); - }); - }, - - beforeUnmount() { - this.textarea.removeEventListener('keydown', this.onKeydown); - - for (const el of Array.from(document.querySelectorAll('body *'))) { - el.removeEventListener('mousedown', this.onMousedown); } - }, - - methods: { - complete(type, value) { - this.$emit('done', { type, value }); - this.$emit('closed'); - - if (type === 'emoji') { - let recents = this.$store.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== value); - recents.unshift(value); - this.$store.set('recentlyUsedEmojis', recents.splice(0, 32)); - } - }, - - setPosition() { - if (this.x + this.$el.offsetWidth > window.innerWidth) { - this.$el.style.left = (window.innerWidth - this.$el.offsetWidth) + 'px'; + } else if (props.type === 'hashtag') { + if (!props.q || props.q == '') { + hashtags.value = JSON.parse(localStorage.getItem('hashtags') || '[]'); + fetching.value = false; + } else { + const cacheKey = `autocomplete:hashtag:${props.q}`; + const cache = sessionStorage.getItem(cacheKey); + if (cache) { + const hashtags = JSON.parse(cache); + hashtags.value = hashtags; + fetching.value = false; } else { - this.$el.style.left = this.x + 'px'; + os.api('hashtags/search', { + query: props.q, + limit: 30 + }).then(searchedHashtags => { + hashtags.value = searchedHashtags as any[]; + fetching.value = false; + // ã‚ャッシュ + sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags)); + }); } + } + } else if (props.type === 'emoji') { + if (!props.q || props.q == '') { + // 最近使ã£ãŸçµµæ–‡å—をサジェスト + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x) as EmojiDef[]; + return; + } - if (this.y + this.$el.offsetHeight > window.innerHeight) { - this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px'; - this.$el.style.marginTop = '0'; - } else { - this.$el.style.top = this.y + 'px'; - this.$el.style.marginTop = 'calc(1em + 8px)'; - } - }, + const matched: EmojiDef[] = []; + const max = 30; - exec() { - this.select = -1; - if (this.$refs.suggests) { - for (const el of Array.from(this.items)) { - el.removeAttribute('data-selected'); - } - } + emojiDb.some(x => { + if (x.name.startsWith(props.q || '') && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); - if (this.type === 'user') { - if (this.q == null) { - this.users = []; - this.fetching = false; - return; - } + if (matched.length < max) { + emojiDb.some(x => { + if (x.name.startsWith(props.q || '') && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + } - const cacheKey = `autocomplete:user:${this.q}`; - const cache = sessionStorage.getItem(cacheKey); - if (cache) { - const users = JSON.parse(cache); - this.users = users; - this.fetching = false; - } else { - os.api('users/search-by-username-and-host', { - username: this.q, - limit: 10, - detail: false - }).then(users => { - this.users = users; - this.fetching = false; + if (matched.length < max) { + emojiDb.some(x => { + if (x.name.includes(props.q || '') && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + } - // ã‚ャッシュ - sessionStorage.setItem(cacheKey, JSON.stringify(users)); - }); - } - } else if (this.type === 'hashtag') { - if (this.q == null || this.q == '') { - this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); - this.fetching = false; - } else { - const cacheKey = `autocomplete:hashtag:${this.q}`; - const cache = sessionStorage.getItem(cacheKey); - if (cache) { - const hashtags = JSON.parse(cache); - this.hashtags = hashtags; - this.fetching = false; - } else { - os.api('hashtags/search', { - query: this.q, - limit: 30 - }).then(hashtags => { - this.hashtags = hashtags; - this.fetching = false; + emojis.value = matched; + } else if (props.type === 'mfmTag') { + if (!props.q || props.q == '') { + mfmTags.value = MFM_TAGS; + return; + } - // ã‚ャッシュ - sessionStorage.setItem(cacheKey, JSON.stringify(hashtags)); - }); - } - } - } else if (this.type === 'emoji') { - if (this.q == null || this.q == '') { - // 最近使ã£ãŸçµµæ–‡å—をサジェスト - this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); - return; - } + mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q || '')); + } +} + +function onMousedown(e: Event) { + if (!contains(rootEl.value, e.target) && (rootEl.value != e.target)) props.close(); +} - const matched = []; - const max = 30; +function onKeydown(e: KeyboardEvent) { + const cancel = () => { + e.preventDefault(); + e.stopPropagation(); + }; - emojiDb.some(x => { - if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - if (matched.length < max) { - emojiDb.some(x => { - if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - } - if (matched.length < max) { - emojiDb.some(x => { - if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - } + switch (e.key) { + case 'Enter': + if (select.value !== -1) { + cancel(); + (items.value[select.value] as any).click(); + } else { + props.close(); + } + break; - this.emojis = matched; - } else if (this.type === 'mfmTag') { - if (this.q == null || this.q == '') { - this.mfmTags = MFM_TAGS; - return; - } + case 'Escape': + cancel(); + props.close(); + break; - this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q)); + case 'ArrowUp': + if (select.value !== -1) { + cancel(); + selectPrev(); + } else { + props.close(); } - }, + break; + + case 'Tab': + case 'ArrowDown': + cancel(); + selectNext(); + break; - onMousedown(e) { - if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close(); - }, + default: + e.stopPropagation(); + props.textarea.focus(); + } +} - onKeydown(e) { - const cancel = () => { - e.preventDefault(); - e.stopPropagation(); - }; +function selectNext() { + if (++select.value >= items.value.length) select.value = 0; + if (items.value.length === 0) select.value = -1; + applySelect(); +} - switch (e.which) { - case 10: // [ENTER] - case 13: // [ENTER] - if (this.select !== -1) { - cancel(); - (this.items[this.select] as any).click(); - } else { - this.close(); - } - break; +function selectPrev() { + if (--select.value < 0) select.value = items.value.length - 1; + applySelect(); +} - case 27: // [ESC] - cancel(); - this.close(); - break; +function applySelect() { + for (const el of Array.from(items.value)) { + el.removeAttribute('data-selected'); + } - case 38: // [↑] - if (this.select !== -1) { - cancel(); - this.selectPrev(); - } else { - this.close(); - } - break; + if (select.value !== -1) { + items.value[select.value].setAttribute('data-selected', 'true'); + (items.value[select.value] as any).focus(); + } +} - case 9: // [TAB] - case 40: // [↓] - cancel(); - this.selectNext(); - break; +function chooseUser() { + props.close(); + os.selectUser().then(user => { + complete('user', user); + props.textarea.focus(); + }); +} - default: - e.stopPropagation(); - this.textarea.focus(); - } - }, +onUpdated(() => { + setPosition(); + items.value = suggests.value?.children || []; +}); - selectNext() { - if (++this.select >= this.items.length) this.select = 0; - if (this.items.length === 0) this.select = -1; - this.applySelect(); - }, +onMounted(() => { + setPosition(); - selectPrev() { - if (--this.select < 0) this.select = this.items.length - 1; - this.applySelect(); - }, + props.textarea.addEventListener('keydown', onKeydown); - applySelect() { - for (const el of Array.from(this.items)) { - el.removeAttribute('data-selected'); - } + for (const el of Array.from(document.querySelectorAll('body *'))) { + el.addEventListener('mousedown', onMousedown); + } - if (this.select !== -1) { - this.items[this.select].setAttribute('data-selected', 'true'); - (this.items[this.select] as any).focus(); - } - }, + nextTick(() => { + exec(); - chooseUser() { - this.close(); - os.selectUser().then(user => { - this.complete('user', user); - this.textarea.focus(); + watch(() => props.q, () => { + nextTick(() => { + exec(); }); - }, + }); + }); +}); + +onBeforeUnmount(() => { + props.textarea.removeEventListener('keydown', onKeydown); - acct + for (const el of Array.from(document.querySelectorAll('body *'))) { + el.removeEventListener('mousedown', onMousedown); } }); </script> diff --git a/packages/client/src/components/avatars.vue b/packages/client/src/components/avatars.vue index e843d26daa..958e5db0a1 100644 --- a/packages/client/src/components/avatars.vue +++ b/packages/client/src/components/avatars.vue @@ -1,30 +1,24 @@ <template> <div> - <div v-for="user in us" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;"> + <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;" :show-indicator="true"/> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - props: { - userIds: { - required: true - }, - }, - data() { - return { - us: [] - }; - }, - async created() { - this.us = await os.api('users/show', { - userIds: this.userIds - }); - } +const props = defineProps<{ + userIds: string[]; +}>(); + +const users = ref([]); + +onMounted(async () => { + users.value = await os.api('users/show', { + userIds: props.userIds + }); }); </script> diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index baa922506e..307fc312bc 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -1,12 +1,14 @@ <template> <div> - <span v-if="!available">{{ $ts.waiting }}<MkEllipsis/></span> - <div ref="captcha"></div> + <span v-if="!available">{{ i18n.locale.waiting }}<MkEllipsis/></span> + <div ref="captchaEl"></div> </div> </template> -<script lang="ts"> -import { defineComponent, PropType } from 'vue'; +<script lang="ts" setup> +import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; type Captcha = { render(container: string | Node, options: { @@ -14,7 +16,7 @@ type Captcha = { }): string; remove(id: string): void; execute(id: string): void; - reset(id: string): void; + reset(id?: string): void; getResponse(id: string): string; }; @@ -29,95 +31,85 @@ declare global { } } -export default defineComponent({ - props: { - provider: { - type: String as PropType<CaptchaProvider>, - required: true, - }, - sitekey: { - type: String, - required: true, - }, - modelValue: { - type: String, - }, - }, +const props = defineProps<{ + provider: CaptchaProvider; + sitekey: string; + modelValue?: string | null; +}>(); - data() { - return { - available: false, - }; - }, +const emit = defineEmits<{ + (ev: 'update:modelValue', v: string | null): void; +}>(); - computed: { - variable(): string { - switch (this.provider) { - case 'hcaptcha': return 'hcaptcha'; - case 'recaptcha': return 'grecaptcha'; - } - }, - loaded(): boolean { - return !!window[this.variable]; - }, - src(): string { - const endpoint = ({ - hcaptcha: 'https://hcaptcha.com/1', - recaptcha: 'https://www.recaptcha.net/recaptcha', - } as Record<CaptchaProvider, string>)[this.provider]; +const available = ref(false); - return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`; - }, - captcha(): Captcha { - return window[this.variable] || {} as unknown as Captcha; - }, - }, +const captchaEl = ref<HTMLDivElement | undefined>(); - created() { - if (this.loaded) { - this.available = true; - } else { - (document.getElementById(this.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { - async: true, - id: this.provider, - src: this.src, - }))) - .addEventListener('load', () => this.available = true); - } - }, +const variable = computed(() => { + switch (props.provider) { + case 'hcaptcha': return 'hcaptcha'; + case 'recaptcha': return 'grecaptcha'; + } +}); + +const loaded = computed(() => !!window[variable.value]); + +const src = computed(() => { + switch (props.provider) { + case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off'; + case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit'; + } +}); + +const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); + +if (loaded.value) { + available.value = true; +} else { + (document.getElementById(props.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { + async: true, + id: props.provider, + src: src.value, + }))) + .addEventListener('load', () => available.value = true); +} + +function reset() { + if (captcha.value?.reset) captcha.value.reset(); +} + +function requestRender() { + if (captcha.value.render && captchaEl.value instanceof Element) { + captcha.value.render(captchaEl.value, { + sitekey: props.sitekey, + theme: defaultStore.state.darkMode ? 'dark' : 'light', + callback: callback, + 'expired-callback': callback, + 'error-callback': callback, + }); + } else { + window.setTimeout(requestRender, 1); + } +} + +function callback(response?: string) { + emit('update:modelValue', typeof response == 'string' ? response : null); +} - mounted() { - if (this.available) { - this.requestRender(); - } else { - this.$watch('available', this.requestRender); - } - }, +onMounted(() => { + if (available.value) { + requestRender(); + } else { + watch(available, requestRender); + } +}); - beforeUnmount() { - this.reset(); - }, +onBeforeUnmount(() => { + reset(); +}); - methods: { - reset() { - if (this.captcha?.reset) this.captcha.reset(); - }, - requestRender() { - if (this.captcha.render && this.$refs.captcha instanceof Element) { - this.captcha.render(this.$refs.captcha, { - sitekey: this.sitekey, - theme: this.$store.state.darkMode ? 'dark' : 'light', - callback: this.callback, - 'expired-callback': this.callback, - 'error-callback': this.callback, - }); - } else { - setTimeout(this.requestRender.bind(this), 1); - } - }, - callback(response?: string) { - this.$emit('update:modelValue', typeof response == 'string' ? response : null); - }, - }, +defineExpose({ + reset, }); + </script> diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue index abde7c8148..0ad5384cd5 100644 --- a/packages/client/src/components/channel-follow-button.vue +++ b/packages/client/src/components/channel-follow-button.vue @@ -6,66 +6,54 @@ > <template v-if="!wait"> <template v-if="isFollowing"> - <span v-if="full">{{ $ts.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else> - <span v-if="full">{{ $ts.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - channel: { - type: Object, - required: true - }, - full: { - type: Boolean, - required: false, - default: false, - }, - }, +const props = withDefaults(defineProps<{ + channel: Record<string, any>; + full?: boolean; +}>(), { + full: false, +}); - data() { - return { - isFollowing: this.channel.isFollowing, - wait: false, - }; - }, +const isFollowing = ref<boolean>(props.channel.isFollowing); +const wait = ref(false); - methods: { - async onClick() { - this.wait = true; +async function onClick() { + wait.value = true; - try { - if (this.isFollowing) { - await os.api('channels/unfollow', { - channelId: this.channel.id - }); - this.isFollowing = false; - } else { - await os.api('channels/follow', { - channelId: this.channel.id - }); - this.isFollowing = true; - } - } catch (e) { - console.error(e); - } finally { - this.wait = false; - } + try { + if (isFollowing.value) { + await os.api('channels/unfollow', { + channelId: props.channel.id + }); + isFollowing.value = false; + } else { + await os.api('channels/follow', { + channelId: props.channel.id + }); + isFollowing.value = true; } + } catch (e) { + console.error(e); + } finally { + wait.value = false; } -}); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/channel-preview.vue b/packages/client/src/components/channel-preview.vue index f2b6de97fd..8d135a192f 100644 --- a/packages/client/src/components/channel-preview.vue +++ b/packages/client/src/components/channel-preview.vue @@ -6,7 +6,7 @@ <div class="status"> <div> <i class="fas fa-users fa-fw"></i> - <I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.locale._channel.usersCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.usersCount }}</b> </template> @@ -14,7 +14,7 @@ </div> <div> <i class="fas fa-pencil-alt fa-fw"></i> - <I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.locale._channel.notesCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.notesCount }}</b> </template> @@ -27,37 +27,26 @@ </article> <footer> <span v-if="channel.lastNotedAt"> - {{ $ts.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> + {{ i18n.locale.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> </span> </footer> </MkA> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - channel: { - type: Object, - required: true - }, - }, +const props = defineProps<{ + channel: Record<string, any>; +}>(); - data() { - return { - }; - }, - - computed: { - bannerStyle() { - if (this.channel.bannerUrl) { - return { backgroundImage: `url(${this.channel.bannerUrl})` }; - } else { - return { backgroundColor: '#4c5e6d' }; - } - } - }, +const bannerStyle = computed(() => { + if (props.channel.bannerUrl) { + return { backgroundImage: `url(${props.channel.bannerUrl})` }; + } else { + return { backgroundColor: '#4c5e6d' }; + } }); </script> diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index c4d0eb85dd..1959271f5d 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -170,10 +170,10 @@ export default defineComponent({ aspectRatio: props.aspectRatio || 2.5, layout: { padding: { - left: 16, - right: 16, - top: 16, - bottom: 8, + left: 0, + right: 0, + top: 0, + bottom: 0, }, }, scales: { diff --git a/packages/client/src/components/code-core.vue b/packages/client/src/components/code-core.vue index b58484c2ac..45a38afe04 100644 --- a/packages/client/src/components/code-core.vue +++ b/packages/client/src/components/code-core.vue @@ -3,33 +3,17 @@ <pre v-else :class="`language-${prismLang}`"><code :class="`language-${prismLang}`" v-html="html"></code></pre> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import 'prismjs'; import 'prismjs/themes/prism-okaidia.css'; -export default defineComponent({ - props: { - code: { - type: String, - required: true - }, - lang: { - type: String, - required: false - }, - inline: { - type: Boolean, - required: false - } - }, - computed: { - prismLang() { - return Prism.languages[this.lang] ? this.lang : 'js'; - }, - html() { - return Prism.highlight(this.code, Prism.languages[this.prismLang], this.prismLang); - } - } -}); +const props = defineProps<{ + code: string; + lang?: string; + inline?: boolean; +}>(); + +const prismLang = computed(() => Prism.languages[props.lang] ? props.lang : 'js'); +const html = computed(() => Prism.highlight(props.code, Prism.languages[prismLang.value], prismLang.value)); </script> diff --git a/packages/client/src/components/code.vue b/packages/client/src/components/code.vue index f5d6c5673a..d6478fd2f8 100644 --- a/packages/client/src/components/code.vue +++ b/packages/client/src/components/code.vue @@ -2,26 +2,14 @@ <XCode :code="code" :lang="lang" :inline="inline"/> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; -export default defineComponent({ - components: { - XCode: defineAsyncComponent(() => import('./code-core.vue')) - }, - props: { - code: { - type: String, - required: true - }, - lang: { - type: String, - required: false - }, - inline: { - type: Boolean, - required: false - } - } -}); +defineProps<{ + code: string; + lang?: string; + inline?: boolean; +}>(); + +const XCode = defineAsyncComponent(() => import('./code-core.vue')); </script> diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue index 4bec7b511e..ccfd11462a 100644 --- a/packages/client/src/components/cw-button.vue +++ b/packages/client/src/components/cw-button.vue @@ -1,45 +1,37 @@ <template> <button class="nrvgflfu _button" @click="toggle"> - <b>{{ modelValue ? $ts._cw.hide : $ts._cw.show }}</b> + <b>{{ modelValue ? i18n.locale._cw.hide : i18n.locale._cw.show }}</b> <span v-if="!modelValue">{{ label }}</span> </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import { length } from 'stringz'; +import * as misskey from 'misskey-js'; import { concat } from '@/scripts/array'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - modelValue: { - type: Boolean, - required: true - }, - note: { - type: Object, - required: true - } - }, - - computed: { - label(): string { - return concat([ - this.note.text ? [this.$t('_cw.chars', { count: length(this.note.text) })] : [], - this.note.files && this.note.files.length !== 0 ? [this.$t('_cw.files', { count: this.note.files.length }) ] : [], - this.note.poll != null ? [this.$ts.poll] : [] - ] as string[][]).join(' / '); - } - }, +const props = defineProps<{ + modelValue: boolean; + note: misskey.entities.Note; +}>(); - methods: { - length, +const emit = defineEmits<{ + (e: 'update:modelValue', v: boolean): void; +}>(); - toggle() { - this.$emit('update:modelValue', !this.modelValue); - } - } +const label = computed(() => { + return concat([ + props.note.text ? [i18n.t('_cw.chars', { count: length(props.note.text) })] : [], + props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length }) ] : [], + props.note.poll != null ? [i18n.locale.poll] : [] + ] as string[][]).join(' / '); }); + +const toggle = () => { + emit('update:modelValue', !props.modelValue); +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/date-separated-list.vue b/packages/client/src/components/date-separated-list.vue index aa84c6f60d..c85a0a6ffc 100644 --- a/packages/client/src/components/date-separated-list.vue +++ b/packages/client/src/components/date-separated-list.vue @@ -1,6 +1,8 @@ <script lang="ts"> import { defineComponent, h, PropType, TransitionGroup } from 'vue'; import MkAd from '@/components/global/ad.vue'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; export default defineComponent({ props: { @@ -30,29 +32,29 @@ export default defineComponent({ }, }, - methods: { - getDateText(time: string) { + setup(props, { slots, expose }) { + function getDateText(time: string) { const date = new Date(time).getDate(); const month = new Date(time).getMonth() + 1; - return this.$t('monthAndDay', { + return i18n.t('monthAndDay', { month: month.toString(), day: date.toString() }); } - }, - render() { - if (this.items.length === 0) return; + if (props.items.length === 0) return; + + const renderChildren = () => props.items.map((item, i) => { + if (!slots || !slots.default) return; - const renderChildren = () => this.items.map((item, i) => { - const el = this.$slots.default({ + const el = slots.default({ item: item })[0]; if (el.key == null && item.id) el.key = item.id; if ( - i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() + i != props.items.length - 1 && + new Date(item.createdAt).getDate() != new Date(props.items[i + 1].createdAt).getDate() ) { const separator = h('div', { class: 'separator', @@ -64,10 +66,10 @@ export default defineComponent({ h('i', { class: 'fas fa-angle-up icon', }), - this.getDateText(item.createdAt) + getDateText(item.createdAt) ]), h('span', [ - this.getDateText(this.items[i + 1].createdAt), + getDateText(props.items[i + 1].createdAt), h('i', { class: 'fas fa-angle-down icon', }) @@ -76,7 +78,7 @@ export default defineComponent({ return [el, separator]; } else { - if (this.ad && item._shouldInsertAd_) { + if (props.ad && item._shouldInsertAd_) { return [h(MkAd, { class: 'a', // advertiseã®æ„(ブãƒãƒƒã‚«ãƒ¼å¯¾ç–) key: item.id + ':ad', @@ -88,18 +90,19 @@ export default defineComponent({ } }); - return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? { - class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''), - name: 'list', - tag: 'div', - 'data-direction': this.direction, - 'data-reversed': this.reversed ? 'true' : 'false', - } : { - class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''), - }, { - default: renderChildren - }); - }, + return () => h( + defaultStore.state.animation ? TransitionGroup : 'div', + defaultStore.state.animation ? { + class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), + name: 'list', + tag: 'div', + 'data-direction': props.direction, + 'data-reversed': props.reversed ? 'true' : 'false', + } : { + class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), + }, + { default: renderChildren }); + } }); </script> diff --git a/packages/client/src/components/debobigego/base.vue b/packages/client/src/components/debobigego/base.vue deleted file mode 100644 index 9ed59abc69..0000000000 --- a/packages/client/src/components/debobigego/base.vue +++ /dev/null @@ -1,65 +0,0 @@ -<template> -<div v-size="{ max: [400] }" class="rbusrurv" :class="{ wide: forceWide }"> - <slot></slot> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - forceWide: { - type: Boolean, - required: false, - default: false, - } - } -}); -</script> - -<style lang="scss" scoped> -.rbusrurv { - // ä»–ã®CSSã‹ã‚‰ã‚‚å‚ç…§ã•れるã®ã§æ¶ˆã•ãªã„よã†ã« - --debobigegoXPadding: 32px; - --debobigegoYPadding: 32px; - - --debobigegoContentHMargin: 16px; - - font-size: 95%; - line-height: 1.3em; - background: var(--bg); - padding: var(--debobigegoYPadding) var(--debobigegoXPadding); - max-width: 750px; - margin: 0 auto; - - &:not(.wide).max-width_400px { - --debobigegoXPadding: 0px; - - > ::v-deep(*) { - ._debobigegoPanel { - border: solid 0.5px var(--divider); - border-radius: 0; - border-left: none; - border-right: none; - } - - ._debobigego_group { - > *:not(._debobigegoNoConcat) { - &:not(:last-child):not(._debobigegoNoConcatPrev) { - &._debobigegoPanel, ._debobigegoPanel { - border-bottom: solid 0.5px var(--divider); - } - } - - &:not(:first-child):not(._debobigegoNoConcatNext) { - &._debobigegoPanel, ._debobigegoPanel { - border-top: none; - } - } - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/button.vue b/packages/client/src/components/debobigego/button.vue deleted file mode 100644 index b883e817a4..0000000000 --- a/packages/client/src/components/debobigego/button.vue +++ /dev/null @@ -1,81 +0,0 @@ -<template> -<div class="yzpgjkxe _debobigegoItem"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <button class="main _button _debobigegoPanel _debobigegoClickable" :class="{ center, primary, danger }"> - <slot></slot> - <div class="suffix"> - <slot name="suffix"></slot> - <div class="icon"> - <slot name="suffixIcon"></slot> - </div> - </div> - </button> - <div class="_debobigegoCaption"><slot name="desc"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - primary: { - type: Boolean, - required: false, - default: false, - }, - danger: { - type: Boolean, - required: false, - default: false, - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - center: { - type: Boolean, - required: false, - default: true, - } - }, -}); -</script> - -<style lang="scss" scoped> -.yzpgjkxe { - > .main { - display: flex; - width: 100%; - box-sizing: border-box; - padding: 14px 16px; - text-align: left; - align-items: center; - - &.center { - display: block; - text-align: center; - } - - &.primary { - color: var(--accent); - } - - &.danger { - color: #ff2a2a; - } - - > .suffix { - display: inline-flex; - margin-left: auto; - opacity: 0.7; - - > .icon { - margin-left: 1em; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/debobigego.scss b/packages/client/src/components/debobigego/debobigego.scss deleted file mode 100644 index 833b656b66..0000000000 --- a/packages/client/src/components/debobigego/debobigego.scss +++ /dev/null @@ -1,52 +0,0 @@ -._debobigegoPanel { - background: var(--panel); - border-radius: var(--radius); - transition: background 0.2s ease; - - &._debobigegoClickable { - &:hover { - //background: var(--panelHighlight); - } - - &:active { - background: var(--panelHighlight); - transition: background 0s; - } - } -} - -._debobigegoLabel, -._debobigegoCaption { - font-size: 80%; - color: var(--fgTransparentWeak); - - &:empty { - display: none; - } -} - -._debobigegoLabel { - position: sticky; - top: var(--stickyTop, 0px); - z-index: 2; - margin: -8px calc(var(--debobigegoXPadding) * -1) 0 calc(var(--debobigegoXPadding) * -1); - padding: 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)) 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)); - background: var(--X17); - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); -} - -._themeChanging_ ._debobigegoLabel { - transition: none !important; - background: transparent; -} - -._debobigegoCaption { - padding: 8px var(--debobigegoContentHMargin) 0 var(--debobigegoContentHMargin); -} - -._debobigegoItem { - & + ._debobigegoItem { - margin-top: 24px; - } -} diff --git a/packages/client/src/components/debobigego/group.vue b/packages/client/src/components/debobigego/group.vue deleted file mode 100644 index 871d3c8dba..0000000000 --- a/packages/client/src/components/debobigego/group.vue +++ /dev/null @@ -1,78 +0,0 @@ -<template> -<div v-size="{ max: [500] }" v-sticky-container class="vrtktovg _debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <div ref="child" class="main _debobigego_group"> - <slot></slot> - </div> - <div class="_debobigegoCaption"><slot name="caption"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, ref } from 'vue'; - -export default defineComponent({ - setup(props, context) { - const child = ref<HTMLElement | null>(null); - - const scanChild = () => { - if (child.value == null) return; - const els = Array.from(child.value.children); - for (let i = 0; i < els.length; i++) { - const el = els[i]; - if (el.classList.contains('_debobigegoNoConcat')) { - if (els[i - 1]) els[i - 1].classList.add('_debobigegoNoConcatPrev'); - if (els[i + 1]) els[i + 1].classList.add('_debobigegoNoConcatNext'); - } - } - }; - - onMounted(() => { - scanChild(); - - const observer = new MutationObserver(records => { - scanChild(); - }); - - observer.observe(child.value, { - childList: true, - subtree: false, - attributes: false, - characterData: false, - }); - }); - - return { - child - }; - } -}); -</script> - -<style lang="scss" scoped> -.vrtktovg { - > .main { - > ::v-deep(*):not(._debobigegoNoConcat) { - &:not(._debobigegoNoConcatNext) { - margin: 0; - } - - &:not(:last-child):not(._debobigegoNoConcatPrev) { - &._debobigegoPanel, ._debobigegoPanel { - border-bottom: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - } - - &:not(:first-child):not(._debobigegoNoConcatNext) { - &._debobigegoPanel, ._debobigegoPanel { - border-top: none; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/info.vue b/packages/client/src/components/debobigego/info.vue deleted file mode 100644 index 41afb03304..0000000000 --- a/packages/client/src/components/debobigego/info.vue +++ /dev/null @@ -1,47 +0,0 @@ -<template> -<div class="fzenkabp _debobigegoItem"> - <div class="_debobigegoPanel" :class="{ warn }"> - <i v-if="warn" class="fas fa-exclamation-triangle"></i> - <i v-else class="fas fa-info-circle"></i> - <slot></slot> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - warn: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.fzenkabp { - > div { - padding: 14px 16px; - font-size: 90%; - background: var(--infoBg); - color: var(--infoFg); - - &.warn { - background: var(--infoWarnBg); - color: var(--infoWarnFg); - } - - > i { - margin-right: 4px; - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/input.vue b/packages/client/src/components/debobigego/input.vue deleted file mode 100644 index 6228a33fe4..0000000000 --- a/packages/client/src/components/debobigego/input.vue +++ /dev/null @@ -1,292 +0,0 @@ -<template> -<FormGroup class="_debobigegoItem"> - <template #label><slot></slot></template> - <div class="ztzhwixg _debobigegoItem" :class="{ inline, disabled }"> - <div ref="icon" class="icon"><slot name="icon"></slot></div> - <div class="input _debobigegoPanel"> - <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> - <input ref="inputEl" - v-model="v" - :type="type" - :disabled="disabled" - :required="required" - :readonly="readonly" - :placeholder="placeholder" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="spellcheck" - :step="step" - :list="id" - @focus="focused = true" - @blur="focused = false" - @keydown="onKeydown($event)" - @input="onInput" - > - <datalist v-if="datalist" :id="id"> - <option v-for="data in datalist" :value="data"/> - </datalist> - <div ref="suffixEl" class="suffix"><slot name="suffix"></slot></div> - </div> - </div> - <template #caption><slot name="desc"></slot></template> - - <FormButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import './debobigego.scss'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; - -export default defineComponent({ - components: { - FormGroup, - FormButton, - }, - props: { - modelValue: { - required: false - }, - 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 - }, - step: { - required: false - }, - datalist: { - type: Array, - required: false, - }, - inline: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - emits: ['change', 'keydown', 'enter', 'update:modelValue'], - setup(props, context) { - const { modelValue, type, autofocus } = toRefs(props); - const v = ref(modelValue.value); - const id = Math.random().toString(); // TODO: uuid? - 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 prefixEl = ref(null); - const suffixEl = ref(null); - - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - const onKeydown = (ev: KeyboardEvent) => { - context.emit('keydown', ev); - - if (ev.code === 'Enter') { - context.emit('enter'); - } - }; - - const updated = () => { - changed.value = false; - if (type?.value === 'number') { - context.emit('update:modelValue', parseFloat(v.value)); - } else { - context.emit('update:modelValue', v.value); - } - }; - - watch(modelValue.value, newValue => { - v.value = newValue; - }); - - watch(v, newValue => { - if (!props.manualSave) { - updated(); - } - - invalid.value = inputEl.value.validity.badInput; - }); - - onMounted(() => { - nextTick(() => { - if (autofocus.value) { - focus(); - } - - // ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒä½œæˆã•ã‚ŒãŸæ™‚ã€éžè¡¨ç¤ºçŠ¶æ…‹ã§ã‚ã‚‹å ´åˆãŒã‚ã‚‹ - // éžè¡¨ç¤ºçŠ¶æ…‹ã ã¨è¦ç´ ã®å¹…ãªã©ã¯0ã«ãªã£ã¦ã—ã¾ã†ã®ã§ã€å®šæœŸçš„ã«è¨ˆç®—ã™ã‚‹ - const clock = setInterval(() => { - if (prefixEl.value) { - if (prefixEl.value.offsetWidth) { - inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; - } - } - if (suffixEl.value) { - if (suffixEl.value.offsetWidth) { - inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; - } - } - }, 100); - - onUnmounted(() => { - clearInterval(clock); - }); - }); - }); - - return { - id, - v, - focused, - invalid, - changed, - filled, - inputEl, - prefixEl, - suffixEl, - focus, - onInput, - onKeydown, - updated, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.ztzhwixg { - position: relative; - - > .icon { - position: absolute; - top: 0; - left: 0; - width: 24px; - text-align: center; - line-height: 32px; - - &:not(:empty) + .input { - margin-left: 28px; - } - } - - > .input { - $height: 48px; - position: relative; - - > input { - display: block; - height: $height; - width: 100%; - margin: 0; - padding: 0 16px; - font: inherit; - font-weight: normal; - font-size: 1em; - line-height: $height; - color: var(--inputText); - background: transparent; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - box-sizing: border-box; - - &[type='file'] { - display: none; - } - } - - > .prefix, - > .suffix { - display: block; - position: absolute; - z-index: 1; - top: 0; - padding: 0 16px; - font-size: 1em; - line-height: $height; - color: var(--inputLabel); - pointer-events: none; - - &:empty { - display: none; - } - - > * { - display: inline-block; - min-width: 16px; - max-width: 150px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } - - > .prefix { - left: 0; - padding-right: 8px; - } - - > .suffix { - right: 0; - padding-left: 8px; - } - } - - &.inline { - display: inline-block; - margin: 0; - } - - &.disabled { - opacity: 0.7; - - &, * { - cursor: not-allowed !important; - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/key-value-view.vue b/packages/client/src/components/debobigego/key-value-view.vue deleted file mode 100644 index 0e034a2d54..0000000000 --- a/packages/client/src/components/debobigego/key-value-view.vue +++ /dev/null @@ -1,38 +0,0 @@ -<template> -<div class="_debobigegoItem"> - <div class="_debobigegoPanel anocepby"> - <span class="key"><slot name="key"></slot></span> - <span class="value"><slot name="value"></slot></span> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - -}); -</script> - -<style lang="scss" scoped> -.anocepby { - display: flex; - align-items: center; - padding: 14px var(--debobigegoContentHMargin); - - > .key { - margin-right: 12px; - white-space: nowrap; - } - - > .value { - margin-left: auto; - opacity: 0.7; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } -} -</style> diff --git a/packages/client/src/components/debobigego/link.vue b/packages/client/src/components/debobigego/link.vue deleted file mode 100644 index de463465d4..0000000000 --- a/packages/client/src/components/debobigego/link.vue +++ /dev/null @@ -1,103 +0,0 @@ -<template> -<div class="qmfkfnzi _debobigegoItem"> - <a v-if="external" class="main _button _debobigegoPanel _debobigegoClickable" :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="fas fa-external-link-alt icon"></i> - </span> - </a> - <MkA v-else class="main _button _debobigegoPanel _debobigegoClickable" :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="fas fa-chevron-right icon"></i> - </span> - </MkA> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - to: { - type: String, - required: true - }, - active: { - type: Boolean, - required: false - }, - external: { - type: Boolean, - required: false - }, - behavior: { - type: String, - required: false, - }, - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.qmfkfnzi { - > .main { - display: flex; - align-items: center; - width: 100%; - box-sizing: border-box; - padding: 14px 16px 14px 14px; - - &:hover { - text-decoration: none; - } - - &.active { - color: var(--accent); - background: var(--panelHighlight); - } - - > .icon { - width: 32px; - margin-right: 2px; - flex-shrink: 0; - text-align: center; - opacity: 0.8; - - &:empty { - display: none; - - & + .text { - padding-left: 4px; - } - } - } - - > .text { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - padding-right: 12px; - } - - > .right { - margin-left: auto; - opacity: 0.7; - - > .text:not(:empty) { - margin-right: 0.75em; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/object-view.vue b/packages/client/src/components/debobigego/object-view.vue deleted file mode 100644 index 68be08560b..0000000000 --- a/packages/client/src/components/debobigego/object-view.vue +++ /dev/null @@ -1,102 +0,0 @@ -<template> -<FormGroup class="_debobigegoItem"> - <template #label><slot></slot></template> - <div class="drooglns _debobigegoItem" :class="{ tall }"> - <div class="input _debobigegoPanel"> - <textarea v-model="v" - class="_monospace" - readonly - :spellcheck="false" - ></textarea> - </div> - </div> - <template #caption><slot name="desc"></slot></template> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent, ref, toRefs, watch } from 'vue'; -import * as JSON5 from 'json5'; -import './debobigego.scss'; -import FormGroup from './group.vue'; - -export default defineComponent({ - components: { - FormGroup, - }, - props: { - value: { - required: false - }, - tall: { - type: Boolean, - required: false, - default: false - }, - pre: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - setup(props, context) { - const { value } = toRefs(props); - const v = ref(''); - - watch(() => value, newValue => { - v.value = JSON5.stringify(newValue.value, null, '\t'); - }, { - immediate: true - }); - - return { - v, - }; - } -}); -</script> - -<style lang="scss" scoped> -.drooglns { - position: relative; - - > .input { - position: relative; - - > textarea { - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 130px; - margin: 0; - padding: 16px var(--debobigegoContentHMargin); - box-sizing: border-box; - font: inherit; - font-weight: normal; - font-size: 1em; - background: transparent; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - color: var(--fg); - tab-size: 2; - white-space: pre; - } - } - - &.tall { - > .input { - > textarea { - min-height: 200px; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/pagination.vue b/packages/client/src/components/debobigego/pagination.vue deleted file mode 100644 index 16779caa42..0000000000 --- a/packages/client/src/components/debobigego/pagination.vue +++ /dev/null @@ -1,42 +0,0 @@ -<template> -<FormGroup class="uljviswt _debobigegoItem"> - <template #label><slot name="label"></slot></template> - <slot :items="items"></slot> - <div v-if="empty" key="_empty_" class="empty"> - <slot name="empty"></slot> - </div> - <FormButton v-show="more" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </FormButton> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; -import paging from '@/scripts/paging'; - -export default defineComponent({ - components: { - FormButton, - FormGroup, - }, - - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - }, -}); -</script> - -<style lang="scss" scoped> -.uljviswt { -} -</style> diff --git a/packages/client/src/components/debobigego/radios.vue b/packages/client/src/components/debobigego/radios.vue deleted file mode 100644 index b4c5841337..0000000000 --- a/packages/client/src/components/debobigego/radios.vue +++ /dev/null @@ -1,112 +0,0 @@ -<script lang="ts"> -import { defineComponent, h } from 'vue'; -import MkRadio from '@/components/form/radio.vue'; -import './debobigego.scss'; - -export default defineComponent({ - components: { - MkRadio - }, - props: { - modelValue: { - required: false - }, - }, - data() { - return { - value: this.modelValue, - } - }, - watch: { - modelValue() { - this.value = this.modelValue; - }, - value() { - this.$emit('update:modelValue', this.value); - } - }, - render() { - const label = this.$slots.desc(); - let options = this.$slots.default(); - - // ãªãœã‹Fragmentã«ãªã‚‹ã“ã¨ãŒã‚ã‚‹ãŸã‚ - if (options.length === 1 && options[0].props == null) options = options[0].children; - - return h('div', { - class: 'cnklmpwm _debobigegoItem' - }, [ - h('div', { - class: '_debobigegoLabel', - }, label), - ...options.map(option => h('button', { - class: '_button _debobigegoPanel _debobigegoClickable', - key: option.key, - onClick: () => this.value = option.props.value, - }, [h('span', { - class: ['check', { checked: this.value === option.props.value }], - }), option.children])) - ]); - } -}); -</script> - -<style lang="scss"> -.cnklmpwm { - > button { - display: block; - width: 100%; - box-sizing: border-box; - padding: 14px 18px; - text-align: left; - - &:not(:first-of-type) { - border-top: none !important; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - &:not(:last-of-type) { - border-bottom: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - - > .check { - display: inline-block; - vertical-align: bottom; - position: relative; - width: 16px; - height: 16px; - margin-right: 8px; - background: none; - border: 2px solid var(--inputBorder); - border-radius: 100%; - transition: inherit; - - &:after { - content: ""; - display: block; - position: absolute; - top: 3px; - right: 3px; - bottom: 3px; - left: 3px; - border-radius: 100%; - opacity: 0; - transform: scale(0); - transition: .4s cubic-bezier(.25,.8,.25,1); - } - - &.checked { - border-color: var(--accent); - - &:after { - background-color: var(--accent); - transform: scale(1); - opacity: 1; - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/range.vue b/packages/client/src/components/debobigego/range.vue deleted file mode 100644 index dc71f25d83..0000000000 --- a/packages/client/src/components/debobigego/range.vue +++ /dev/null @@ -1,122 +0,0 @@ -<template> -<div class="ifitouly _debobigegoItem" :class="{ focused, disabled }"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <div class="_debobigegoPanel main"> - <input - ref="input" - v-model="v" - type="range" - :disabled="disabled" - :min="min" - :max="max" - :step="step" - @focus="focused = true" - @blur="focused = false" - @input="$emit('update:value', $event.target.value)" - /> - </div> - <div class="_debobigegoCaption"><slot name="caption"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - value: { - type: Number, - required: false, - default: 0 - }, - disabled: { - type: Boolean, - required: false, - default: false - }, - min: { - type: Number, - required: false, - default: 0 - }, - max: { - type: Number, - required: false, - default: 100 - }, - step: { - type: Number, - required: false, - default: 1 - }, - }, - data() { - return { - v: this.value, - focused: false - }; - }, - watch: { - value(v) { - this.v = parseFloat(v); - } - }, -}); -</script> - -<style lang="scss" scoped> -.ifitouly { - position: relative; - - > .main { - padding: 22px 16px; - - > input { - display: block; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--X10); - height: 4px; - width: 100%; - box-sizing: border-box; - margin: 0; - outline: 0; - border: 0; - border-radius: 7px; - - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } - - &::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - box-sizing: content-box; - } - - &::-moz-range-thumb { - -moz-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/select.vue b/packages/client/src/components/debobigego/select.vue deleted file mode 100644 index 081bbfe302..0000000000 --- a/packages/client/src/components/debobigego/select.vue +++ /dev/null @@ -1,145 +0,0 @@ -<template> -<div class="yrtfrpux _debobigegoItem" :class="{ disabled, inline }"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <div ref="icon" class="icon"><slot name="icon"></slot></div> - <div class="input _debobigegoPanel _debobigegoClickable" @click="focus"> - <div ref="prefix" class="prefix"><slot name="prefix"></slot></div> - <select ref="input" - v-model="v" - :required="required" - :disabled="disabled" - @focus="focused = true" - @blur="focused = false" - > - <slot></slot> - </select> - <div class="suffix"> - <i class="fas fa-chevron-down"></i> - </div> - </div> - <div class="_debobigegoCaption"><slot name="caption"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - modelValue: { - required: false - }, - required: { - type: Boolean, - required: false - }, - disabled: { - type: Boolean, - required: false - }, - inline: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - }; - }, - computed: { - v: { - get() { - return this.modelValue; - }, - set(v) { - this.$emit('update:modelValue', v); - } - }, - }, - methods: { - focus() { - this.$refs.input.focus(); - } - } -}); -</script> - -<style lang="scss" scoped> -.yrtfrpux { - position: relative; - - > .icon { - position: absolute; - top: 0; - left: 0; - width: 24px; - text-align: center; - line-height: 32px; - - &:not(:empty) + .input { - margin-left: 28px; - } - } - - > .input { - display: flex; - position: relative; - - > select { - display: block; - flex: 1; - width: 100%; - padding: 0 16px; - font: inherit; - font-weight: normal; - font-size: 1em; - height: 48px; - background: none; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - appearance: none; - -webkit-appearance: none; - color: var(--fg); - - option, - optgroup { - color: var(--fg); - background: var(--bg); - } - } - - > .prefix, - > .suffix { - display: block; - align-self: center; - justify-self: center; - font-size: 1em; - line-height: 32px; - color: var(--inputLabel); - pointer-events: none; - - &:empty { - display: none; - } - - > * { - display: block; - min-width: 16px; - } - } - - > .prefix { - padding-right: 4px; - } - - > .suffix { - padding: 0 16px 0 0; - opacity: 0.7; - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/suspense.vue b/packages/client/src/components/debobigego/suspense.vue deleted file mode 100644 index acb0b64424..0000000000 --- a/packages/client/src/components/debobigego/suspense.vue +++ /dev/null @@ -1,101 +0,0 @@ -<template> -<transition name="fade" mode="out-in"> - <div v-if="pending" class="_debobigegoItem"> - <div class="_debobigegoPanel"> - <MkLoading/> - </div> - </div> - <div v-else-if="resolved" class="_debobigegoItem"> - <slot :result="result"></slot> - </div> - <div v-else class="_debobigegoItem"> - <div class="_debobigegoPanel eiurkvay"> - <div><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</div> - <MkButton inline class="retry" @click="retry"><i class="fas fa-redo-alt"></i> {{ $ts.retry }}</MkButton> - </div> - </div> -</transition> -</template> - -<script lang="ts"> -import { defineComponent, PropType, ref, watch } from 'vue'; -import './debobigego.scss'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - p: { - type: Function as PropType<() => Promise<any>>, - required: true, - } - }, - - setup(props, context) { - 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(); - }; - - return { - pending, - resolved, - rejected, - result, - retry, - }; - } -}); -</script> - -<style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.eiurkvay { - padding: 16px; - text-align: center; - - > .retry { - margin-top: 16px; - } -} -</style> diff --git a/packages/client/src/components/debobigego/switch.vue b/packages/client/src/components/debobigego/switch.vue deleted file mode 100644 index 239140f730..0000000000 --- a/packages/client/src/components/debobigego/switch.vue +++ /dev/null @@ -1,132 +0,0 @@ -<template> -<div class="ijnpvmgr _debobigegoItem"> - <div class="main _debobigegoPanel _debobigegoClickable" - :class="{ disabled, checked }" - :aria-checked="checked" - :aria-disabled="disabled" - @click.prevent="toggle" - > - <input - ref="input" - type="checkbox" - :disabled="disabled" - @keydown.enter="toggle" - > - <span v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button"> - <span class="handle"></span> - </span> - <span class="label"> - <span><slot></slot></span> - </span> - </div> - <div class="_debobigegoCaption"><slot name="desc"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - modelValue: { - type: Boolean, - default: false - }, - disabled: { - type: Boolean, - default: false - } - }, - computed: { - checked(): boolean { - return this.modelValue; - } - }, - methods: { - toggle() { - if (this.disabled) return; - this.$emit('update:modelValue', !this.checked); - } - } -}); -</script> - -<style lang="scss" scoped> -.ijnpvmgr { - > .main { - position: relative; - display: flex; - padding: 14px 16px; - cursor: pointer; - - > * { - user-select: none; - } - - > input { - position: absolute; - width: 0; - height: 0; - opacity: 0; - margin: 0; - } - - > .button { - position: relative; - display: inline-block; - flex-shrink: 0; - margin: 0; - width: 34px; - height: 22px; - background: var(--switchBg); - outline: none; - border-radius: 999px; - transition: all 0.3s; - cursor: pointer; - - > .handle { - position: absolute; - top: 0; - left: 3px; - bottom: 0; - margin: auto 0; - border-radius: 100%; - transition: background-color 0.3s, transform 0.3s; - width: 16px; - height: 16px; - background-color: #fff; - pointer-events: none; - } - } - - > .label { - margin-left: 12px; - display: block; - transition: inherit; - color: var(--fg); - - > span { - display: block; - line-height: 20px; - transition: inherit; - } - } - - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } - - &.checked { - > .button { - background-color: var(--accent); - - > .handle { - transform: translateX(12px); - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/textarea.vue b/packages/client/src/components/debobigego/textarea.vue deleted file mode 100644 index ca5b35c49e..0000000000 --- a/packages/client/src/components/debobigego/textarea.vue +++ /dev/null @@ -1,161 +0,0 @@ -<template> -<FormGroup class="_debobigegoItem"> - <template #label><slot></slot></template> - <div class="rivhosbp _debobigegoItem" :class="{ tall, pre }"> - <div class="input _debobigegoPanel"> - <textarea ref="input" v-model="v" - :class="{ code, _monospace: code }" - :required="required" - :readonly="readonly" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="!code" - @input="onInput" - @focus="focused = true" - @blur="focused = false" - ></textarea> - </div> - </div> - <template #caption><slot name="desc"></slot></template> - - <FormButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent, ref, toRefs, watch } from 'vue'; -import './debobigego.scss'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; - -export default defineComponent({ - components: { - FormGroup, - FormButton, - }, - props: { - modelValue: { - required: false - }, - required: { - type: Boolean, - required: false - }, - readonly: { - type: Boolean, - required: false - }, - pattern: { - type: String, - required: false - }, - autocomplete: { - type: String, - required: false - }, - code: { - type: Boolean, - required: false - }, - tall: { - type: Boolean, - required: false, - default: false - }, - pre: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - setup(props, context) { - const { modelValue } = toRefs(props); - const v = ref(modelValue.value); - const changed = ref(false); - const inputEl = ref(null); - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - - const updated = () => { - changed.value = false; - context.emit('update:modelValue', v.value); - }; - - watch(modelValue.value, newValue => { - v.value = newValue; - }); - - watch(v, newValue => { - if (!props.manualSave) { - updated(); - } - }); - - return { - v, - updated, - changed, - focus, - onInput, - }; - } -}); -</script> - -<style lang="scss" scoped> -.rivhosbp { - position: relative; - - > .input { - position: relative; - - > textarea { - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 130px; - margin: 0; - padding: 16px; - box-sizing: border-box; - font: inherit; - font-weight: normal; - font-size: 1em; - background: transparent; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - color: var(--fg); - - &.code { - tab-size: 2; - } - } - } - - &.tall { - > .input { - > textarea { - min-height: 200px; - } - } - } - - &.pre { - > .input { - > textarea { - white-space: pre; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/tuple.vue b/packages/client/src/components/debobigego/tuple.vue deleted file mode 100644 index 1d2a6cb55e..0000000000 --- a/packages/client/src/components/debobigego/tuple.vue +++ /dev/null @@ -1,36 +0,0 @@ -<template> -<div v-size="{ max: [500] }" class="wthhikgt _debobigegoItem"> - <slot></slot> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ -}); -</script> - -<style lang="scss" scoped> -.wthhikgt { - position: relative; - display: flex; - - > ::v-deep(*) { - flex: 1; - margin: 0; - - &:not(:last-child) { - margin-right: 16px; - } - } - - &.max-width_500px { - display: block; - - > ::v-deep(*) { - margin: inherit; - } - } -} -</style> diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index c2fa1b02b8..b6b649cde9 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="$emit('closed')"> +<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')"> <div class="mk-dialog"> <div v-if="icon" class="icon"> <i :class="icon"></i> @@ -14,7 +14,7 @@ </div> <header v-if="title"><Mfm :text="title"/></header> <div v-if="text" class="body"><Mfm :text="text"/></div> - <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"> + <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> <template v-if="input.type === 'password'" #prefix><i class="fas fa-lock"></i></template> </MkInput> <MkSelect v-if="select" v-model="selectedValue" autofocus> @@ -28,8 +28,8 @@ </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" class="buttons"> - <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? $ts.ok : $ts.gotIt }}</MkButton> - <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ $ts.cancel }}</MkButton> + <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.locale.ok : i18n.locale.gotIt }}</MkButton> + <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.locale.cancel }}</MkButton> </div> <div v-if="actions" class="buttons"> <MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton> @@ -38,118 +38,108 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkModal, - MkButton, - MkInput, - MkSelect, - }, +type Input = { + type: HTMLInputElement['type']; + placeholder?: string | null; + default: any | null; +}; - props: { - type: { - type: String, - required: false, - default: 'info' - }, - title: { - type: String, - required: false - }, - text: { - type: String, - required: false - }, - input: { - required: false - }, - select: { - required: false - }, - icon: { - required: false - }, - actions: { - required: false - }, - showOkButton: { - type: Boolean, - default: true - }, - showCancelButton: { - type: Boolean, - default: false - }, - cancelableByBgClick: { - type: Boolean, - default: true - }, - }, +type Select = { + items: { + value: string; + text: string; + }[]; + groupedItems: { + label: string; + items: { + value: string; + text: string; + }[]; + }[]; + default: string | null; +}; - emits: ['done', 'closed'], +const props = withDefaults(defineProps<{ + type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting'; + title: string; + text?: string; + input?: Input; + select?: Select; + icon?: string; + actions?: { + text: string; + primary?: boolean, + callback: (...args: any[]) => void; + }[]; + showOkButton?: boolean; + showCancelButton?: boolean; + cancelableByBgClick?: boolean; +}>(), { + type: 'info', + showOkButton: true, + showCancelButton: false, + cancelableByBgClick: true, +}); - data() { - return { - inputValue: this.input && this.input.default ? this.input.default : null, - selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, - }; - }, +const emit = defineEmits<{ + (e: 'done', v: { canceled: boolean; result: any }): void; + (e: 'closed'): void; +}>(); - mounted() { - document.addEventListener('keydown', this.onKeydown); - }, +const modal = ref<InstanceType<typeof MkModal>>(); - beforeUnmount() { - document.removeEventListener('keydown', this.onKeydown); - }, +const inputValue = ref(props.input?.default || null); +const selectedValue = ref(props.select?.default || null); - methods: { - done(canceled, result?) { - this.$emit('done', { canceled, result }); - this.$refs.modal.close(); - }, +function done(canceled: boolean, result?) { + emit('done', { canceled, result }); + modal.value?.close(); +} - async ok() { - if (!this.showOkButton) return; +async function ok() { + if (!props.showOkButton) return; - const result = - this.input ? this.inputValue : - this.select ? this.selectedValue : - true; - this.done(false, result); - }, + const result = + props.input ? inputValue.value : + props.select ? selectedValue.value : + true; + done(false, result); +} - cancel() { - this.done(true); - }, +function cancel() { + done(true); +} +/* +function onBgClick() { + if (props.cancelableByBgClick) cancel(); +} +*/ +function onKeydown(e: KeyboardEvent) { + if (e.key === 'Escape') cancel(); +} - onBgClick() { - if (this.cancelableByBgClick) { - this.cancel(); - } - }, +function onInputKeydown(e: KeyboardEvent) { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + ok(); + } +} - onKeydown(e) { - if (e.which === 27) { // ESC - this.cancel(); - } - }, +onMounted(() => { + document.addEventListener('keydown', onKeydown); +}); - onInputKeydown(e) { - if (e.which === 13) { // Enter - e.preventDefault(); - e.stopPropagation(); - this.ok(); - } - } - } +onBeforeUnmount(() => { + document.removeEventListener('keydown', onKeydown); }); </script> diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue index e94b6b8bcb..81b80e7e8e 100644 --- a/packages/client/src/components/drive-file-thumbnail.vue +++ b/packages/client/src/components/drive-file-thumbnail.vue @@ -14,71 +14,42 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as Misskey from 'misskey-js'; import ImgWithBlurhash from '@/components/img-with-blurhash.vue'; -import { ColdDeviceStorage } from '@/store'; -export default defineComponent({ - components: { - ImgWithBlurhash - }, - props: { - file: { - type: Object, - required: true - }, - fit: { - type: String, - required: false, - default: 'cover' - }, - }, - data() { - return { - isContextmenuShowing: false, - isDragging: false, +const props = defineProps<{ + file: Misskey.entities.DriveFile; + fit: string; +}>(); - }; - }, - computed: { - is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' { - if (this.file.type.startsWith('image/')) return 'image'; - if (this.file.type.startsWith('video/')) return 'video'; - if (this.file.type === 'audio/midi') return 'midi'; - if (this.file.type.startsWith('audio/')) return 'audio'; - if (this.file.type.endsWith('/csv')) return 'csv'; - if (this.file.type.endsWith('/pdf')) return 'pdf'; - if (this.file.type.startsWith('text/')) return 'textfile'; - if ([ - "application/zip", - "application/x-cpio", - "application/x-bzip", - "application/x-bzip2", - "application/java-archive", - "application/x-rar-compressed", - "application/x-tar", - "application/gzip", - "application/x-7z-compressed" - ].some(e => e === this.file.type)) return 'archive'; - return 'unknown'; - }, - isThumbnailAvailable(): boolean { - return this.file.thumbnailUrl - ? (this.is === 'image' || this.is === 'video') - : false; - }, - }, - mounted() { - const audioTag = this.$refs.volumectrl as HTMLAudioElement; - if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume'); - }, - methods: { - volumechange() { - const audioTag = this.$refs.volumectrl as HTMLAudioElement; - ColdDeviceStorage.set('mediaVolume', audioTag.volume); - } - } +const is = computed(() => { + if (props.file.type.startsWith('image/')) return 'image'; + if (props.file.type.startsWith('video/')) return 'video'; + if (props.file.type === 'audio/midi') return 'midi'; + if (props.file.type.startsWith('audio/')) return 'audio'; + if (props.file.type.endsWith('/csv')) return 'csv'; + if (props.file.type.endsWith('/pdf')) return 'pdf'; + if (props.file.type.startsWith('text/')) return 'textfile'; + if ([ + "application/zip", + "application/x-cpio", + "application/x-bzip", + "application/x-bzip2", + "application/java-archive", + "application/x-rar-compressed", + "application/x-tar", + "application/gzip", + "application/x-7z-compressed" + ].some(e => e === props.file.type)) return 'archive'; + return 'unknown'; +}); + +const isThumbnailAvailable = computed(() => { + return props.file.thumbnailUrl + ? (is.value === 'image' as const || is.value === 'video') + : false; }); </script> diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue index 75537dfe3e..6d84511277 100644 --- a/packages/client/src/components/drive-select-dialog.vue +++ b/packages/client/src/components/drive-select-dialog.vue @@ -7,64 +7,51 @@ @click="cancel()" @close="cancel()" @ok="ok()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> - {{ multiple ? ((type === 'file') ? $ts.selectFiles : $ts.selectFolders) : ((type === 'file') ? $ts.selectFile : $ts.selectFolder) }} + {{ multiple ? ((type === 'file') ? i18n.locale.selectFiles : i18n.locale.selectFolders) : ((type === 'file') ? i18n.locale.selectFile : i18n.locale.selectFolder) }} <span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> </template> <XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/> </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XDrive from './drive.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import number from '@/filters/number'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive, - XModalWindow, - }, - - props: { - type: { - type: String, - required: false, - default: 'file' - }, - multiple: { - type: Boolean, - default: false - } - }, +withDefaults(defineProps<{ + type?: 'file' | 'folder'; + multiple: boolean; +}>(), { + type: 'file', +}); - emits: ['done', 'closed'], +const emit = defineEmits<{ + (e: 'done', r?: Misskey.entities.DriveFile[]): void; + (e: 'closed'): void; +}>(); - data() { - return { - selected: [] - }; - }, +const dialog = ref<InstanceType<typeof XModalWindow>>(); - methods: { - ok() { - this.$emit('done', this.selected); - this.$refs.dialog.close(); - }, +const selected = ref<Misskey.entities.DriveFile[]>([]); - cancel() { - this.$emit('done'); - this.$refs.dialog.close(); - }, +function ok() { + emit('done', selected.value); + dialog.value?.close(); +} - onChangeSelection(xs) { - this.selected = xs; - }, +function cancel() { + emit('done'); + dialog.value?.close(); +} - number - } -}); +function onChangeSelection(files: Misskey.entities.DriveFile[]) { + selected.value = files; +} </script> diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue index 43f07ebe76..8b60bf7794 100644 --- a/packages/client/src/components/drive-window.vue +++ b/packages/client/src/components/drive-window.vue @@ -3,42 +3,27 @@ :initial-width="800" :initial-height="500" :can-resize="true" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> - {{ $ts.drive }} + {{ i18n.locale.drive }} </template> <XDrive :initial-folder="initialFolder"/> </XWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; import XDrive from './drive.vue'; import XWindow from '@/components/ui/window.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive, - XWindow, - }, +defineProps<{ + initialFolder?: Misskey.entities.DriveFolder; +}>(); - props: { - initialFolder: { - type: Object, - required: false - }, - }, - - emits: ['closed'], - - data() { - return { - }; - }, - - methods: { - - } -}); +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue index 511647229e..fd6a813838 100644 --- a/packages/client/src/components/drive.file.vue +++ b/packages/client/src/components/drive.file.vue @@ -8,17 +8,17 @@ @dragstart="onDragstart" @dragend="onDragend" > - <div v-if="$i.avatarId == file.id" class="label"> + <div v-if="$i?.avatarId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ $ts.avatar }}</p> + <p>{{ i18n.locale.avatar }}</p> </div> - <div v-if="$i.bannerId == file.id" class="label"> + <div v-if="$i?.bannerId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ $ts.banner }}</p> + <p>{{ i18n.locale.banner }}</p> </div> <div v-if="file.isSensitive" class="label red"> <img src="/client-assets/label-red.svg"/> - <p>{{ $ts.nsfw }}</p> + <p>{{ i18n.locale.nsfw }}</p> </div> <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> @@ -30,179 +30,155 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - MkDriveFileThumbnail - }, +const props = withDefaults(defineProps<{ + file: Misskey.entities.DriveFile; + isSelected?: boolean; + selectMode?: boolean; +}>(), { + isSelected: false, + selectMode: false, +}); - props: { - file: { - type: Object, - required: true, - }, - isSelected: { - type: Boolean, - required: false, - default: false, - }, - selectMode: { - type: Boolean, - required: false, - default: false, - } - }, +const emit = defineEmits<{ + (e: 'chosen', r: Misskey.entities.DriveFile): void; + (e: 'dragstart'): void; + (e: 'dragend'): void; +}>(); - emits: ['chosen'], +const isDragging = ref(false); - data() { - return { - isDragging: false - }; - }, +const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`); - computed: { - // TODO: parentã¸ã®å‚ç…§ã‚’ç„¡ãã™ - browser(): any { - return this.$parent; - }, - title(): string { - return `${this.file.name}\n${this.file.type} ${bytes(this.file.size)}`; - } - }, - - methods: { - getMenu() { - return [{ - text: this.$ts.rename, - icon: 'fas fa-i-cursor', - action: this.rename - }, { - text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, - icon: this.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', - action: this.toggleSensitive - }, { - text: this.$ts.describeFile, - icon: 'fas fa-i-cursor', - action: this.describe - }, null, { - text: this.$ts.copyUrl, - icon: 'fas fa-link', - action: this.copyUrl - }, { - type: 'a', - href: this.file.url, - target: '_blank', - text: this.$ts.download, - icon: 'fas fa-download', - download: this.file.name - }, null, { - text: this.$ts.delete, - icon: 'fas fa-trash-alt', - danger: true, - action: this.deleteFile - }]; - }, +function getMenu() { + return [{ + text: i18n.locale.rename, + icon: 'fas fa-i-cursor', + action: rename + }, { + text: props.file.isSensitive ? i18n.locale.unmarkAsSensitive : i18n.locale.markAsSensitive, + icon: props.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', + action: toggleSensitive + }, { + text: i18n.locale.describeFile, + icon: 'fas fa-i-cursor', + action: describe + }, null, { + text: i18n.locale.copyUrl, + icon: 'fas fa-link', + action: copyUrl + }, { + type: 'a', + href: props.file.url, + target: '_blank', + text: i18n.locale.download, + icon: 'fas fa-download', + download: props.file.name + }, null, { + text: i18n.locale.delete, + icon: 'fas fa-trash-alt', + danger: true, + action: deleteFile + }]; +} - onClick(ev) { - if (this.selectMode) { - this.$emit('chosen', this.file); - } else { - os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); - } - }, +function onClick(ev: MouseEvent) { + if (props.selectMode) { + emit('chosen', props.file); + } else { + os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); + } +} - onContextmenu(e) { - os.contextMenu(this.getMenu(), e); - }, +function onContextmenu(e: MouseEvent) { + os.contextMenu(getMenu(), e); +} - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(this.file)); - this.isDragging = true; +function onDragstart(e: DragEvent) { + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); + } + isDragging.value = true; - // 親ブラウザã«å¯¾ã—ã¦ã€ãƒ‰ãƒ©ãƒƒã‚°ãŒé–‹å§‹ã•れãŸãƒ•ラグを立ã¦ã‚‹ - // (=ã‚ãªãŸã®åä¾›ãŒã€ãƒ‰ãƒ©ãƒƒã‚°ã‚’é–‹å§‹ã—ã¾ã—ãŸã‚ˆ) - this.browser.isDragSource = true; - }, + emit('dragstart'); +} - onDragend(e) { - this.isDragging = false; - this.browser.isDragSource = false; - }, +function onDragend() { + isDragging.value = false; + emit('dragend'); +} - rename() { - os.inputText({ - title: this.$ts.renameFile, - placeholder: this.$ts.inputNewFileName, - default: this.file.name, - allowEmpty: false - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/files/update', { - fileId: this.file.id, - name: name - }); - }); - }, +function rename() { + os.inputText({ + title: i18n.locale.renameFile, + placeholder: i18n.locale.inputNewFileName, + default: props.file.name, + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/files/update', { + fileId: props.file.id, + name: name + }); + }); +} - describe() { - os.popup(import('@/components/media-caption.vue'), { - title: this.$ts.describeFile, - input: { - placeholder: this.$ts.inputNewDescription, - default: this.file.comment !== null ? this.file.comment : '', - }, - image: this.file - }, { - done: result => { - if (!result || result.canceled) return; - let comment = result.result; - os.api('drive/files/update', { - fileId: this.file.id, - comment: comment.length == 0 ? null : comment - }); - } - }, 'closed'); +function describe() { + os.popup(import('@/components/media-caption.vue'), { + title: i18n.locale.describeFile, + input: { + placeholder: i18n.locale.inputNewDescription, + default: props.file.comment !== null ? props.file.comment : '', }, - - toggleSensitive() { + image: props.file + }, { + done: result => { + if (!result || result.canceled) return; + let comment = result.result; os.api('drive/files/update', { - fileId: this.file.id, - isSensitive: !this.file.isSensitive + fileId: props.file.id, + comment: comment.length == 0 ? null : comment }); - }, - - copyUrl() { - copyToClipboard(this.file.url); - os.success(); - }, - - addApp() { - alert('not implemented yet'); - }, + } + }, 'closed'); +} - async deleteFile() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('driveFileDeleteConfirm', { name: this.file.name }), - }); - if (canceled) return; +function toggleSensitive() { + os.api('drive/files/update', { + fileId: props.file.id, + isSensitive: !props.file.isSensitive + }); +} - os.api('drive/files/delete', { - fileId: this.file.id - }); - }, +function copyUrl() { + copyToClipboard(props.file.url); + os.success(); +} +/* +function addApp() { + alert('not implemented yet'); +} +*/ +async function deleteFile() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('driveFileDeleteConfirm', { name: props.file.name }), + }); - bytes - } -}); + if (canceled) return; + os.api('drive/files/delete', { + fileId: props.file.id + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index aaba736cf8..20a6343cfe 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -19,243 +19,233 @@ <template v-if="!hover"><i class="fas fa-folder fa-fw"></i></template> {{ folder.name }} </p> - <p v-if="$store.state.uploadFolder == folder.id" class="upload"> - {{ $ts.uploadFolder }} + <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> + {{ i18n.locale.uploadFolder }} </p> <button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - props: { - folder: { - type: Object, - required: true, - }, - isSelected: { - type: Boolean, - required: false, - default: false, - }, - selectMode: { - type: Boolean, - required: false, - default: false, - } - }, +const props = withDefaults(defineProps<{ + folder: Misskey.entities.DriveFolder; + isSelected?: boolean; + selectMode?: boolean; +}>(), { + isSelected: false, + selectMode: false, +}); - emits: ['chosen'], +const emit = defineEmits<{ + (ev: 'chosen', v: Misskey.entities.DriveFolder): void; + (ev: 'move', v: Misskey.entities.DriveFolder): void; + (ev: 'upload', file: File, folder: Misskey.entities.DriveFolder); + (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (ev: 'dragstart'): void; + (ev: 'dragend'): void; +}>(); - data() { - return { - hover: false, - draghover: false, - isDragging: false, - }; - }, +const hover = ref(false); +const draghover = ref(false); +const isDragging = ref(false); - computed: { - browser(): any { - return this.$parent; - }, - title(): string { - return this.folder.name; - } - }, +const title = computed(() => props.folder.name); - methods: { - checkboxClicked(e) { - this.$emit('chosen', this.folder); - }, +function checkboxClicked() { + emit('chosen', props.folder); +} - onClick() { - this.browser.move(this.folder); - }, +function onClick() { + emit('move', props.folder); +} - onMouseover() { - this.hover = true; - }, +function onMouseover() { + hover.value = true; +} - onMouseout() { - this.hover = false - }, +function onMouseout() { + hover.value = false +} - onDragover(e) { - // 自分自身ãŒãƒ‰ãƒ©ãƒƒã‚°ã•れã¦ã„ã‚‹å ´åˆ - if (this.isDragging) { - // 自分自身ã«ã¯ãƒ‰ãƒãƒƒãƒ—ã•ã›ãªã„ - e.dataTransfer.dropEffect = 'none'; - return; - } +function onDragover(ev: DragEvent) { + if (!ev.dataTransfer) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + // 自分自身ãŒãƒ‰ãƒ©ãƒƒã‚°ã•れã¦ã„ã‚‹å ´åˆ + if (isDragging.value) { + // 自分自身ã«ã¯ãƒ‰ãƒãƒƒãƒ—ã•ã›ãªã„ + ev.dataTransfer.dropEffect = 'none'; + return; + } - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } - }, + const isFile = ev.dataTransfer.items[0].kind == 'file'; + const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - onDragenter() { - if (!this.isDragging) this.draghover = true; - }, + if (isFile || isDriveFile || isDriveFolder) { + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + ev.dataTransfer.dropEffect = 'none'; + } +} - onDragleave() { - this.draghover = false; - }, +function onDragenter() { + if (!isDragging.value) draghover.value = true; +} - onDrop(e) { - this.draghover = false; +function onDragleave() { + draghover.value = false; +} - // ファイルã ã£ãŸã‚‰ - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.browser.upload(file, this.folder); - } - return; - } +function onDrop(ev: DragEvent) { + draghover.value = false; - //#region ドライブã®ãƒ•ァイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.browser.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder.id - }); - } - //#endregion + if (!ev.dataTransfer) return; + + // ファイルã ã£ãŸã‚‰ + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { + emit('upload', file, props.folder); + } + return; + } - //#region ドライブã®ãƒ•ォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); + //#region ドライブã®ãƒ•ァイル + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + emit('removeFile', file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: props.folder.id + }); + } + //#endregion - // 移動先ãŒè‡ªåˆ†è‡ªèº«ãªã‚‰reject - if (folder.id == this.folder.id) return; + //#region ドライブã®ãƒ•ォルダ + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const folder = JSON.parse(driveFolder); - this.browser.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder.id - }).then(() => { - // noop - }).catch(err => { - switch (err) { - case 'detected-circular-definition': - os.alert({ - title: this.$ts.unableToProcess, - text: this.$ts.circularReferenceFolder - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.somethingHappened - }); - } - }); + // 移動先ãŒè‡ªåˆ†è‡ªèº«ãªã‚‰reject + if (folder.id == props.folder.id) return; + + emit('removeFolder', folder.id); + os.api('drive/folders/update', { + folderId: folder.id, + parentId: props.folder.id + }).then(() => { + // noop + }).catch(err => { + switch (err) { + case 'detected-circular-definition': + os.alert({ + title: i18n.locale.unableToProcess, + text: i18n.locale.circularReferenceFolder + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.somethingHappened + }); } - //#endregion - }, + }); + } + //#endregion +} - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(this.folder)); - this.isDragging = true; +function onDragstart(ev: DragEvent) { + if (!ev.dataTransfer) return; - // 親ブラウザã«å¯¾ã—ã¦ã€ãƒ‰ãƒ©ãƒƒã‚°ãŒé–‹å§‹ã•れãŸãƒ•ラグを立ã¦ã‚‹ - // (=ã‚ãªãŸã®åä¾›ãŒã€ãƒ‰ãƒ©ãƒƒã‚°ã‚’é–‹å§‹ã—ã¾ã—ãŸã‚ˆ) - this.browser.isDragSource = true; - }, + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder)); + isDragging.value = true; - onDragend() { - this.isDragging = false; - this.browser.isDragSource = false; - }, + // 親ブラウザã«å¯¾ã—ã¦ã€ãƒ‰ãƒ©ãƒƒã‚°ãŒé–‹å§‹ã•れãŸãƒ•ラグを立ã¦ã‚‹ + // (=ã‚ãªãŸã®åä¾›ãŒã€ãƒ‰ãƒ©ãƒƒã‚°ã‚’é–‹å§‹ã—ã¾ã—ãŸã‚ˆ) + emit('dragstart'); +} - go() { - this.browser.move(this.folder.id); - }, +function onDragend() { + isDragging.value = false; + emit('dragend'); +} - newWindow() { - this.browser.newWindow(this.folder); - }, +function go() { + emit('move', props.folder.id); +} - rename() { - os.inputText({ - title: this.$ts.renameFolder, - placeholder: this.$ts.inputNewFolderName, - default: this.folder.name - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/update', { - folderId: this.folder.id, - name: name - }); - }); - }, +function rename() { + os.inputText({ + title: i18n.locale.renameFolder, + placeholder: i18n.locale.inputNewFolderName, + default: props.folder.name + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/update', { + folderId: props.folder.id, + name: name + }); + }); +} - deleteFolder() { - os.api('drive/folders/delete', { - folderId: this.folder.id - }).then(() => { - if (this.$store.state.uploadFolder === this.folder.id) { - this.$store.set('uploadFolder', null); - } - }).catch(err => { - switch(err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': - os.alert({ - type: 'error', - title: this.$ts.unableToDelete, - text: this.$ts.hasChildFilesOrFolders - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.unableToDelete - }); - } - }); - }, +function deleteFolder() { + os.api('drive/folders/delete', { + folderId: props.folder.id + }).then(() => { + if (defaultStore.state.uploadFolder === props.folder.id) { + defaultStore.set('uploadFolder', null); + } + }).catch(err => { + switch(err.id) { + case 'b0fc8a17-963c-405d-bfbc-859a487295e1': + os.alert({ + type: 'error', + title: i18n.locale.unableToDelete, + text: i18n.locale.hasChildFilesOrFolders + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.unableToDelete + }); + } + }); +} - setAsUploadFolder() { - this.$store.set('uploadFolder', this.folder.id); - }, +function setAsUploadFolder() { + defaultStore.set('uploadFolder', props.folder.id); +} - onContextmenu(e) { - os.contextMenu([{ - text: this.$ts.openInWindow, - icon: 'fas fa-window-restore', - action: () => { - os.popup(import('./drive-window.vue'), { - initialFolder: this.folder - }, { - }, 'closed'); - } - }, null, { - text: this.$ts.rename, - icon: 'fas fa-i-cursor', - action: this.rename - }, null, { - text: this.$ts.delete, - icon: 'fas fa-trash-alt', - danger: true, - action: this.deleteFolder - }], e); - }, - } -}); +function onContextmenu(ev: MouseEvent) { + os.contextMenu([{ + text: i18n.locale.openInWindow, + icon: 'fas fa-window-restore', + action: () => { + os.popup(import('./drive-window.vue'), { + initialFolder: props.folder + }, { + }, 'closed'); + } + }, null, { + text: i18n.locale.rename, + icon: 'fas fa-i-cursor', + action: rename, + }, null, { + text: i18n.locale.delete, + icon: 'fas fa-trash-alt', + danger: true, + action: deleteFolder, + }], ev); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue index 4f0e6ce0e9..7c35c5d3da 100644 --- a/packages/client/src/components/drive.nav-folder.vue +++ b/packages/client/src/components/drive.nav-folder.vue @@ -8,114 +8,111 @@ @drop.stop="onDrop" > <i v-if="folder == null" class="fas fa-cloud"></i> - <span>{{ folder == null ? $ts.drive : folder.name }}</span> + <span>{{ folder == null ? i18n.locale.drive : folder.name }}</span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - folder: { - type: Object, - required: false, - } - }, +const props = defineProps<{ + folder?: Misskey.entities.DriveFolder; + parentFolder: Misskey.entities.DriveFolder | null; +}>(); - data() { - return { - hover: false, - draghover: false, - }; - }, +const emit = defineEmits<{ + (e: 'move', v?: Misskey.entities.DriveFolder): void; + (e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; + (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; +}>(); - computed: { - browser(): any { - return this.$parent; - } - }, +const hover = ref(false); +const draghover = ref(false); - methods: { - onClick() { - this.browser.move(this.folder); - }, +function onClick() { + emit('move', props.folder); +} - onMouseover() { - this.hover = true; - }, +function onMouseover() { + hover.value = true; +} - onMouseout() { - this.hover = false; - }, +function onMouseout() { + hover.value = false; +} - onDragover(e) { - // ã“ã®ãƒ•ォルダãŒãƒ«ãƒ¼ãƒˆã‹ã¤ã‚«ãƒ¬ãƒ³ãƒˆãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãªã‚‰ãƒ‰ãƒãƒƒãƒ—ç¦æ¢ - if (this.folder == null && this.browser.folder == null) { - e.dataTransfer.dropEffect = 'none'; - } +function onDragover(e: DragEvent) { + if (!e.dataTransfer) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + // ã“ã®ãƒ•ォルダãŒãƒ«ãƒ¼ãƒˆã‹ã¤ã‚«ãƒ¬ãƒ³ãƒˆãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãªã‚‰ãƒ‰ãƒãƒƒãƒ—ç¦æ¢ + if (props.folder == null && props.parentFolder == null) { + e.dataTransfer.dropEffect = 'none'; + } - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - return false; - }, + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } - onDragenter() { - if (this.folder || this.browser.folder) this.draghover = true; - }, + return false; +} - onDragleave() { - if (this.folder || this.browser.folder) this.draghover = false; - }, +function onDragenter() { + if (props.folder || props.parentFolder) draghover.value = true; +} - onDrop(e) { - this.draghover = false; +function onDragleave() { + if (props.folder || props.parentFolder) draghover.value = false; +} - // ファイルã ã£ãŸã‚‰ - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.browser.upload(file, this.folder); - } - return; - } +function onDrop(e: DragEvent) { + draghover.value = false; - //#region ドライブã®ãƒ•ァイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.browser.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder ? this.folder.id : null - }); - } - //#endregion + if (!e.dataTransfer) return; - //#region ドライブã®ãƒ•ォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); - // 移動先ãŒè‡ªåˆ†è‡ªèº«ãªã‚‰reject - if (this.folder && folder.id == this.folder.id) return; - this.browser.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder ? this.folder.id : null - }); - } - //#endregion + // ファイルã ã£ãŸã‚‰ + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + emit('upload', file, props.folder); } + return; + } + + //#region ドライブã®ãƒ•ァイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + emit('removeFile', file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: props.folder ? props.folder.id : null + }); } -}); + //#endregion + + //#region ドライブã®ãƒ•ォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const folder = JSON.parse(driveFolder); + // 移動先ãŒè‡ªåˆ†è‡ªèº«ãªã‚‰reject + if (props.folder && folder.id == props.folder.id) return; + emit('removeFolder', folder.id); + os.api('drive/folders/update', { + folderId: folder.id, + parentId: props.folder ? props.folder.id : null + }); + } + //#endregion +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index 46bcd42558..e27b0a5fbb 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -2,10 +2,24 @@ <div class="yfudmmck"> <nav> <div class="path" @contextmenu.prevent.stop="() => {}"> - <XNavFolder :class="{ current: folder == null }"/> + <XNavFolder + :class="{ current: folder == null }" + :parent-folder="folder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> <template v-for="f in hierarchyFolders"> <span class="separator"><i class="fas fa-angle-right"></i></span> - <XNavFolder :folder="f"/> + <XNavFolder + :folder="f" + :parent-folder="folder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> </template> <span v-if="folder != null" class="separator"><i class="fas fa-angle-right"></i></span> <span v-if="folder != null" class="folder current">{{ folder.name }}</span> @@ -22,616 +36,601 @@ > <div ref="contents" class="contents"> <div v-show="folders.length > 0" ref="foldersContainer" class="folders"> - <XFolder v-for="(f, i) in folders" :key="f.id" v-anim="i" class="folder" :folder="f" :select-mode="select === 'folder'" :is-selected="selectedFolders.some(x => x.id === f.id)" @chosen="chooseFolder"/> + <XFolder + v-for="(f, i) in folders" + :key="f.id" + v-anim="i" + class="folder" + :folder="f" + :select-mode="select === 'folder'" + :is-selected="selectedFolders.some(x => x.id === f.id)" + @chosen="chooseFolder" + @move="move" + @upload="upload" + @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> - <MkButton v-if="moreFolders" ref="moreFolders">{{ $ts.loadMore }}</MkButton> + <MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.locale.loadMore }}</MkButton> </div> <div v-show="files.length > 0" ref="filesContainer" class="files"> - <XFile v-for="(file, i) in files" :key="file.id" v-anim="i" class="file" :file="file" :select-mode="select === 'file'" :is-selected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile"/> + <XFile + v-for="(file, i) in files" + :key="file.id" + v-anim="i" + class="file" + :file="file" + :select-mode="select === 'file'" + :is-selected="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> - <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ $ts.loadMore }}</MkButton> + <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.locale.loadMore }}</MkButton> </div> <div v-if="files.length == 0 && folders.length == 0 && !fetching" class="empty"> - <p v-if="draghover">{{ $t('empty-draghover') }}</p> - <p v-if="!draghover && folder == null"><strong>{{ $ts.emptyDrive }}</strong><br/>{{ $t('empty-drive-description') }}</p> - <p v-if="!draghover && folder != null">{{ $ts.emptyFolder }}</p> + <p v-if="draghover">{{ i18n.t('empty-draghover') }}</p> + <p v-if="!draghover && folder == null"><strong>{{ i18n.locale.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p> + <p v-if="!draghover && folder != null">{{ i18n.locale.emptyFolder }}</p> </div> </div> <MkLoading v-if="fetching"/> </div> <div v-if="draghover" class="dropzone"></div> - <input ref="fileInput" type="file" accept="*/*" multiple="multiple" tabindex="-1" @change="onChangeFileInput"/> + <input ref="fileInput" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/> </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import XNavFolder from './drive.nav-folder.vue'; import XFolder from './drive.folder.vue'; import XFile from './drive.file.vue'; import MkButton from './ui/button.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNavFolder, - XFolder, - XFile, - MkButton, - }, - - props: { - initialFolder: { - type: Object, - required: false - }, - type: { - type: String, - required: false, - default: undefined - }, - multiple: { - type: Boolean, - required: false, - default: false - }, - select: { - type: String, - required: false, - default: null - } - }, - - emits: ['selected', 'change-selection', 'move-root', 'cd', 'open-folder'], - - data() { - return { - /** - * ç¾åœ¨ã®éšŽå±¤(フォルダ) - * * null ã§ãƒ«ãƒ¼ãƒˆã‚’表㙠- */ - folder: null, +const props = withDefaults(defineProps<{ + initialFolder?: Misskey.entities.DriveFolder; + type?: string; + multiple?: boolean; + select?: 'file' | 'folder' | null; +}>(), { + multiple: false, + select: null, +}); - files: [], - folders: [], - moreFiles: false, - moreFolders: false, - hierarchyFolders: [], - selectedFiles: [], - selectedFolders: [], - uploadings: os.uploads, - connection: null, +const emit = defineEmits<{ + (e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; + (e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; + (e: 'move-root'): void; + (e: 'cd', v: Misskey.entities.DriveFolder | null): void; + (e: 'open-folder', v: Misskey.entities.DriveFolder): void; +}>(); - /** - * ドãƒãƒƒãƒ—ã•れよã†ã¨ã—ã¦ã„ã‚‹ã‹ - */ - draghover: false, +const loadMoreFiles = ref<InstanceType<typeof MkButton>>(); +const fileInput = ref<HTMLInputElement>(); - /** - * è‡ªä¿¡ã®æ‰€æœ‰ã™ã‚‹ã‚¢ã‚¤ãƒ†ãƒ ãŒãƒ‰ãƒ©ãƒƒã‚°ã‚’スタートã•ã›ãŸã‹ - * (自分自身ã®éšŽå±¤ã«ãƒ‰ãƒãƒƒãƒ—ã§ããªã„よã†ã«ã™ã‚‹ãŸã‚ã®ãƒ•ラグ) - */ - isDragSource: false, +const folder = ref<Misskey.entities.DriveFolder | null>(null); +const files = ref<Misskey.entities.DriveFile[]>([]); +const folders = ref<Misskey.entities.DriveFolder[]>([]); +const moreFiles = ref(false); +const moreFolders = ref(false); +const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]); +const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); +const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); +const uploadings = os.uploads; +const connection = stream.useChannel('drive'); - fetching: true, +// ドãƒãƒƒãƒ—ã•れよã†ã¨ã—ã¦ã„ã‚‹ã‹ +const draghover = ref(false); - ilFilesObserver: new IntersectionObserver( - (entries) => entries.some((entry) => entry.isIntersecting) - && !this.fetching && this.moreFiles && - this.fetchMoreFiles() - ), - moreFilesElement: null as Element, +// è‡ªèº«ã®æ‰€æœ‰ã™ã‚‹ã‚¢ã‚¤ãƒ†ãƒ ãŒãƒ‰ãƒ©ãƒƒã‚°ã‚’スタートã•ã›ãŸã‹ +// (自分自身ã®éšŽå±¤ã«ãƒ‰ãƒãƒƒãƒ—ã§ããªã„よã†ã«ã™ã‚‹ãŸã‚ã®ãƒ•ラグ) +const isDragSource = ref(false); - }; - }, +const fetching = ref(true); - watch: { - folder() { - this.$emit('cd', this.folder); - } - }, +const ilFilesObserver = new IntersectionObserver( + (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() +) - mounted() { - if (this.$store.state.enableInfiniteScroll && this.$refs.loadMoreFiles) { - this.$nextTick(() => { - this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) - }); - } +watch(folder, () => emit('cd', folder.value)); - this.connection = markRaw(os.stream.useChannel('drive')); - - this.connection.on('fileCreated', this.onStreamDriveFileCreated); - this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); - this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); - this.connection.on('folderCreated', this.onStreamDriveFolderCreated); - this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); - this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted); +function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) { + addFile(file, true); +} - if (this.initialFolder) { - this.move(this.initialFolder); - } else { - this.fetch(); - } - }, +function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) { + const current = folder.value ? folder.value.id : null; + if (current != file.folderId) { + removeFile(file); + } else { + addFile(file, true); + } +} - activated() { - if (this.$store.state.enableInfiniteScroll) { - this.$nextTick(() => { - this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) - }); - } - }, +function onStreamDriveFileDeleted(fileId: string) { + removeFile(fileId); +} - beforeUnmount() { - this.connection.dispose(); - this.ilFilesObserver.disconnect(); - }, +function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) { + addFolder(createdFolder, true); +} - methods: { - onStreamDriveFileCreated(file) { - this.addFile(file, true); - }, +function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) { + const current = folder.value ? folder.value.id : null; + if (current != updatedFolder.parentId) { + removeFolder(updatedFolder); + } else { + addFolder(updatedFolder, true); + } +} - onStreamDriveFileUpdated(file) { - const current = this.folder ? this.folder.id : null; - if (current != file.folderId) { - this.removeFile(file); - } else { - this.addFile(file, true); - } - }, +function onStreamDriveFolderDeleted(folderId: string) { + removeFolder(folderId); +} - onStreamDriveFileDeleted(fileId) { - this.removeFile(fileId); - }, +function onDragover(e: DragEvent): any { + if (!e.dataTransfer) return; - onStreamDriveFolderCreated(folder) { - this.addFolder(folder, true); - }, + // ドラッグ元ãŒè‡ªåˆ†è‡ªèº«ã®æ‰€æœ‰ã™ã‚‹ã‚¢ã‚¤ãƒ†ãƒ ã ã£ãŸã‚‰ + if (isDragSource.value) { + // 自分自身ã«ã¯ãƒ‰ãƒãƒƒãƒ—ã•ã›ãªã„ + e.dataTransfer.dropEffect = 'none'; + return; + } - onStreamDriveFolderUpdated(folder) { - const current = this.folder ? this.folder.id : null; - if (current != folder.parentId) { - this.removeFolder(folder); - } else { - this.addFolder(folder, true); - } - }, + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } - onStreamDriveFolderDeleted(folderId) { - this.removeFolder(folderId); - }, + return false; +} - onDragover(e): any { - // ドラッグ元ãŒè‡ªåˆ†è‡ªèº«ã®æ‰€æœ‰ã™ã‚‹ã‚¢ã‚¤ãƒ†ãƒ ã ã£ãŸã‚‰ - if (this.isDragSource) { - // 自分自身ã«ã¯ãƒ‰ãƒãƒƒãƒ—ã•ã›ãªã„ - e.dataTransfer.dropEffect = 'none'; - return; - } +function onDragenter() { + if (!isDragSource.value) draghover.value = true; +} - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; +function onDragleave() { + draghover.value = false; +} - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } +function onDrop(e: DragEvent): any { + draghover.value = false; - return false; - }, + if (!e.dataTransfer) return; - onDragenter(e) { - if (!this.isDragSource) this.draghover = true; - }, + // ドãƒãƒƒãƒ—ã•れã¦ããŸã‚‚ã®ãŒãƒ•ァイルã ã£ãŸã‚‰ + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + upload(file, folder.value); + } + return; + } - onDragleave(e) { - this.draghover = false; - }, + //#region ドライブã®ãƒ•ァイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + if (files.value.some(f => f.id == file.id)) return; + removeFile(file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: folder.value ? folder.value.id : null + }); + } + //#endregion - onDrop(e): any { - this.draghover = false; + //#region ドライブã®ãƒ•ォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const droppedFolder = JSON.parse(driveFolder); - // ドãƒãƒƒãƒ—ã•れã¦ããŸã‚‚ã®ãŒãƒ•ァイルã ã£ãŸã‚‰ - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.upload(file, this.folder); - } - return; + // 移動先ãŒè‡ªåˆ†è‡ªèº«ãªã‚‰reject + if (folder.value && droppedFolder.id == folder.value.id) return false; + if (folders.value.some(f => f.id == droppedFolder.id)) return false; + removeFolder(droppedFolder.id); + os.api('drive/folders/update', { + folderId: droppedFolder.id, + parentId: folder.value ? folder.value.id : null + }).then(() => { + // noop + }).catch(err => { + switch (err) { + case 'detected-circular-definition': + os.alert({ + title: i18n.locale.unableToProcess, + text: i18n.locale.circularReferenceFolder + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.somethingHappened + }); } + }); + } + //#endregion +} - //#region ドライブã®ãƒ•ァイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - if (this.files.some(f => f.id == file.id)) return; - this.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder ? this.folder.id : null - }); - } - //#endregion +function selectLocalFile() { + fileInput.value?.click(); +} - //#region ドライブã®ãƒ•ォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); +function urlUpload() { + os.inputText({ + title: i18n.locale.uploadFromUrl, + type: 'url', + placeholder: i18n.locale.uploadFromUrlDescription + }).then(({ canceled, result: url }) => { + if (canceled || !url) return; + os.api('drive/files/upload-from-url', { + url: url, + folderId: folder.value ? folder.value.id : undefined + }); - // 移動先ãŒè‡ªåˆ†è‡ªèº«ãªã‚‰reject - if (this.folder && folder.id == this.folder.id) return false; - if (this.folders.some(f => f.id == folder.id)) return false; - this.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder ? this.folder.id : null - }).then(() => { - // noop - }).catch(err => { - switch (err) { - case 'detected-circular-definition': - os.alert({ - title: this.$ts.unableToProcess, - text: this.$ts.circularReferenceFolder - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.somethingHappened - }); - } - }); - } - //#endregion - }, + os.alert({ + title: i18n.locale.uploadFromUrlRequested, + text: i18n.locale.uploadFromUrlMayTakeTime + }); + }); +} - selectLocalFile() { - (this.$refs.fileInput as any).click(); - }, +function createFolder() { + os.inputText({ + title: i18n.locale.createFolder, + placeholder: i18n.locale.folderName + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/create', { + name: name, + parentId: folder.value ? folder.value.id : undefined + }).then(createdFolder => { + addFolder(createdFolder, true); + }); + }); +} - urlUpload() { - os.inputText({ - title: this.$ts.uploadFromUrl, - type: 'url', - placeholder: this.$ts.uploadFromUrlDescription - }).then(({ canceled, result: url }) => { - if (canceled) return; - os.api('drive/files/upload-from-url', { - url: url, - folderId: this.folder ? this.folder.id : undefined - }); +function renameFolder(folderToRename: Misskey.entities.DriveFolder) { + os.inputText({ + title: i18n.locale.renameFolder, + placeholder: i18n.locale.inputNewFolderName, + default: folderToRename.name + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/update', { + folderId: folderToRename.id, + name: name + }).then(updatedFolder => { + // FIXME: ç”»é¢ã‚’æ›´æ–°ã™ã‚‹ãŸã‚ã«è‡ªåˆ†è‡ªèº«ã«ç§»å‹• + move(updatedFolder); + }); + }); +} +function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { + os.api('drive/folders/delete', { + folderId: folderToDelete.id + }).then(() => { + // 削除時ã«è¦ªãƒ•ォルダã«ç§»å‹• + move(folderToDelete.parentId); + }).catch(err => { + switch(err.id) { + case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ - title: this.$ts.uploadFromUrlRequested, - text: this.$ts.uploadFromUrlMayTakeTime + type: 'error', + title: i18n.locale.unableToDelete, + text: i18n.locale.hasChildFilesOrFolders }); - }); - }, - - createFolder() { - os.inputText({ - title: this.$ts.createFolder, - placeholder: this.$ts.folderName - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/create', { - name: name, - parentId: this.folder ? this.folder.id : undefined - }).then(folder => { - this.addFolder(folder, true); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.unableToDelete }); - }); - }, + } + }); +} - renameFolder(folder) { - os.inputText({ - title: this.$ts.renameFolder, - placeholder: this.$ts.inputNewFolderName, - default: folder.name - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/update', { - folderId: folder.id, - name: name - }).then(folder => { - // FIXME: ç”»é¢ã‚’æ›´æ–°ã™ã‚‹ãŸã‚ã«è‡ªåˆ†è‡ªèº«ã«ç§»å‹• - this.move(folder); - }); - }); - }, +function onChangeFileInput() { + if (!fileInput.value?.files) return; + for (const file of Array.from(fileInput.value.files)) { + upload(file, folder.value); + } +} - deleteFolder(folder) { - os.api('drive/folders/delete', { - folderId: folder.id - }).then(() => { - // 削除時ã«è¦ªãƒ•ォルダã«ç§»å‹• - this.move(folder.parentId); - }).catch(err => { - switch(err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': - os.alert({ - type: 'error', - title: this.$ts.unableToDelete, - text: this.$ts.hasChildFilesOrFolders - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.unableToDelete - }); - } - }); - }, +function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { + os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => { + addFile(res, true); + }); +} - onChangeFileInput() { - for (const file of Array.from((this.$refs.fileInput as any).files)) { - this.upload(file, this.folder); - } - }, +function chooseFile(file: Misskey.entities.DriveFile) { + const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id); + if (props.multiple) { + if (isAlreadySelected) { + selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id); + } else { + selectedFiles.value.push(file); + } + emit('change-selection', selectedFiles.value); + } else { + if (isAlreadySelected) { + emit('selected', file); + } else { + selectedFiles.value = [file]; + emit('change-selection', [file]); + } + } +} - upload(file, folder) { - if (folder && typeof folder == 'object') folder = folder.id; - os.upload(file, folder).then(res => { - this.addFile(res, true); - }); - }, +function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { + const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id); + if (props.multiple) { + if (isAlreadySelected) { + selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id); + } else { + selectedFolders.value.push(folderToChoose); + } + emit('change-selection', selectedFolders.value); + } else { + if (isAlreadySelected) { + emit('selected', folderToChoose); + } else { + selectedFolders.value = [folderToChoose]; + emit('change-selection', [folderToChoose]); + } + } +} - chooseFile(file) { - const isAlreadySelected = this.selectedFiles.some(f => f.id == file.id); - if (this.multiple) { - if (isAlreadySelected) { - this.selectedFiles = this.selectedFiles.filter(f => f.id != file.id); - } else { - this.selectedFiles.push(file); - } - this.$emit('change-selection', this.selectedFiles); - } else { - if (isAlreadySelected) { - this.$emit('selected', file); - } else { - this.selectedFiles = [file]; - this.$emit('change-selection', [file]); - } - } - }, +function move(target?: Misskey.entities.DriveFolder) { + if (!target) { + goRoot(); + return; + } else if (typeof target == 'object') { + target = target.id; + } - chooseFolder(folder) { - const isAlreadySelected = this.selectedFolders.some(f => f.id == folder.id); - if (this.multiple) { - if (isAlreadySelected) { - this.selectedFolders = this.selectedFolders.filter(f => f.id != folder.id); - } else { - this.selectedFolders.push(folder); - } - this.$emit('change-selection', this.selectedFolders); - } else { - if (isAlreadySelected) { - this.$emit('selected', folder); - } else { - this.selectedFolders = [folder]; - this.$emit('change-selection', [folder]); - } - } - }, + fetching.value = true; - move(target) { - if (target == null) { - this.goRoot(); - return; - } else if (typeof target == 'object') { - target = target.id; - } + os.api('drive/folders/show', { + folderId: target + }).then(folderToMove => { + folder.value = folderToMove; + hierarchyFolders.value = []; - this.fetching = true; + const dive = folderToDive => { + hierarchyFolders.value.unshift(folderToDive); + if (folderToDive.parent) dive(folderToDive.parent); + }; + + if (folderToMove.parent) dive(folderToMove.parent); - os.api('drive/folders/show', { - folderId: target - }).then(folder => { - this.folder = folder; - this.hierarchyFolders = []; + emit('open-folder', folderToMove); + fetch(); + }); +} - const dive = folder => { - this.hierarchyFolders.unshift(folder); - if (folder.parent) dive(folder.parent); - }; +function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { + const current = folder.value ? folder.value.id : null; + if (current != folderToAdd.parentId) return; - if (folder.parent) dive(folder.parent); + if (folders.value.some(f => f.id == folderToAdd.id)) { + const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id); + folders.value[exist] = folderToAdd; + return; + } - this.$emit('open-folder', folder); - this.fetch(); - }); - }, + if (unshift) { + folders.value.unshift(folderToAdd); + } else { + folders.value.push(folderToAdd); + } +} - addFolder(folder, unshift = false) { - const current = this.folder ? this.folder.id : null; - if (current != folder.parentId) return; +function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { + const current = folder.value ? folder.value.id : null; + if (current != fileToAdd.folderId) return; - if (this.folders.some(f => f.id == folder.id)) { - const exist = this.folders.map(f => f.id).indexOf(folder.id); - this.folders[exist] = folder; - return; - } + if (files.value.some(f => f.id == fileToAdd.id)) { + const exist = files.value.map(f => f.id).indexOf(fileToAdd.id); + files.value[exist] = fileToAdd; + return; + } - if (unshift) { - this.folders.unshift(folder); - } else { - this.folders.push(folder); - } - }, + if (unshift) { + files.value.unshift(fileToAdd); + } else { + files.value.push(fileToAdd); + } +} - addFile(file, unshift = false) { - const current = this.folder ? this.folder.id : null; - if (current != file.folderId) return; +function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) { + const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove; + folders.value = folders.value.filter(f => f.id != folderIdToRemove); +} - if (this.files.some(f => f.id == file.id)) { - const exist = this.files.map(f => f.id).indexOf(file.id); - this.files[exist] = file; - return; - } +function removeFile(file: Misskey.entities.DriveFile | string) { + const fileId = typeof file === 'object' ? file.id : file; + files.value = files.value.filter(f => f.id != fileId); +} - if (unshift) { - this.files.unshift(file); - } else { - this.files.push(file); - } - }, +function appendFile(file: Misskey.entities.DriveFile) { + addFile(file); +} - removeFolder(folder) { - if (typeof folder == 'object') folder = folder.id; - this.folders = this.folders.filter(f => f.id != folder); - }, +function appendFolder(folderToAppend: Misskey.entities.DriveFolder) { + addFolder(folderToAppend); +} +/* +function prependFile(file: Misskey.entities.DriveFile) { + addFile(file, true); +} - removeFile(file) { - if (typeof file == 'object') file = file.id; - this.files = this.files.filter(f => f.id != file); - }, +function prependFolder(folderToPrepend: Misskey.entities.DriveFolder) { + addFolder(folderToPrepend, true); +} +*/ +function goRoot() { + // æ—¢ã«rootã«ã„ã‚‹ãªã‚‰ä½•ã‚‚ã—ãªã„ + if (folder.value == null) return; - appendFile(file) { - this.addFile(file); - }, + folder.value = null; + hierarchyFolders.value = []; + emit('move-root'); + fetch(); +} - appendFolder(folder) { - this.addFolder(folder); - }, +async function fetch() { + folders.value = []; + files.value = []; + moreFolders.value = false; + moreFiles.value = false; + fetching.value = true; - prependFile(file) { - this.addFile(file, true); - }, + const foldersMax = 30; + const filesMax = 30; - prependFolder(folder) { - this.addFolder(folder, true); - }, + const foldersPromise = os.api('drive/folders', { + folderId: folder.value ? folder.value.id : null, + limit: foldersMax + 1 + }).then(fetchedFolders => { + if (fetchedFolders.length == foldersMax + 1) { + moreFolders.value = true; + fetchedFolders.pop(); + } + return fetchedFolders; + }); - goRoot() { - // æ—¢ã«rootã«ã„ã‚‹ãªã‚‰ä½•ã‚‚ã—ãªã„ - if (this.folder == null) return; + const filesPromise = os.api('drive/files', { + folderId: folder.value ? folder.value.id : null, + type: props.type, + limit: filesMax + 1 + }).then(fetchedFiles => { + if (fetchedFiles.length == filesMax + 1) { + moreFiles.value = true; + fetchedFiles.pop(); + } + return fetchedFiles; + }); - this.folder = null; - this.hierarchyFolders = []; - this.$emit('move-root'); - this.fetch(); - }, + const [fetchedFolders, fetchedFiles] = await Promise.all([foldersPromise, filesPromise]); - fetch() { - this.folders = []; - this.files = []; - this.moreFolders = false; - this.moreFiles = false; - this.fetching = true; + for (const x of fetchedFolders) appendFolder(x); + for (const x of fetchedFiles) appendFile(x); - let fetchedFolders = null; - let fetchedFiles = null; + fetching.value = false; +} - const foldersMax = 30; - const filesMax = 30; +function fetchMoreFiles() { + fetching.value = true; - // フォルダ一覧å–å¾— - os.api('drive/folders', { - folderId: this.folder ? this.folder.id : null, - limit: foldersMax + 1 - }).then(folders => { - if (folders.length == foldersMax + 1) { - this.moreFolders = true; - folders.pop(); - } - fetchedFolders = folders; - complete(); - }); + const max = 30; - // ファイル一覧å–å¾— - os.api('drive/files', { - folderId: this.folder ? this.folder.id : null, - type: this.type, - limit: filesMax + 1 - }).then(files => { - if (files.length == filesMax + 1) { - this.moreFiles = true; - files.pop(); - } - fetchedFiles = files; - complete(); - }); + // ファイル一覧å–å¾— + os.api('drive/files', { + folderId: folder.value ? folder.value.id : null, + type: props.type, + untilId: files.value[files.value.length - 1].id, + limit: max + 1 + }).then(files => { + if (files.length == max + 1) { + moreFiles.value = true; + files.pop(); + } else { + moreFiles.value = false; + } + for (const x of files) appendFile(x); + fetching.value = false; + }); +} - let flag = false; - const complete = () => { - if (flag) { - for (const x of fetchedFolders) this.appendFolder(x); - for (const x of fetchedFiles) this.appendFile(x); - this.fetching = false; - } else { - flag = true; - } - }; - }, +function getMenu() { + return [{ + text: i18n.locale.addFile, + type: 'label' + }, { + text: i18n.locale.upload, + icon: 'fas fa-upload', + action: () => { selectLocalFile(); } + }, { + text: i18n.locale.fromUrl, + icon: 'fas fa-link', + action: () => { urlUpload(); } + }, null, { + text: folder.value ? folder.value.name : i18n.locale.drive, + type: 'label' + }, folder.value ? { + text: i18n.locale.renameFolder, + icon: 'fas fa-i-cursor', + action: () => { renameFolder(folder.value); } + } : undefined, folder.value ? { + text: i18n.locale.deleteFolder, + icon: 'fas fa-trash-alt', + action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } + } : undefined, { + text: i18n.locale.createFolder, + icon: 'fas fa-folder-plus', + action: () => { createFolder(); } + }]; +} - fetchMoreFiles() { - this.fetching = true; +function showMenu(ev: MouseEvent) { + os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); +} - const max = 30; +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); +} - // ファイル一覧å–å¾— - os.api('drive/files', { - folderId: this.folder ? this.folder.id : null, - type: this.type, - untilId: this.files[this.files.length - 1].id, - limit: max + 1 - }).then(files => { - if (files.length == max + 1) { - this.moreFiles = true; - files.pop(); - } else { - this.moreFiles = false; - } - for (const x of files) this.appendFile(x); - this.fetching = false; - }); - }, +onMounted(() => { + if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) { + nextTick(() => { + ilFilesObserver.observe(loadMoreFiles.value?.$el) + }); + } - getMenu() { - return [{ - text: this.$ts.addFile, - type: 'label' - }, { - text: this.$ts.upload, - icon: 'fas fa-upload', - action: () => { this.selectLocalFile(); } - }, { - text: this.$ts.fromUrl, - icon: 'fas fa-link', - action: () => { this.urlUpload(); } - }, null, { - text: this.folder ? this.folder.name : this.$ts.drive, - type: 'label' - }, this.folder ? { - text: this.$ts.renameFolder, - icon: 'fas fa-i-cursor', - action: () => { this.renameFolder(this.folder); } - } : undefined, this.folder ? { - text: this.$ts.deleteFolder, - icon: 'fas fa-trash-alt', - action: () => { this.deleteFolder(this.folder); } - } : undefined, { - text: this.$ts.createFolder, - icon: 'fas fa-folder-plus', - action: () => { this.createFolder(); } - }]; - }, + connection.on('fileCreated', onStreamDriveFileCreated); + connection.on('fileUpdated', onStreamDriveFileUpdated); + connection.on('fileDeleted', onStreamDriveFileDeleted); + connection.on('folderCreated', onStreamDriveFolderCreated); + connection.on('folderUpdated', onStreamDriveFolderUpdated); + connection.on('folderDeleted', onStreamDriveFolderDeleted); - showMenu(ev) { - os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); - }, + if (props.initialFolder) { + move(props.initialFolder); + } else { + fetch(); + } +}); - onContextmenu(ev) { - os.contextMenu(this.getMenu(), ev); - }, +onActivated(() => { + if (defaultStore.state.enableInfiniteScroll) { + nextTick(() => { + ilFilesObserver.observe(loadMoreFiles.value?.$el) + }); } }); + +onBeforeUnmount(() => { + connection.dispose(); + ilFilesObserver.disconnect(); +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker-dialog.vue b/packages/client/src/components/emoji-picker-dialog.vue index 51c634dd8e..f06a24636c 100644 --- a/packages/client/src/components/emoji-picker-dialog.vue +++ b/packages/client/src/components/emoji-picker-dialog.vue @@ -1,58 +1,65 @@ <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'middle'" :prefer-type="asReactionPicker && $store.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" :transparent-bg="true" :manual-showing="manualShowing" :src="src" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')"> - <MkEmojiPicker ref="picker" class="ryghynhb _popup _shadow" :class="{ drawer: type === 'drawer' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" :as-drawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen"/> +<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" + :src="src" + @click="modal?.close()" + @opening="opening" + @close="emit('close')" + @closed="emit('closed')" +> + <MkEmojiPicker + ref="picker" + class="ryghynhb _popup _shadow" + :class="{ drawer: type === 'drawer' }" + :show-pinned="showPinned" + :as-reaction-picker="asReactionPicker" + :as-drawer="type === 'drawer'" + :max-height="maxHeight" + @chosen="chosen" + /> </MkModal> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkEmojiPicker from '@/components/emoji-picker.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkModal, - MkEmojiPicker, - }, - - props: { - manualShowing: { - type: Boolean, - required: false, - default: null, - }, - src: { - required: false - }, - showPinned: { - required: false, - default: true - }, - asReactionPicker: { - required: false - }, - }, - - emits: ['done', 'close', 'closed'], +withDefaults(defineProps<{ + manualShowing?: boolean; + src?: HTMLElement; + showPinned?: boolean; + asReactionPicker?: boolean; +}>(), { + manualShowing: false, + showPinned: true, + asReactionPicker: false, +}); - data() { - return { +const emit = defineEmits<{ + (e: 'done', v: any): void; + (e: 'close'): void; + (e: 'closed'): void; +}>(); - }; - }, +const modal = ref<InstanceType<typeof MkModal>>(); +const picker = ref<InstanceType<typeof MkEmojiPicker>>(); - methods: { - chosen(emoji: any) { - this.$emit('done', emoji); - this.$refs.modal.close(); - }, +function chosen(emoji: any) { + emit('done', emoji); + modal.value?.close(); +} - opening() { - this.$refs.picker.reset(); - this.$refs.picker.focus(); - } - } -}); +function opening() { + picker.value?.reset(); + picker.value?.focus(); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker-window.vue b/packages/client/src/components/emoji-picker-window.vue index 0ffa0c1187..4d27fb48ba 100644 --- a/packages/client/src/components/emoji-picker-window.vue +++ b/packages/client/src/components/emoji-picker-window.vue @@ -5,50 +5,33 @@ :can-resize="false" :mini="true" :front="true" - @closed="$emit('closed')" + @closed="emit('closed')" > <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> </MkWindow> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkWindow from '@/components/ui/window.vue'; import MkEmojiPicker from '@/components/emoji-picker.vue'; -export default defineComponent({ - components: { - MkWindow, - MkEmojiPicker, - }, - - props: { - src: { - required: false - }, - showPinned: { - required: false, - default: true - }, - asReactionPicker: { - required: false - }, - }, - - emits: ['chosen', 'closed'], - - data() { - return { +withDefaults(defineProps<{ + src?: HTMLElement; + showPinned?: boolean; + asReactionPicker?: boolean; +}>(), { + showPinned: true, +}); - }; - }, +const emit = defineEmits<{ + (e: 'chosen', v: any): void; + (e: 'closed'): void; +}>(); - methods: { - chosen(emoji: any) { - this.$emit('chosen', emoji); - }, - } -}); +function chosen(emoji: any) { + emit('chosen', emoji); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker.section.vue b/packages/client/src/components/emoji-picker.section.vue index 08c4f6813d..1026e894d1 100644 --- a/packages/client/src/components/emoji-picker.section.vue +++ b/packages/client/src/components/emoji-picker.section.vue @@ -7,7 +7,7 @@ <button v-for="emoji in emojis" :key="emoji" class="_button" - @click="chosen(emoji, $event)" + @click="emit('chosen', emoji, $event)" > <MkEmoji :emoji="emoji" :normal="true"/> </button> @@ -15,35 +15,19 @@ </section> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { getStaticImageUrl } from '@/scripts/get-static-image-url'; +<script lang="ts" setup> +import { ref } from 'vue'; -export default defineComponent({ - props: { - emojis: { - required: true, - }, - initialShown: { - required: false - } - }, +const props = defineProps<{ + emojis: string[]; + initialShown?: boolean; +}>(); - emits: ['chosen'], +const emit = defineEmits<{ + (e: 'chosen', v: string, ev: MouseEvent): void; +}>(); - data() { - return { - getStaticImageUrl, - shown: this.initialShown, - }; - }, - - methods: { - chosen(emoji: any, ev) { - this.$parent.chosen(emoji, ev); - }, - } -}); +const shown = ref(!!props.initialShown); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index a8eed1ca21..96670fa58c 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,18 +1,18 @@ <template> -<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : null }"> - <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()"> +<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> + <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.locale.search" @paste.stop="paste" @keyup.enter="done()"> <div ref="emojis" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0"> <button v-for="emoji in searchResultCustom" - :key="emoji" + :key="emoji.id" class="_button" :title="emoji.name" tabindex="0" @click="chosen(emoji, $event)" > - <MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> - <img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> + <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> + <img :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> </button> </div> <div v-if="searchResultUnicode.length > 0"> @@ -43,9 +43,9 @@ </section> <section> - <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ $ts.recentUsed }}</header> + <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.locale.recentUsed }}</header> <div> - <button v-for="emoji in $store.state.recentlyUsedEmojis" + <button v-for="emoji in recentlyUsedEmojis" :key="emoji" class="_button" @click="chosen(emoji, $event)" @@ -56,12 +56,12 @@ </section> </div> <div> - <header class="_acrylic">{{ $ts.customEmojis }}</header> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection> + <header class="_acrylic">{{ i18n.locale.customEmojis }}</header> + <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.locale.other }}</XSection> </div> <div> - <header class="_acrylic">{{ $ts.emoji }}</header> - <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection> + <header class="_acrylic">{{ i18n.locale.emoji }}</header> + <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> </div> </div> <div class="tabs"> @@ -73,277 +73,272 @@ </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { emojilist } from '@/scripts/emojilist'; +<script lang="ts" setup> +import { ref, computed, watch, onMounted } from 'vue'; +import * as Misskey from 'misskey-js'; +import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import Ripple from '@/components/ripple.vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { isMobile } from '@/scripts/is-mobile'; -import { emojiCategories } from '@/instance'; +import { emojiCategories, instance } from '@/instance'; import XSection from './emoji-picker.section.vue'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - XSection - }, - - props: { - showPinned: { - required: false, - default: true, - }, - asReactionPicker: { - required: false, - }, - maxHeight: { - type: Number, - required: false, - }, - asDrawer: { - type: Boolean, - required: false - }, - }, +const props = withDefaults(defineProps<{ + showPinned?: boolean; + asReactionPicker?: boolean; + maxHeight?: number; + asDrawer?: boolean; +}>(), { + showPinned: true, +}); - emits: ['chosen'], +const emit = defineEmits<{ + (e: 'chosen', v: string): void; +}>(); - data() { - return { - emojilist: markRaw(emojilist), - getStaticImageUrl, - pinned: this.$store.reactiveState.reactions, - width: this.asReactionPicker ? this.$store.state.reactionPickerWidth : 3, - height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2, - big: this.asReactionPicker ? isTouchUsing : false, - customEmojiCategories: emojiCategories, - customEmojis: this.$instance.emojis, - q: null, - searchResultCustom: [], - searchResultUnicode: [], - tab: 'index', - categories: ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'], - }; - }, +const search = ref<HTMLInputElement>(); +const emojis = ref<HTMLDivElement>(); - watch: { - q() { - this.$refs.emojis.scrollTop = 0; +const { + reactions: pinned, + reactionPickerWidth, + reactionPickerHeight, + disableShowingAnimatedImages, + recentlyUsedEmojis, +} = defaultStore.reactiveState; - if (this.q == null || this.q === '') { - this.searchResultCustom = []; - this.searchResultUnicode = []; - return; - } +const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3); +const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2); +const big = props.asReactionPicker ? isTouchUsing : false; +const customEmojiCategories = emojiCategories; +const customEmojis = instance.emojis; +const q = ref<string | null>(null); +const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]); +const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); +const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); - const q = this.q.replace(/:/g, ''); +watch(q, () => { + if (emojis.value) emojis.value.scrollTop = 0; - const searchCustom = () => { - const max = 8; - const emojis = this.customEmojis; - const matches = new Set(); + if (q.value == null || q.value === '') { + searchResultCustom.value = []; + searchResultUnicode.value = []; + return; + } - const exactMatch = emojis.find(e => e.name === q); - if (exactMatch) matches.add(exactMatch); + const newQ = q.value.replace(/:/g, ''); - if (q.includes(' ')) { // AND検索 - const keywords = q.split(' '); + const searchCustom = () => { + const max = 8; + const emojis = customEmojis; + const matches = new Set<Misskey.entities.CustomEmoji>(); - // åå‰ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + const exactMatch = emojis.find(e => e.name === newQ); + if (exactMatch) matches.add(exactMatch); - // åå‰ã¾ãŸã¯ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } else { - for (const emoji of emojis) { - if (emoji.name.startsWith(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + if (newQ.includes(' ')) { // AND検索 + const keywords = newQ.split(' '); - for (const emoji of emojis) { - if (emoji.aliases.some(alias => alias.startsWith(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // åå‰ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.name.includes(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // åå‰ã¾ãŸã¯ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } else { + for (const emoji of emojis) { + if (emoji.name.startsWith(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.aliases.some(alias => alias.includes(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; } + } + if (matches.size >= max) return matches; - return matches; - }; + for (const emoji of emojis) { + if (emoji.name.includes(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - const searchUnicode = () => { - const max = 8; - const emojis = this.emojilist; - const matches = new Set(); + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } - const exactMatch = emojis.find(e => e.name === q); - if (exactMatch) matches.add(exactMatch); + return matches; + }; - if (q.includes(' ')) { // AND検索 - const keywords = q.split(' '); + const searchUnicode = () => { + const max = 8; + const emojis = emojilist; + const matches = new Set<UnicodeEmojiDef>(); - // åå‰ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + const exactMatch = emojis.find(e => e.name === newQ); + if (exactMatch) matches.add(exactMatch); - // åå‰ã¾ãŸã¯ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ - 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; - } - } - } else { - for (const emoji of emojis) { - if (emoji.name.startsWith(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + if (newQ.includes(' ')) { // AND検索 + const keywords = newQ.split(' '); - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.startsWith(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // åå‰ã«ã‚ーワードãŒå«ã¾ã‚Œã¦ã„ã‚‹ + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.name.includes(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - 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; + } + } + } else { + for (const emoji of emojis) { + if (emoji.name.startsWith(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.includes(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } + for (const emoji of emojis) { + if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; } + } + if (matches.size >= max) return matches; - return matches; - }; + for (const emoji of emojis) { + if (emoji.name.includes(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - this.searchResultCustom = Array.from(searchCustom()); - this.searchResultUnicode = Array.from(searchUnicode()); + for (const emoji of emojis) { + if (emoji.keywords.some(keyword => keyword.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } } - }, - mounted() { - this.focus(); - }, + return matches; + }; - methods: { - focus() { - if (!isMobile && !isTouchUsing) { - this.$refs.search.focus({ - preventScroll: true - }); - } - }, + searchResultCustom.value = Array.from(searchCustom()); + searchResultUnicode.value = Array.from(searchUnicode()); +}); - reset() { - this.$refs.emojis.scrollTop = 0; - this.q = ''; - }, +function focus() { + if (!isMobile && !isTouchUsing) { + search.value?.focus({ + preventScroll: true + }); + } +} - getKey(emoji: any) { - return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); - }, +function reset() { + if (emojis.value) emojis.value.scrollTop = 0; + q.value = ''; +} - chosen(emoji: any, ev) { - if (ev) { - const el = ev.currentTarget || ev.target; - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - os.popup(Ripple, { x, y }, {}, 'end'); - } +function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string { + return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); +} - const key = this.getKey(emoji); - this.$emit('chosen', key); +function chosen(emoji: any, ev?: MouseEvent) { + const el = ev && (ev.currentTarget || ev.target) as HTMLElement | null | undefined; + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.left + (el.offsetWidth / 2); + const y = rect.top + (el.offsetHeight / 2); + os.popup(Ripple, { x, y }, {}, 'end'); + } - // 最近使ã£ãŸçµµæ–‡å—æ›´æ–° - if (!this.pinned.includes(key)) { - let recents = this.$store.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== key); - recents.unshift(key); - this.$store.set('recentlyUsedEmojis', recents.splice(0, 32)); - } - }, + const key = getKey(emoji); + emit('chosen', key); - paste(event) { - const paste = (event.clipboardData || window.clipboardData).getData('text'); - if (this.done(paste)) { - event.preventDefault(); - } - }, + // 最近使ã£ãŸçµµæ–‡å—æ›´æ–° + if (!pinned.value.includes(key)) { + let recents = defaultStore.state.recentlyUsedEmojis; + recents = recents.filter((e: any) => e !== key); + recents.unshift(key); + defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); + } +} - done(query) { - if (query == null) query = this.q; - if (query == null) return; - const q = query.replace(/:/g, ''); - const exactMatchCustom = this.customEmojis.find(e => e.name === q); - if (exactMatchCustom) { - this.chosen(exactMatchCustom); - return true; - } - const exactMatchUnicode = this.emojilist.find(e => e.char === q || e.name === q); - if (exactMatchUnicode) { - this.chosen(exactMatchUnicode); - return true; - } - if (this.searchResultCustom.length > 0) { - this.chosen(this.searchResultCustom[0]); - return true; - } - if (this.searchResultUnicode.length > 0) { - this.chosen(this.searchResultUnicode[0]); - return true; - } - }, +function paste(event: ClipboardEvent) { + const paste = (event.clipboardData || window.clipboardData).getData('text'); + if (done(paste)) { + event.preventDefault(); } +} + +function done(query?: any): boolean | void { + if (query == null) query = q.value; + if (query == null || typeof query !== 'string') return; + + const q2 = query.replace(/:/g, ''); + const exactMatchCustom = customEmojis.find(e => e.name === q2); + if (exactMatchCustom) { + chosen(exactMatchCustom); + return true; + } + const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2); + if (exactMatchUnicode) { + chosen(exactMatchUnicode); + return true; + } + if (searchResultCustom.value.length > 0) { + chosen(searchResultCustom.value[0]); + return true; + } + if (searchResultUnicode.value.length > 0) { + chosen(searchResultUnicode.value[0]); + return true; + } +} + +onMounted(() => { + focus(); +}); + +defineExpose({ + focus, + reset, }); </script> diff --git a/packages/client/src/components/featured-photos.vue b/packages/client/src/components/featured-photos.vue index af5892c98e..e58b5d2849 100644 --- a/packages/client/src/components/featured-photos.vue +++ b/packages/client/src/components/featured-photos.vue @@ -2,25 +2,15 @@ <div v-if="meta" class="xfbouadm" :style="{ backgroundImage: `url(${ meta.backgroundImageUrl })` }"></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; -export default defineComponent({ - components: { - }, +const meta = ref<Misskey.entities.DetailedInstanceMetadata>(); - data() { - return { - meta: null, - }; - }, - - created() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - }, +os.api('meta', { detail: true }).then(gotMeta => { + meta.value = gotMeta; }); </script> diff --git a/packages/client/src/components/file-type-icon.vue b/packages/client/src/components/file-type-icon.vue index be1af5e501..11d28188cc 100644 --- a/packages/client/src/components/file-type-icon.vue +++ b/packages/client/src/components/file-type-icon.vue @@ -4,25 +4,12 @@ </span> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { computed } from 'vue'; -export default defineComponent({ - props: { - type: { - type: String, - required: true, - } - }, - data() { - return { - }; - }, - computed: { - kind(): string { - return this.type.split('/')[0]; - } - } -}); +const props = defineProps<{ + type: string; +}>(); + +const kind = computed(() => props.type.split('/')[0]); </script> diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue index 7136261914..345edb6441 100644 --- a/packages/client/src/components/follow-button.vue +++ b/packages/client/src/components/follow-button.vue @@ -6,128 +6,110 @@ > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> - <span v-if="full">{{ $ts.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> + <span v-if="full">{{ i18n.locale.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> </template> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- ã¤ã¾ã‚Šãƒªãƒ¢ãƒ¼ãƒˆãƒ•ã‚©ãƒãƒ¼ã®å ´åˆã€‚ --> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse"></i> </template> <template v-else-if="isFollowing"> - <span v-if="full">{{ $ts.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> - <span v-if="full">{{ $ts.followRequest }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.followRequest }}</span><i class="fas fa-plus"></i> </template> <template v-else-if="!isFollowing && !user.isLocked"> - <span v-if="full">{{ $ts.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { stream } from '@/stream'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - full: { - type: Boolean, - required: false, - default: false, - }, - large: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - isFollowing: this.user.isFollowing, - hasPendingFollowRequestFromYou: this.user.hasPendingFollowRequestFromYou, - wait: false, - connection: null, - }; - }, - - created() { - // 渡ã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼æƒ…å ±ãŒä¸å®Œå…¨ãªå ´åˆ - if (this.user.isFollowing == null) { - os.api('users/show', { - userId: this.user.id - }).then(u => { - this.isFollowing = u.isFollowing; - this.hasPendingFollowRequestFromYou = u.hasPendingFollowRequestFromYou; - }); - } - }, - - mounted() { - this.connection = markRaw(os.stream.useChannel('main')); +const props = withDefaults(defineProps<{ + user: Misskey.entities.UserDetailed, + full?: boolean, + large?: boolean, +}>(), { + full: false, + large: false, +}); - this.connection.on('follow', this.onFollowChange); - this.connection.on('unfollow', this.onFollowChange); - }, +const isFollowing = ref(props.user.isFollowing); +const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou); +const wait = ref(false); +const connection = stream.useChannel('main'); - beforeUnmount() { - this.connection.dispose(); - }, +if (props.user.isFollowing == null) { + os.api('users/show', { + userId: props.user.id + }).then(u => { + isFollowing.value = u.isFollowing; + hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou; + }); +} - methods: { - onFollowChange(user) { - if (user.id == this.user.id) { - this.isFollowing = user.isFollowing; - this.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; - } - }, +function onFollowChange(user: Misskey.entities.UserDetailed) { + if (user.id == props.user.id) { + isFollowing.value = user.isFollowing; + hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou; + } +} - async onClick() { - this.wait = true; +async function onClick() { + wait.value = true; - try { - if (this.isFollowing) { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('unfollowConfirm', { name: this.user.name || this.user.username }), - }); + try { + if (isFollowing.value) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), + }); - if (canceled) return; + if (canceled) return; - await os.api('following/delete', { - userId: this.user.id - }); - } else { - if (this.hasPendingFollowRequestFromYou) { - await os.api('following/requests/cancel', { - userId: this.user.id - }); - } else if (this.user.isLocked) { - await os.api('following/create', { - userId: this.user.id - }); - this.hasPendingFollowRequestFromYou = true; - } else { - await os.api('following/create', { - userId: this.user.id - }); - this.hasPendingFollowRequestFromYou = true; - } - } - } catch (e) { - console.error(e); - } finally { - this.wait = false; + await os.api('following/delete', { + userId: props.user.id + }); + } else { + if (hasPendingFollowRequestFromYou.value) { + await os.api('following/requests/cancel', { + userId: props.user.id + }); + } else if (props.user.isLocked) { + await os.api('following/create', { + userId: props.user.id + }); + hasPendingFollowRequestFromYou.value = true; + } else { + await os.api('following/create', { + userId: props.user.id + }); + hasPendingFollowRequestFromYou.value = true; } } + } catch (e) { + console.error(e); + } finally { + wait.value = false; } +} + +onMounted(() => { + connection.on('follow', onFollowChange); + connection.on('unfollow', onFollowChange); +}); + +onBeforeUnmount(() => { + connection.dispose(); }); </script> diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue index b03a6133b4..c74e1ac75e 100644 --- a/packages/client/src/components/forgot-password.vue +++ b/packages/client/src/components/forgot-password.vue @@ -2,72 +2,64 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="$refs.dialog.close()" - @closed="$emit('closed')" + @close="dialog.close()" + @closed="emit('closed')" > - <template #header>{{ $ts.forgotPassword }}</template> + <template #header>{{ i18n.locale.forgotPassword }}</template> - <form v-if="$instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> + <form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> <div class="main _formRoot"> <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> - <template #label>{{ $ts.username }}</template> + <template #label>{{ i18n.locale.username }}</template> <template #prefix>@</template> </MkInput> <MkInput v-model="email" class="_formBlock" type="email" spellcheck="false" required> - <template #label>{{ $ts.emailAddress }}</template> - <template #caption>{{ $ts._forgotPassword.enterEmail }}</template> + <template #label>{{ i18n.locale.emailAddress }}</template> + <template #caption>{{ i18n.locale._forgotPassword.enterEmail }}</template> </MkInput> - <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> + <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.locale.send }}</MkButton> </div> <div class="sub"> - <MkA to="/about" class="_link">{{ $ts._forgotPassword.ifNoEmail }}</MkA> + <MkA to="/about" class="_link">{{ i18n.locale._forgotPassword.ifNoEmail }}</MkA> </div> </form> - <div v-else> - {{ $ts._forgotPassword.contactAdmin }} + <div v-else class="bafecedb"> + {{ i18n.locale._forgotPassword.contactAdmin }} </div> </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import * as os from '@/os'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XModalWindow, - MkButton, - MkInput, - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +let dialog: InstanceType<typeof XModalWindow> = $ref(); - data() { - return { - username: '', - email: '', - processing: false, - }; - }, +let username = $ref(''); +let email = $ref(''); +let processing = $ref(false); - methods: { - async onSubmit() { - this.processing = true; - await os.apiWithDialog('request-reset-password', { - username: this.username, - email: this.email, - }); - - this.$emit('done'); - this.$refs.dialog.close(); - } - } -}); +async function onSubmit() { + processing = true; + await os.apiWithDialog('request-reset-password', { + username, + email, + }); + emit('done'); + dialog.close(); +} </script> <style lang="scss" scoped> @@ -81,4 +73,8 @@ export default defineComponent({ padding: 24px; } } + +.bafecedb { + padding: 24px; +} </style> diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue new file mode 100644 index 0000000000..571afe50c0 --- /dev/null +++ b/packages/client/src/components/form/folder.vue @@ -0,0 +1,107 @@ +<template> +<div class="dwzlatin" :class="{ opened }"> + <div class="header _button" @click="toggle"> + <span class="icon"><slot name="icon"></slot></span> + <span class="text"><slot name="label"></slot></span> + <span class="right"> + <span class="text"><slot name="suffix"></slot></span> + <i v-if="opened" class="fas fa-angle-up icon"></i> + <i v-else class="fas fa-angle-down icon"></i> + </span> + </div> + <keep-alive> + <div v-if="openedAtLeastOnce" v-show="opened" class="body"> + <MkSpacer :margin-min="14" :margin-max="22"> + <slot></slot> + </MkSpacer> + </div> + </keep-alive> +</div> +</template> + +<script lang="ts" setup> +const props = withDefaults(defineProps<{ + defaultOpen: boolean; +}>(), { + defaultOpen: false, +}) + +let opened = $ref(props.defaultOpen); +let openedAtLeastOnce = $ref(props.defaultOpen); + +const toggle = () => { + opened = !opened; + if (opened) { + openedAtLeastOnce = true; + } +}; +</script> + +<style lang="scss" scoped> +.dwzlatin { + display: block; + + > .header { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + padding: 12px 14px 12px 14px; + background: var(--buttonBg); + border-radius: 6px; + + &:hover { + text-decoration: none; + background: var(--buttonHoverBg); + } + + &.active { + color: var(--accent); + background: var(--buttonHoverBg); + } + + > .icon { + margin-right: 0.75em; + flex-shrink: 0; + text-align: center; + opacity: 0.8; + + &:empty { + display: none; + + & + .text { + padding-left: 4px; + } + } + } + + > .text { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 12px; + } + + > .right { + margin-left: auto; + opacity: 0.7; + white-space: nowrap; + + > .text:not(:empty) { + margin-right: 0.75em; + } + } + } + + > .body { + background: var(--panel); + border-radius: 0 0 6px 6px; + } + + &.opened { + > .header { + border-radius: 6px 6px 0 0; + } + } +} +</style> diff --git a/packages/client/src/components/form/group.vue b/packages/client/src/components/form/group.vue index 2fc203f1b9..1e8376ca44 100644 --- a/packages/client/src/components/form/group.vue +++ b/packages/client/src/components/form/group.vue @@ -1,5 +1,5 @@ <template> -<div v-sticky-container v-panel class="adfeebaf _formBlock"> +<div v-sticky-container class="adfeebaf _formBlock"> <div class="label"><slot name="label"></slot></div> <div class="main _formRoot"> <slot></slot> @@ -17,6 +17,7 @@ export default defineComponent({ <style lang="scss" scoped> .adfeebaf { padding: 24px 24px; + border: solid 1px var(--divider); border-radius: var(--radius); > .label { diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue index 3533f4f27b..7165671af3 100644 --- a/packages/client/src/components/form/input.vue +++ b/packages/client/src/components/form/input.vue @@ -167,7 +167,7 @@ export default defineComponent({ // ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒä½œæˆã•ã‚ŒãŸæ™‚ã€éžè¡¨ç¤ºçŠ¶æ…‹ã§ã‚ã‚‹å ´åˆãŒã‚ã‚‹ // éžè¡¨ç¤ºçŠ¶æ…‹ã ã¨è¦ç´ ã®å¹…ãªã©ã¯0ã«ãªã£ã¦ã—ã¾ã†ã®ã§ã€å®šæœŸçš„ã«è¨ˆç®—ã™ã‚‹ - const clock = setInterval(() => { + const clock = window.setInterval(() => { if (prefixEl.value) { if (prefixEl.value.offsetWidth) { inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; @@ -181,7 +181,7 @@ export default defineComponent({ }, 100); onUnmounted(() => { - clearInterval(clock); + window.clearInterval(clock); }); }); }); diff --git a/packages/client/src/components/form/pagination.vue b/packages/client/src/components/form/pagination.vue deleted file mode 100644 index 3d3b40a783..0000000000 --- a/packages/client/src/components/form/pagination.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> -<FormSlot> - <template #label><slot name="label"></slot></template> - <div class="abcaccfa"> - <slot :items="items"></slot> - <div v-if="empty" key="_empty_" class="empty"> - <slot name="empty"></slot> - </div> - <MkButton v-show="more" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</FormSlot> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import FormSlot from './slot.vue'; -import paging from '@/scripts/paging'; - -export default defineComponent({ - components: { - MkButton, - FormSlot, - }, - - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - }, -}); -</script> - -<style lang="scss" scoped> -.abcaccfa { -} -</style> diff --git a/packages/client/src/components/form/radio.vue b/packages/client/src/components/form/radio.vue index f0b8c71376..2becbec6f3 100644 --- a/packages/client/src/components/form/radio.vue +++ b/packages/client/src/components/form/radio.vue @@ -1,6 +1,6 @@ <template> <div - v-panel + v-adaptive-border class="novjtctn" :class="{ disabled, checked }" :aria-checked="checked" @@ -53,7 +53,10 @@ export default defineComponent({ display: inline-block; text-align: left; cursor: pointer; - padding: 11px 14px; + padding: 10px 12px; + background-color: var(--panel); + background-clip: padding-box !important; + border: solid 1px var(--panel); border-radius: 6px; transition: all 0.3s; @@ -69,9 +72,13 @@ export default defineComponent({ } } + &:hover { + border-color: var(--inputBorderHover) !important; + } + &.checked { - background: var(--accentedBg) !important; - border-color: var(--accent); + background-color: var(--accentedBg) !important; + border-color: var(--accentedBg) !important; color: var(--accent); &, * { @@ -89,11 +96,6 @@ export default defineComponent({ } } - &:hover { - border-color: var(--inputBorderHover); - color: var(--accent); - } - > input { position: absolute; width: 0; diff --git a/packages/client/src/components/form/section.vue b/packages/client/src/components/form/section.vue index bc2ab966b8..c6e34ef1cc 100644 --- a/packages/client/src/components/form/section.vue +++ b/packages/client/src/components/form/section.vue @@ -1,5 +1,5 @@ <template> -<div v-size="{ max: [500] }" v-sticky-container class="vrtktovh _formBlock"> +<div class="vrtktovh _formBlock"> <div class="label"><slot name="label"></slot></div> <div class="main _formRoot"> <slot></slot> @@ -7,20 +7,13 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - -}); +<script lang="ts" setup> </script> <style lang="scss" scoped> .vrtktovh { - margin: 0; border-top: solid 0.5px var(--divider); border-bottom: solid 0.5px var(--divider); - padding: 24px 0; & + .vrtktovh { border-top: none; @@ -36,7 +29,7 @@ export default defineComponent({ > .label { font-weight: bold; - padding: 0 0 16px 0; + margin: 1.5em 0 16px 0; &:empty { display: none; @@ -44,6 +37,7 @@ export default defineComponent({ } > .main { + margin: 1.5em 0; } } </style> diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue index afc53ca9c8..87196027a8 100644 --- a/packages/client/src/components/form/select.vue +++ b/packages/client/src/components/form/select.vue @@ -117,7 +117,7 @@ export default defineComponent({ // ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒä½œæˆã•ã‚ŒãŸæ™‚ã€éžè¡¨ç¤ºçŠ¶æ…‹ã§ã‚ã‚‹å ´åˆãŒã‚ã‚‹ // éžè¡¨ç¤ºçŠ¶æ…‹ã ã¨è¦ç´ ã®å¹…ãªã©ã¯0ã«ãªã£ã¦ã—ã¾ã†ã®ã§ã€å®šæœŸçš„ã«è¨ˆç®—ã™ã‚‹ - const clock = setInterval(() => { + const clock = window.setInterval(() => { if (prefixEl.value) { if (prefixEl.value.offsetWidth) { inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; @@ -131,7 +131,7 @@ export default defineComponent({ }, 100); onUnmounted(() => { - clearInterval(clock); + window.clearInterval(clock); }); }); }); diff --git a/packages/client/src/components/form/split.vue b/packages/client/src/components/form/split.vue new file mode 100644 index 0000000000..676b293967 --- /dev/null +++ b/packages/client/src/components/form/split.vue @@ -0,0 +1,27 @@ +<template> +<div class="terlnhxf _formBlock"> + <slot></slot> +</div> +</template> + +<script lang="ts" setup> +const props = withDefaults(defineProps<{ + minWidth: number; +}>(), { + minWidth: 210, +}); + +const minWidth = props.minWidth + 'px'; +</script> + +<style lang="scss" scoped> +.terlnhxf { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(v-bind('minWidth'), 1fr)); + grid-gap: 12px; + + > ::v-deep(*) { + margin: 0 !important; + } +} +</style> diff --git a/packages/client/src/components/form/suspense.vue b/packages/client/src/components/form/suspense.vue index 4d5debe604..2ad55dacae 100644 --- a/packages/client/src/components/form/suspense.vue +++ b/packages/client/src/components/form/suspense.vue @@ -1,5 +1,5 @@ <template> -<transition name="fade" mode="out-in"> +<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="pending"> <MkLoading/> </div> diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue index aa9b09215e..f8a07b4caa 100644 --- a/packages/client/src/components/form/switch.vue +++ b/packages/client/src/components/form/switch.vue @@ -13,7 +13,8 @@ <i class="check fas fa-check"></i> </span> <span class="label"> - <span @click="toggle"><slot></slot></span> + <!-- TODO: ç„¡åslotã®æ–¹ã¯å»ƒæ¢ --> + <span @click="toggle"><slot name="label"></slot><slot></slot></span> <p class="caption"><slot name="caption"></slot></p> </span> </div> @@ -110,7 +111,7 @@ export default defineComponent({ } > .label { - margin-left: 16px; + margin-left: 12px; margin-top: 2px; display: block; transition: inherit; diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index 77ee7525a4..cf7385ca22 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -4,130 +4,114 @@ </a> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { inject } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { router } from '@/router'; import { url } from '@/config'; -import { popout } from '@/scripts/popout'; -import { ColdDeviceStorage } from '@/store'; +import { popout as popout_ } from '@/scripts/popout'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - inject: { - navHook: { - default: null - }, - sideViewHook: { - default: null - } - }, +const props = withDefaults(defineProps<{ + to: string; + activeClass?: null | string; + behavior?: null | 'window' | 'browser' | 'modalWindow'; +}>(), { + activeClass: null, + behavior: null, +}); - props: { - to: { - type: String, - required: true, - }, - activeClass: { - type: String, - required: false, - }, - behavior: { - type: String, - required: false, - }, - }, +const navHook = inject('navHook', null); +const sideViewHook = inject('sideViewHook', null); - computed: { - active() { - if (this.activeClass == null) return false; - const resolved = router.resolve(this.to); - if (resolved.path == this.$route.path) return true; - if (resolved.name == null) return false; - if (this.$route.name == null) return false; - return resolved.name == this.$route.name; - } - }, +const active = $computed(() => { + if (props.activeClass == null) return false; + const resolved = router.resolve(props.to); + if (resolved.path === router.currentRoute.value.path) return true; + if (resolved.name == null) return false; + if (router.currentRoute.value.name == null) return false; + return resolved.name === router.currentRoute.value.name; +}); - methods: { - onContextmenu(e) { - if (window.getSelection().toString() !== '') return; - os.contextMenu([{ - type: 'label', - text: this.to, - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.to); - } - }, this.sideViewHook ? { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.sideViewHook(this.to); - } - } : undefined, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.to); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.to, '_blank'); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(`${url}${this.to}`); - } - }], e); - }, +function onContextmenu(ev) { + const selection = window.getSelection(); + if (selection && selection.toString() !== '') return; + os.contextMenu([{ + type: 'label', + text: props.to, + }, { + icon: 'fas fa-window-maximize', + text: i18n.locale.openInWindow, + action: () => { + os.pageWindow(props.to); + } + }, sideViewHook ? { + icon: 'fas fa-columns', + text: i18n.locale.openInSideView, + action: () => { + sideViewHook(props.to); + } + } : undefined, { + icon: 'fas fa-expand-alt', + text: i18n.locale.showInPage, + action: () => { + router.push(props.to); + } + }, null, { + icon: 'fas fa-external-link-alt', + text: i18n.locale.openInNewTab, + action: () => { + window.open(props.to, '_blank'); + } + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: () => { + copyToClipboard(`${url}${props.to}`); + } + }], ev); +} - window() { - os.pageWindow(this.to); - }, +function openWindow() { + os.pageWindow(props.to); +} - modalWindow() { - os.modalPageWindow(this.to); - }, +function modalWindow() { + os.modalPageWindow(props.to); +} - popout() { - popout(this.to); - }, +function popout() { + popout_(props.to); +} - nav() { - if (this.behavior === 'browser') { - location.href = this.to; - return; - } +function nav() { + if (props.behavior === 'browser') { + location.href = props.to; + return; + } - if (this.behavior) { - if (this.behavior === 'window') { - return this.window(); - } else if (this.behavior === 'modalWindow') { - return this.modalWindow(); - } - } + if (props.behavior) { + if (props.behavior === 'window') { + return openWindow(); + } else if (props.behavior === 'modalWindow') { + return modalWindow(); + } + } - if (this.navHook) { - this.navHook(this.to); - } else { - if (this.$store.state.defaultSideView && this.sideViewHook && this.to !== '/') { - return this.sideViewHook(this.to); - } + if (navHook) { + navHook(props.to); + } else { + if (defaultStore.state.defaultSideView && sideViewHook && props.to !== '/') { + return sideViewHook(props.to); + } - if (this.$router.currentRoute.value.path === this.to) { - window.scroll({ top: 0, behavior: 'smooth' }); - } else { - this.$router.push(this.to); - } - } + if (router.currentRoute.value.path === props.to) { + window.scroll({ top: 0, behavior: 'smooth' }); + } else { + router.push(props.to); } } -}); +} </script> diff --git a/packages/client/src/components/global/acct.vue b/packages/client/src/components/global/acct.vue index 018826153c..c3e806b5fb 100644 --- a/packages/client/src/components/global/acct.vue +++ b/packages/client/src/components/global/acct.vue @@ -5,28 +5,17 @@ </span> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import * as misskey from 'misskey-js'; import { toUnicode } from 'punycode/'; -import { host } from '@/config'; +import { host as hostRaw } from '@/config'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - detail: { - type: Boolean, - default: false - }, - }, - data() { - return { - host: toUnicode(host), - }; - } -}); +defineProps<{ + user: misskey.entities.UserDetailed; + detail?: boolean; +}>(); + +const host = toUnicode(hostRaw); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/global/ad.vue b/packages/client/src/components/global/ad.vue index 49046b00a7..180dabb2a2 100644 --- a/packages/client/src/components/global/ad.vue +++ b/packages/client/src/components/global/ad.vue @@ -20,7 +20,7 @@ <script lang="ts"> import { defineComponent, ref } from 'vue'; -import { Instance, instance } from '@/instance'; +import { instance } from '@/instance'; import { host } from '@/config'; import MkButton from '@/components/ui/button.vue'; import { defaultStore } from '@/store'; @@ -48,9 +48,9 @@ export default defineComponent({ showMenu.value = !showMenu.value; }; - const choseAd = (): Instance['ads'][number] | null => { + const choseAd = (): (typeof instance)['ads'][number] | null => { if (props.specify) { - return props.specify as Instance['ads'][number]; + return props.specify as (typeof instance)['ads'][number]; } const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? { diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue index 300e5e079f..27cfb6e4d4 100644 --- a/packages/client/src/components/global/avatar.vue +++ b/packages/client/src/components/global/avatar.vue @@ -1,74 +1,54 @@ <template> -<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :title="acct(user)" @click="onClick"> +<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick"> <img class="inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> </span> -<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :to="userPage(user)" :title="acct(user)" :target="target"> +<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target"> <img class="inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> </MkA> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, watch } from 'vue'; +import * as misskey from 'misskey-js'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { acct, userPage } from '@/filters/user'; import MkUserOnlineIndicator from '@/components/user-online-indicator.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkUserOnlineIndicator - }, - props: { - user: { - type: Object, - required: true - }, - target: { - required: false, - default: null - }, - disableLink: { - required: false, - default: false - }, - disablePreview: { - required: false, - default: false - }, - showIndicator: { - required: false, - default: false - } - }, - emits: ['click'], - computed: { - cat(): boolean { - return this.user.isCat; - }, - url(): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(this.user.avatarUrl) - : this.user.avatarUrl; - }, - }, - watch: { - 'user.avatarBlurhash'() { - if (this.$el == null) return; - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - } - }, - mounted() { - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - }, - methods: { - onClick(e) { - this.$emit('click', e); - }, - acct, - userPage - } +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + target?: string | null; + disableLink?: boolean; + disablePreview?: boolean; + showIndicator?: boolean; +}>(), { + target: null, + disableLink: false, + disablePreview: false, + showIndicator: false, +}); + +const emit = defineEmits<{ + (e: 'click', ev: MouseEvent): void; +}>(); + +const url = $computed(() => defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(props.user.avatarUrl) + : props.user.avatarUrl); + +function onClick(ev: MouseEvent) { + emit('click', ev); +} + +let color = $ref(); + +watch(() => props.user.avatarBlurhash, () => { + color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); +}, { + immediate: true, }); </script> diff --git a/packages/client/src/components/global/error.vue b/packages/client/src/components/global/error.vue index d759186167..98b96fb414 100644 --- a/packages/client/src/components/global/error.vue +++ b/packages/client/src/components/global/error.vue @@ -8,19 +8,8 @@ </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - MkButton, - }, - data() { - return { - }; - }, -}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue index 7bde53c12e..43ea1395ed 100644 --- a/packages/client/src/components/global/loading.vue +++ b/packages/client/src/components/global/loading.vue @@ -4,27 +4,17 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - inline: { - type: Boolean, - required: false, - default: false - }, - colored: { - type: Boolean, - required: false, - default: true - }, - mini: { - type: Boolean, - required: false, - default: false - }, - } +const props = withDefaults(defineProps<{ + inline?: boolean; + colored?: boolean; + mini?: boolean; +}>(), { + inline: false, + colored: true, + mini: false, }); </script> diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index ab20404909..243d8614ba 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -1,15 +1,23 @@ <template> -<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/> +<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MfmCore from '@/components/mfm'; -export default defineComponent({ - components: { - MfmCore - } +const props = withDefaults(defineProps<{ + text: string; + plain?: boolean; + nowrap?: boolean; + author?: any; + customEmojis?: any; + isNote?: boolean; +}>(), { + plain: false, + nowrap: false, + author: null, + isNote: true, }); </script> diff --git a/packages/client/src/components/global/spacer.vue b/packages/client/src/components/global/spacer.vue index e2f1d1aec7..8a1d7a4e8a 100644 --- a/packages/client/src/components/global/spacer.vue +++ b/packages/client/src/components/global/spacer.vue @@ -40,7 +40,7 @@ export default defineComponent({ return; } - if (rect.width > props.contentMax || rect.width > 500) { + if (rect.width > props.contentMax || (rect.width > 360 && window.innerWidth > 400)) { margin.value = props.marginMax; } else { margin.value = props.marginMin; diff --git a/packages/client/src/components/global/sticky-container.vue b/packages/client/src/components/global/sticky-container.vue index 859b2c1d73..89d397f082 100644 --- a/packages/client/src/components/global/sticky-container.vue +++ b/packages/client/src/components/global/sticky-container.vue @@ -45,7 +45,7 @@ export default defineComponent({ calc(); const observer = new MutationObserver(() => { - setTimeout(() => { + window.setTimeout(() => { calc(); }, 100); }); diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index 6a330a2307..d2788264c5 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -1,73 +1,57 @@ <template> <time :title="absolute"> - <template v-if="mode == 'relative'">{{ relative }}</template> - <template v-else-if="mode == 'absolute'">{{ absolute }}</template> - <template v-else-if="mode == 'detail'">{{ absolute }} ({{ relative }})</template> + <template v-if="mode === 'relative'">{{ relative }}</template> + <template v-else-if="mode === 'absolute'">{{ absolute }}</template> + <template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template> </time> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onUnmounted } from 'vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - time: { - type: [Date, String], - required: true - }, - mode: { - type: String, - default: 'relative' - } - }, - data() { - return { - tickId: null, - now: new Date() - }; - }, - computed: { - _time(): Date { - return typeof this.time == 'string' ? new Date(this.time) : this.time; - }, - absolute(): string { - return this._time.toLocaleString(); - }, - relative(): string { - const time = this._time; - const ago = (this.now.getTime() - time.getTime()) / 1000/*ms*/; - return ( - ago >= 31536000 ? this.$t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : - ago >= 2592000 ? this.$t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : - ago >= 604800 ? this.$t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : - ago >= 86400 ? this.$t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : - ago >= 3600 ? this.$t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : - ago >= 60 ? this.$t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : - ago >= 10 ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? this.$ts._ago.justNow : - ago < -1 ? this.$ts._ago.future : - this.$ts._ago.unknown); - } - }, - created() { - if (this.mode == 'relative' || this.mode == 'detail') { - this.tickId = window.requestAnimationFrame(this.tick); - } - }, - unmounted() { - if (this.mode === 'relative' || this.mode === 'detail') { - window.clearTimeout(this.tickId); - } - }, - methods: { - tick() { - // TODO: パフォーマンスå‘上ã®ãŸã‚ã€ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒç”»é¢å†…ã«è¡¨ç¤ºã•れã¦ã„ã‚‹å ´åˆã®ã¿æ›´æ–°ã™ã‚‹ - this.now = new Date(); +const props = withDefaults(defineProps<{ + time: Date | string; + mode?: 'relative' | 'absolute' | 'detail'; +}>(), { + mode: 'relative', +}); + +const _time = typeof props.time == 'string' ? new Date(props.time) : props.time; +const absolute = _time.toLocaleString(); - this.tickId = setTimeout(() => { - window.requestAnimationFrame(this.tick); - }, 10000); - } - } +let now = $ref(new Date()); +const relative = $computed(() => { + const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; + return ( + ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : + ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : + ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : + ago >= 86400 ? i18n.t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : + ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : + ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : + ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : + ago >= -1 ? i18n.locale._ago.justNow : + ago < -1 ? i18n.locale._ago.future : + i18n.locale._ago.unknown); }); + +function tick() { + // TODO: パフォーマンスå‘上ã®ãŸã‚ã€ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒç”»é¢å†…ã«è¡¨ç¤ºã•れã¦ã„ã‚‹å ´åˆã®ã¿æ›´æ–°ã™ã‚‹ + now = new Date(); + + tickId = window.setTimeout(() => { + window.requestAnimationFrame(tick); + }, 10000); +} + +let tickId: number; + +if (props.mode === 'relative' || props.mode === 'detail') { + tickId = window.requestAnimationFrame(tick); + + onUnmounted(() => { + window.clearTimeout(tickId); + }); +} </script> diff --git a/packages/client/src/components/global/user-name.vue b/packages/client/src/components/global/user-name.vue index bc93a8ea30..090de3df30 100644 --- a/packages/client/src/components/global/user-name.vue +++ b/packages/client/src/components/global/user-name.vue @@ -2,19 +2,14 @@ <Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - nowrap: { - type: Boolean, - default: true - }, - } +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + nowrap?: boolean; +}>(), { + nowrap: true, }); </script> diff --git a/packages/client/src/components/google.vue b/packages/client/src/components/google.vue index a39168b80f..210ca72bfe 100644 --- a/packages/client/src/components/google.vue +++ b/packages/client/src/components/google.vue @@ -5,31 +5,18 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { ref } from 'vue'; -export default defineComponent({ - props: { - q: { - type: String, - required: true, - } - }, - data() { - return { - query: null, - }; - }, - mounted() { - this.query = this.q; - }, - methods: { - search() { - window.open(`https://www.google.com/search?q=${this.query}`, '_blank'); - } - } -}); +const props = defineProps<{ + q: string; +}>(); + +const query = ref(props.q); + +const search = () => { + window.open(`https://www.google.com/search?q=${query.value}`, '_blank'); +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/image-viewer.vue b/packages/client/src/components/image-viewer.vue index 8584b91a61..c39076df16 100644 --- a/packages/client/src/components/image-viewer.vue +++ b/packages/client/src/components/image-viewer.vue @@ -1,8 +1,8 @@ <template> -<MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> +<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="$refs.modal.close()"/> + <img :src="image.url" :alt="image.comment" :title="image.comment" @click="modal.close()"/> <footer> <span>{{ image.type }}</span> <span>{{ bytes(image.size) }}</span> @@ -12,31 +12,23 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<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/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, - - props: { - image: { - type: Object, - required: true - }, - }, +const props = withDefaults(defineProps<{ + image: misskey.entities.DriveFile; +}>(), { +}); - emits: ['closed'], +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); - methods: { - bytes, - number, - } -}); +const modal = $ref<InstanceType<typeof MkModal>>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/img-with-blurhash.vue b/packages/client/src/components/img-with-blurhash.vue index a000c699b6..06ad764403 100644 --- a/packages/client/src/components/img-with-blurhash.vue +++ b/packages/client/src/components/img-with-blurhash.vue @@ -5,67 +5,43 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import { decode } from 'blurhash'; -export default defineComponent({ - props: { - src: { - type: String, - required: false, - default: null - }, - hash: { - type: String, - required: true - }, - alt: { - type: String, - required: false, - default: '', - }, - title: { - type: String, - required: false, - default: null, - }, - size: { - type: Number, - required: false, - default: 64 - }, - cover: { - type: Boolean, - required: false, - default: true, - } - }, +const props = withDefaults(defineProps<{ + src?: string | null; + hash: string; + alt?: string; + title?: string | null; + size?: number; + cover?: boolean; +}>(), { + src: null, + alt: '', + title: null, + size: 64, + cover: true, +}); - data() { - return { - loaded: false, - }; - }, +const canvas = $ref<HTMLCanvasElement>(); +let loaded = $ref(false); - mounted() { - this.draw(); - }, +function draw() { + if (props.hash == null) return; + const pixels = decode(props.hash, props.size, props.size); + const ctx = canvas.getContext('2d'); + const imageData = ctx!.createImageData(props.size, props.size); + imageData.data.set(pixels); + ctx!.putImageData(imageData, 0, 0); +} - methods: { - draw() { - if (this.hash == null) return; - const pixels = decode(this.hash, this.size, this.size); - const ctx = (this.$refs.canvas as HTMLCanvasElement).getContext('2d'); - const imageData = ctx!.createImageData(this.size, this.size); - imageData.data.set(pixels); - ctx!.putImageData(imageData, 0, 0); - }, +function onLoad() { + loaded = true; +} - onLoad() { - this.loaded = true; - } - } +onMounted(() => { + draw(); }); </script> diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue index bc62998a4a..409c3a49ca 100644 --- a/packages/client/src/components/instance-stats.vue +++ b/packages/client/src/components/instance-stats.vue @@ -1,5 +1,5 @@ <template> -<div class="zbcjwnqg" style="margin-top: -8px;"> +<div class="zbcjwnqg"> <div class="selects" style="display: flex;"> <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> <optgroup :label="$ts.federation"> @@ -29,16 +29,16 @@ <option value="day">{{ $ts.perDay }}</option> </MkSelect> </div> - <MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart> + <div class="chart"> + <MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart> + </div> </div> </template> <script lang="ts"> -import { defineComponent, onMounted, ref, watch } from 'vue'; +import { defineComponent, ref } from 'vue'; import MkSelect from '@/components/form/select.vue'; import MkChart from '@/components/chart.vue'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; export default defineComponent({ components: { @@ -74,7 +74,10 @@ export default defineComponent({ <style lang="scss" scoped> .zbcjwnqg { > .selects { - padding: 8px 16px 0 16px; + } + + > .chart { + padding: 8px 0 0 0; } } </style> diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue index 1ce5a1c2c1..77fd8bb344 100644 --- a/packages/client/src/components/instance-ticker.vue +++ b/packages/client/src/components/instance-ticker.vue @@ -1,41 +1,22 @@ <template> <div class="hpaizdrt" :style="bg"> - <img v-if="info.faviconUrl" class="icon" :src="info.faviconUrl"/> - <span class="name">{{ info.name }}</span> + <img v-if="instance.faviconUrl" class="icon" :src="instance.faviconUrl"/> + <span class="name">{{ instance.name }}</span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import { instanceName } from '@/config'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - instance: { - type: Object, - required: false - }, - }, +const props = defineProps<{ + instance: any; // TODO +}>(); - data() { - return { - info: this.instance || { - faviconUrl: '/favicon.ico', - name: instanceName, - themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content - } - } - }, +const themeColor = props.instance.themeColor || '#777777'; - computed: { - bg(): any { - const themeColor = this.info.themeColor || '#777777'; - return { - background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` - }; - } - } -}); +const bg = { + background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/key-value.vue b/packages/client/src/components/key-value.vue index 6a9a948ce9..da98abd77c 100644 --- a/packages/client/src/components/key-value.vue +++ b/packages/client/src/components/key-value.vue @@ -1,5 +1,5 @@ <template> -<div class="alqyeyti"> +<div class="alqyeyti" :class="{ oneline }"> <div class="key"> <slot name="key"></slot> </div> @@ -22,6 +22,11 @@ export default defineComponent({ required: false, default: null, }, + oneline: { + type: Boolean, + required: false, + default: false, + }, }, setup(props) { @@ -39,10 +44,30 @@ export default defineComponent({ <style lang="scss" scoped> .alqyeyti { + > .key, > .value { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + > .key { font-size: 0.85em; padding: 0 0 0.25em 0; opacity: 0.75; } + + &.oneline { + display: flex; + + > .key { + width: 30%; + font-size: 1em; + padding: 0 8px 0 0; + } + + > .value { + width: 70%; + } + } } </style> diff --git a/packages/client/src/components/link.vue b/packages/client/src/components/link.vue index 8b8cde6510..317c931cec 100644 --- a/packages/client/src/components/link.vue +++ b/packages/client/src/components/link.vue @@ -1,82 +1,36 @@ <template> -<component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" +<component :is="self ? 'MkA' : 'a'" ref="el" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" :title="url" - @mouseover="onMouseover" - @mouseleave="onMouseleave" > <slot></slot> <i v-if="target === '_blank'" class="fas fa-external-link-square-alt icon"></i> </component> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import { url as local } from '@/config'; -import { isTouchUsing } from '@/scripts/touch'; +import { useTooltip } from '@/scripts/use-tooltip'; import * as os from '@/os'; -export default defineComponent({ - props: { - url: { - type: String, - required: true, - }, - rel: { - type: String, - required: false, - } - }, - data() { - const self = this.url.startsWith(local); - return { - local, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - showTimer: null, - hideTimer: null, - checkTimer: null, - close: null, - }; - }, - methods: { - async showPreview() { - if (!document.body.contains(this.$el)) return; - if (this.close) return; +const props = withDefaults(defineProps<{ + url: string; + rel?: null | string; +}>(), { +}); - const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), { - url: this.url, - source: this.$el - }); +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; - this.close = () => { - dispose(); - }; +const el = $ref(); - this.checkTimer = setInterval(() => { - if (!document.body.contains(this.$el)) this.closePreview(); - }, 1000); - }, - closePreview() { - if (this.close) { - clearInterval(this.checkTimer); - this.close(); - this.close = null; - } - }, - onMouseover() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.showPreview, 500); - }, - onMouseleave() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.closePreview, 500); - } - } +useTooltip($$(el), (showing) => { + os.popup(import('@/components/url-preview-popup.vue'), { + showing, + url: props.url, + source: el, + }, {}, 'closed'); }); </script> diff --git a/packages/client/src/components/media-banner.vue b/packages/client/src/components/media-banner.vue index 9dbfe3d0c6..5093f11e97 100644 --- a/packages/client/src/components/media-banner.vue +++ b/packages/client/src/components/media-banner.vue @@ -6,7 +6,7 @@ <span>{{ $ts.clickToShow }}</span> </div> <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio"> - <audio ref="audio" + <audio ref="audioEl" class="audio" :src="media.url" :title="media.name" @@ -25,34 +25,26 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted } from 'vue'; +import * as misskey from 'misskey-js'; import { ColdDeviceStorage } from '@/store'; -export default defineComponent({ - props: { - media: { - type: Object, - required: true - } - }, - data() { - return { - hide: true, - }; - }, - mounted() { - const audioTag = this.$refs.audio as HTMLAudioElement; - if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume'); - }, - methods: { - volumechange() { - const audioTag = this.$refs.audio as HTMLAudioElement; - ColdDeviceStorage.set('mediaVolume', audioTag.volume); - }, - }, -}) +const props = withDefaults(defineProps<{ + media: misskey.entities.DriveFile; +}>(), { +}); + +const audioEl = $ref<HTMLAudioElement | null>(); +let hide = $ref(true); + +function volumechange() { + if (audioEl) ColdDeviceStorage.set('mediaVolume', audioEl.volume); +} + +onMounted(() => { + if (audioEl) audioEl.volume = ColdDeviceStorage.get('mediaVolume'); +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue index 2970d06c97..efcbb12922 100644 --- a/packages/client/src/components/media-list.vue +++ b/packages/client/src/components/media-list.vue @@ -3,7 +3,7 @@ <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container"> <div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length"> - <template v-for="media in mediaList"> + <template v-for="media in mediaList.filter(media => previewable(media))"> <XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/> <XImage v-else-if="media.type.startsWith('image')" :key="media.id" class="image" :data-id="media.id" :image="media" :raw="raw"/> </template> @@ -22,6 +22,7 @@ import XBanner from './media-banner.vue'; import XImage from './media-image.vue'; import XVideo from './media-video.vue'; import * as os from '@/os'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; import { defaultStore } from '@/store'; export default defineComponent({ @@ -44,18 +45,23 @@ export default defineComponent({ onMounted(() => { const lightbox = new PhotoSwipeLightbox({ - dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => { - const item = { - src: media.url, - w: media.properties.width, - h: media.properties.height, - alt: media.name, - }; - if (media.properties.orientation != null && media.properties.orientation >= 5) { - [item.w, item.h] = [item.h, item.w]; - } - return item; - }), + dataSource: props.mediaList + .filter(media => { + if (media.type === 'image/svg+xml') return true; // svgã®webpublicã¯pngãªã®ã§true + return media.type.startsWith('image') && FILE_TYPE_BROWSERSAFE.includes(media.type); + }) + .map(media => { + const item = { + src: media.url, + w: media.properties.width, + h: media.properties.height, + alt: media.name, + }; + if (media.properties.orientation != null && media.properties.orientation >= 5) { + [item.w, item.h] = [item.h, item.w]; + } + return item; + }), gallery: gallery.value, children: '.image', thumbSelector: '.image', @@ -99,7 +105,9 @@ export default defineComponent({ }); const previewable = (file: misskey.entities.DriveFile): boolean => { - return file.type.startsWith('video') || file.type.startsWith('image'); + if (file.type === 'image/svg+xml') return true; // svgã®webpublic/thumbnailã¯pngãªã®ã§true + // FILE_TYPE_BROWSERSAFEã«é©åˆã—ãªã„ã‚‚ã®ã¯ãƒ–ラウザã§è¡¨ç¤ºã™ã‚‹ã®ã«ä¸é©åˆ‡ + return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type); }; return { diff --git a/packages/client/src/components/media-video.vue b/packages/client/src/components/media-video.vue index a0dc57b657..680eb27e64 100644 --- a/packages/client/src/components/media-video.vue +++ b/packages/client/src/components/media-video.vue @@ -22,26 +22,16 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { defaultStore } from '@/store'; -export default defineComponent({ - props: { - video: { - type: Object, - required: true - } - }, - data() { - return { - hide: true, - }; - }, - created() { - this.hide = (this.$store.state.nsfw === 'force') ? true : this.video.isSensitive && (this.$store.state.nsfw !== 'ignore'); - }, -}); +const props = defineProps<{ + video: misskey.entities.DriveFile; +}>(); + +const hide = ref((defaultStore.state.nsfw === 'force') ? true : props.video.isSensitive && (defaultStore.state.nsfw !== 'ignore')); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/mini-chart.vue b/packages/client/src/components/mini-chart.vue index 2eb9ae8cbe..8c74eae876 100644 --- a/packages/client/src/components/mini-chart.vue +++ b/packages/client/src/components/mini-chart.vue @@ -63,10 +63,10 @@ export default defineComponent({ this.draw(); // VueãŒä½•æ•…ã‹Watchを発動ã•ã›ãªã„å ´åˆãŒã‚ã‚‹ã®ã§ - this.clock = setInterval(this.draw, 1000); + this.clock = window.setInterval(this.draw, 1000); }, beforeUnmount() { - clearInterval(this.clock); + window.clearInterval(this.clock); }, methods: { draw() { diff --git a/packages/client/src/components/modal-page-window.vue b/packages/client/src/components/modal-page-window.vue index 3de1980820..2e17d5d030 100644 --- a/packages/client/src/components/modal-page-window.vue +++ b/packages/client/src/components/modal-page-window.vue @@ -153,8 +153,8 @@ export default defineComponent({ this.$refs.window.close(); }, - onContextmenu(e) { - os.contextMenu(this.contextmenu, e); + onContextmenu(ev: MouseEvent) { + os.contextMenu(this.contextmenu, ev); } }, }); diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index 55a02f1e73..a3b30f726e 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -4,12 +4,13 @@ v-show="!isDeleted" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" + ref="el" class="lxwezrsl _block" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > - <XSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/> - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> + <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"/> <i class="fas fa-retweet"></i> @@ -107,7 +108,7 @@ </footer> </div> </article> - <XSub 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="reply" :detail="true"/> </div> <div v-else class="_panel muted" @click="muted = false"> <I18n :src="$ts.userSaysSomething" tag="small"> @@ -120,764 +121,171 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; -import XNoteHeader from './note-header.vue'; +import * as misskey from 'misskey-js'; +import MkNoteSub from './MkNoteSub.vue'; import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; import XPoll from './poll.vue'; import XRenoteButton from './renote-button.vue'; +import MkUrlPreview from '@/components/url-preview.vue'; +import MkInstanceTicker from '@/components/instance-ticker.vue'; import { pleaseLogin } from '@/scripts/please-login'; -import { focusPrev, focusNext } from '@/scripts/focus'; -import { url } from '@/config'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; import { checkWordMute } from '@/scripts/check-word-mute'; import { userPage } from '@/filters/user'; import { notePage } from '@/filters/note'; import * as os from '@/os'; -import { noteActions, noteViewInterruptors } from '@/store'; +import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { getNoteMenu } from '@/scripts/get-note-menu'; +import { useNoteCapture } from '@/scripts/use-note-capture'; -// TODO: note.vueã¨ã»ã¼åŒã˜ãªã®ã§å…±é€šåŒ–ã—ãŸã„ -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - XRenoteButton, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - inject: { - inChannel: { - default: null - }, - }, +const inChannel = inject('inChannel', null); - props: { - note: { - type: Object, - required: true - }, - }, +const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null +); - emits: ['update:note'], +const el = ref<HTMLElement>(); +const menuButton = ref<HTMLElement>(); +const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); +const renoteTime = ref<HTMLElement>(); +const reactButton = ref<HTMLElement>(); +let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note); +const isMyRenote = $i && ($i.id === props.note.userId); +const showContent = ref(false); +const isDeleted = ref(false); +const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); +const translation = ref(null); +const translating = ref(false); +const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; +const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); +const conversation = ref<misskey.entities.Note[]>([]); +const replies = ref<misskey.entities.Note[]>([]); - data() { - return { - connection: null, - conversation: [], - replies: [], - showContent: false, - isDeleted: false, - muted: false, - translation: null, - translating: false, - notePage, - }; - }, +const keymap = { + 'r': () => reply(true), + 'e|a|plus': () => react(true), + 'q': () => renoteButton.value.renote(true), + 'esc': blur, + 'm|o': () => menu(true), + 's': () => showContent.value != showContent.value, +}; - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.$refs.renoteButton.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = os.stream; - } - - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); +useNoteCapture({ + appearNote: $$(appearNote), + rootEl: el, +}); - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } +function reply(viaKeyboard = false): void { + pleaseLogin(); + os.post({ + reply: appearNote, + animation: !viaKeyboard, + }, () => { + focus(); + }); +} - os.api('notes/children', { - noteId: this.appearNote.id, - limit: 30 - }).then(replies => { - this.replies = replies; +function react(viaKeyboard = false): void { + pleaseLogin(); + blur(); + reactionPicker.show(reactButton.value, reaction => { + os.api('notes/reactions/create', { + noteId: appearNote.id, + reaction: reaction }); + }, () => { + focus(); + }); +} - if (this.appearNote.replyId) { - os.api('notes/conversation', { - noteId: this.appearNote.replyId - }).then(conversation => { - this.conversation = conversation.reverse(); - }); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); +function undoReact(note): void { + const oldReaction = note.myReaction; + if (!oldReaction) return; + os.api('notes/reactions/delete', { + noteId: note.id + }); +} - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); +function onContextmenu(ev: MouseEvent): void { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: ã“ã®ãƒŽãƒ¼ãƒˆãŒã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°çµŒç”±ã§æµã‚Œã¦ããŸå ´åˆã®ã¿ sr ã™ã‚‹ - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; + }; + if (isLink(ev.target)) return; + if (window.getSelection().toString() !== '') return; - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.focus(); - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(); - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - toggleThreadMute(mute: boolean) { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - this.$instance.translatorAvailable ? { - icon: 'fas fa-language', - text: this.$ts.translate, - action: this.translate - } : undefined, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - statePromise.then(state => state.isMutedThread ? { - icon: 'fas fa-comment-slash', - text: this.$ts.unmuteThread, - action: () => this.toggleThreadMute(false) - } : { - icon: 'fas fa-comment-slash', - text: this.$ts.muteThread, - action: () => this.toggleThreadMute(true) - }), - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /*...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(this.focus); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, + if (defaultStore.state.useReactionPickerForContextMenu) { + ev.preventDefault(); + react(); + } else { + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus); + } +} - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, +function menu(viaKeyboard = false): void { + os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, { + viaKeyboard + }).then(focus); +} - async translate() { - if (this.translation != null) return; - this.translating = true; - const res = await os.api('notes/translate', { - noteId: this.appearNote.id, - targetLang: localStorage.getItem('lang') || navigator.language, +function showRenoteMenu(viaKeyboard = false): void { + if (!isMyRenote) return; + os.popupMenu([{ + text: i18n.locale.unrenote, + icon: 'fas fa-trash-alt', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: props.note.id }); - this.translating = false; - this.translation = res; - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, + isDeleted.value = true; + } + }], renoteTime.value, { + viaKeyboard: viaKeyboard + }); +} - focusBefore() { - focusPrev(this.$el); - }, +function focus() { + el.value.focus(); +} - focusAfter() { - focusNext(this.$el); - }, +function blur() { + el.value.blur(); +} - userPage - } +os.api('notes/children', { + noteId: appearNote.id, + limit: 30 +}).then(res => { + replies.value = res; }); + +if (appearNote.replyId) { + os.api('notes/conversation', { + noteId: appearNote.replyId + }).then(res => { + conversation.value = res.reverse(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-header.vue b/packages/client/src/components/note-header.vue index 26e725c6b8..56a3a37e75 100644 --- a/packages/client/src/components/note-header.vue +++ b/packages/client/src/components/note-header.vue @@ -19,30 +19,16 @@ </header> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; -import * as os from '@/os'; -export default defineComponent({ - props: { - note: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - notePage, - userPage - } -}); +defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-preview.vue b/packages/client/src/components/note-preview.vue index bdcb8d5eed..a78b499654 100644 --- a/packages/client/src/components/note-preview.vue +++ b/packages/client/src/components/note-preview.vue @@ -14,20 +14,12 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - components: { - }, - - props: { - text: { - type: String, - required: true - } - }, -}); +const props = defineProps<{ + text: string; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index 135f06602d..c6907787b5 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -9,40 +9,26 @@ <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> + <MkNoteSubNoteContent class="text" :note="note"/> </div> </div> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; +import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; -import * as os from '@/os'; -export default defineComponent({ - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - props: { - note: { - type: Object, - required: true - } - }, - - data() { - return { - showContent: false - }; - } -}); +const showContent = $ref(false); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index c4040388a9..fc89c2777b 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -2,20 +2,21 @@ <div v-if="!muted" v-show="!isDeleted" + ref="el" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" class="tkcbzcuz" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ $ts.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ $ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ $ts.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ $ts.featured }}</div> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> + <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ i18n.locale.pinnedNote }}</div> + <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.locale.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.locale.hideThisNote }} <i class="fas fa-times"></i></button></div> + <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ i18n.locale.featured }}</div> <div v-if="isRenote" class="renote"> <MkAvatar class="avatar" :user="note.user"/> <i class="fas fa-retweet"></i> - <I18n :src="$ts.renotedBy" tag="span"> + <I18n :src="i18n.locale.renotedBy" tag="span"> <template #user> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> <MkUserName :user="note.user"/> @@ -47,7 +48,7 @@ </p> <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.locale.private }})</span> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <a v-if="appearNote.renote != null" class="rp">RN:</a> @@ -66,7 +67,7 @@ <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ $ts.showMore }}</span> + <span>{{ i18n.locale.showMore }}</span> </button> </div> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> @@ -93,7 +94,7 @@ </article> </div> <div v-else class="muted" @click="muted = false"> - <I18n :src="$ts.userSaysSomething" tag="small"> + <I18n :src="i18n.locale.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> <MkUserName :user="appearNote.user"/> @@ -103,11 +104,11 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; +import * as misskey from 'misskey-js'; +import MkNoteSub from './MkNoteSub.vue'; import XNoteHeader from './note-header.vue'; import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; @@ -115,744 +116,164 @@ import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; import XPoll from './poll.vue'; import XRenoteButton from './renote-button.vue'; +import MkUrlPreview from '@/components/url-preview.vue'; +import MkInstanceTicker from '@/components/instance-ticker.vue'; import { pleaseLogin } from '@/scripts/please-login'; import { focusPrev, focusNext } from '@/scripts/focus'; -import { url } from '@/config'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; import { checkWordMute } from '@/scripts/check-word-mute'; import { userPage } from '@/filters/user'; import * as os from '@/os'; -import { noteActions, noteViewInterruptors } from '@/store'; +import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { getNoteMenu } from '@/scripts/get-note-menu'; +import { useNoteCapture } from '@/scripts/use-note-capture'; -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - XRenoteButton, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - inject: { - inChannel: { - default: null - }, - }, +const inChannel = inject('inChannel', null); - props: { - note: { - type: Object, - required: true - }, - pinned: { - type: Boolean, - required: false, - default: false - }, - }, +const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null +); - emits: ['update:note'], +const el = ref<HTMLElement>(); +const menuButton = ref<HTMLElement>(); +const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); +const renoteTime = ref<HTMLElement>(); +const reactButton = ref<HTMLElement>(); +let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note); +const isMyRenote = $i && ($i.id === props.note.userId); +const showContent = ref(false); +const collapsed = ref(appearNote.cw == null && appearNote.text != null && ( + (appearNote.text.split('\n').length > 9) || + (appearNote.text.length > 500) +)); +const isDeleted = ref(false); +const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); +const translation = ref(null); +const translating = ref(false); +const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; +const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); - data() { - return { - connection: null, - replies: [], - showContent: false, - collapsed: false, - isDeleted: false, - muted: false, - translation: null, - translating: false, - }; - }, +const keymap = { + 'r': () => reply(true), + 'e|a|plus': () => react(true), + 'q': () => renoteButton.value.renote(true), + 'up|k|shift+tab': focusBefore, + 'down|j|tab': focusAfter, + 'esc': blur, + 'm|o': () => menu(true), + 's': () => showContent.value != showContent.value, +}; - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.$refs.renoteButton.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = os.stream; - } - - this.collapsed = this.appearNote.cw == null && this.appearNote.text && ( - (this.appearNote.text.split('\n').length > 9) || - (this.appearNote.text.length > 500) - ); - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - }, +useNoteCapture({ + appearNote: $$(appearNote), + rootEl: el, +}); - mounted() { - this.capture(true); +function reply(viaKeyboard = false): void { + pleaseLogin(); + os.post({ + reply: appearNote, + animation: !viaKeyboard, + }, () => { + focus(); + }); +} - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, +function react(viaKeyboard = false): void { + pleaseLogin(); + blur(); + reactionPicker.show(reactButton.value, reaction => { + os.api('notes/reactions/create', { + noteId: appearNote.id, + reaction: reaction + }); + }, () => { + focus(); + }); +} - beforeUnmount() { - this.decapture(true); +function undoReact(note): void { + const oldReaction = note.myReaction; + if (!oldReaction) return; + os.api('notes/reactions/delete', { + noteId: note.id + }); +} - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); +function onContextmenu(ev: MouseEvent): void { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: ã“ã®ãƒŽãƒ¼ãƒˆãŒã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°çµŒç”±ã§æµã‚Œã¦ããŸå ´åˆã®ã¿ sr ã™ã‚‹ - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, + }; + if (isLink(ev.target)) return; + if (window.getSelection().toString() !== '') return; - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.focus(); - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(); - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - toggleThreadMute(mute: boolean) { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - this.$instance.translatorAvailable ? { - icon: 'fas fa-language', - text: this.$ts.translate, - action: this.translate - } : undefined, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - statePromise.then(state => state.isMutedThread ? { - icon: 'fas fa-comment-slash', - text: this.$ts.unmuteThread, - action: () => this.toggleThreadMute(false) - } : { - icon: 'fas fa-comment-slash', - text: this.$ts.muteThread, - action: () => this.toggleThreadMute(true) - }), - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /* - ...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(this.focus); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, + if (defaultStore.state.useReactionPickerForContextMenu) { + ev.preventDefault(); + react(); + } else { + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus); + } +} - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, +function menu(viaKeyboard = false): void { + os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, { + viaKeyboard + }).then(focus); +} - async translate() { - if (this.translation != null) return; - this.translating = true; - const res = await os.api('notes/translate', { - noteId: this.appearNote.id, - targetLang: localStorage.getItem('lang') || navigator.language, +function showRenoteMenu(viaKeyboard = false): void { + if (!isMyRenote) return; + os.popupMenu([{ + text: i18n.locale.unrenote, + icon: 'fas fa-trash-alt', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: props.note.id }); - this.translating = false; - this.translation = res; - }, + isDeleted.value = true; + } + }], renoteTime.value, { + viaKeyboard: viaKeyboard + }); +} - focus() { - this.$el.focus(); - }, +function focus() { + el.value.focus(); +} - blur() { - this.$el.blur(); - }, +function blur() { + el.value.blur(); +} - focusBefore() { - focusPrev(this.$el); - }, +function focusBefore() { + focusPrev(el.value); +} - focusAfter() { - focusNext(this.$el); - }, +function focusAfter() { + focusNext(el.value); +} - userPage - } -}); +function readPromo() { + os.api('promo/read', { + noteId: appearNote.id + }); + isDeleted.value = true; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue index 4136f72b1b..41bec5a579 100644 --- a/packages/client/src/components/notes.vue +++ b/packages/client/src/components/notes.vue @@ -1,114 +1,42 @@ <template> -<transition name="fade" mode="out-in"> - <MkLoading v-if="fetching"/> - - <MkError v-else-if="error" @retry="init()"/> - - <div v-else-if="empty" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noNotes }}</div> - </div> - - <div v-else class="giivymft" :class="{ noGap }"> - <div v-show="more && reversed" style="margin-bottom: var(--margin);"> - <MkButton style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMoreFeature"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotes }}</div> </div> + </template> - <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true" class="notes"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note" @update:note="updated(note, $event)"/> - </XList> - - <div v-show="more && !reversed" style="margin-top: var(--margin);"> - <MkButton v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> + <template #default="{ items: notes }"> + <div class="giivymft" :class="{ noGap }"> + <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes"> + <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/> + </XList> </div> - </div> -</transition> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import XNote from './note.vue'; -import XList from './date-separated-list.vue'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNote, XList, MkButton, - }, - - mixins: [ - paging({ - before: (self) => { - self.$emit('before'); - }, - - after: (self, e) => { - self.$emit('after', e); - } - }), - ], - - props: { - pagination: { - required: true - }, - prop: { - type: String, - required: false - }, - noGap: { - type: Boolean, - required: false, - default: false - }, - }, +<script lang="ts" setup> +import { ref } from 'vue'; +import XNote from '@/components/note.vue'; +import XList from '@/components/date-separated-list.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; - emits: ['before', 'after'], +const props = defineProps<{ + pagination: Paging; + noGap?: boolean; +}>(); - computed: { - notes(): any[] { - return this.prop ? this.items.map(item => item[this.prop]) : this.items; - }, +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); - reversed(): boolean { - return this.pagination.reversed; - } - }, - - methods: { - updated(oldValue, newValue) { - const i = this.notes.findIndex(n => n === oldValue); - if (this.prop) { - this.items[i][this.prop] = newValue; - } else { - this.items[i] = newValue; - } - }, - - focus() { - this.$refs.notes.focus(); - } - } +defineExpose({ + pagingComponent, }); </script> <style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - .giivymft { &.noGap { > .notes { diff --git a/packages/client/src/components/notification-toast.vue b/packages/client/src/components/notification-toast.vue index 5449409ccc..b2ab1029ad 100644 --- a/packages/client/src/components/notification-toast.vue +++ b/packages/client/src/components/notification-toast.vue @@ -1,6 +1,6 @@ <template> <div class="mk-notification-toast" :style="{ zIndex }"> - <transition name="notification-toast" appear @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'notification-toast' : ''" appear @after-leave="$emit('closed')"> <XNotification v-if="showing" :notification="notification" class="notification _acrylic"/> </transition> </div> @@ -29,7 +29,7 @@ export default defineComponent({ }; }, mounted() { - setTimeout(() => { + window.setTimeout(() => { this.showing = false; }, 6000); } diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index 37a88edc64..5659c899be 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -74,6 +74,7 @@ import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; import * as os from '@/os'; +import { stream } from '@/stream'; import { useTooltip } from '@/scripts/use-tooltip'; export default defineComponent({ @@ -106,7 +107,7 @@ export default defineComponent({ if (!props.notification.isRead) { const readObserver = new IntersectionObserver((entries, observer) => { if (!entries.some(entry => entry.isIntersecting)) return; - os.stream.send('readNotification', { + stream.send('readNotification', { id: props.notification.id }); observer.disconnect(); @@ -114,7 +115,7 @@ export default defineComponent({ readObserver.observe(elRef.value); - const connection = os.stream.useChannel('main'); + const connection = stream.useChannel('main'); connection.on('readAllNotifications', () => readObserver.disconnect()); onUnmounted(() => { diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index f3e5ee32f7..5a77b5487e 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -1,158 +1,77 @@ <template> -<transition name="fade" mode="out-in"> - <MkLoading v-if="fetching"/> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotifications }}</div> + </div> + </template> - <MkError v-else-if="error" @retry="init()"/> - - <p v-else-if="empty" class="mfcuwfyp">{{ $ts.noNotifications }}</p> - - <div v-else> - <XList v-slot="{ item: notification }" class="elsfgstc" :items="items" :no-gap="true"> - <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" @update:note="noteUpdated(notification.note, $event)"/> + <template #default="{ items: notifications }"> + <XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true"> + <XNote 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"/> </XList> - - <MkButton v-show="more" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" primary style="margin: var(--margin) auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</transition> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent, PropType, markRaw } from 'vue'; -import paging from '@/scripts/paging'; -import XNotification from './notification.vue'; -import XList from './date-separated-list.vue'; -import XNote from './note.vue'; +<script lang="ts" setup> +import { defineComponent, PropType, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; +import XNotification from '@/components/notification.vue'; +import XList from '@/components/date-separated-list.vue'; +import XNote from '@/components/note.vue'; import * as os from '@/os'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNotification, - XList, - XNote, - MkButton, - }, - - mixins: [ - paging({}), - ], - - props: { - includeTypes: { - type: Array as PropType<typeof notificationTypes[number][]>, - required: false, - default: null, - }, - unreadOnly: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - connection: null, - pagination: { - endpoint: 'i/notifications', - limit: 10, - params: () => ({ - includeTypes: this.allIncludeTypes || undefined, - unreadOnly: this.unreadOnly, - }) - }, - }; - }, - - computed: { - allIncludeTypes() { - return this.includeTypes ?? notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x)); - } - }, +import { stream } from '@/stream'; +import { $i } from '@/account'; - watch: { - includeTypes: { - handler() { - this.reload(); - }, - deep: true - }, - unreadOnly: { - handler() { - this.reload(); - }, - }, - // TODO: vue/vuexã®ãƒã‚°ã‹ä»•様ã‹ã¯ä¸æ˜Žãªã‚‚ã®ã®ã€ãƒ—ãƒãƒ•ィール更新ã™ã‚‹ãªã©ã—㦠$i ãŒæ›´æ–°ã•れるã¨ã€ - // mutingNotificationTypes ã«å¤‰åŒ–ãŒç„¡ãã¦ã‚‚ã“ã®ãƒãƒ³ãƒ‰ãƒ©ãƒ¼ãŒå‘¼ã³å‡ºã•れ無駄ãªãƒªãƒãƒ¼ãƒ‰ãŒç™ºç”Ÿã™ã‚‹ã®ã‚’ç›´ã™ - '$i.mutingNotificationTypes': { - handler() { - if (this.includeTypes === null) { - this.reload(); - } - }, - deep: true - } - }, +const props = defineProps<{ + includeTypes?: PropType<typeof notificationTypes[number][]>; + unreadOnly?: boolean; +}>(); - mounted() { - this.connection = markRaw(os.stream.useChannel('main')); - this.connection.on('notification', this.onNotification); - }, +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); - beforeUnmount() { - this.connection.dispose(); - }, +const allIncludeTypes = computed(() => props.includeTypes ?? notificationTypes.filter(x => !$i.mutingNotificationTypes.includes(x))); - methods: { - onNotification(notification) { - const isMuted = !this.allIncludeTypes.includes(notification.type); - if (isMuted || document.visibilityState === 'visible') { - os.stream.send('readNotification', { - id: notification.id - }); - } +const pagination: Paging = { + endpoint: 'i/notifications' as const, + limit: 10, + params: computed(() => ({ + includeTypes: allIncludeTypes.value || undefined, + unreadOnly: props.unreadOnly, + })), +}; - if (!isMuted) { - this.prepend({ - ...notification, - isRead: document.visibilityState === 'visible' - }); - } - }, +const onNotification = (notification) => { + const isMuted = !allIncludeTypes.value.includes(notification.type); + if (isMuted || document.visibilityState === 'visible') { + stream.send('readNotification', { + id: notification.id + }); + } - noteUpdated(oldValue, newValue) { - const i = this.items.findIndex(n => n.note === oldValue); - this.items[i] = { - ...this.items[i], - note: newValue - }; - }, + if (!isMuted) { + pagingComponent.value.prepend({ + ...notification, + isRead: document.visibilityState === 'visible' + }); } +}; + +onMounted(() => { + const connection = stream.useChannel('main'); + connection.on('notification', onNotification); + onUnmounted(() => { + connection.dispose(); + }); }); </script> <style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.mfcuwfyp { - margin: 0; - padding: 16px; - text-align: center; - color: var(--fg); -} - .elsfgstc { background: var(--panel); } diff --git a/packages/client/src/components/object-view.value.vue b/packages/client/src/components/object-view.value.vue new file mode 100644 index 0000000000..6f388636dd --- /dev/null +++ b/packages/client/src/components/object-view.value.vue @@ -0,0 +1,108 @@ +<template> +<div class="igpposuu _monospace"> + <div v-if="value === null" class="null">null</div> + <div v-else-if="typeof value === 'boolean'" class="boolean">{{ value ? 'true' : 'false' }}</div> + <div v-else-if="typeof value === 'string'" class="string">"{{ value }}"</div> + <div v-else-if="typeof value === 'number'" class="number">{{ number(value) }}</div> + <div v-else-if="Array.isArray(value)" class="array"> + <button @click="collapsed_ = !collapsed_">[ {{ collapsed_ ? '+' : '-' }} ]</button> + <template v-if="!collapsed_"> + <div v-for="i in value.length" class="element"> + {{ i }}: <XValue :value="value[i - 1]" collapsed/> + </div> + </template> + </div> + <div v-else-if="typeof value === 'object'" class="object"> + <button @click="collapsed_ = !collapsed_">{ {{ collapsed_ ? '+' : '-' }} }</button> + <template v-if="!collapsed_"> + <div v-for="k in Object.keys(value)" class="kv"> + <div class="k">{{ k }}:</div> + <div class="v"><XValue :value="value[k]" collapsed/></div> + </div> + </template> + </div> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent, ref } from 'vue'; +import number from '@/filters/number'; + +export default defineComponent({ + name: 'XValue', + + props: { + value: { + type: Object, + required: true, + }, + collapsed: { + type: Boolean, + required: false, + default: false, + }, + }, + + setup(props) { + const collapsed_ = ref(props.collapsed); + + return { + number, + collapsed_, + }; + } +}); +</script> + +<style lang="scss" scoped> +.igpposuu { + display: inline; + + > .null { + display: inline; + opacity: 0.7; + } + + > .boolean { + display: inline; + color: var(--codeBoolean); + } + + > .string { + display: inline; + color: var(--codeString); + } + + > .number { + display: inline; + color: var(--codeNumber); + } + + > .array { + display: inline; + + > .element { + display: block; + padding-left: 16px; + } + } + + > .object { + display: inline; + + > .kv { + display: block; + padding-left: 16px; + + > .k { + display: inline; + margin-right: 8px; + } + + > .v { + display: inline; + } + } + } +} +</style> diff --git a/packages/client/src/components/object-view.vue b/packages/client/src/components/object-view.vue new file mode 100644 index 0000000000..e9db96de8c --- /dev/null +++ b/packages/client/src/components/object-view.vue @@ -0,0 +1,33 @@ +<template> +<div class="zhyxdalp"> + <XValue :value="value" :collapsed="false"/> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent } from 'vue'; +import XValue from './object-view.value.vue'; + +export default defineComponent({ + components: { + XValue + }, + + props: { + value: { + type: Object, + required: true, + }, + }, + + setup(props) { + + } +}); +</script> + +<style lang="scss" scoped> +.zhyxdalp { + +} +</style> diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue index fad0cf1593..6f3f23a2d3 100644 --- a/packages/client/src/components/poll-editor.vue +++ b/packages/client/src/components/poll-editor.vue @@ -3,7 +3,7 @@ <p v-if="choices.length < 2" class="caution"> <i class="fas fa-exclamation-triangle"></i>{{ $ts._poll.noOnlyOneChoice }} </p> - <ul ref="choices"> + <ul> <li v-for="(choice, i) in choices" :key="i"> <MkInput class="input" :model-value="choice" :placeholder="$t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)"> </MkInput> @@ -14,8 +14,8 @@ </ul> <MkButton v-if="choices.length < 10" class="add" @click="add">{{ $ts.add }}</MkButton> <MkButton v-else class="add" disabled>{{ $ts._poll.noMore }}</MkButton> + <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <section> - <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <div> <MkSelect v-model="expiration"> <template #label>{{ $ts._poll.expiration }}</template> @@ -31,7 +31,7 @@ <template #label>{{ $ts._poll.deadlineTime }}</template> </MkInput> </section> - <section v-if="expiration === 'after'"> + <section v-else-if="expiration === 'after'"> <MkInput v-model="after" type="number" class="input"> <template #label>{{ $ts._poll.duration }}</template> </MkInput> @@ -47,8 +47,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, watch } from 'vue'; import { addTime } from '@/scripts/time'; import { formatDateTimeString } from '@/scripts/format-time-string'; import MkInput from './form/input.vue'; @@ -56,131 +56,91 @@ import MkSelect from './form/select.vue'; import MkSwitch from './form/switch.vue'; import MkButton from './ui/button.vue'; -export default defineComponent({ - components: { - MkInput, - MkSelect, - MkSwitch, - MkButton, - }, +const props = defineProps<{ + modelValue: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }; +}>(); +const emit = defineEmits<{ + (ev: 'update:modelValue', v: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }): void; +}>(); - props: { - poll: { - type: Object, - required: true - } - }, - - emits: ['updated'], - - data() { - return { - choices: this.poll.choices, - multiple: this.poll.multiple, - expiration: 'infinite', - atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'), - atTime: '00:00', - after: 0, - unit: 'second', - }; - }, +const choices = ref(props.modelValue.choices); +const multiple = ref(props.modelValue.multiple); +const expiration = ref('infinite'); +const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd')); +const atTime = ref('00:00'); +const after = ref(0); +const unit = ref('second'); - watch: { - choices: { - handler() { - this.$emit('updated', this.get()); - }, - deep: true - }, - multiple: { - handler() { - this.$emit('updated', this.get()); - }, - }, - expiration: { - handler() { - this.$emit('updated', this.get()); - }, - }, - atDate: { - handler() { - this.$emit('updated', this.get()); - }, - }, - after: { - handler() { - this.$emit('updated', this.get()); - }, - }, - unit: { - handler() { - this.$emit('updated', this.get()); - }, - }, - }, +if (props.modelValue.expiresAt) { + expiration.value = 'at'; + atDate.value = atTime.value = props.modelValue.expiresAt; +} else if (typeof props.modelValue.expiredAfter === 'number') { + expiration.value = 'after'; + after.value = props.modelValue.expiredAfter / 1000; +} else { + expiration.value = 'infinite'; +} - created() { - const poll = this.poll; - if (poll.expiresAt) { - this.expiration = 'at'; - this.atDate = this.atTime = poll.expiresAt; - } else if (typeof poll.expiredAfter === 'number') { - this.expiration = 'after'; - this.after = poll.expiredAfter / 1000; - } else { - this.expiration = 'infinite'; - } - }, +function onInput(i, value) { + choices.value[i] = value; +} - methods: { - onInput(i, e) { - this.choices[i] = e; - }, +function add() { + choices.value.push(''); + // TODO + // nextTick(() => { + // (this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); + // }); +} - add() { - this.choices.push(''); - this.$nextTick(() => { - // TODO - //(this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); - }); - }, +function remove(i) { + choices.value = choices.value.filter((_, _i) => _i != i); +} - remove(i) { - this.choices = this.choices.filter((_, _i) => _i != i); - }, +function get() { + const calcAt = () => { + return new Date(`${atDate.value} ${atTime.value}`).getTime(); + }; - get() { - const at = () => { - return new Date(`${this.atDate} ${this.atTime}`).getTime(); - }; + const calcAfter = () => { + let base = parseInt(after.value); + switch (unit.value) { + case 'day': base *= 24; + case 'hour': base *= 60; + case 'minute': base *= 60; + case 'second': return base *= 1000; + default: return null; + } + }; - const after = () => { - let base = parseInt(this.after); - switch (this.unit) { - case 'day': base *= 24; - case 'hour': base *= 60; - case 'minute': base *= 60; - case 'second': return base *= 1000; - default: return null; - } - }; + return { + choices: choices.value, + multiple: multiple.value, + ...( + expiration.value === 'at' ? { expiresAt: calcAt() } : + expiration.value === 'after' ? { expiredAfter: calcAfter() } : {} + ) + }; +} - return { - choices: this.choices, - multiple: this.multiple, - ...( - this.expiration === 'at' ? { expiresAt: at() } : - this.expiration === 'after' ? { expiredAfter: after() } : {} - ) - }; - }, - } +watch([choices, multiple, expiration, atDate, atTime, after, unit], () => emit('update:modelValue', get()), { + deep: true, }); </script> <style lang="scss" scoped> .zmdxowus { - padding: 8px; + padding: 8px 16px; > .caution { margin: 0 0 8px 0; @@ -216,7 +176,7 @@ export default defineComponent({ } > .add { - margin: 8px 0 0 0; + margin: 8px 0; z-index: 1; } @@ -225,21 +185,27 @@ export default defineComponent({ > div { margin: 0 8px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; &:last-child { flex: 1 0 auto; + > div { + flex-grow: 1; + } + > section { - align-items: center; + // MAGIC: Prevent div above from growing unless wrapped to its own line + flex-grow: 9999; + align-items: end; display: flex; - margin: -32px 0 0; - - > &:first-child { - margin-right: 16px; - } + gap: 4px; > .input { - flex: 1 0 auto; + flex: 1 1 auto; } } } diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 0782ce22e5..0c8181b481 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -10,7 +10,7 @@ </div> </template> </XDraggable> - <p class="remain">{{ 4 - files.length }}/4</p> + <p class="remain">{{ 16 - files.length }}/16</p> </div> </template> @@ -41,7 +41,6 @@ export default defineComponent({ data() { return { menu: null as Promise<null> | null, - }; }, @@ -99,10 +98,12 @@ export default defineComponent({ }, { done: result => { if (!result || result.canceled) return; - let comment = result.result; + let comment = result.result.length == 0 ? null : result.result; os.api('drive/files/update', { fileId: file.id, - comment: comment.length == 0 ? null : comment + comment: comment, + }).then(() => { + file.comment = comment; }); } }, 'closed'); diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 4265c575e2..ed78c5a3fb 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -8,25 +8,28 @@ > <header> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> + <button v-click-anime v-tooltip="i18n.locale.switchAccount" class="account _button" @click="openAccountMenu"> + <MkAvatar :user="postAccount ?? $i" class="avatar"/> + </button> <div> - <span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span> + <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> + <button ref="visibilityButton" v-tooltip="i18n.locale.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> </button> - <button v-tooltip="$ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> + <button v-tooltip="i18n.locale.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> <button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> </div> </header> <div class="form" :class="{ fixed }"> <XNoteSimple v-if="reply" class="preview" :note="reply"/> <XNoteSimple v-if="renote" class="preview" :note="renote"/> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> + <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ i18n.locale.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ $ts.recipient }}</span> + <span style="margin-right: 8px;">{{ i18n.locale.recipient }}</span> <div class="visibleUsers"> <span v-for="u in visibleUsers" :key="u.id"> <MkAcct :user="u"/> @@ -35,21 +38,21 @@ <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> </div> </div> - <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> - <input v-show="useCw" ref="cw" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> - <textarea ref="text" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <input v-show="withHashtags" ref="hashtags" v-model="hashtags" class="hashtags" :placeholder="$ts.hashtags" list="hashtags"> + <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.locale.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.locale.add }}</button></MkInfo> + <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.locale.annotation" @keydown="onKeydown"> + <textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> + <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.locale.hashtags" list="hashtags"> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> - <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> + <XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/> <footer> - <button v-tooltip="$ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="$ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="$ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="$ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="$ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> - <button v-tooltip="$ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="$ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> + <button v-tooltip="i18n.locale.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> + <button v-tooltip="i18n.locale.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> + <button v-tooltip="i18n.locale.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> + <button v-tooltip="i18n.locale.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> + <button v-tooltip="i18n.locale.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> + <button v-tooltip="i18n.locale.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> + <button v-if="postFormActions.length > 0" v-tooltip="i18n.locale.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> </footer> <datalist id="hashtags"> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> @@ -58,667 +61,623 @@ </div> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { inject, watch, nextTick, onMounted } from 'vue'; +import * as mfm from 'mfm-js'; +import * as misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { length } from 'stringz'; import { toASCII } from 'punycode/'; import XNoteSimple from './note-simple.vue'; import XNotePreview from './note-preview.vue'; -import * as mfm from 'mfm-js'; +import XPostFormAttaches from './post-form-attaches.vue'; +import XPollEditor from './poll-editor.vue'; import { host, url } from '@/config'; import { erase, unique } from '@/scripts/array'; import { extractMentions } from '@/scripts/extract-mentions'; import * as Acct from 'misskey-js/built/acct'; import { formatTimeString } from '@/scripts/format-time-string'; import { Autocomplete } from '@/scripts/autocomplete'; -import { noteVisibilities } from 'misskey-js'; import * as os from '@/os'; +import { stream } from '@/stream'; import { selectFiles } from '@/scripts/select-file'; import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; import { throttle } from 'throttle-debounce'; import MkInfo from '@/components/ui/info.vue'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; -export default defineComponent({ - components: { - XNoteSimple, - XNotePreview, - XPostFormAttaches: defineAsyncComponent(() => import('./post-form-attaches.vue')), - XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue')), - MkInfo, - }, +const modal = inject('modal'); - inject: ['modal'], +const props = withDefaults(defineProps<{ + reply?: misskey.entities.Note; + renote?: misskey.entities.Note; + channel?: any; // TODO + mention?: misskey.entities.User; + specified?: misskey.entities.User; + initialText?: string; + initialVisibility?: typeof misskey.noteVisibilities; + initialFiles?: misskey.entities.DriveFile[]; + initialLocalOnly?: boolean; + initialVisibleUsers?: misskey.entities.User[]; + initialNote?: misskey.entities.Note; + share?: boolean; + fixed?: boolean; + autofocus?: boolean; +}>(), { + initialVisibleUsers: [], + autofocus: true, +}); - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - channel: { - type: Object, - required: false - }, - mention: { - type: Object, - required: false - }, - specified: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialVisibility: { - type: String, - required: false - }, - initialFiles: { - type: Array, - required: false - }, - initialLocalOnly: { - type: Boolean, - required: false - }, - initialVisibleUsers: { - type: Array, - required: false, - default: () => [] - }, - initialNote: { - type: Object, - required: false - }, - share: { - type: Boolean, - required: false, - default: false - }, - fixed: { - type: Boolean, - required: false, - default: false - }, - autofocus: { - type: Boolean, - required: false, - default: true - }, - }, +const emit = defineEmits<{ + (ev: 'posted'): void; + (ev: 'cancel'): void; + (ev: 'esc'): void; +}>(); - emits: ['posted', 'cancel', 'esc'], +const textareaEl = $ref<HTMLTextAreaElement | null>(null); +const cwInputEl = $ref<HTMLInputElement | null>(null); +const hashtagsInputEl = $ref<HTMLInputElement | null>(null); +const visibilityButton = $ref<HTMLElement | null>(null); - data() { - return { - posting: false, - text: '', - files: [], - poll: null, - useCw: false, - showPreview: false, - cw: null, - localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, - visibility: (this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility) as typeof noteVisibilities[number], - visibleUsers: [], - autocomplete: null, - draghover: false, - quoteId: null, - hasNotSpecifiedMentions: false, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - imeText: '', - typing: throttle(3000, () => { - if (this.channel) { - os.stream.send('typingOnChannel', { channel: this.channel.id }); - } - }), - postFormActions, - }; - }, +let posting = $ref(false); +let text = $ref(props.initialText ?? ''); +let files = $ref(props.initialFiles ?? []); +let poll = $ref<{ + choices: string[]; + multiple: boolean; + expiresAt: string | null; + expiredAfter: string | null; +} | null>(null); +let useCw = $ref(false); +let showPreview = $ref(false); +let cw = $ref<string | null>(null); +let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); +let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]); +let visibleUsers = $ref(props.initialVisibleUsers ?? []); +let autocomplete = $ref(null); +let draghover = $ref(false); +let quoteId = $ref(null); +let hasNotSpecifiedMentions = $ref(false); +let recentHashtags = $ref(JSON.parse(localStorage.getItem('hashtags') || '[]')); +let imeText = $ref(''); - computed: { - draftKey(): string { - let key = this.channel ? `channel:${this.channel.id}` : ''; +const typing = throttle(3000, () => { + if (props.channel) { + stream.send('typingOnChannel', { channel: props.channel.id }); + } +}); - if (this.renote) { - key += `renote:${this.renote.id}`; - } else if (this.reply) { - key += `reply:${this.reply.id}`; - } else { - key += 'note'; - } +const draftKey = $computed((): string => { + let key = props.channel ? `channel:${props.channel.id}` : ''; - return key; - }, + if (props.renote) { + key += `renote:${props.renote.id}`; + } else if (props.reply) { + key += `reply:${props.reply.id}`; + } else { + key += 'note'; + } - placeholder(): string { - if (this.renote) { - return this.$ts._postForm.quotePlaceholder; - } else if (this.reply) { - return this.$ts._postForm.replyPlaceholder; - } else if (this.channel) { - return this.$ts._postForm.channelPlaceholder; - } else { - const xs = [ - this.$ts._postForm._placeholders.a, - this.$ts._postForm._placeholders.b, - this.$ts._postForm._placeholders.c, - this.$ts._postForm._placeholders.d, - this.$ts._postForm._placeholders.e, - this.$ts._postForm._placeholders.f - ]; - return xs[Math.floor(Math.random() * xs.length)]; - } - }, + return key; +}); - submitText(): string { - return this.renote - ? this.$ts.quote - : this.reply - ? this.$ts.reply - : this.$ts.note; - }, +const placeholder = $computed((): string => { + if (props.renote) { + return i18n.locale._postForm.quotePlaceholder; + } else if (props.reply) { + return i18n.locale._postForm.replyPlaceholder; + } else if (props.channel) { + return i18n.locale._postForm.channelPlaceholder; + } else { + const xs = [ + i18n.locale._postForm._placeholders.a, + i18n.locale._postForm._placeholders.b, + i18n.locale._postForm._placeholders.c, + i18n.locale._postForm._placeholders.d, + i18n.locale._postForm._placeholders.e, + i18n.locale._postForm._placeholders.f + ]; + return xs[Math.floor(Math.random() * xs.length)]; + } +}); - textLength(): number { - return length((this.text + this.imeText).trim()); - }, +const submitText = $computed((): string => { + return props.renote + ? i18n.locale.quote + : props.reply + ? i18n.locale.reply + : i18n.locale.note; +}); - canPost(): boolean { - return !this.posting && - (1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) && - (this.textLength <= this.max) && - (!this.poll || this.poll.choices.length >= 2); - }, +const textLength = $computed((): number => { + return length((text + imeText).trim()); +}); - max(): number { - return this.$instance ? this.$instance.maxNoteTextLength : 1000; - }, +const maxTextLength = $computed((): number => { + return instance ? instance.maxNoteTextLength : 1000; +}); - withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'), - hashtags: defaultStore.makeGetterSetter('postFormHashtags'), - }, +const canPost = $computed((): boolean => { + return !posting && + (1 <= textLength || 1 <= files.length || !!poll || !!props.renote) && + (textLength <= maxTextLength) && + (!poll || poll.choices.length >= 2); +}); - watch: { - text() { - this.checkMissingMention(); - }, - visibleUsers: { - handler() { - this.checkMissingMention(); - }, - deep: true - } - }, +const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags')); +const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags')); - mounted() { - if (this.initialText) { - this.text = this.initialText; - } +watch($$(text), () => { + checkMissingMention(); +}); - if (this.initialVisibility) { - this.visibility = this.initialVisibility; - } +watch($$(visibleUsers), () => { + checkMissingMention(); +}, { + deep: true, +}); - if (this.initialFiles) { - this.files = this.initialFiles; - } +if (props.mention) { + text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`; + text += ' '; +} - if (typeof this.initialLocalOnly === 'boolean') { - this.localOnly = this.initialLocalOnly; - } +if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) { + text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; +} - if (this.initialVisibleUsers) { - this.visibleUsers = this.initialVisibleUsers; - } +if (props.reply && props.reply.text != null) { + const ast = mfm.parse(props.reply.text); + const otherHost = props.reply.user.host; - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } + for (const x of extractMentions(ast)) { + const mention = x.host ? + `@${x.username}@${toASCII(x.host)}` : + (otherHost == null || otherHost == host) ? + `@${x.username}` : + `@${x.username}@${toASCII(otherHost)}`; - if (this.reply && (this.reply.user.username != this.$i.username || (this.reply.user.host != null && this.reply.user.host != host))) { - this.text = `@${this.reply.user.username}${this.reply.user.host != null ? '@' + toASCII(this.reply.user.host) : ''} `; - } + // 自分ã¯é™¤å¤– + if ($i.username == x.username && x.host == null) continue; + if ($i.username == x.username && x.host == host) continue; - if (this.reply && this.reply.text != null) { - const ast = mfm.parse(this.reply.text); - const otherHost = this.reply.user.host; + // é‡è¤‡ã¯é™¤å¤– + if (text.indexOf(`${mention} `) != -1) continue; - for (const x of extractMentions(ast)) { - const mention = x.host ? - `@${x.username}@${toASCII(x.host)}` : - (otherHost == null || otherHost == host) ? - `@${x.username}` : - `@${x.username}@${toASCII(otherHost)}`; + text += `${mention} `; + } +} - // 自分ã¯é™¤å¤– - if (this.$i.username == x.username && x.host == null) continue; - if (this.$i.username == x.username && x.host == host) continue; +if (props.channel) { + visibility = 'public'; + localOnly = true; // TODO: ãƒãƒ£ãƒ³ãƒãƒ«ãŒé€£åˆã™ã‚‹ã‚ˆã†ã«ãªã£ãŸæŠ˜ã«ã¯æ¶ˆã™ +} - // é‡è¤‡ã¯é™¤å¤– - if (this.text.indexOf(`${mention} `) != -1) continue; +// 公開以外ã¸ã®ãƒªãƒ—ライ時ã¯å…ƒã®å…¬é–‹ç¯„囲を引ãç¶™ã +if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) { + visibility = props.reply.visibility; + if (props.reply.visibility === 'specified') { + os.api('users/show', { + userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId) + }).then(users => { + visibleUsers.push(...users); + }); - this.text += `${mention} `; - } + if (props.reply.userId !== $i.id) { + os.api('users/show', { userId: props.reply.userId }).then(user => { + visibleUsers.push(user); + }); } + } +} - if (this.channel) { - this.visibility = 'public'; - this.localOnly = true; // TODO: ãƒãƒ£ãƒ³ãƒãƒ«ãŒé€£åˆã™ã‚‹ã‚ˆã†ã«ãªã£ãŸæŠ˜ã«ã¯æ¶ˆã™ - } +if (props.specified) { + visibility = 'specified'; + visibleUsers.push(props.specified); +} - // 公開以外ã¸ã®ãƒªãƒ—ライ時ã¯å…ƒã®å…¬é–‹ç¯„囲を引ãç¶™ã - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - os.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); +// keep cw when reply +if (defaultStore.state.keepCw && props.reply && props.reply.cw) { + useCw = true; + cw = props.reply.cw; +} - if (this.reply.userId !== this.$i.id) { - os.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } +function watchForDraft() { + watch($$(text), () => saveDraft()); + watch($$(useCw), () => saveDraft()); + watch($$(cw), () => saveDraft()); + watch($$(poll), () => saveDraft()); + watch($$(files), () => saveDraft(), { deep: true }); + watch($$(visibility), () => saveDraft()); + watch($$(localOnly), () => saveDraft()); +} - if (this.specified) { - this.visibility = 'specified'; - this.visibleUsers.push(this.specified); - } +function checkMissingMention() { + if (visibility === 'specified') { + const ast = mfm.parse(text); - // keep cw when reply - if (this.$store.state.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; + for (const x of extractMentions(ast)) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + hasNotSpecifiedMentions = true; + return; + } } + hasNotSpecifiedMentions = false; + } +} - if (this.autofocus) { - this.focus(); +function addMissingMention() { + const ast = mfm.parse(text); - this.$nextTick(() => { - this.focus(); + for (const x of extractMentions(ast)) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + os.api('users/show', { username: x.username, host: x.host }).then(user => { + visibleUsers.push(user); }); } + } +} - // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); - new Autocomplete(this.$refs.cw, this, { model: 'cw' }); - new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' }); +function togglePoll() { + if (poll) { + poll = null; + } else { + poll = { + choices: ['', ''], + multiple: false, + expiresAt: null, + expiredAfter: null, + }; + } +} - this.$nextTick(() => { - // 書ãã‹ã‘ã®æŠ•ç¨¿ã‚’å¾©å…ƒ - if (!this.share && !this.mention && !this.specified) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey]; - if (draft) { - this.text = draft.data.text; - this.useCw = draft.data.useCw; - this.cw = draft.data.cw; - this.visibility = draft.data.visibility; - this.localOnly = draft.data.localOnly; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = draft.data.poll; - } - } - } +function addTag(tag: string) { + insertTextAtCursor(textareaEl, ` #${tag} `); +} - // 削除ã—ã¦ç·¨é›† - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = { - choices: init.poll.choices.map(x => x.text), - multiple: init.poll.multiple, - expiresAt: init.poll.expiresAt, - expiredAfter: init.poll.expiredAfter, - }; - } - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } +function focus() { + textareaEl.focus(); +} - this.$nextTick(() => this.watch()); - }); - }, +function chooseFileFrom(ev) { + selectFiles(ev.currentTarget || ev.target, i18n.locale.attachFile).then(files_ => { + for (const file of files_) { + files.push(file); + } + }); +} - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('useCw', () => this.saveDraft()); - this.$watch('cw', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft(), { deep: true }); - this.$watch('visibility', () => this.saveDraft()); - this.$watch('localOnly', () => this.saveDraft()); - }, +function detachFile(id) { + files = files.filter(x => x.id != id); +} - checkMissingMention() { - if (this.visibility === 'specified') { - const ast = mfm.parse(this.text); +function updateFiles(_files) { + files = _files; +} - for (const x of extractMentions(ast)) { - if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { - this.hasNotSpecifiedMentions = true; - return; - } - } - this.hasNotSpecifiedMentions = false; - } - }, +function updateFileSensitive(file, sensitive) { + files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive; +} - addMissingMention() { - const ast = mfm.parse(this.text); +function updateFileName(file, name) { + files[files.findIndex(x => x.id === file.id)].name = name; +} - for (const x of extractMentions(ast)) { - if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { - os.api('users/show', { username: x.username, host: x.host }).then(user => { - this.visibleUsers.push(user); - }); - } - } - }, +function upload(file: File, name?: string) { + os.upload(file, defaultStore.state.uploadFolder, name).then(res => { + files.push(res); + }); +} - togglePoll() { - if (this.poll) { - this.poll = null; - } else { - this.poll = { - choices: ['', ''], - multiple: false, - expiresAt: null, - expiredAfter: null, - }; +function setVisibility() { + if (props.channel) { + // TODO: information dialog + return; + } + + os.popup(import('./visibility-picker.vue'), { + currentVisibility: visibility, + currentLocalOnly: localOnly, + src: visibilityButton, + }, { + changeVisibility: v => { + visibility = v; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('visibility', visibility); } }, + changeLocalOnly: v => { + localOnly = v; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('localOnly', localOnly); + } + } + }, 'closed'); +} - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, +function addVisibleUser() { + os.selectUser().then(user => { + visibleUsers.push(user); + }); +} - focus() { - (this.$refs.text as any).focus(); - }, +function removeVisibleUser(user) { + visibleUsers = erase(user, visibleUsers); +} - chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, this.$ts.attachFile).then(files => { - for (const file of files) { - this.files.push(file); - } - }); - }, +function clear() { + text = ''; + files = []; + poll = null; + quoteId = null; +} - detachFile(id) { - this.files = this.files.filter(x => x.id != id); - }, +function onKeydown(e: KeyboardEvent) { + if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post(); + if (e.which === 27) emit('esc'); + typing(); +} - updateFiles(files) { - this.files = files; - }, +function onCompositionUpdate(e: CompositionEvent) { + imeText = e.data; + typing(); +} - updateFileSensitive(file, sensitive) { - this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; - }, +function onCompositionEnd(e: CompositionEvent) { + imeText = ''; +} - updateFileName(file, name) { - this.files[this.files.findIndex(x => x.id === file.id)].name = name; - }, +async function onPaste(e: ClipboardEvent) { + for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { + if (item.kind == 'file') { + const file = item.getAsFile(); + const lio = file.name.lastIndexOf('.'); + const ext = lio >= 0 ? file.name.slice(lio) : ''; + const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; + upload(file, formatted); + } + } - upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { - this.files.push(res); - }); - }, + const paste = e.clipboardData.getData('text'); - onPollUpdate(poll) { - this.poll = poll; - this.saveDraft(); - }, + if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) { + e.preventDefault(); - setVisibility() { - if (this.channel) { - // TODO: information dialog + os.confirm({ + type: 'info', + text: i18n.locale.quoteQuestion, + }).then(({ canceled }) => { + if (canceled) { + insertTextAtCursor(textareaEl, paste); return; } - os.popup(import('./visibility-picker.vue'), { - currentVisibility: this.visibility, - currentLocalOnly: this.localOnly, - src: this.$refs.visibilityButton - }, { - changeVisibility: visibility => { - this.visibility = visibility; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('visibility', visibility); - } - }, - changeLocalOnly: localOnly => { - this.localOnly = localOnly; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('localOnly', localOnly); - } - } - }, 'closed'); - }, - - addVisibleUser() { - os.selectUser().then(user => { - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, + quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; + }); + } +} - clear() { - this.text = ''; - this.files = []; - this.poll = null; - this.quoteId = null; - }, +function onDragover(e) { + if (!e.dataTransfer.items[0]) return; + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + if (isFile || isDriveFile) { + e.preventDefault(); + draghover = true; + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } +} - onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - if (e.which === 27) this.$emit('esc'); - this.typing(); - }, +function onDragenter(e) { + draghover = true; +} - onCompositionUpdate(e: CompositionEvent) { - this.imeText = e.data; - this.typing(); - }, +function onDragleave(e) { + draghover = false; +} - onCompositionEnd(e: CompositionEvent) { - this.imeText = ''; - }, +function onDrop(e): void { + draghover = false; - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - this.upload(file, formatted); - } - } + // ファイルã ã£ãŸã‚‰ + if (e.dataTransfer.files.length > 0) { + e.preventDefault(); + for (const x of Array.from(e.dataTransfer.files)) upload(x); + return; + } - const paste = e.clipboardData.getData('text'); + //#region ドライブã®ãƒ•ァイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + files.push(file); + e.preventDefault(); + } + //#endregion +} - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); +function saveDraft() { + const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - os.confirm({ - type: 'info', - text: this.$ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } + data[draftKey] = { + updatedAt: new Date(), + data: { + text: text, + useCw: useCw, + cw: cw, + visibility: visibility, + localOnly: localOnly, + files: files, + poll: poll + } + }; - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, + localStorage.setItem('drafts', JSON.stringify(data)); +} - onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, +function deleteDraft() { + const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - onDragenter(e) { - this.draghover = true; - }, + delete data[draftKey]; - onDragleave(e) { - this.draghover = false; - }, + localStorage.setItem('drafts', JSON.stringify(data)); +} - onDrop(e): void { - this.draghover = false; +async function post() { + let data = { + text: text == '' ? undefined : text, + fileIds: files.length > 0 ? files.map(f => f.id) : undefined, + replyId: props.reply ? props.reply.id : undefined, + renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined, + channelId: props.channel ? props.channel.id : undefined, + poll: poll, + cw: useCw ? cw || '' : undefined, + localOnly: localOnly, + visibility: visibility, + visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined, + }; - // ファイルã ã£ãŸã‚‰ - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } + if (withHashtags && hashtags && hashtags.trim() !== '') { + const hashtags = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); + data.text = data.text ? `${data.text} ${hashtags}` : hashtags; + } - //#region ドライブã®ãƒ•ァイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - e.preventDefault(); - } - //#endregion - }, + // plugin + if (notePostInterruptors.length > 0) { + for (const interruptor of notePostInterruptors) { + data = await interruptor.handler(JSON.parse(JSON.stringify(data))); + } + } - saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + let token = undefined; - data[this.draftKey] = { - updatedAt: new Date(), - data: { - text: this.text, - useCw: this.useCw, - cw: this.cw, - visibility: this.visibility, - localOnly: this.localOnly, - files: this.files, - poll: this.poll - } - }; + if (postAccount) { + const storedAccounts = await getAccounts(); + token = storedAccounts.find(x => x.id === postAccount.id)?.token; + } - localStorage.setItem('drafts', JSON.stringify(data)); - }, + posting = true; + os.api('notes/create', data, token).then(() => { + clear(); + nextTick(() => { + deleteDraft(); + emit('posted'); + if (data.text && data.text != '') { + const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); + const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; + localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); + } + posting = false; + postAccount = null; + }); + }).catch(err => { + posting = false; + os.alert({ + type: 'error', + text: err.message + '\n' + (err as any).id, + }); + }); +} - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); +function cancel() { + emit('cancel'); +} - delete data[this.draftKey]; +function insertMention() { + os.selectUser().then(user => { + insertTextAtCursor(textareaEl, '@' + Acct.toString(user) + ' '); + }); +} - localStorage.setItem('drafts', JSON.stringify(data)); - }, +async function insertEmoji(ev: MouseEvent) { + os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl); +} - async post() { - let data = { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - channelId: this.channel ? this.channel.id : undefined, - poll: this.poll, - cw: this.useCw ? this.cw || '' : undefined, - localOnly: this.localOnly, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - }; +function showActions(ev) { + os.popupMenu(postFormActions.map(action => ({ + text: action.title, + action: () => { + action.handler({ + text: text + }, (key, value) => { + if (key === 'text') { text = value; } + }); + } + })), ev.currentTarget || ev.target); +} - if (this.withHashtags && this.hashtags && this.hashtags.trim() !== '') { - const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - data.text = data.text ? `${data.text} ${hashtags}` : hashtags; - } +let postAccount = $ref<misskey.entities.UserDetailed | null>(null); - // plugin - if (notePostInterruptors.length > 0) { - for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); - } +function openAccountMenu(ev: MouseEvent) { + openAccountMenu_({ + withExtraOperation: false, + includeCurrentAccount: true, + active: postAccount != null ? postAccount.id : $i.id, + onChoose: (account) => { + if (account.id === $i.id) { + postAccount = null; + } else { + postAccount = account; } - - this.posting = true; - os.api('notes/create', data).then(() => { - this.clear(); - this.$nextTick(() => { - this.deleteDraft(); - this.$emit('posted'); - if (data.text && data.text != '') { - const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - this.posting = false; - }); - }).catch(err => { - this.posting = false; - os.alert({ - type: 'error', - text: err.message + '\n' + (err as any).id, - }); - }); }, + }, ev); +} - cancel() { - this.$emit('cancel'); - }, +onMounted(() => { + if (props.autofocus) { + focus(); - insertMention() { - os.selectUser().then(user => { - insertTextAtCursor(this.$refs.text, '@' + Acct.toString(user) + ' '); - }); - }, + nextTick(() => { + focus(); + }); + } - async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); - }, + // TODO: detach when unmount + new Autocomplete(textareaEl, $$(text)); + new Autocomplete(cwInputEl, $$(cw)); + new Autocomplete(hashtagsInputEl, $$(hashtags)); - showActions(ev) { - os.popupMenu(postFormActions.map(action => ({ - text: action.title, - action: () => { - action.handler({ - text: this.text - }, (key, value) => { - if (key === 'text') { this.text = value; } - }); + nextTick(() => { + // 書ãã‹ã‘ã®æŠ•ç¨¿ã‚’å¾©å…ƒ + if (!props.share && !props.mention && !props.specified) { + const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey]; + if (draft) { + text = draft.data.text; + useCw = draft.data.useCw; + cw = draft.data.cw; + visibility = draft.data.visibility; + localOnly = draft.data.localOnly; + files = (draft.data.files || []).filter(e => e); + if (draft.data.poll) { + poll = draft.data.poll; } - })), ev.currentTarget || ev.target); + } } - } + + // 削除ã—ã¦ç·¨é›† + if (props.initialNote) { + const init = props.initialNote; + text = init.text ? init.text : ''; + files = init.files; + cw = init.cw; + useCw = init.cw != null; + if (init.poll) { + poll = { + choices: init.poll.choices.map(x => x.text), + multiple: init.poll.multiple, + expiresAt: init.poll.expiresAt, + expiredAfter: init.poll.expiredAfter, + }; + } + visibility = init.visibility; + localOnly = init.localOnly; + quoteId = init.renote ? init.renote.id : null; + } + + nextTick(() => watchForDraft()); + }); }); </script> @@ -742,6 +701,19 @@ export default defineComponent({ line-height: 66px; } + > .account { + height: 100%; + aspect-ratio: 1/1; + display: inline-flex; + vertical-align: bottom; + + > .avatar { + width: 28px; + height: 28px; + margin: auto; + } + } + > div { position: absolute; top: 0; diff --git a/packages/client/src/components/reaction-icon.vue b/packages/client/src/components/reaction-icon.vue index c0ec955e32..5638c9a816 100644 --- a/packages/client/src/components/reaction-icon.vue +++ b/packages/client/src/components/reaction-icon.vue @@ -1,25 +1,13 @@ <template> -<MkEmoji :emoji="reaction" :custom-emojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/> +<MkEmoji :emoji="reaction" :custom-emojis="customEmojis || []" :is-reaction="true" :normal="true" :no-style="noStyle"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - reaction: { - type: String, - required: true - }, - customEmojis: { - required: false, - default: () => [] - }, - noStyle: { - type: Boolean, - required: false, - default: false - }, - }, -}); +const props = defineProps<{ + reaction: string; + customEmojis?: any[]; // TODO + noStyle?: boolean; +}>(); </script> diff --git a/packages/client/src/components/reaction-tooltip.vue b/packages/client/src/components/reaction-tooltip.vue index dda8e7c6d7..1b2a024e21 100644 --- a/packages/client/src/components/reaction-tooltip.vue +++ b/packages/client/src/components/reaction-tooltip.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')"> <div class="beeadbfb"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> <div class="name">{{ reaction.replace('@.', '') }}</div> @@ -7,31 +7,20 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; import XReactionIcon from './reaction-icon.vue'; -export default defineComponent({ - components: { - MkTooltip, - XReactionIcon, - }, - props: { - reaction: { - type: String, - required: true, - }, - emojis: { - type: Array, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + reaction: string; + emojis: any[]; // TODO + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/reactions-viewer.details.vue b/packages/client/src/components/reactions-viewer.details.vue index d6374517a2..8cec8dfa2f 100644 --- a/packages/client/src/components/reactions-viewer.details.vue +++ b/packages/client/src/components/reactions-viewer.details.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')"> <div class="bqxuuuey"> <div class="reaction"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> @@ -16,39 +16,22 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; import XReactionIcon from './reaction-icon.vue'; -export default defineComponent({ - components: { - MkTooltip, - XReactionIcon - }, - props: { - reaction: { - type: String, - required: true, - }, - users: { - type: Array, - required: true, - }, - count: { - type: Number, - required: true, - }, - emojis: { - type: Array, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + reaction: string; + users: any[]; // TODO + count: number; + emojis: any[]; // TODO + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/reactions-viewer.vue b/packages/client/src/components/reactions-viewer.vue index 59fcbb7129..a9bf51f65f 100644 --- a/packages/client/src/components/reactions-viewer.vue +++ b/packages/client/src/components/reactions-viewer.vue @@ -4,31 +4,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; +import { $i } from '@/account'; import XReaction from './reactions-viewer.reaction.vue'; -export default defineComponent({ - components: { - XReaction - }, - props: { - note: { - type: Object, - required: true - }, - }, - data() { - return { - initialReactions: new Set(Object.keys(this.note.reactions)) - }; - }, - computed: { - isMe(): boolean { - return this.$i && this.$i.id === this.note.userId; - }, - }, -}); +const props = defineProps<{ + note: misskey.entities.Note; +}>(); + +const initialReactions = new Set(Object.keys(props.note.reactions)); + +const isMe = computed(() => $i && $i.id === props.note.userId); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/remote-caution.vue b/packages/client/src/components/remote-caution.vue index c496ea8f48..aa623f0fb0 100644 --- a/packages/client/src/components/remote-caution.vue +++ b/packages/client/src/components/remote-caution.vue @@ -2,22 +2,10 @@ <div class="jmgmzlwq _block"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i>{{ $ts.remoteUserCaution }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $ts.showOnRemote }}</a></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({ - props: { - href: { - type: String, - required: true - }, - }, - data() { - return { - }; - } -}); +<script lang="ts" setup> +defineProps<{ + href: string; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/renote.details.vue b/packages/client/src/components/renote.details.vue index e3ef15c753..cdbc71bdce 100644 --- a/packages/client/src/components/renote.details.vue +++ b/packages/client/src/components/renote.details.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="emit('closed')"> <div class="beaffaef"> <div v-for="u in users" :key="u.id" class="user"> <MkAvatar class="avatar" :user="u"/> @@ -10,29 +10,19 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; -export default defineComponent({ - components: { - MkTooltip, - }, - props: { - users: { - type: Array, - required: true, - }, - count: { - type: Number, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + users: any[]; // TODO + count: number; + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/ripple.vue b/packages/client/src/components/ripple.vue index 272eacbc6e..401e78e304 100644 --- a/packages/client/src/components/ripple.vue +++ b/packages/client/src/components/ripple.vue @@ -94,7 +94,7 @@ export default defineComponent({ } onMounted(() => { - setTimeout(() => { + window.setTimeout(() => { context.emit('end'); }, 1100); }); diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue index 2edd10f539..5c2048e7b0 100644 --- a/packages/client/src/components/signin-dialog.vue +++ b/packages/client/src/components/signin-dialog.vue @@ -2,8 +2,8 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="$refs.dialog.close()" - @closed="$emit('closed')" + @close="dialog.close()" + @closed="emit('closed')" > <template #header>{{ $ts.login }}</template> @@ -11,32 +11,26 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkSignin from './signin.vue'; -export default defineComponent({ - components: { - MkSignin, - XModalWindow, - }, +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, +}); - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +const dialog = $ref<InstanceType<typeof XModalWindow>>(); - methods: { - onLogin(res) { - this.$emit('done', res); - this.$refs.dialog.close(); - } - } -}); +function onLogin(res) { + emit('done', res); + dialog.close(); +} </script> diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue index 30fe3bf7d3..bda2495ba7 100644 --- a/packages/client/src/components/signup-dialog.vue +++ b/packages/client/src/components/signup-dialog.vue @@ -2,7 +2,7 @@ <XModalWindow ref="dialog" :width="366" :height="500" - @close="$refs.dialog.close()" + @close="dialog.close()" @closed="$emit('closed')" > <template #header>{{ $ts.signup }}</template> @@ -15,36 +15,30 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import XSignup from './signup.vue'; -export default defineComponent({ - components: { - XSignup, - XModalWindow, - }, +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, +}); - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +const dialog = $ref<InstanceType<typeof XModalWindow>>(); - methods: { - onSignup(res) { - this.$emit('done', res); - this.$refs.dialog.close(); - }, +function onSignup(res) { + emit('done', res); + dialog.close(); +} - onSignupEmailPending() { - this.$refs.dialog.close(); - } - } -}); +function onSignupEmailPending() { + dialog.close(); +} </script> diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue index efa202ce2f..d6a37d07be 100644 --- a/packages/client/src/components/sub-note-content.vue +++ b/packages/client/src/components/sub-note-content.vue @@ -21,35 +21,21 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XPoll from './poll.vue'; import XMediaList from './media-list.vue'; -import * as os from '@/os'; +import * as misskey from 'misskey-js'; -export default defineComponent({ - components: { - XPoll, - XMediaList, - }, - props: { - note: { - type: Object, - required: true - } - }, - data() { - return { - collapsed: false, - }; - }, - created() { - this.collapsed = this.note.cw == null && this.note.text && ( - (this.note.text.split('\n').length > 9) || - (this.note.text.length > 500) - ); - } -}); +const props = defineProps<{ + note: misskey.entities.Note; +}>(); + +const collapsed = $ref( + props.note.cw == null && props.note.text != null && ( + (props.note.text.split('\n').length > 9) || + (props.note.text.length > 500) + )); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/taskmanager.api-window.vue b/packages/client/src/components/taskmanager.api-window.vue deleted file mode 100644 index 6ec4da3a59..0000000000 --- a/packages/client/src/components/taskmanager.api-window.vue +++ /dev/null @@ -1,72 +0,0 @@ -<template> -<XWindow ref="window" - :initial-width="370" - :initial-height="450" - :can-resize="true" - @close="$refs.window.close()" - @closed="$emit('closed')" -> - <template #header>Req Viewer</template> - - <div class="rlkneywz"> - <MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);"> - <option value="req">Request</option> - <option value="res">Response</option> - </MkTab> - - <code v-if="tab === 'req'" class="_monospace">{{ reqStr }}</code> - <code v-if="tab === 'res'" class="_monospace">{{ resStr }}</code> - </div> -</XWindow> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import XWindow from '@/components/ui/window.vue'; -import MkTab from '@/components/tab.vue'; - -export default defineComponent({ - components: { - XWindow, - MkTab, - }, - - props: { - req: { - required: true, - } - }, - - emits: ['closed'], - - data() { - return { - tab: 'req', - reqStr: JSON5.stringify(this.req.req, null, '\t'), - resStr: JSON5.stringify(this.req.res, null, '\t'), - } - }, - - methods: { - } -}); -</script> - -<style lang="scss" scoped> -.rlkneywz { - display: flex; - flex-direction: column; - height: 100%; - - > code { - display: block; - flex: 1; - padding: 8px; - overflow: auto; - font-size: 0.9em; - tab-size: 2; - white-space: pre; - } -} -</style> diff --git a/packages/client/src/components/taskmanager.vue b/packages/client/src/components/taskmanager.vue deleted file mode 100644 index 6901d88c2c..0000000000 --- a/packages/client/src/components/taskmanager.vue +++ /dev/null @@ -1,233 +0,0 @@ -<template> -<XWindow ref="window" :initial-width="650" :initial-height="420" :can-resize="true" @closed="$emit('closed')"> - <template #header> - <i class="fas fa-terminal" style="margin-right: 0.5em;"></i>Task Manager - </template> - <div class="qljqmnzj _monospace"> - <MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);"> - <option value="windows">Windows</option> - <option value="stream">Stream</option> - <option value="streamPool">Stream (Pool)</option> - <option value="api">API</option> - </MkTab> - - <div class="content"> - <div v-if="tab === 'windows'" v-follow class="windows"> - <div class="header"> - <div>#ID</div> - <div>Component</div> - <div>Action</div> - </div> - <div v-for="p in popups"> - <div>#{{ p.id }}</div> - <div>{{ p.component.name ? p.component.name : '<anonymous>' }}</div> - <div><button class="_textButton" @click="killPopup(p)">Kill</button></div> - </div> - </div> - <div v-if="tab === 'stream'" v-follow class="stream"> - <div class="header"> - <div>#ID</div> - <div>Ch</div> - <div>Handle</div> - <div>In</div> - <div>Out</div> - </div> - <div v-for="c in connections"> - <div>#{{ c.id }}</div> - <div>{{ c.channel }}</div> - <div v-if="c.users !== null">(shared)<span v-if="c.name">{{ ' ' + c.name }}</span></div> - <div v-else>{{ c.name ? c.name : '<anonymous>' }}</div> - <div>{{ c.in }}</div> - <div>{{ c.out }}</div> - </div> - </div> - <div v-if="tab === 'streamPool'" v-follow class="streamPool"> - <div class="header"> - <div>#ID</div> - <div>Ch</div> - <div>Users</div> - </div> - <div v-for="p in pools"> - <div>#{{ p.id }}</div> - <div>{{ p.channel }}</div> - <div>{{ p.users }}</div> - </div> - </div> - <div v-if="tab === 'api'" v-follow class="api"> - <div class="header"> - <div>#ID</div> - <div>Endpoint</div> - <div>State</div> - </div> - <div v-for="req in apiRequests" @click="showReq(req)"> - <div>#{{ req.id }}</div> - <div>{{ req.endpoint }}</div> - <div class="state" :class="req.state">{{ req.state }}</div> - </div> - </div> - </div> - - <footer> - <div><span class="label">Windows</span>{{ popups.length }}</div> - <div><span class="label">Stream</span>{{ connections.length }}</div> - <div><span class="label">Stream (Pool)</span>{{ pools.length }}</div> - </footer> - </div> -</XWindow> -</template> - -<script lang="ts"> -import { defineComponent, markRaw, onBeforeUnmount, ref, shallowRef } from 'vue'; -import XWindow from '@/components/ui/window.vue'; -import MkTab from '@/components/tab.vue'; -import MkButton from '@/components/ui/button.vue'; -import follow from '@/directives/follow-append'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XWindow, - MkTab, - MkButton, - }, - - directives: { - follow - }, - - props: { - }, - - emits: ['closed'], - - setup() { - const connections = shallowRef([]); - const pools = shallowRef([]); - const refreshStreamInfo = () => { - console.log(os.stream.sharedConnectionPools, os.stream.sharedConnections, os.stream.nonSharedConnections); - const conn = os.stream.sharedConnections.map(c => ({ - id: c.id, name: c.name, channel: c.channel, users: c.pool.users, in: c.inCount, out: c.outCount, - })).concat(os.stream.nonSharedConnections.map(c => ({ - id: c.id, name: c.name, channel: c.channel, users: null, in: c.inCount, out: c.outCount, - }))); - conn.sort((a, b) => (a.id > b.id) ? 1 : -1); - connections.value = conn; - pools.value = os.stream.sharedConnectionPools; - }; - const interval = setInterval(refreshStreamInfo, 1000); - onBeforeUnmount(() => { - clearInterval(interval); - }); - - const killPopup = p => { - os.popups.value = os.popups.value.filter(x => x !== p); - }; - - const showReq = req => { - os.popup(import('./taskmanager.api-window.vue'), { - req: req - }, { - }, 'closed'); - }; - - return { - tab: ref('stream'), - popups: os.popups, - apiRequests: os.apiRequests, - connections, - pools, - killPopup, - showReq, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.qljqmnzj { - display: flex; - flex-direction: column; - height: 100%; - - > .content { - flex: 1; - overflow: auto; - - > div { - display: table; - width: 100%; - padding: 16px; - box-sizing: border-box; - - > div { - display: table-row; - - &:nth-child(even) { - //background: rgba(0, 0, 0, 0.1); - } - - &.header { - opacity: 0.7; - } - - > div { - display: table-cell; - white-space: nowrap; - - &:not(:last-child) { - padding-right: 8px; - } - } - } - - &.api { - > div { - &:not(.header) { - cursor: pointer; - - &:hover { - color: var(--accent); - } - } - - > .state { - &.pending { - color: var(--warn); - } - - &.success { - color: var(--success); - } - - &.failed { - color: var(--error); - } - } - } - } - } - } - - > footer { - display: flex; - width: 100%; - padding: 8px 16px; - box-sizing: border-box; - border-top: solid 0.5px var(--divider); - font-size: 0.9em; - - > div { - flex: 1; - - > .label { - opacity: 0.7; - margin-right: 0.5em; - - &:after { - content: ":"; - } - } - } - } -} -</style> diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue index f8a800872f..59956b9526 100644 --- a/packages/client/src/components/timeline.vue +++ b/packages/client/src/components/timeline.vue @@ -1,183 +1,143 @@ <template> -<XNotes ref="tl" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/> +<XNotes ref="tlComponent" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { ref, computed, provide, onUnmounted } from 'vue'; import XNotes from './notes.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as sound from '@/scripts/sound'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + src: string; + list?: string; + antenna?: string; + channel?: string; + sound?: boolean; +}>(); - provide() { - return { - inChannel: this.src === 'channel' - }; - }, +const emit = defineEmits<{ + (e: 'note'): void; + (e: 'queue', count: number): void; +}>(); - props: { - src: { - type: String, - required: true - }, - list: { - type: String, - required: false - }, - antenna: { - type: String, - required: false - }, - channel: { - type: String, - required: false - }, - sound: { - type: Boolean, - required: false, - default: false, - } - }, - - emits: ['note', 'queue', 'before', 'after'], +provide('inChannel', computed(() => props.src === 'channel')); - data() { - return { - connection: null, - connection2: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - query: {}, - date: null - }; - }, +const tlComponent: InstanceType<typeof XNotes> = $ref(); - created() { - const prepend = note => { - (this.$refs.tl as any).prepend(note); +const prepend = note => { + tlComponent.pagingComponent?.prepend(note); - this.$emit('note'); + emit('note'); - if (this.sound) { - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - } - }; + if (props.sound) { + sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note'); + } +}; - const onUserAdded = () => { - (this.$refs.tl as any).reload(); - }; +const onUserAdded = () => { + tlComponent.pagingComponent?.reload(); +}; - const onUserRemoved = () => { - (this.$refs.tl as any).reload(); - }; +const onUserRemoved = () => { + tlComponent.pagingComponent?.reload(); +}; - const onChangeFollowing = () => { - if (!this.$refs.tl.backed) { - this.$refs.tl.reload(); - } - }; +const onChangeFollowing = () => { + if (!tlComponent.pagingComponent?.backed) { + tlComponent.pagingComponent?.reload(); + } +}; - let endpoint; +let endpoint; +let query; +let connection; +let connection2; - if (this.src == 'antenna') { - endpoint = 'antennas/notes'; - this.query = { - antennaId: this.antenna - }; - this.connection = markRaw(os.stream.useChannel('antenna', { - antennaId: this.antenna - })); - this.connection.on('note', prepend); - } else if (this.src == 'home') { - endpoint = 'notes/timeline'; - this.connection = markRaw(os.stream.useChannel('homeTimeline')); - this.connection.on('note', prepend); +if (props.src === 'antenna') { + endpoint = 'antennas/notes'; + query = { + antennaId: props.antenna + }; + connection = stream.useChannel('antenna', { + antennaId: props.antenna + }); + connection.on('note', prepend); +} else if (props.src === 'home') { + endpoint = 'notes/timeline'; + connection = stream.useChannel('homeTimeline'); + connection.on('note', prepend); - this.connection2 = markRaw(os.stream.useChannel('main')); - this.connection2.on('follow', onChangeFollowing); - this.connection2.on('unfollow', onChangeFollowing); - } else if (this.src == 'local') { - endpoint = 'notes/local-timeline'; - this.connection = markRaw(os.stream.useChannel('localTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'social') { - endpoint = 'notes/hybrid-timeline'; - this.connection = markRaw(os.stream.useChannel('hybridTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'global') { - endpoint = 'notes/global-timeline'; - this.connection = markRaw(os.stream.useChannel('globalTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'mentions') { - endpoint = 'notes/mentions'; - this.connection = markRaw(os.stream.useChannel('main')); - this.connection.on('mention', prepend); - } else if (this.src == 'directs') { - endpoint = 'notes/mentions'; - this.query = { - visibility: 'specified' - }; - const onNote = note => { - if (note.visibility == 'specified') { - prepend(note); - } - }; - this.connection = markRaw(os.stream.useChannel('main')); - this.connection.on('mention', onNote); - } else if (this.src == 'list') { - endpoint = 'notes/user-list-timeline'; - this.query = { - listId: this.list - }; - this.connection = markRaw(os.stream.useChannel('userList', { - listId: this.list - })); - this.connection.on('note', prepend); - this.connection.on('userAdded', onUserAdded); - this.connection.on('userRemoved', onUserRemoved); - } else if (this.src == 'channel') { - endpoint = 'channels/timeline'; - this.query = { - channelId: this.channel - }; - this.connection = markRaw(os.stream.useChannel('channel', { - channelId: this.channel - })); - this.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'); + connection.on('note', prepend); +} else if (props.src === 'social') { + endpoint = 'notes/hybrid-timeline'; + connection = stream.useChannel('hybridTimeline'); + connection.on('note', prepend); +} else if (props.src === 'global') { + endpoint = 'notes/global-timeline'; + connection = stream.useChannel('globalTimeline'); + connection.on('note', prepend); +} else if (props.src === 'mentions') { + endpoint = 'notes/mentions'; + connection = stream.useChannel('main'); + connection.on('mention', prepend); +} else if (props.src === 'directs') { + endpoint = 'notes/mentions'; + query = { + visibility: 'specified' + }; + const onNote = note => { + if (note.visibility == 'specified') { + prepend(note); } + }; + connection = stream.useChannel('main'); + connection.on('mention', onNote); +} else if (props.src === 'list') { + endpoint = 'notes/user-list-timeline'; + query = { + listId: props.list + }; + connection = stream.useChannel('userList', { + listId: props.list + }); + connection.on('note', prepend); + connection.on('userAdded', onUserAdded); + connection.on('userRemoved', onUserRemoved); +} else if (props.src === 'channel') { + endpoint = 'channels/timeline'; + query = { + channelId: props.channel + }; + connection = stream.useChannel('channel', { + channelId: props.channel + }); + connection.on('note', prepend); +} - this.pagination = { - endpoint: endpoint, - limit: 10, - params: init => ({ - untilDate: this.date?.getTime(), - ...this.baseQuery, ...this.query - }) - }; - }, - - beforeUnmount() { - this.connection.dispose(); - if (this.connection2) this.connection2.dispose(); - }, +const pagination = { + endpoint: endpoint, + limit: 10, + params: query, +}; - methods: { - focus() { - this.$refs.tl.focus(); - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } +onUnmounted(() => { + connection.dispose(); + if (connection2) connection2.dispose(); }); + +/* TODO +const timetravel = (date?: Date) => { + this.date = date; + this.$refs.tl.reload(); +}; +*/ </script> diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue index 914704c527..c114379716 100644 --- a/packages/client/src/components/toast.vue +++ b/packages/client/src/components/toast.vue @@ -1,6 +1,6 @@ <template> <div class="mk-toast"> - <transition name="toast" appear @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'toast' : ''" appear @after-leave="emit('closed')"> <div v-if="showing" class="body _acrylic" :style="{ zIndex }"> <div class="message"> {{ message }} @@ -10,29 +10,25 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - props: { - message: { - type: String, - required: true, - }, - }, - emits: ['closed'], - data() { - return { - showing: true, - zIndex: os.claimZIndex('high'), - }; - }, - mounted() { - setTimeout(() => { - this.showing = false; - }, 4000); - } +defineProps<{ + message: string; +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); + +const showing = ref(true); +const zIndex = os.claimZIndex('high'); + +onMounted(() => { + window.setTimeout(() => { + showing.value = false; + }, 4000); }); </script> diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue index 804a2e2720..c7b6c8ba96 100644 --- a/packages/client/src/components/ui/button.vue +++ b/packages/client/src/components/ui/button.vue @@ -117,14 +117,14 @@ export default defineComponent({ const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY); - setTimeout(() => { + window.setTimeout(() => { ripple.style.transform = 'scale(' + (scale / 2) + ')'; }, 1); - setTimeout(() => { + window.setTimeout(() => { ripple.style.transition = 'all 1s ease'; ripple.style.opacity = '0'; }, 1000); - setTimeout(() => { + window.setTimeout(() => { if (this.$refs.ripples) this.$refs.ripples.removeChild(ripple); }, 2000); } diff --git a/packages/client/src/components/ui/container.vue b/packages/client/src/components/ui/container.vue index fcd9f32290..7c595d8116 100644 --- a/packages/client/src/components/ui/container.vue +++ b/packages/client/src/components/ui/container.vue @@ -10,7 +10,7 @@ </button> </div> </header> - <transition name="container-toggle" + <transition :name="$store.state.animation ? 'container-toggle' : ''" @enter="enter" @after-enter="afterEnter" @leave="leave" diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue index 9795b1d81a..fe1602b2bb 100644 --- a/packages/client/src/components/ui/folder.vue +++ b/packages/client/src/components/ui/folder.vue @@ -8,7 +8,7 @@ <template v-else><i class="fas fa-angle-down"></i></template> </button> </header> - <transition name="folder-toggle" + <transition :name="$store.state.animation ? 'folder-toggle' : ''" @enter="enter" @after-enter="afterEnter" @leave="leave" diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index 6f3f277b11..41165c8d33 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -24,7 +24,7 @@ <span>{{ item.text }}</span> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </a> - <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" @click="clicked(item.action, $event)"> + <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)"> <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </button> diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index 3e2e59b27c..c691c8c6d0 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -211,7 +211,7 @@ export default defineComponent({ contentClicking = true; window.addEventListener('mouseup', e => { // click イベントより先㫠mouseup イベントãŒç™ºç”Ÿã™ã‚‹ã‹ã‚‚ã—れãªã„ã®ã§ã¡ã‚‡ã£ã¨å¾…㤠- setTimeout(() => { + window.setTimeout(() => { contentClicking = false; }, 100); }, { passive: true, once: true }); diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 64af4a54f7..13f3215671 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -1,5 +1,5 @@ <template> -<transition name="fade" mode="out-in"> +<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <MkError v-else-if="error" @retry="init()"/> @@ -13,43 +13,269 @@ </slot> </div> - <div v-else class="cxiknjgy"> + <div v-else ref="rootEl"> <slot :items="items"></slot> - <div v-show="more" key="_more_" class="more _gap"> - <MkButton v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> + <div v-show="more" key="_more_" class="cxiknjgy _gap"> + <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> + {{ $ts.loadMore }} </MkButton> + <MkLoading v-else class="loading"/> </div> </div> </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from './button.vue'; -import paging from '@/scripts/paging'; +<script lang="ts" setup> +import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue'; +import * as misskey from 'misskey-js'; +import * as os from '@/os'; +import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; +import MkButton from '@/components/ui/button.vue'; -export default defineComponent({ - components: { - MkButton - }, +const SECOND_FETCH_LIMIT = 30; - mixins: [ - paging({}), - ], +export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = { + endpoint: E; + limit: number; + params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>; - props: { - pagination: { - required: true - }, + /** + * 検索APIã®ã‚ˆã†ãªã€ãƒšãƒ¼ã‚¸ãƒ³ã‚°ä¸å¯ãªã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‚’利用ã™ã‚‹å ´åˆ + * (ãã®ã‚ˆã†ãªAPIã‚’ã“ã®é–¢æ•°ã§ä½¿ã†ã®ã¯è‹¥å¹²çŸ›ç›¾ã—ã¦ã‚‹ã‘ã©) + */ + noPaging?: boolean; - disableAutoLoad: { - type: Boolean, - required: false, - default: false, + /** + * items é…列ã®ä¸èº«ã‚’é€†é †ã«ã™ã‚‹(æ–°ã—ã„æ–¹ãŒæœ€å¾Œ) + */ + reversed?: boolean; + + offsetMode?: boolean; +}; + +const props = withDefaults(defineProps<{ + pagination: Paging; + disableAutoLoad?: boolean; + displayLimit?: number; +}>(), { + displayLimit: 30, +}); + +const emit = defineEmits<{ + (e: 'queue', count: number): void; +}>(); + +type Item = { id: string; [another: string]: unknown; }; + +const rootEl = ref<HTMLElement>(); +const items = ref<Item[]>([]); +const queue = ref<Item[]>([]); +const offset = ref(0); +const fetching = ref(true); +const moreFetching = ref(false); +const more = ref(false); +const backed = ref(false); // é¡ã‚Šä¸ã‹å¦ã‹ +const isBackTop = ref(false); +const empty = computed(() => items.value.length === 0); +const error = ref(false); + +const init = async (): Promise<void> => { + queue.value = []; + fetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1, + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + if (props.pagination.reversed) { + if (i === res.length - 2) item._shouldInsertAd_ = true; + } else { + if (i === 3) item._shouldInsertAd_ = true; + } + } + if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse() : res; + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse() : res; + more.value = false; + } + offset.value = res.length; + error.value = false; + fetching.value = false; + }, e => { + error.value = true; + fetching.value = false; + }); +}; + +const reload = (): void => { + items.value = []; + init(); +}; + +const fetchMore = async (): Promise<void> => { + if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; + moreFetching.value = true; + backed.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + untilId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, + }), + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + if (props.pagination.reversed) { + if (i === res.length - 9) item._shouldInsertAd_ = true; + } else { + if (i === 10) item._shouldInsertAd_ = true; + } + } + if (res.length > SECOND_FETCH_LIMIT) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = false; + } + offset.value += res.length; + moreFetching.value = false; + }, e => { + moreFetching.value = false; + }); +}; + +const fetchMoreAhead = async (): Promise<void> => { + if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; + moreFetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + sinceId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, + }), + }).then(res => { + if (res.length > SECOND_FETCH_LIMIT) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = false; + } + offset.value += res.length; + moreFetching.value = false; + }, e => { + moreFetching.value = false; + }); +}; + +const prepend = (item: Item): void => { + if (props.pagination.reversed) { + if (rootEl.value) { + const container = getScrollContainer(rootEl.value); + if (container == null) return; // TODO? + + const pos = getScrollPosition(rootEl.value); + const viewHeight = container.clientHeight; + const height = container.scrollHeight; + const isBottom = (pos + viewHeight > height - 32); + if (isBottom) { + // オーãƒãƒ¼ãƒ•ãƒãƒ¼ã—ãŸã‚‰å¤ã„ã‚¢ã‚¤ãƒ†ãƒ ã¯æ¨ã¦ã‚‹ + if (items.value.length >= props.displayLimit) { + // ã“ã®ã‚„り方ã ã¨Vue 3.2以é™ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‹•ã‹ãªããªã‚‹ + //items.value = items.value.slice(-props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.shift(); + } + more.value = true; + } + } } - }, + items.value.push(item); + // TODO + } else { + // åˆå›žè¡¨ç¤ºæ™‚ã¯unshiftã ã‘ã§OK + if (!rootEl.value) { + items.value.unshift(item); + return; + } + + const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value)); + + if (isTop) { + // Prepend the item + items.value.unshift(item); + + // オーãƒãƒ¼ãƒ•ãƒãƒ¼ã—ãŸã‚‰å¤ã„ã‚¢ã‚¤ãƒ†ãƒ ã¯æ¨ã¦ã‚‹ + if (items.value.length >= props.displayLimit) { + // ã“ã®ã‚„り方ã ã¨Vue 3.2以é™ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‹•ã‹ãªããªã‚‹ + //this.items = items.value.slice(0, props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.pop(); + } + more.value = true; + } + } else { + queue.value.push(item); + onScrollTop(rootEl.value, () => { + for (const item of queue.value) { + prepend(item); + } + queue.value = []; + }); + } + } +}; + +const append = (item: Item): void => { + items.value.push(item); +}; + +const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => { + const i = items.value.findIndex(item => item.id === id); + items.value[i] = replacer(items.value[i]); +}; + +if (props.pagination.params && isRef(props.pagination.params)) { + watch(props.pagination.params, init, { deep: true }); +} + +watch(queue, (a, b) => { + if (a.length === 0 && b.length === 0) return; + emit('queue', queue.value.length); +}, { deep: true }); + +init(); + +onActivated(() => { + isBackTop.value = false; +}); + +onDeactivated(() => { + isBackTop.value = window.scrollY === 0; +}); + +defineExpose({ + items, + backed, + reload, + fetchMoreAhead, + prepend, + append, + updateItem, }); </script> @@ -64,11 +290,9 @@ export default defineComponent({ } .cxiknjgy { - > .more > .button { + > .button { margin-left: auto; margin-right: auto; - height: 48px; - min-width: 150px; } } </style> diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index 2e48ab623e..394b068352 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -1,5 +1,5 @@ <template> -<transition name="tooltip" appear @after-leave="$emit('closed')"> +<transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="$emit('closed')"> <div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }"> <slot>{{ text }}</slot> </div> diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index bd33289ccc..fa32ecfdef 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -147,9 +147,9 @@ export default defineComponent({ } }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { if (this.contextmenu) { - os.contextMenu(this.contextmenu, e); + os.contextMenu(this.contextmenu, ev); } }, diff --git a/packages/client/src/components/url-preview-popup.vue b/packages/client/src/components/url-preview-popup.vue index c345bafcf9..5f3717ab91 100644 --- a/packages/client/src/components/url-preview-popup.vue +++ b/packages/client/src/components/url-preview-popup.vue @@ -1,6 +1,6 @@ <template> <div class="fgmtyycl" :style="{ zIndex, top: top + 'px', left: left + 'px' }"> - <transition name="zoom" @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'zoom' : ''" @after-leave="$emit('closed')"> <MkUrlPreview v-if="showing" class="_popup _shadow" :url="url"/> </transition> </div> diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index fe88985a62..6c57957617 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -4,10 +4,10 @@ <iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> </div> <div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter"> - <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', left: `${tweetLeft}px`, width: `${tweetLeft < 0 ? 'auto' : '100%'}`, height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> + <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=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> </div> <div v-else v-size="{ max: [400, 350] }" class="mk-url-preview"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <component :is="self ? 'MkA' : 'a'" v-if="!fetching" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`"> <button v-if="!playerEnabled && player.url" class="_button" :title="$ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button> @@ -32,110 +32,80 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted } from 'vue'; import { url as local, lang } from '@/config'; -import * as os from '@/os'; -export default defineComponent({ - props: { - url: { - type: String, - require: true - }, - - detail: { - type: Boolean, - required: false, - default: false - }, - - compact: { - type: Boolean, - required: false, - default: false - }, - }, - - data() { - const self = this.url.startsWith(local); - return { - local, - fetching: true, - title: null, - description: null, - thumbnail: null, - icon: null, - sitename: null, - player: { - url: null, - width: null, - height: null - }, - tweetId: null, - tweetExpanded: this.detail, - embedId: `embed${Math.random().toString().replace(/\D/,'')}`, - tweetHeight: 150, - tweetLeft: 0, - playerEnabled: false, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - }; - }, +const props = withDefaults(defineProps<{ + url: string; + detail?: boolean; + compact?: boolean; +}>(), { + detail: false, + compact: false, +}); - created() { - const requestUrl = new URL(this.url); +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; +let fetching = $ref(true); +let title = $ref<string | null>(null); +let description = $ref<string | null>(null); +let thumbnail = $ref<string | null>(null); +let icon = $ref<string | null>(null); +let sitename = $ref<string | null>(null); +let player = $ref({ + url: null, + width: null, + height: null +}); +let playerEnabled = $ref(false); +let tweetId = $ref<string | null>(null); +let tweetExpanded = $ref(props.detail); +const embedId = `embed${Math.random().toString().replace(/\D/,'')}`; +let tweetHeight = $ref(150); - if (requestUrl.hostname == 'twitter.com') { - const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); - if (m) this.tweetId = m[1]; - } +const requestUrl = new URL(props.url); - if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { - requestUrl.hostname = 'www.youtube.com'; - } +if (requestUrl.hostname == 'twitter.com') { + const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); + if (m) tweetId = m[1]; +} - const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); +if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { + requestUrl.hostname = 'www.youtube.com'; +} - requestUrl.hash = ''; +const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); - fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { - res.json().then(info => { - if (info.url == null) return; - this.title = info.title; - this.description = info.description; - this.thumbnail = info.thumbnail; - this.icon = info.icon; - this.sitename = info.sitename; - this.fetching = false; - this.player = info.player; - }) - }); +requestUrl.hash = ''; - (window as any).addEventListener('message', this.adjustTweetHeight); - }, +fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { + res.json().then(info => { + if (info.url == null) return; + title = info.title; + description = info.description; + thumbnail = info.thumbnail; + icon = info.icon; + sitename = info.sitename; + fetching = false; + player = info.player; + }) +}); - mounted() { - // 300pxãªã„ã¨çµ¶å¯¾å³ã«ã¯ã¿å‡ºã‚‹ã®ã§å·¦ã«ç§»å‹•ã—ã¦ã—ã¾ã† - const areaWidth = (this.$el as any)?.clientWidth; - if (areaWidth && areaWidth < 300) this.tweetLeft = areaWidth - 241; - }, +function adjustTweetHeight(message: any) { + if (message.origin !== 'https://platform.twitter.com') return; + const embed = message.data?.['twttr.embed']; + if (embed?.method !== 'twttr.private.resize') return; + if (embed?.id !== embedId) return; + const height = embed?.params[0]?.height; + if (height) tweetHeight = height; +} - beforeUnmount() { - (window as any).removeEventListener('message', this.adjustTweetHeight); - }, +(window as any).addEventListener('message', adjustTweetHeight); - methods: { - adjustTweetHeight(message: any) { - if (message.origin !== 'https://platform.twitter.com') return; - const embed = message.data?.['twttr.embed']; - if (embed?.method !== 'twttr.private.resize') return; - if (embed?.id !== this.embedId) return; - const height = embed?.params[0]?.height; - if (height) this.tweetHeight = height; - }, - }, +onUnmounted(() => { + (window as any).removeEventListener('message', adjustTweetHeight); }); </script> diff --git a/packages/client/src/components/user-info.vue b/packages/client/src/components/user-info.vue index 779a71358d..6a25d412fc 100644 --- a/packages/client/src/components/user-info.vue +++ b/packages/client/src/components/user-info.vue @@ -27,32 +27,14 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import * as misskey from 'misskey-js'; import MkFollowButton from './follow-button.vue'; import { userPage } from '@/filters/user'; -export default defineComponent({ - components: { - MkFollowButton - }, - - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - userPage, - } -}); +defineProps<{ + user: misskey.entities.UserDetailed; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/user-list.vue b/packages/client/src/components/user-list.vue index 2148dab608..3e273721c7 100644 --- a/packages/client/src/components/user-list.vue +++ b/packages/client/src/components/user-list.vue @@ -1,91 +1,39 @@ <template> -<MkError v-if="error" @retry="init()"/> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noUsers }}</div> + </div> + </template> -<div v-else class="efvhhmdq _isolated"> - <div v-if="empty" class="no-users"> - <p>{{ $ts.noUsers }}</p> - </div> - <div class="users"> - <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> - </div> - <button v-show="more" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" class="more" :class="{ fetching: moreFetching }" :disabled="moreFetching" @click="fetchMore"> - <template v-if="moreFetching"><i class="fas fa-spinner fa-pulse fa-fw"></i></template>{{ moreFetching ? $ts.loading : $ts.loadMore }} - </button> -</div> + <template #default="{ items: users }"> + <div class="efvhhmdq"> + <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> + </div> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import MkUserInfo from './user-info.vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import MkUserInfo from '@/components/user-info.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; import { userPage } from '@/filters/user'; -export default defineComponent({ - components: { - MkUserInfo, - }, +const props = defineProps<{ + pagination: Paging; + noGap?: boolean; +}>(); - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - extract: { - required: false - }, - expanded: { - type: Boolean, - default: true - }, - }, - - computed: { - users() { - return this.extract ? this.extract(this.items) : this.items; - } - }, - - methods: { - userPage - } -}); +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); </script> <style lang="scss" scoped> .efvhhmdq { - > .no-users { - text-align: center; - } - - > .users { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - } - - > .more { - display: block; - width: 100%; - padding: 16px; - - &:hover { - background: rgba(#000, 0.025); - } - - &:active { - background: rgba(#000, 0.05); - } - - &.fetching { - cursor: wait; - } - - > i { - margin-right: 4px; - } - } + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: var(--margin); } </style> diff --git a/packages/client/src/components/user-online-indicator.vue b/packages/client/src/components/user-online-indicator.vue index 93e9dea57b..a87b0aeff5 100644 --- a/packages/client/src/components/user-online-indicator.vue +++ b/packages/client/src/components/user-online-indicator.vue @@ -2,26 +2,21 @@ <div v-tooltip="text" class="fzgwjkgc" :class="user.onlineStatus"></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - computed: { - text(): string { - switch (this.user.onlineStatus) { - case 'online': return this.$ts.online; - case 'active': return this.$ts.active; - case 'offline': return this.$ts.offline; - case 'unknown': return this.$ts.unknown; - } - } +const text = $computed(() => { + switch (props.user.onlineStatus) { + case 'online': return i18n.locale.online; + case 'active': return i18n.locale.active; + case 'offline': return i18n.locale.offline; + case 'unknown': return i18n.locale.unknown; } }); </script> diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue index f85a32fbe7..51c5330564 100644 --- a/packages/client/src/components/user-preview.vue +++ b/packages/client/src/components/user-preview.vue @@ -1,5 +1,5 @@ <template> -<transition name="popup" appear @after-leave="$emit('closed')"> +<transition :name="$store.state.animation ? 'popup' : ''" appear @after-leave="$emit('closed')"> <div v-if="showing" class="fxxzrfni _popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { $emit('mouseover'); }" @mouseleave="() => { $emit('mouseleave'); }"> <div v-if="fetched" class="info"> <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> diff --git a/packages/client/src/components/user-select-dialog.vue b/packages/client/src/components/user-select-dialog.vue index ba2975478b..dbef34d547 100644 --- a/packages/client/src/components/user-select-dialog.vue +++ b/packages/client/src/components/user-select-dialog.vue @@ -1,5 +1,5 @@ <template> -<XModalWindow ref="dialog" +<XModalWindow ref="dialogEl" :with-ok-button="true" :ok-button-disabled="selected == null" @click="cancel()" @@ -8,20 +8,20 @@ @closed="$emit('closed')" > <template #header>{{ $ts.selectUser }}</template> - <div class="tbhwbxda _monolithic_"> - <div class="_section"> - <div class="_inputSplit"> - <MkInput ref="username" v-model="username" class="input" @update:modelValue="search"> + <div class="tbhwbxda"> + <div class="form"> + <FormSplit :min-width="170"> + <MkInput ref="usernameEl" v-model="username" @update:modelValue="search"> <template #label>{{ $ts.username }}</template> <template #prefix>@</template> </MkInput> - <MkInput v-model="host" class="input" @update:modelValue="search"> + <MkInput v-model="host" @update:modelValue="search"> <template #label>{{ $ts.host }}</template> <template #prefix>@</template> </MkInput> - </div> + </FormSplit> </div> - <div v-if="username != '' || host != ''" class="_section result" :class="{ hit: users.length > 0 }"> + <div v-if="username != '' || host != ''" class="result" :class="{ hit: users.length > 0 }"> <div v-if="users.length > 0" class="users"> <div v-for="user in users" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()"> <MkAvatar :user="user" class="avatar" :show-indicator="true"/> @@ -35,7 +35,7 @@ <span>{{ $ts.noUsers }}</span> </div> </div> - <div v-if="username == '' && host == ''" class="_section recent"> + <div v-if="username == '' && host == ''" class="recent"> <div class="users"> <div v-for="user in recentUsers" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()"> <MkAvatar :user="user" class="avatar" :show-indicator="true"/> @@ -50,87 +50,89 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkInput from './form/input.vue'; +<script lang="ts" setup> +import { nextTick, onMounted } from 'vue'; +import * as misskey from 'misskey-js'; +import MkInput from '@/components/form/input.vue'; +import FormSplit from '@/components/form/split.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import * as os from '@/os'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkInput, - XModalWindow, - }, - - props: { - }, +const emit = defineEmits<{ + (e: 'ok', selected: misskey.entities.UserDetailed): void; + (e: 'cancel'): void; + (e: 'closed'): void; +}>(); - emits: ['ok', 'cancel', 'closed'], +let username = $ref(''); +let host = $ref(''); +let users: misskey.entities.UserDetailed[] = $ref([]); +let recentUsers: misskey.entities.UserDetailed[] = $ref([]); +let selected: misskey.entities.UserDetailed | null = $ref(null); +let usernameEl: HTMLElement = $ref(); +let dialogEl = $ref(); - data() { - return { - username: '', - host: '', - recentUsers: [], - users: [], - selected: null, - }; - }, +const focus = () => { + if (usernameEl) { + usernameEl.focus(); + } +}; - async mounted() { - this.focus(); +const search = () => { + if (username === '' && host === '') { + users = []; + return; + } + os.api('users/search-by-username-and-host', { + username: username, + host: host, + limit: 10, + detail: false + }).then(_users => { + users = _users; + }); +}; - this.$nextTick(() => { - this.focus(); - }); +const ok = () => { + if (selected == null) return; + emit('ok', selected); + dialogEl.close(); - this.recentUsers = await os.api('users/show', { - userIds: this.$store.state.recentlyUsedUsers - }); - }, + // 最近使ã£ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼æ›´æ–° + let recents = defaultStore.state.recentlyUsedUsers; + recents = recents.filter(x => x !== selected.id); + recents.unshift(selected.id); + defaultStore.set('recentlyUsedUsers', recents.splice(0, 16)); +}; - methods: { - search() { - if (this.username == '' && this.host == '') { - this.users = []; - return; - } - os.api('users/search-by-username-and-host', { - username: this.username, - host: this.host, - limit: 10, - detail: false - }).then(users => { - this.users = users; - }); - }, +const cancel = () => { + emit('cancel'); + dialogEl.close(); +}; - focus() { - this.$refs.username.focus(); - }, +onMounted(() => { + focus(); - ok() { - this.$emit('ok', this.selected); - this.$refs.dialog.close(); + nextTick(() => { + focus(); + }); - // 最近使ã£ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼æ›´æ–° - let recents = this.$store.state.recentlyUsedUsers; - recents = recents.filter(x => x !== this.selected.id); - recents.unshift(this.selected.id); - this.$store.set('recentlyUsedUsers', recents.splice(0, 16)); - }, - - cancel() { - this.$emit('cancel'); - this.$refs.dialog.close(); - }, - } + os.api('users/show', { + userIds: defaultStore.state.recentlyUsedUsers, + }).then(users => { + recentUsers = users; + }); }); </script> <style lang="scss" scoped> .tbhwbxda { - > ._section { + > .form { + padding: 0 var(--root-margin); + } + + > .result, > .recent { display: flex; flex-direction: column; overflow: auto; diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue index 4200f4354e..4b20063a51 100644 --- a/packages/client/src/components/visibility-picker.vue +++ b/packages/client/src/components/visibility-picker.vue @@ -1,28 +1,28 @@ <template> -<MkModal ref="modal" :z-priority="'high'" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> +<MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="gqyayizv _popup"> - <button key="public" class="_button" :class="{ active: v == 'public' }" data-index="1" @click="choose('public')"> + <button key="public" class="_button" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')"> <div><i class="fas fa-globe"></i></div> <div> <span>{{ $ts._visibility.public }}</span> <span>{{ $ts._visibility.publicDescription }}</span> </div> </button> - <button key="home" class="_button" :class="{ active: v == 'home' }" data-index="2" @click="choose('home')"> + <button key="home" class="_button" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')"> <div><i class="fas fa-home"></i></div> <div> <span>{{ $ts._visibility.home }}</span> <span>{{ $ts._visibility.homeDescription }}</span> </div> </button> - <button key="followers" class="_button" :class="{ active: v == 'followers' }" data-index="3" @click="choose('followers')"> + <button key="followers" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')"> <div><i class="fas fa-unlock"></i></div> <div> <span>{{ $ts._visibility.followers }}</span> <span>{{ $ts._visibility.followersDescription }}</span> </div> </button> - <button key="specified" :disabled="localOnly" class="_button" :class="{ active: v == 'specified' }" data-index="4" @click="choose('specified')"> + <button key="specified" :disabled="localOnly" class="_button" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')"> <div><i class="fas fa-envelope"></i></div> <div> <span>{{ $ts._visibility.specified }}</span> @@ -42,49 +42,40 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { nextTick, watch } from 'vue'; +import * as misskey from 'misskey-js'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, - props: { - currentVisibility: { - type: String, - required: true - }, - currentLocalOnly: { - type: Boolean, - required: true - }, - src: { - required: false - }, - }, - emits: ['change-visibility', 'change-local-only', 'closed'], - data() { - return { - v: this.currentVisibility, - localOnly: this.currentLocalOnly, - } - }, - watch: { - localOnly() { - this.$emit('change-local-only', this.localOnly); - } - }, - methods: { - choose(visibility) { - this.v = visibility; - this.$emit('change-visibility', visibility); - this.$nextTick(() => { - this.$refs.modal.close(); - }); - }, - } +const modal = $ref<InstanceType<typeof MkModal>>(); + +const props = withDefaults(defineProps<{ + currentVisibility: typeof misskey.noteVisibilities[number]; + currentLocalOnly: boolean; + src?: HTMLElement; +}>(), { +}); + +const emit = defineEmits<{ + (e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; + (e: 'changeLocalOnly', v: boolean): void; + (e: 'closed'): void; +}>(); + +let v = $ref(props.currentVisibility); +let localOnly = $ref(props.currentLocalOnly); + +watch($$(localOnly), () => { + emit('changeLocalOnly', localOnly); }); + +function choose(visibility: typeof misskey.noteVisibilities[number]): void { + v = visibility; + emit('changeVisibility', visibility); + nextTick(() => { + modal.close(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/waiting-dialog.vue b/packages/client/src/components/waiting-dialog.vue index 10aedbd8f6..7dfcc55695 100644 --- a/packages/client/src/components/waiting-dialog.vue +++ b/packages/client/src/components/waiting-dialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="$emit('closed')"> +<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> <div class="iuyakobc" :class="{ iconOnly: (text == null) || success }"> <i v-if="success" class="fas fa-check icon success"></i> <i v-else class="fas fa-spinner fa-pulse icon waiting"></i> @@ -8,49 +8,30 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch, ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, +const modal = ref<InstanceType<typeof MkModal>>(); - props: { - success: { - type: Boolean, - required: true, - }, - showing: { - type: Boolean, - required: true, - }, - text: { - type: String, - required: false, - }, - }, +const props = defineProps<{ + success: boolean; + showing: boolean; + text?: string; +}>(); - emits: ['done', 'closed'], +const emit = defineEmits<{ + (e: 'done'); + (e: 'closed'); +}>(); - data() { - return { - }; - }, - - watch: { - showing() { - if (!this.showing) this.done(); - } - }, +function done() { + emit('done'); + modal.value.close(); +} - methods: { - done() { - this.$emit('done'); - this.$refs.modal.close(); - }, - } +watch(() => props.showing, () => { + if (!props.showing) done(); }); </script> diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue index 12f7129253..ccde5fbe55 100644 --- a/packages/client/src/components/widgets.vue +++ b/packages/client/src/components/widgets.vue @@ -10,7 +10,7 @@ <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> </header> <XDraggable - v-model="_widgets" + v-model="widgets_" item-key="id" animation="150" > @@ -18,7 +18,7 @@ <div class="customize-container"> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button> - <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" @updateProps="updateWidget(element.id, $event)"/> + <component :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/> </div> </template> </XDraggable> @@ -28,7 +28,7 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +import { defineComponent, defineAsyncComponent, reactive, ref, computed } from 'vue'; import { v4 as uuid } from 'uuid'; import MkSelect from '@/components/form/select.vue'; import MkButton from '@/components/ui/button.vue'; @@ -54,50 +54,47 @@ export default defineComponent({ emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'], - data() { - return { - widgetAdderSelected: null, - widgetDefs, - settings: {}, + setup(props, context) { + const widgetRefs = reactive({}); + const configWidget = (id: string) => { + widgetRefs[id].configure(); }; - }, - - computed: { - _widgets: { - get() { - return this.widgets; - }, - set(value) { - this.$emit('updateWidgets', value); - } - } - }, - - methods: { - configWidget(id) { - this.settings[id](); - }, + const widgetAdderSelected = ref(null); + const addWidget = () => { + if (widgetAdderSelected.value == null) return; - addWidget() { - if (this.widgetAdderSelected == null) return; - - this.$emit('addWidget', { - name: this.widgetAdderSelected, + context.emit('addWidget', { + name: widgetAdderSelected.value, id: uuid(), - data: {} + data: {}, }); - this.widgetAdderSelected = null; - }, - - removeWidget(widget) { - this.$emit('removeWidget', widget); - }, + widgetAdderSelected.value = null; + }; + const removeWidget = (widget) => { + context.emit('removeWidget', widget); + }; + const updateWidget = (id, data) => { + context.emit('updateWidget', { id, data }); + }; + const widgets_ = computed({ + get: () => props.widgets, + set: (value) => { + context.emit('updateWidgets', value); + }, + }); - updateWidget(id, data) { - this.$emit('updateWidget', { id, data }); - }, - } + return { + widgetRefs, + configWidget, + widgetAdderSelected, + widgetDefs, + addWidget, + removeWidget, + updateWidget, + widgets_, + }; + }, }); </script> diff --git a/packages/client/src/const.ts b/packages/client/src/const.ts new file mode 100644 index 0000000000..505cf2748e --- /dev/null +++ b/packages/client/src/const.ts @@ -0,0 +1,44 @@ +// ブラウザã§ç›´æŽ¥è¡¨ç¤ºã™ã‚‹ã“ã¨ã‚’許å¯ã™ã‚‹ãƒ•ァイルã®ç¨®é¡žã®ãƒªã‚¹ãƒˆ +// ã“ã“ã«å«ã¾ã‚Œãªã„ã‚‚ã®ã¯ application/octet-stream ã¨ã—ã¦ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã•れる +// SVGã¯XSSを生むã®ã§è¨±å¯ã—ãªã„ +export const FILE_TYPE_BROWSERSAFE = [ + // Images + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/x-icon', + + // OggS + 'audio/opus', + 'video/ogg', + 'audio/ogg', + 'application/ogg', + + // ISO/IEC base media file format + 'video/quicktime', + 'video/mp4', + 'audio/mp4', + 'video/x-m4v', + 'audio/x-m4a', + 'video/3gpp', + 'video/3gpp2', + + 'video/mpeg', + 'audio/mpeg', + + 'video/webm', + 'audio/webm', + + 'audio/aac', + 'audio/x-flac', + 'audio/vnd.wave', +]; +/* +https://github.com/sindresorhus/file-type/blob/main/supported.js +https://github.com/sindresorhus/file-type/blob/main/core.js +https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers +*/ diff --git a/packages/client/src/directives/anim.ts b/packages/client/src/directives/anim.ts index 1ceef984d8..04e1c6a404 100644 --- a/packages/client/src/directives/anim.ts +++ b/packages/client/src/directives/anim.ts @@ -10,7 +10,7 @@ export default { }, mounted(src, binding, vn) { - setTimeout(() => { + window.setTimeout(() => { src.style.opacity = '1'; src.style.transform = 'none'; }, 1); diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index e14ee81dff..fffde14874 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -21,7 +21,7 @@ export default { self.close = () => { if (self._close) { - clearInterval(self.checkTimer); + window.clearInterval(self.checkTimer); self._close(); self._close = null; } @@ -61,19 +61,19 @@ export default { }); el.addEventListener(start, () => { - clearTimeout(self.showTimer); - clearTimeout(self.hideTimer); - self.showTimer = setTimeout(self.show, delay); + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.showTimer = window.setTimeout(self.show, delay); }, { passive: true }); el.addEventListener(end, () => { - clearTimeout(self.showTimer); - clearTimeout(self.hideTimer); - self.hideTimer = setTimeout(self.close, delay); + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.hideTimer = window.setTimeout(self.close, delay); }, { passive: true }); el.addEventListener('click', () => { - clearTimeout(self.showTimer); + window.clearTimeout(self.showTimer); self.close(); }); }, @@ -85,6 +85,6 @@ export default { unmounted(el, binding, vn) { const self = el._tooltipDirective_; - clearInterval(self.checkTimer); + window.clearInterval(self.checkTimer); }, } as Directive; diff --git a/packages/client/src/directives/user-preview.ts b/packages/client/src/directives/user-preview.ts index 68d9e2816c..cdd2afa194 100644 --- a/packages/client/src/directives/user-preview.ts +++ b/packages/client/src/directives/user-preview.ts @@ -30,11 +30,11 @@ export class UserPreview { source: this.el }, { mouseover: () => { - clearTimeout(this.hideTimer); + window.clearTimeout(this.hideTimer); }, mouseleave: () => { - clearTimeout(this.showTimer); - this.hideTimer = setTimeout(this.close, 500); + window.clearTimeout(this.showTimer); + this.hideTimer = window.setTimeout(this.close, 500); }, }, 'closed'); @@ -44,10 +44,10 @@ export class UserPreview { } }; - this.checkTimer = setInterval(() => { + this.checkTimer = window.setInterval(() => { if (!document.body.contains(this.el)) { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); this.close(); } }, 1000); @@ -56,7 +56,7 @@ export class UserPreview { @autobind private close() { if (this.promise) { - clearInterval(this.checkTimer); + window.clearInterval(this.checkTimer); this.promise.cancel(); this.promise = null; } @@ -64,21 +64,21 @@ export class UserPreview { @autobind private onMouseover() { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.show, 500); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.showTimer = window.setTimeout(this.show, 500); } @autobind private onMouseleave() { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.close, 500); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.hideTimer = window.setTimeout(this.close, 500); } @autobind private onClick() { - clearTimeout(this.showTimer); + window.clearTimeout(this.showTimer); this.close(); } @@ -94,7 +94,7 @@ export class UserPreview { this.el.removeEventListener('mouseover', this.onMouseover); this.el.removeEventListener('mouseleave', this.onMouseleave); this.el.removeEventListener('click', this.onClick); - clearInterval(this.checkTimer); + window.clearInterval(this.checkTimer); } } diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 82a1e169ce..af70aec70a 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -13,10 +13,8 @@ if (localStorage.getItem('accounts') != null) { } //#endregion -import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue'; -import compareVersions from 'compare-versions'; +import * as compareVersions from 'compare-versions'; import widgets from '@/widgets'; import directives from '@/directives'; @@ -26,7 +24,8 @@ import { router } from '@/router'; import { applyTheme } from '@/scripts/theme'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { i18n } from '@/i18n'; -import { stream, confirm, alert, post, popup, toast } from '@/os'; +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'; @@ -73,18 +72,6 @@ if (_DEV_) { }); } -if (defaultStore.state.reportError && !_DEV_) { - Sentry.init({ - dsn: 'https://fd273254a07a4b61857607a9ea05d629@o501808.ingest.sentry.io/5583438', - tracesSampleRate: 1.0, - }); - - Sentry.setTag('misskey_version', version); - Sentry.setTag('ui', ui); - Sentry.setTag('lang', lang); - Sentry.setTag('host', host); -} - // タッãƒãƒ‡ãƒã‚¤ã‚¹ã§CSSã®:hoverを機能ã•ã›ã‚‹ document.addEventListener('touchend', () => {}, { passive: true }); @@ -185,7 +172,6 @@ const app = createApp(await ( !$i ? import('@/ui/visitor.vue') : ui === 'deck' ? import('@/ui/deck.vue') : ui === 'desktop' ? import('@/ui/desktop.vue') : - ui === 'chat' ? import('@/ui/chat/index.vue') : ui === 'classic' ? import('@/ui/classic.vue') : import('@/ui/universal.vue') ).then(x => x.default)); diff --git a/packages/client/src/menu.ts b/packages/client/src/menu.ts index bd155ba16d..184779f21f 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/menu.ts @@ -163,22 +163,11 @@ export const menuDef = reactive({ icon: 'fas fa-laugh', to: '/emojis', }, - games: { - title: 'games', - icon: 'fas fa-gamepad', - to: '/games/reversi', - }, scratchpad: { title: 'scratchpad', icon: 'fas fa-terminal', to: '/scratchpad', }, - rooms: { - title: 'rooms', - icon: 'fas fa-door-closed', - show: computed(() => $i != null), - to: computed(() => `/@${$i.username}/room`), - }, ui: { title: 'switchUi', icon: 'fas fa-columns', @@ -204,13 +193,6 @@ export const menuDef = reactive({ localStorage.setItem('ui', 'classic'); unisonReload(); } - }, { - text: 'Chat (β)', - active: ui === 'chat', - action: () => { - localStorage.setItem('ui', 'chat'); - unisonReload(); - } }, /*{ text: i18n.locale.desktop + ' (β)', active: ui === 'desktop', diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 4ed69e0ec0..c16ea717ad 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -4,19 +4,13 @@ import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vu import { EventEmitter } from 'eventemitter3'; import insertTextAtCursor from 'insert-text-at-cursor'; import * as Misskey from 'misskey-js'; -import * as Sentry from '@sentry/browser'; -import { apiUrl, debug, url } from '@/config'; +import { apiUrl, url } from '@/config'; import MkPostFormDialog from '@/components/post-form-dialog.vue'; import MkWaitingDialog from '@/components/waiting-dialog.vue'; import { resolve } from '@/router'; import { $i } from '@/account'; -import { defaultStore } from '@/store'; - -export const stream = markRaw(new Misskey.Stream(url, $i)); export const pendingApiRequestsCount = ref(0); -let apiRequestsCount = 0; // for debug -export const apiRequests = ref([]); // for debug const apiClient = new Misskey.api.APIClient({ origin: url, @@ -29,18 +23,6 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s pendingApiRequestsCount.value--; }; - const log = debug ? reactive({ - id: ++apiRequestsCount, - endpoint, - req: markRaw(data), - res: null, - state: 'pending', - }) : null; - if (debug) { - apiRequests.value.push(log); - if (apiRequests.value.length > 128) apiRequests.value.shift(); - } - const promise = new Promise((resolve, reject) => { // Append a credential if ($i) (data as any).i = $i.token; @@ -57,34 +39,10 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s if (res.status === 200) { resolve(body); - if (debug) { - log!.res = markRaw(JSON.parse(JSON.stringify(body))); - log!.state = 'success'; - } } else if (res.status === 204) { resolve(); - if (debug) { - log!.state = 'success'; - } } else { reject(body.error); - if (debug) { - log!.res = markRaw(body.error); - log!.state = 'failed'; - } - - if (defaultStore.state.reportError && !_DEV_) { - Sentry.withScope((scope) => { - scope.setTag('api_endpoint', endpoint); - scope.setContext('api params', data); - scope.setContext('api error info', body.info); - scope.setTag('api_error_id', body.id); - scope.setTag('api_error_code', body.code); - scope.setTag('api_error_kind', body.kind); - scope.setLevel(Sentry.Severity.Error); - Sentry.captureMessage('API error'); - }); - } } }).catch(reject); }); @@ -125,7 +83,7 @@ export function promiseDialog<T extends Promise<any>>( onSuccess(res); } else { success.value = true; - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 1000); } @@ -181,7 +139,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom const id = ++popupIdCount; const dispose = () => { // ã“ã®setTimeoutãŒç„¡ã„ã¨æŒ™å‹•ãŒãŠã‹ã—ããªã‚‹(autocompleteãŒé–‰ã˜ãªããªã‚‹)。Vueã®ãƒã‚°ï¼Ÿ - setTimeout(() => { + window.setTimeout(() => { popups.value = popups.value.filter(popup => popup.id !== id); }, 0); }; @@ -371,7 +329,7 @@ export function select(props: { export function success() { return new Promise((resolve, reject) => { const showing = ref(true); - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 1000); popup(import('@/components/waiting-dialog.vue'), { @@ -583,7 +541,7 @@ export const uploads = ref<{ img: string; }[]>([]); -export function upload(file: File, folder?: any, name?: string) { +export function upload(file: File, folder?: any, name?: string): Promise<Misskey.entities.DriveFile> { if (folder && typeof folder == 'object') folder = folder.id; return new Promise((resolve, reject) => { @@ -612,7 +570,7 @@ export function upload(file: File, folder?: any, name?: string) { const xhr = new XMLHttpRequest(); xhr.open('POST', apiUrl + '/drive/files/create', true); xhr.onload = (ev) => { - if (ev.target == null || ev.target.response == null) { + if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { // TODO: 消ã™ã®ã§ã¯ãªãã¦å†é€ã§ãるよã†ã«ã—ãŸã„ uploads.value = uploads.value.filter(x => x.id != id); diff --git a/packages/client/src/pages/_error_.vue b/packages/client/src/pages/_error_.vue index 2f8f08b5cf..7540995707 100644 --- a/packages/client/src/pages/_error_.vue +++ b/packages/client/src/pages/_error_.vue @@ -1,68 +1,61 @@ <template> -<MkLoading v-if="!loaded" /> +<MkLoading v-if="!loaded"/> <transition :name="$store.state.animation ? 'zoom' : ''" appear> <div v-show="loaded" class="mjndxjch"> <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> - <p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p> - <p v-if="version === meta.version">{{ $ts.pageLoadErrorDescription }}</p> - <p v-else-if="serverIsDead">{{ $ts.serverIsDead }}</p> + <p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.locale.pageLoadError }}</b></p> + <p v-if="meta && (version === meta.version)">{{ i18n.locale.pageLoadErrorDescription }}</p> + <p v-else-if="serverIsDead">{{ i18n.locale.serverIsDead }}</p> <template v-else> - <p>{{ $ts.newVersionOfClientAvailable }}</p> - <p>{{ $ts.youShouldUpgradeClient }}</p> - <MkButton class="button primary" @click="reload">{{ $ts.reload }}</MkButton> + <p>{{ i18n.locale.newVersionOfClientAvailable }}</p> + <p>{{ i18n.locale.youShouldUpgradeClient }}</p> + <MkButton class="button primary" @click="reload">{{ i18n.locale.reload }}</MkButton> </template> - <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p> + <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.locale.troubleshooting }}</MkA></p> <p v-if="error" class="error">ERROR: {{ error }}</p> </div> </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkButton from '@/components/ui/button.vue'; import * as symbols from '@/symbols'; import { version } from '@/config'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, - props: { - error: { - required: false, - } - }, - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.error, - icon: 'fas fa-exclamation-triangle' - }, - loaded: false, - serverIsDead: false, - meta: {} as any, - version, - }; - }, - created() { - os.api('meta', { - detail: false - }).then(meta => { - this.loaded = true; - this.serverIsDead = false; - this.meta = meta; - localStorage.setItem('v', meta.version); - }, () => { - this.loaded = true; - this.serverIsDead = true; - }); - }, - methods: { - reload() { - unisonReload(); - }, +const props = withDefaults(defineProps<{ + error?: Error; +}>(), { +}); + +let loaded = $ref(false); +let serverIsDead = $ref(false); +let meta = $ref<misskey.entities.LiteInstanceMetadata | null>(null); + +os.api('meta', { + detail: false, +}).then(res => { + loaded = true; + serverIsDead = false; + meta = res; + localStorage.setItem('v', res.version); +}, () => { + loaded = true; + serverIsDead = true; +}); + +function reload() { + unisonReload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.error, + icon: 'fas fa-exclamation-triangle', }, }); </script> diff --git a/packages/client/src/pages/_loading_.vue b/packages/client/src/pages/_loading_.vue index 05c6af1cd7..1dd2e46e10 100644 --- a/packages/client/src/pages/_loading_.vue +++ b/packages/client/src/pages/_loading_.vue @@ -2,9 +2,5 @@ <MkLoading/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({}); +<script lang="ts" setup> </script> diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index 855a21e493..8119f33051 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -3,36 +3,39 @@ <MkSpacer :content-max="600" :margin-min="20"> <div class="_formRoot znqjceqz"> <div id="debug"></div> - <div ref="about" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }"> + <div ref="containerEl" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }"> <img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/> <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(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> </div> <div class="_formBlock" style="text-align: center;"> - {{ $ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ $ts.learnMore }}</a> + {{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a> + </div> + <div class="_formBlock" style="text-align: center;"> + <MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly â¤]"/> #Misskey</MkButton> </div> <FormSection> <div class="_formLinks"> <FormLink to="https://github.com/misskey-dev/misskey" external> <template #icon><i class="fas fa-code"></i></template> - {{ $ts._aboutMisskey.source }} + {{ i18n.locale._aboutMisskey.source }} <template #suffix>GitHub</template> </FormLink> <FormLink to="https://crowdin.com/project/misskey" external> <template #icon><i class="fas fa-language"></i></template> - {{ $ts._aboutMisskey.translation }} + {{ i18n.locale._aboutMisskey.translation }} <template #suffix>Crowdin</template> </FormLink> <FormLink to="https://www.patreon.com/syuilo" external> <template #icon><i class="fas fa-hand-holding-medical"></i></template> - {{ $ts._aboutMisskey.donate }} + {{ i18n.locale._aboutMisskey.donate }} <template #suffix>Patreon</template> </FormLink> </div> </FormSection> <FormSection> - <template #label>{{ $ts._aboutMisskey.contributors }}</template> + <template #label>{{ i18n.locale._aboutMisskey.contributors }}</template> <div class="_formLinks"> <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink> <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink> @@ -44,27 +47,30 @@ <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink> <FormLink to="https://github.com/marihachi" external>@marihachi</FormLink> </div> - <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template> + <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.locale._aboutMisskey.allContributors }}</MkLink></template> </FormSection> <FormSection> - <template #label><Mfm text="$[jelly â¤]"/> {{ $ts._aboutMisskey.patrons }}</template> + <template #label><Mfm text="$[jelly â¤]"/> {{ i18n.locale._aboutMisskey.patrons }}</template> <div v-for="patron in patrons" :key="patron">{{ patron }}</div> - <template #caption>{{ $ts._aboutMisskey.morePatrons }}</template> + <template #caption>{{ i18n.locale._aboutMisskey.morePatrons }}</template> </FormSection> </div> </MkSpacer> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { nextTick, onBeforeUnmount } from 'vue'; import { version } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; -import MkKeyValue from '@/components/key-value.vue'; +import MkButton from '@/components/ui/button.vue'; import MkLink from '@/components/link.vue'; import { physics } from '@/scripts/physics'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; +import * as os from '@/os'; const patrons = [ 'ã¾ã£ã¡ã‚ƒã¨ãƒ¼ã«ã‚…', @@ -145,59 +151,53 @@ const patrons = [ 'è‰æš®ã›ã›ã›', ]; -export default defineComponent({ - components: { - FormSection, - FormLink, - MkKeyValue, - MkLink, - }, +let easterEggReady = false; +let easterEggEmojis = $ref([]); +let easterEggEngine = $ref(null); +const containerEl = $ref<HTMLElement>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.aboutMisskey, - icon: null - }, - version, - patrons, - easterEggReady: false, - easterEggEmojis: [], - easterEggEngine: null, - } - }, +function iconLoaded() { + const emojis = defaultStore.state.reactions; + const containerWidth = containerEl.offsetWidth; + for (let i = 0; i < 32; i++) { + easterEggEmojis.push({ + id: i.toString(), + top: -(128 + (Math.random() * 256)), + left: (Math.random() * containerWidth), + emoji: emojis[Math.floor(Math.random() * emojis.length)], + }); + } - beforeUnmount() { - if (this.easterEggEngine) { - this.easterEggEngine.stop(); - } - }, + nextTick(() => { + easterEggReady = true; + }); +} - methods: { - iconLoaded() { - const emojis = this.$store.state.reactions; - const containerWidth = this.$refs.about.offsetWidth; - for (let i = 0; i < 32; i++) { - this.easterEggEmojis.push({ - id: i.toString(), - top: -(128 + (Math.random() * 256)), - left: (Math.random() * containerWidth), - emoji: emojis[Math.floor(Math.random() * emojis.length)], - }); - } +function gravity() { + if (!easterEggReady) return; + easterEggReady = false; + easterEggEngine = physics(containerEl); +} - this.$nextTick(() => { - this.easterEggReady = true; - }); - }, +function iLoveMisskey() { + os.post({ + initialText: 'I $[jelly â¤] #Misskey', + }); +} - gravity() { - if (!this.easterEggReady) return; - this.easterEggReady = false; - this.easterEggEngine = physics(this.$refs.about); - } +onBeforeUnmount(() => { + if (easterEggEngine) { + easterEggEngine.stop(); } }); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.aboutMisskey, + icon: null, + bg: 'var(--bg)', + }, +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/about.vue b/packages/client/src/pages/about.vue index 04f68b7201..a5984c548d 100644 --- a/packages/client/src/pages/about.vue +++ b/packages/client/src/pages/about.vue @@ -24,7 +24,7 @@ </FormSection> <FormSection> - <div class="_inputSplit _formBlock"> + <FormSplit> <MkKeyValue class="_formBlock"> <template #key>{{ $ts.administrator }}</template> <template #value>{{ $instance.maintainerName }}</template> @@ -33,14 +33,14 @@ <template #key>{{ $ts.contact }}</template> <template #value>{{ $instance.maintainerEmail }}</template> </MkKeyValue> - </div> + </FormSplit> <FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" class="_formBlock" external>{{ $ts.tos }}</FormLink> </FormSection> <FormSuspense :p="initStats"> <FormSection> <template #label>{{ $ts.statistics }}</template> - <div class="_inputSplit"> + <FormSplit> <MkKeyValue class="_formBlock"> <template #key>{{ $ts.users }}</template> <template #value>{{ number(stats.originalUsersCount) }}</template> @@ -49,7 +49,7 @@ <template #key>{{ $ts.notes }}</template> <template #value>{{ number(stats.originalNotesCount) }}</template> </MkKeyValue> - </div> + </FormSplit> </FormSection> </FormSuspense> @@ -67,46 +67,33 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import { version, instanceName } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import * as symbols from '@/symbols'; import { host } from '@/config'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkKeyValue, - FormSection, - FormLink, - FormSuspense, - }, +const stats = ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceInfo, - icon: 'fas fa-info-circle' - }, - host, - version, - instanceName, - stats: null, - initStats: () => os.api('stats', { - }).then((stats) => { - this.stats = stats; - }) - } - }, +const initStats = () => os.api('stats', { +}).then((res) => { + stats.value = res; +}); - methods: { - number - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.instanceInfo, + icon: 'fas fa-info-circle', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 8df20097b3..92f93797ce 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -34,27 +34,7 @@ --> <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);"> - <div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap"> - <div class="_content target"> - <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> - <div class="info"> - <MkUserName class="name" :user="report.targetUser"/> - <div class="acct">@{{ acct(report.targetUser) }}</div> - </div> - </div> - <div class="_content"> - <div> - <Mfm :text="report.comment"/> - </div> - <hr> - <div>Reporter: <MkAcct :user="report.reporter"/></div> - <div><MkTime :time="report.createdAt"/></div> - </div> - <div class="_footer"> - <div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div> - <MkButton v-if="!report.resolved" primary @click="resolve(report)">{{ $ts.abuseMarkAsResolved }}</MkButton> - </div> - </div> + <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> </MkPagination> </div> </div> @@ -62,22 +42,21 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; import MkPagination from '@/components/ui/pagination.vue'; -import { acct } from '@/filters/user'; +import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - MkButton, MkInput, MkSelect, MkPagination, + XAbuseReport, }, emits: ['info'], @@ -95,44 +74,20 @@ export default defineComponent({ reporterOrigin: 'combined', targetUserOrigin: 'combined', pagination: { - endpoint: 'admin/abuse-user-reports', + endpoint: 'admin/abuse-user-reports' as const, limit: 10, - params: () => ({ + params: computed(() => ({ state: this.state, reporterOrigin: this.reporterOrigin, targetUserOrigin: this.targetUserOrigin, - }), + })), }, } }, - watch: { - state() { - this.$refs.reports.reload(); - }, - - reporterOrigin() { - this.$refs.reports.reload(); - }, - - targetUserOrigin() { - this.$refs.reports.reload(); - }, - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { - acct, - - resolve(report) { - os.apiWithDialog('admin/resolve-abuse-user-report', { - reportId: report.id, - }).then(() => { - this.$refs.reports.removeItem(item => item.id === report.id); - }); + resolved(reportId) { + this.$refs.reports.removeItem(item => item.id === reportId); }, } }); @@ -142,29 +97,4 @@ export default defineComponent({ .lcixvhis { margin: var(--margin); } - -.bcekxzvu { - > .target { - display: flex; - width: 100%; - box-sizing: border-box; - text-align: left; - align-items: center; - - > .avatar { - width: 42px; - height: 42px; - } - - > .info { - margin-left: 0.3em; - padding: 0 8px; - flex: 1; - - > .name { - font-weight: bold; - } - } - } -} </style> diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index d12ed8563e..8f164caa99 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -23,14 +23,14 @@ <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> </div> --> - <div class="_inputSplit"> + <FormSplit> <MkInput v-model="ad.ratio" type="number"> <template #label>{{ $ts.ratio }}</template> </MkInput> <MkInput v-model="ad.expiresAt" type="date"> <template #label>{{ $ts.expiration }}</template> </MkInput> - </div> + </FormSplit> <MkTextarea v-model="ad.memo" class="_formBlock"> <template #label>{{ $ts.memo }}</template> </MkTextarea> @@ -49,6 +49,7 @@ import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -58,6 +59,7 @@ export default defineComponent({ MkInput, MkTextarea, FormRadios, + FormSplit, }, emits: ['info'], @@ -85,10 +87,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.ads.unshift({ diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index 3614cb1441..a0d720bb29 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -61,10 +61,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.announcements.unshift({ diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 5a97083841..82ab155317 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -1,70 +1,55 @@ <template> -<FormBase> +<div> <FormSuspense :p="init"> - <FormRadios v-model="provider"> - <template #desc><i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}</template> - <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option> - <option value="hcaptcha">hCaptcha</option> - <option value="recaptcha">reCAPTCHA</option> - </FormRadios> + <div class="_formRoot"> + <FormRadios v-model="provider" class="_formBlock"> + <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option> + <option value="hcaptcha">hCaptcha</option> + <option value="recaptcha">reCAPTCHA</option> + </FormRadios> - <template v-if="provider === 'hcaptcha'"> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">hCaptcha</div> - <div class="main"> - <FormInput v-model="hcaptchaSiteKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.hcaptchaSiteKey }}</span> - </FormInput> - <FormInput v-model="hcaptchaSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.hcaptchaSecretKey }}</span> - </FormInput> - </div> - </div> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.preview }}</div> - <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> + <template v-if="provider === 'hcaptcha'"> + <FormInput v-model="hcaptchaSiteKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.hcaptchaSiteKey }}</template> + </FormInput> + <FormInput v-model="hcaptchaSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.hcaptchaSecretKey }}</template> + </FormInput> + <FormSlot class="_formBlock"> + <template #label>{{ $ts.preview }}</template> <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/> - </div> - </div> - </template> - <template v-else-if="provider === 'recaptcha'"> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">reCAPTCHA</div> - <div class="main"> - <FormInput v-model="recaptchaSiteKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.recaptchaSiteKey }}</span> - </FormInput> - <FormInput v-model="recaptchaSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.recaptchaSecretKey }}</span> - </FormInput> - </div> - </div> - <div v-if="recaptchaSiteKey" v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.preview }}</div> - <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> + </FormSlot> + </template> + <template v-else-if="provider === 'recaptcha'"> + <FormInput v-model="recaptchaSiteKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.recaptchaSiteKey }}</template> + </FormInput> + <FormInput v-model="recaptchaSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.recaptchaSecretKey }}</template> + </FormInput> + <FormSlot v-if="recaptchaSiteKey" class="_formBlock"> + <template #label>{{ $ts.preview }}</template> <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/> - </div> - </div> - </template> + </FormSlot> + </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormRadios from '@/components/debobigego/radios.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormRadios from '@/components/form/radios.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSlot from '@/components/form/slot.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -73,11 +58,9 @@ export default defineComponent({ components: { FormRadios, FormInput, - FormBase, - FormGroup, FormButton, - FormInfo, FormSuspense, + FormSlot, MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')), }, @@ -99,10 +82,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index b09f1ad867..3a835eeafa 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -1,28 +1,18 @@ <template> -<FormBase> +<MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> <FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory"> - <FormGroup v-for="table in database" :key="table[0]"> - <template #label>{{ table[0] }}</template> - <FormKeyValueView> - <template #key>Size</template> - <template #value>{{ bytes(table[1].size) }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>Records</template> - <template #value>{{ number(table[1].count) }}</template> - </FormKeyValueView> - </FormGroup> + <MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;"> + <template #key>{{ table[0] }}</template> + <template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template> + </MkKeyValue> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import bytes from '@/filters/bytes'; @@ -31,10 +21,7 @@ import number from '@/filters/number'; export default defineComponent({ components: { FormSuspense, - FormKeyValueView, - FormBase, - FormGroup, - FormLink, + MkKeyValue, }, emits: ['info'], @@ -50,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { bytes, number, } diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 873a853918..6491a453ab 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -1,50 +1,55 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch> + <div class="_formRoot"> + <FormSwitch v-model="enableEmail" class="_formBlock"> + <template #label>{{ $ts.enableEmail }}</template> + <template #caption>{{ $ts.emailConfigInfo }}</template> + </FormSwitch> - <template v-if="enableEmail"> - <FormInput v-model="email" type="email"> - <span>{{ $ts.emailAddress }}</span> - </FormInput> + <template v-if="enableEmail"> + <FormInput v-model="email" type="email" class="_formBlock"> + <template #label>{{ $ts.emailAddress }}</template> + </FormInput> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.smtpConfig }}</div> - <div class="main"> - <FormInput v-model="smtpHost"> - <span>{{ $ts.smtpHost }}</span> - </FormInput> - <FormInput v-model="smtpPort" type="number"> - <span>{{ $ts.smtpPort }}</span> - </FormInput> - <FormInput v-model="smtpUser"> - <span>{{ $ts.smtpUser }}</span> - </FormInput> - <FormInput v-model="smtpPass" type="password"> - <span>{{ $ts.smtpPass }}</span> - </FormInput> - <FormInfo>{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> - <FormSwitch v-model="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch> - </div> - </div> - - <FormButton @click="testEmail">{{ $ts.testEmail }}</FormButton> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSection> + <template #label>{{ $ts.smtpConfig }}</template> + <FormSplit :min-width="280"> + <FormInput v-model="smtpHost" class="_formBlock"> + <template #label>{{ $ts.smtpHost }}</template> + </FormInput> + <FormInput v-model="smtpPort" type="number" class="_formBlock"> + <template #label>{{ $ts.smtpPort }}</template> + </FormInput> + </FormSplit> + <FormSplit :min-width="280"> + <FormInput v-model="smtpUser" class="_formBlock"> + <template #label>{{ $ts.smtpUser }}</template> + </FormInput> + <FormInput v-model="smtpPass" type="password" class="_formBlock"> + <template #label>{{ $ts.smtpPass }}</template> + </FormInput> + </FormSplit> + <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> + <FormSwitch v-model="smtpSecure" class="_formBlock"> + <template #label>{{ $ts.smtpSecure }}</template> + <template #caption>{{ $ts.smtpSecureInfo }}</template> + </FormSwitch> + </FormSection> + </template> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -53,9 +58,8 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSplit, + FormSection, FormInfo, FormSuspense, }, @@ -68,6 +72,16 @@ export default defineComponent({ title: this.$ts.emailServer, icon: 'fas fa-envelope', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + text: this.$ts.testEmail, + handler: this.testEmail, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, enableEmail: false, email: null, @@ -79,10 +93,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index a45d92fa16..2e3903426e 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -95,7 +95,7 @@ export default defineComponent({ }); if (canceled) return; - os.api('admin/emoji/remove', { + os.api('admin/emoji/delete', { id: this.emoji.id }).then(() => { this.$emit('done', { diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index 49277325a0..5b1dfe565a 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -6,11 +6,22 @@ <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> </MkInput> - <MkPagination ref="emojis" :pagination="pagination"> + <MkSwitch v-model="selectMode" style="margin: 8px 0;"> + <template #label>Select mode</template> + </MkSwitch> + <div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkButton inline @click="selectAll">Select all</MkButton> + <MkButton inline @click="setCategoryBulk">Set category</MkButton> + <MkButton inline @click="addTagBulk">Add tag</MkButton> + <MkButton inline @click="removeTagBulk">Remove tag</MkButton> + <MkButton inline @click="setTagBulk">Set tag</MkButton> + <MkButton inline danger @click="delBulk">Delete</MkButton> + </div> + <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> - <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)"> + <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> <img :src="emoji.url" class="img" :alt="emoji.name"/> <div class="body"> <div class="name _monospace">{{ emoji.name }}</div> @@ -23,7 +34,7 @@ </div> <div v-else-if="tab === 'remote'" class="remote"> - <div class="_inputSplit"> + <FormSplit> <MkInput v-model="queryRemote" :debounce="true" type="search"> <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> @@ -31,8 +42,8 @@ <MkInput v-model="host" :debounce="true"> <template #label>{{ $ts.host }}</template> </MkInput> - </div> - <MkPagination ref="remoteEmojis" :pagination="remotePagination"> + </FormSplit> + <MkPagination :pagination="remotePagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> @@ -51,146 +62,233 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent, toRef } from 'vue'; +<script lang="ts" setup> +import { computed, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; -import { selectFiles } from '@/scripts/select-file'; +import MkSwitch from '@/components/form/switch.vue'; +import FormSplit from '@/components/form/split.vue'; +import { selectFile, selectFiles } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkTab, - MkButton, - MkInput, - MkPagination, - }, +const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +const tab = ref('local'); +const query = ref(null); +const queryRemote = ref(null); +const host = ref(null); +const selectMode = ref(false); +const selectedEmojis = ref<string[]>([]); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.customEmojis, - icon: 'fas fa-laugh', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.addEmoji, - handler: this.add, - }, { - icon: 'fas fa-ellipsis-h', - handler: this.menu, - }], - tabs: [{ - active: this.tab === 'local', - title: this.$ts.local, - onClick: () => { this.tab = 'local'; }, - }, { - active: this.tab === 'remote', - title: this.$ts.remote, - onClick: () => { this.tab = 'remote'; }, - },] - })), - tab: 'local', - query: null, - queryRemote: null, - host: '', - pagination: { - endpoint: 'admin/emoji/list', - limit: 30, - params: computed(() => ({ - query: (this.query && this.query !== '') ? this.query : null - })) - }, - remotePagination: { - endpoint: 'admin/emoji/list-remote', - limit: 30, - params: computed(() => ({ - query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null, - host: (this.host && this.host !== '') ? this.host : null - })) - }, - } - }, +const pagination = { + endpoint: 'admin/emoji/list' as const, + limit: 30, + params: computed(() => ({ + query: (query.value && query.value !== '') ? query.value : null, + })), +}; - async mounted() { - this.$emit('info', toRef(this, symbols.PAGE_INFO)); - }, +const remotePagination = { + endpoint: 'admin/emoji/list-remote' as const, + limit: 30, + params: computed(() => ({ + query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, + host: (host.value && host.value !== '') ? host.value : null, + })), +}; - methods: { - async add(e) { - const files = await selectFiles(e.currentTarget || e.target, null); +const selectAll = () => { + if (selectedEmojis.value.length > 0) { + selectedEmojis.value = []; + } else { + selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); + } +}; - const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { - fileId: file.id, - }))); - promise.then(() => { - this.$refs.emojis.reload(); - }); - os.promiseDialog(promise); - }, +const toggleSelect = (emoji) => { + if (selectedEmojis.value.includes(emoji.id)) { + selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id); + } else { + selectedEmojis.value.push(emoji.id); + } +}; - edit(emoji) { - os.popup(import('./emoji-edit-dialog.vue'), { - emoji: emoji - }, { - done: result => { - if (result.updated) { - this.$refs.emojis.replaceItem(item => item.id === emoji.id, { - ...emoji, - ...result.updated - }); - } else if (result.deleted) { - this.$refs.emojis.removeItem(item => item.id === emoji.id); - } - }, - }, 'closed'); - }, +const add = async (ev: MouseEvent) => { + const files = await selectFiles(ev.currentTarget || ev.target, null); - im(emoji) { - os.apiWithDialog('admin/emoji/copy', { - emojiId: emoji.id, - }); - }, + const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { + fileId: file.id, + }))); + promise.then(() => { + emojisPaginationComponent.value.reload(); + }); + os.promiseDialog(promise); +}; - remoteMenu(emoji, ev) { - os.popupMenu([{ - type: 'label', - text: ':' + emoji.name + ':', - }, { - text: this.$ts.import, - icon: 'fas fa-plus', - action: () => { this.im(emoji) } - }], ev.currentTarget || ev.target); +const edit = (emoji) => { + os.popup(import('./emoji-edit-dialog.vue'), { + emoji: emoji + }, { + done: result => { + if (result.updated) { + emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { + ...emoji, + ...result.updated + }); + } else if (result.deleted) { + emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + } }, + }, 'closed'); +}; - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); +const im = (emoji) => { + os.apiWithDialog('admin/emoji/copy', { + emojiId: emoji.id, + }); +}; + +const remoteMenu = (emoji, ev: MouseEvent) => { + os.popupMenu([{ + type: 'label', + text: ':' + emoji.name + ':', + }, { + text: i18n.locale.import, + icon: 'fas fa-plus', + action: () => { im(emoji) } + }], ev.currentTarget || ev.target); +}; + +const menu = (ev: MouseEvent) => { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); } - } + }, { + icon: 'fas fa-upload', + text: i18n.locale.import, + action: async () => { + const file = await selectFile(ev.currentTarget || ev.target); + os.api('admin/emoji/import-zip', { + fileId: file.id, + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.importRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); + } + }], ev.currentTarget || ev.target); +}; + +const setCategoryBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Category', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-category-bulk', { + ids: selectedEmojis.value, + category: result, + }); + emojisPaginationComponent.value.reload(); +}; + +const addTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/add-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const removeTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/remove-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const setTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const delBulk = async () => { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.deleteConfirm, + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/delete-bulk', { + ids: selectedEmojis.value, + }); + emojisPaginationComponent.value.reload(); +}; + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.locale.addEmoji, + handler: add, + }, { + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + tabs: [{ + active: tab.value === 'local', + title: i18n.locale.local, + onClick: () => { tab.value = 'local'; }, + }, { + active: tab.value === 'remote', + title: i18n.locale.remote, + onClick: () => { tab.value = 'remote'; }, + },] + })), }); </script> @@ -210,11 +308,16 @@ export default defineComponent({ > .emoji { display: flex; align-items: center; - padding: 12px; + padding: 11px; text-align: left; + border: solid 1px var(--panel); &:hover { - color: var(--accent); + border-color: var(--inputBorderHover); + } + + &.selected { + border-color: var(--accent); } > .img { diff --git a/packages/client/src/pages/admin/files-settings.vue b/packages/client/src/pages/admin/files-settings.vue deleted file mode 100644 index df25bd0fb2..0000000000 --- a/packages/client/src/pages/admin/files-settings.vue +++ /dev/null @@ -1,93 +0,0 @@ -<template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="cacheRemoteFiles"> - {{ $ts.cacheRemoteFiles }} - <template #desc>{{ $ts.cacheRemoteFilesDescription }}</template> - </FormSwitch> - - <FormSwitch v-model="proxyRemoteFiles"> - {{ $ts.proxyRemoteFiles }} - <template #desc>{{ $ts.proxyRemoteFilesDescription }}</template> - </FormSwitch> - - <FormInput v-model="localDriveCapacityMb" type="number"> - <span>{{ $ts.driveCapacityPerLocalAccount }}</span> - <template #suffix>MB</template> - <template #desc>{{ $ts.inMb }}</template> - </FormInput> - - <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles"> - <span>{{ $ts.driveCapacityPerRemoteAccount }}</span> - <template #suffix>MB</template> - <template #desc>{{ $ts.inMb }}</template> - </FormInput> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; - -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, - FormButton, - FormSuspense, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.files, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - }, - cacheRemoteFiles: false, - proxyRemoteFiles: false, - localDriveCapacityMb: 0, - remoteDriveCapacityMb: 0, - } - }, - - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - async init() { - const meta = await os.api('meta', { detail: true }); - this.cacheRemoteFiles = meta.cacheRemoteFiles; - this.proxyRemoteFiles = meta.proxyRemoteFiles; - this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; - this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; - }, - save() { - os.apiWithDialog('admin/update-meta', { - cacheRemoteFiles: this.cacheRemoteFiles, - proxyRemoteFiles: this.proxyRemoteFiles, - localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), - remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), - }).then(() => { - fetchInstance(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 032e394a66..87dd12f489 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -19,7 +19,7 @@ <option value="local">{{ $ts.local }}</option> <option value="remote">{{ $ts.remote }}</option> </MkSelect> - <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> <template #label>{{ $ts.host }}</template> </MkInput> </div> @@ -55,7 +55,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -95,33 +95,17 @@ export default defineComponent({ type: null, searchHost: '', pagination: { - endpoint: 'admin/drive/files', + endpoint: 'admin/drive/files' as const, limit: 10, - params: () => ({ + params: computed(() => ({ type: (this.type && this.type !== '') ? this.type : null, origin: this.origin, - hostname: (this.hostname && this.hostname !== '') ? this.hostname : null, - }), + hostname: (this.searchHost && this.searchHost !== '') ? this.searchHost : null, + })), }, } }, - watch: { - type() { - this.$refs.files.reload(); - }, - origin() { - this.$refs.files.reload(); - }, - searchHost() { - this.$refs.files.reload(); - }, - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { clear() { os.confirm({ diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index e363d1bd03..350e7defc6 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -3,14 +3,14 @@ <div v-if="!narrow || page == null" class="nav"> <MkHeader :info="header"></MkHeader> - <MkSpacer :content-max="700"> + <MkSpacer :content-max="700" :margin-min="16"> <div class="lxpfedzu"> <div class="banner"> <img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> </div> <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo> + <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> </div> @@ -19,7 +19,7 @@ <div class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </MkStickyContainer> </div> </div> @@ -29,9 +29,6 @@ import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/ui/super-menu.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import MkInfo from '@/components/ui/info.vue'; import { scroll } from '@/scripts/scroll'; import { instance } from '@/instance'; @@ -41,10 +38,7 @@ import { lookupUser } from '@/scripts/lookup-user'; export default defineComponent({ components: { - FormBase, MkSuperMenu, - FormGroup, - FormButton, MkInfo, }, @@ -72,7 +66,9 @@ export default defineComponent({ const narrow = ref(false); const view = ref(null); const el = ref(null); - const onInfo = (viewInfo) => { + const pageChanged = (page) => { + if (page == null) return; + const viewInfo = page[symbols.PAGE_INFO]; if (isRef(viewInfo)) { watch(viewInfo, () => { childInfo.value = viewInfo.value; @@ -163,11 +159,6 @@ export default defineComponent({ to: '/admin/settings', active: page.value === 'settings', }, { - icon: 'fas fa-cloud', - text: i18n.locale.files, - to: '/admin/files-settings', - active: page.value === 'files-settings', - }, { icon: 'fas fa-envelope', text: i18n.locale.emailServer, to: '/admin/email-settings', @@ -183,11 +174,6 @@ export default defineComponent({ to: '/admin/security', active: page.value === 'security', }, { - icon: 'fas fa-bolt', - text: 'ServiceWorker', - to: '/admin/service-worker', - active: page.value === 'service-worker', - }, { icon: 'fas fa-globe', text: i18n.locale.relays, to: '/admin/relays', @@ -236,17 +222,11 @@ export default defineComponent({ case 'database': return defineAsyncComponent(() => import('./database.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue')); - case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue')); case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); case 'security': return defineAsyncComponent(() => import('./security.vue')); - case 'bot-protection': return defineAsyncComponent(() => import('./bot-protection.vue')); - case 'service-worker': return defineAsyncComponent(() => import('./service-worker.vue')); case 'relays': return defineAsyncComponent(() => import('./relays.vue')); case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); - case 'integrations/twitter': return defineAsyncComponent(() => import('./integrations-twitter.vue')); - case 'integrations/github': return defineAsyncComponent(() => import('./integrations-github.vue')); - case 'integrations/discord': return defineAsyncComponent(() => import('./integrations-discord.vue')); case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); @@ -333,7 +313,7 @@ export default defineComponent({ narrow, view, el, - onInfo, + pageChanged, childInfo, pageProps, component, diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 2e899de687..6cadc7df39 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -1,39 +1,29 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormTextarea v-model="blockedHosts"> + <FormTextarea v-model="blockedHosts" class="_formBlock"> <span>{{ $ts.blockedInstances }}</span> - <template #desc>{{ $ts.blockedInstancesDescription }}</template> + <template #caption>{{ $ts.blockedInstancesDescription }}</template> </FormTextarea> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, FormButton, FormTextarea, - FormInfo, FormSuspense, }, @@ -50,10 +40,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/instance.vue b/packages/client/src/pages/admin/instance.vue deleted file mode 100644 index 51fcb8675a..0000000000 --- a/packages/client/src/pages/admin/instance.vue +++ /dev/null @@ -1,291 +0,0 @@ -<template> -<XModalWindow ref="dialog" - :width="520" - :height="500" - @close="$refs.dialog.close()" - @closed="$emit('closed')" -> - <template #header>{{ instance.host }}</template> - <div class="mk-instance-info"> - <div class="_table section"> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.software }}</div> - <div class="_data">{{ instance.softwareName || '?' }}</div> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.version }}</div> - <div class="_data">{{ instance.softwareVersion || '?' }}</div> - </div> - </div> - </div> - <div class="_table data section"> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.registeredAt }}</div> - <div class="_data">{{ new Date(instance.caughtAt).toLocaleString() }} (<MkTime :time="instance.caughtAt"/>)</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.following }}</div> - <button class="_data _textButton" @click="showFollowing()">{{ number(instance.followingCount) }}</button> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.followers }}</div> - <button class="_data _textButton" @click="showFollowers()">{{ number(instance.followersCount) }}</button> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.users }}</div> - <button class="_data _textButton" @click="showUsers()">{{ number(instance.usersCount) }}</button> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.notes }}</div> - <div class="_data">{{ number(instance.notesCount) }}</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.files }}</div> - <div class="_data">{{ number(instance.driveFiles) }}</div> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.storageUsage }}</div> - <div class="_data">{{ bytes(instance.driveUsage) }}</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.latestRequestSentAt }}</div> - <div class="_data"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.latestStatus }}</div> - <div class="_data">{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.latestRequestReceivedAt }}</div> - <div class="_data"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div> - </div> - </div> - </div> - <div class="chart"> - <div class="header"> - <span class="label">{{ $ts.charts }}</span> - <div class="selects"> - <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> - <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option> - <option value="instance-users">{{ $ts._instanceCharts.users }}</option> - <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option> - <option value="instance-notes">{{ $ts._instanceCharts.notes }}</option> - <option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option> - <option value="instance-ff">{{ $ts._instanceCharts.ff }}</option> - <option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option> - <option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option> - <option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option> - <option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option> - <option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> - </MkSelect> - <MkSelect v-model="chartSpan" style="margin: 0;"> - <option value="hour">{{ $ts.perHour }}</option> - <option value="day">{{ $ts.perDay }}</option> - </MkSelect> - </div> - </div> - <div class="chart"> - <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart> - </div> - </div> - <div class="operations section"> - <span class="label">{{ $ts.operations }}</span> - <MkSwitch v-model="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch> - <MkSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch> - <details> - <summary>{{ $ts.deleteAllFiles }}</summary> - <MkButton style="margin: 0.5em 0 0.5em 0;" @click="deleteAllFiles()"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton> - </details> - <details> - <summary>{{ $ts.removeAllFollowing }}</summary> - <MkButton style="margin: 0.5em 0 0.5em 0;" @click="removeAllFollowing()"><i class="fas fa-minus-circle"></i> {{ $ts.removeAllFollowing }}</MkButton> - <MkInfo warn>{{ $t('removeAllFollowingDescription', { host: instance.host }) }}</MkInfo> - </details> - </div> - <details class="metadata section"> - <summary class="label">{{ $ts.metadata }}</summary> - <pre><code>{{ JSON.stringify(instance, null, 2) }}</code></pre> - </details> - </div> -</XModalWindow> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import XModalWindow from '@/components/ui/modal-window.vue'; -import MkSelect from '@/components/form/select.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkInfo from '@/components/ui/info.vue'; -import MkChart from '@/components/chart.vue'; -import bytes from '@/filters/bytes'; -import number from '@/filters/number'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XModalWindow, - MkSelect, - MkButton, - MkSwitch, - MkInfo, - MkChart, - }, - - props: { - instance: { - type: Object, - required: true - } - }, - - emits: ['closed'], - - data() { - return { - isSuspended: this.instance.isSuspended, - chartSrc: 'requests', - chartSpan: 'hour', - }; - }, - - computed: { - meta() { - return this.$instance; - }, - - isBlocked() { - return this.meta && this.meta.blockedHosts && this.meta.blockedHosts.includes(this.instance.host); - } - }, - - watch: { - isSuspended() { - os.api('admin/federation/update-instance', { - host: this.instance.host, - isSuspended: this.isSuspended - }); - }, - }, - - methods: { - changeBlock(e) { - os.api('admin/update-meta', { - blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host) - }); - }, - - removeAllFollowing() { - os.apiWithDialog('admin/federation/remove-all-following', { - host: this.instance.host - }); - }, - - deleteAllFiles() { - os.apiWithDialog('admin/federation/delete-all-files', { - host: this.instance.host - }); - }, - - showFollowing() { - // TODO: ページé·ç§» - }, - - showFollowers() { - // TODO: ページé·ç§» - }, - - showUsers() { - // TODO: ページé·ç§» - }, - - bytes, - - number - } -}); -</script> - -<style lang="scss" scoped> -.mk-instance-info { - overflow: auto; - - > .section { - padding: 16px 32px; - - @media (max-width: 500px) { - padding: 8px 16px; - } - - &:not(:first-child) { - border-top: solid 0.5px var(--divider); - } - } - - > .chart { - border-top: solid 0.5px var(--divider); - padding: 16px 0 12px 0; - - > .header { - padding: 0 32px; - - @media (max-width: 500px) { - padding: 0 16px; - } - - > .label { - font-size: 80%; - opacity: 0.7; - } - - > .selects { - display: flex; - } - } - - > .chart { - padding: 0 16px; - - @media (max-width: 500px) { - padding: 0; - } - } - } - - > .operations { - > .label { - font-size: 80%; - opacity: 0.7; - } - - > .switch { - margin: 16px 0; - } - } - - > .metadata { - > .label { - font-size: 80%; - opacity: 0.7; - } - - > pre > code { - display: block; - max-height: 200px; - overflow: auto; - } - } -} -</style> diff --git a/packages/client/src/pages/admin/integrations-discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 383031f3d1..8fc340150a 100644 --- a/packages/client/src/pages/admin/integrations-discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableDiscordIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableDiscordIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableDiscordIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo> - <FormInput v-model="discordClientId"> + <FormInput v-model="discordClientId" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client ID + <template #label>Client ID</template> </FormInput> - <FormInput v-model="discordClientSecret"> + <FormInput v-model="discordClientSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client Secret + <template #label>Client Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations-github.vue b/packages/client/src/pages/admin/integrations.github.vue index ecb2fd67fa..d9db9c00f1 100644 --- a/packages/client/src/pages/admin/integrations-github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableGithubIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableGithubIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableGithubIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo> - <FormInput v-model="githubClientId"> + <FormInput v-model="githubClientId" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client ID + <template #label>Client ID</template> </FormInput> - <FormInput v-model="githubClientSecret"> + <FormInput v-model="githubClientSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client Secret + <template #label>Client Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations-twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index 1404102c57..1f8074535a 100644 --- a/packages/client/src/pages/admin/integrations-twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableTwitterIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableTwitterIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableTwitterIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo> - <FormInput v-model="twitterConsumerKey"> + <FormInput v-model="twitterConsumerKey" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Consumer Key + <template #label>Consumer Key</template> </FormInput> - <FormInput v-model="twitterConsumerSecret"> + <FormInput v-model="twitterConsumerSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Consumer Secret + <template #label>Consumer Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index c21eebc1c6..91d03fef31 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -1,46 +1,48 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormLink to="/admin/integrations/twitter"> - <i class="fab fa-twitter"></i> Twitter + <FormFolder class="_formBlock"> + <template #icon><i class="fab fa-twitter"></i></template> + <template #label>Twitter</template> <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> - <FormLink to="/admin/integrations/github"> - <i class="fab fa-github"></i> GitHub + <XTwitter/> + </FormFolder> + <FormFolder to="/admin/integrations/github" class="_formBlock"> + <template #icon><i class="fab fa-github"></i></template> + <template #label>GitHub</template> <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> - <FormLink to="/admin/integrations/discord"> - <i class="fab fa-discord"></i> Discord + <XGithub/> + </FormFolder> + <FormFolder to="/admin/integrations/discord" class="_formBlock"> + <template #icon><i class="fab fa-discord"></i></template> + <template #label>Discord</template> <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> + <XDiscord/> + </FormFolder> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormFolder from '@/components/form/folder.vue'; +import FormSecion from '@/components/form/section.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import XTwitter from './integrations.twitter.vue'; +import XGithub from './integrations.github.vue'; +import XDiscord from './integrations.discord.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormLink, - FormInput, - FormBase, - FormGroup, - FormButton, - FormTextarea, - FormInfo, + FormFolder, + FormSecion, FormSuspense, + XTwitter, + XGithub, + XDiscord, }, emits: ['info'], @@ -58,10 +60,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue index 05b64b235c..1de297fd93 100644 --- a/packages/client/src/pages/admin/metrics.vue +++ b/packages/client/src/pages/admin/metrics.vue @@ -76,7 +76,6 @@ import MkwFederation from '../../widgets/federation.vue'; import { version, url } from '@/config'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; -import MkInstanceInfo from './instance.vue'; Chart.register( ArcElement, @@ -101,6 +100,7 @@ const alpha = (hex, a) => { return `rgba(${r}, ${g}, ${b}, ${a})`; }; import * as os from '@/os'; +import { stream } from '@/stream'; export default defineComponent({ components: { @@ -119,7 +119,7 @@ export default defineComponent({ stats: null, serverInfo: null, connection: null, - queueConnection: markRaw(os.stream.useChannel('queueStats')), + queueConnection: markRaw(stream.useChannel('queueStats')), memUsage: 0, chartCpuMem: null, chartNet: null, @@ -150,7 +150,7 @@ export default defineComponent({ os.api('admin/server-info', {}).then(res => { this.serverInfo = res; - this.connection = markRaw(os.stream.useChannel('serverStats')); + this.connection = markRaw(stream.useChannel('serverStats')); this.connection.on('stats', this.onStats); this.connection.on('statsLog', this.onStatsLog); this.connection.send('requestLog', { diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index 8984686b5e..6c5be220f8 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -1,76 +1,78 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch> + <div class="_formRoot"> + <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch> - <template v-if="useObjectStorage"> - <FormInput v-model="objectStorageBaseUrl"> - <span>{{ $ts.objectStorageBaseUrl }}</span> - <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template> - </FormInput> + <template v-if="useObjectStorage"> + <FormInput v-model="objectStorageBaseUrl" class="_formBlock"> + <template #label>{{ $ts.objectStorageBaseUrl }}</template> + <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageBucket"> - <span>{{ $ts.objectStorageBucket }}</span> - <template #desc>{{ $ts.objectStorageBucketDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageBucket" class="_formBlock"> + <template #label>{{ $ts.objectStorageBucket }}</template> + <template #caption>{{ $ts.objectStorageBucketDesc }}</template> + </FormInput> - <FormInput v-model="objectStoragePrefix"> - <span>{{ $ts.objectStoragePrefix }}</span> - <template #desc>{{ $ts.objectStoragePrefixDesc }}</template> - </FormInput> + <FormInput v-model="objectStoragePrefix" class="_formBlock"> + <template #label>{{ $ts.objectStoragePrefix }}</template> + <template #caption>{{ $ts.objectStoragePrefixDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageEndpoint"> - <span>{{ $ts.objectStorageEndpoint }}</span> - <template #desc>{{ $ts.objectStorageEndpointDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageEndpoint" class="_formBlock"> + <template #label>{{ $ts.objectStorageEndpoint }}</template> + <template #caption>{{ $ts.objectStorageEndpointDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageRegion"> - <span>{{ $ts.objectStorageRegion }}</span> - <template #desc>{{ $ts.objectStorageRegionDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageRegion" class="_formBlock"> + <template #label>{{ $ts.objectStorageRegion }}</template> + <template #caption>{{ $ts.objectStorageRegionDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageAccessKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>Access key</span> - </FormInput> + <FormSplit :min-width="280"> + <FormInput v-model="objectStorageAccessKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Access key</template> + </FormInput> - <FormInput v-model="objectStorageSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>Secret key</span> - </FormInput> + <FormInput v-model="objectStorageSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Secret key</template> + </FormInput> + </FormSplit> - <FormSwitch v-model="objectStorageUseSSL"> - {{ $ts.objectStorageUseSSL }} - <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template> - </FormSwitch> + <FormSwitch v-model="objectStorageUseSSL" class="_formBlock"> + <template #label>{{ $ts.objectStorageUseSSL }}</template> + <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageUseProxy"> - {{ $ts.objectStorageUseProxy }} - <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template> - </FormSwitch> + <FormSwitch v-model="objectStorageUseProxy" class="_formBlock"> + <template #label>{{ $ts.objectStorageUseProxy }}</template> + <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageSetPublicRead"> - {{ $ts.objectStorageSetPublicRead }} - </FormSwitch> + <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock"> + <template #label>{{ $ts.objectStorageSetPublicRead }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageS3ForcePathStyle"> - s3ForcePathStyle - </FormSwitch> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock"> + <template #label>s3ForcePathStyle</template> + </FormSwitch> + </template> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormGroup from '@/components/form/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -79,10 +81,10 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormGroup, - FormButton, FormSuspense, + FormSplit, + FormSection, }, emits: ['info'], @@ -93,6 +95,12 @@ export default defineComponent({ title: this.$ts.objectStorage, icon: 'fas fa-cloud', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, useObjectStorage: false, objectStorageBaseUrl: null, @@ -110,10 +118,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index eb214a21c8..6b588e88aa 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -1,34 +1,17 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormGroup> - <FormInput v-model="summalyProxy"> - <template #prefix><i class="fas fa-link"></i></template> - Summaly Proxy URL - </FormInput> - </FormGroup> - <FormGroup> - <FormInput v-model="deeplAuthKey"> - <template #prefix><i class="fas fa-key"></i></template> - DeepL Auth Key - </FormInput> - <FormSwitch v-model="deeplIsPro"> - Pro account - </FormSwitch> - </FormGroup> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + none </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -37,9 +20,7 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSection, FormSuspense, }, @@ -51,29 +32,22 @@ export default defineComponent({ title: this.$ts.other, icon: 'fas fa-cogs', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, - summalyProxy: '', - deeplAuthKey: '', - deeplIsPro: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); - this.summalyProxy = meta.summalyProxy; - this.deeplAuthKey = meta.deeplAuthKey; - this.deeplIsPro = meta.deeplIsPro; }, save() { os.apiWithDialog('admin/update-meta', { - summalyProxy: this.summalyProxy, - deeplAuthKey: this.deeplAuthKey, - deeplIsPro: this.deeplIsPro, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index da5fc0ba6d..b8ae8ad9e1 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -19,7 +19,7 @@ <MkContainer :foldable="true" class="charts"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template> - <div style="padding-top: 12px;"> + <div style="padding: 12px;"> <MkInstanceStats :chart-limit="500" :detailed="true"/> </div> </MkContainer> @@ -67,7 +67,6 @@ <script lang="ts"> import { computed, defineComponent, markRaw, version as vueVersion } from 'vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; import MkInstanceStats from '@/components/instance-stats.vue'; import MkButton from '@/components/ui/button.vue'; import MkSelect from '@/components/form/select.vue'; @@ -78,15 +77,14 @@ import MkQueueChart from '@/components/queue-chart.vue'; import { version, url } from '@/config'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; -import MkInstanceInfo from './instance.vue'; import XMetrics from './metrics.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ components: { MkNumberDiff, - FormKeyValueView, MkInstanceStats, MkContainer, MkFolder, @@ -113,13 +111,11 @@ export default defineComponent({ notesComparedToThePrevDay: null, fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - queueStatsConnection: markRaw(os.stream.useChannel('queueStats')), + queueStatsConnection: markRaw(stream.useChannel('queueStats')), } }, async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('meta', { detail: true }).then(meta => { this.meta = meta; }); @@ -160,9 +156,7 @@ export default defineComponent({ host: q }); } - os.popup(MkInstanceInfo, { - instance: instance - }, {}, 'closed'); + // TODO }, bytes, diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 14ef92a747..5c4fbffa0c 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -1,42 +1,32 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.proxyAccount }}</template> - <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> - </FormKeyValueView> - <template #caption>{{ $ts.proxyAccountDescription }}</template> - </FormGroup> + <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> + </MkKeyValue> - <FormButton primary @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> + <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkKeyValue from '@/components/key-value.vue'; +import FormButton from '@/components/ui/button.vue'; +import MkInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormKeyValueView, - FormInput, - FormBase, - FormGroup, + MkKeyValue, FormButton, - FormTextarea, - FormInfo, + MkInfo, FormSuspense, }, @@ -54,10 +44,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 37a87089cb..522210d933 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -1,28 +1,25 @@ <template> -<FormBase> +<MkSpacer :content-max="800"> <XQueue :connection="connection" domain="inbox"> <template #title>In</template> </XQueue> <XQueue :connection="connection" domain="deliver"> <template #title>Out</template> </XQueue> - <FormButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton> -</FormBase> + <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton> +</MkSpacer> </template> <script lang="ts"> import { defineComponent, markRaw } from 'vue'; import MkButton from '@/components/ui/button.vue'; import XQueue from './queue.chart.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormButton, MkButton, XQueue, }, @@ -36,13 +33,11 @@ export default defineComponent({ icon: 'fas fa-clipboard-list', bg: 'var(--bg)', }, - connection: markRaw(os.stream.useChannel('queueStats')), + connection: markRaw(stream.useChannel('queueStats')), } }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$nextTick(() => { this.connection.send('requestLog', { id: Math.random().toString().substr(2, 8), diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index 3e2f1c6f26..bb840db0a2 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -1,32 +1,27 @@ <template> -<FormBase class="relaycxt"> - <FormButton primary @click="addRelay"><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton> - - <div v-for="relay in relays" :key="relay.inbox" class="_debobigegoItem"> - <div class="_debobigegoPanel" style="padding: 16px;"> - <div>{{ relay.inbox }}</div> - <div>{{ $t(`_relayStatus.${relay.status}`) }}</div> - <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> +<MkSpacer :content-max="800"> + <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel _block" style="padding: 16px;"> + <div>{{ relay.inbox }}</div> + <div class="status"> + <i v-if="relay.status === 'accepted'" class="fas fa-check icon accepted"></i> + <i v-else-if="relay.status === 'rejected'" class="fas fa-ban icon rejected"></i> + <i v-else class="fas fa-clock icon requesting"></i> + <span>{{ $t(`_relayStatus.${relay.status}`) }}</span> </div> + <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> </div> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormButton, MkButton, - MkInput, }, emits: ['info'], @@ -37,6 +32,12 @@ export default defineComponent({ title: this.$ts.relays, icon: 'fas fa-globe', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.addRelay, + handler: this.addRelay, + }], }, relays: [], inbox: '', @@ -47,10 +48,6 @@ export default defineComponent({ this.refresh(); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addRelay() { const { canceled, result: inbox } = await os.inputText({ @@ -94,5 +91,22 @@ export default defineComponent({ </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> diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index adfb2e786c..d069891647 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -1,44 +1,58 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormLink to="/admin/bot-protection"> - <i class="fas fa-shield-alt"></i> {{ $ts.botProtection }} - <template v-if="enableHcaptcha" #suffix>hCaptcha</template> - <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> - </FormLink> + <div class="_formRoot"> + <FormFolder class="_formBlock"> + <template #icon><i class="fas fa-shield-alt"></i></template> + <template #label>{{ $ts.botProtection }}</template> + <template v-if="enableHcaptcha" #suffix>hCaptcha</template> + <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> + <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> - <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch> + <XBotProtection/> + </FormFolder> - <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch> + <FormFolder class="_formBlock"> + <template #label>Summaly Proxy</template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <div class="_formRoot"> + <FormInput v-model="summalyProxy" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>Summaly Proxy URL</template> + </FormInput> + + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> + </FormFolder> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormFolder from '@/components/form/folder.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSection from '@/components/form/section.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import XBotProtection from './bot-protection.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormLink, + FormFolder, FormSwitch, - FormBase, - FormGroup, - FormButton, FormInfo, + FormSection, FormSuspense, + FormButton, + FormInput, + XBotProtection, }, emits: ['info'], @@ -50,30 +64,23 @@ export default defineComponent({ icon: 'fas fa-lock', bg: 'var(--bg)', }, + summalyProxy: '', enableHcaptcha: false, enableRecaptcha: false, - enableRegistration: false, - emailRequiredForSignup: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); + this.summalyProxy = meta.summalyProxy; this.enableHcaptcha = meta.enableHcaptcha; this.enableRecaptcha = meta.enableRecaptcha; - this.enableRegistration = !meta.disableRegistration; - this.emailRequiredForSignup = meta.emailRequiredForSignup; }, - + save() { os.apiWithDialog('admin/update-meta', { - disableRegistration: !this.enableRegistration, - emailRequiredForSignup: this.emailRequiredForSignup, + summalyProxy: this.summalyProxy, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/service-worker.vue b/packages/client/src/pages/admin/service-worker.vue deleted file mode 100644 index f34cb03e4e..0000000000 --- a/packages/client/src/pages/admin/service-worker.vue +++ /dev/null @@ -1,85 +0,0 @@ -<template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableServiceWorker"> - {{ $ts.enableServiceworker }} - <template #desc>{{ $ts.serviceworkerInfo }}</template> - </FormSwitch> - - <template v-if="enableServiceWorker"> - <FormInput v-model="swPublicKey"> - <template #prefix><i class="fas fa-key"></i></template> - Public key - </FormInput> - - <FormInput v-model="swPrivateKey"> - <template #prefix><i class="fas fa-key"></i></template> - Private key - </FormInput> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; - -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, - FormButton, - FormSuspense, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: 'ServiceWorker', - icon: 'fas fa-bolt', - bg: 'var(--bg)', - }, - enableServiceWorker: false, - swPublicKey: null, - swPrivateKey: null, - } - }, - - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - async init() { - const meta = await os.api('meta', { detail: true }); - this.enableServiceWorker = meta.enableServiceWorker; - this.swPublicKey = meta.swPublickey; - this.swPrivateKey = meta.swPrivateKey; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableServiceWorker: this.enableServiceWorker, - swPublicKey: this.swPublicKey, - swPrivateKey: this.swPrivateKey, - }).then(() => { - fetchInstance(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index d88445abdb..a4bac93834 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -1,72 +1,146 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormInput v-model="name"> - <span>{{ $ts.instanceName }}</span> - </FormInput> + <div class="_formRoot"> + <FormInput v-model="name" class="_formBlock"> + <template #label>{{ $ts.instanceName }}</template> + </FormInput> - <FormTextarea v-model="description"> - <span>{{ $ts.instanceDescription }}</span> - </FormTextarea> + <FormTextarea v-model="description" class="_formBlock"> + <template #label>{{ $ts.instanceDescription }}</template> + </FormTextarea> - <FormInput v-model="iconUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.iconUrl }}</span> - </FormInput> + <FormInput v-model="iconUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.iconUrl }}</template> + </FormInput> - <FormInput v-model="bannerUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.bannerUrl }}</span> - </FormInput> + <FormInput v-model="bannerUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.bannerUrl }}</template> + </FormInput> - <FormInput v-model="backgroundImageUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.backgroundImageUrl }}</span> - </FormInput> + <FormInput v-model="backgroundImageUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.backgroundImageUrl }}</template> + </FormInput> - <FormInput v-model="tosUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.tosUrl }}</span> - </FormInput> + <FormInput v-model="tosUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.tosUrl }}</template> + </FormInput> - <FormInput v-model="maintainerName"> - <span>{{ $ts.maintainerName }}</span> - </FormInput> + <FormSplit :min-width="300"> + <FormInput v-model="maintainerName" class="_formBlock"> + <template #label>{{ $ts.maintainerName }}</template> + </FormInput> - <FormInput v-model="maintainerEmail" type="email"> - <template #prefix><i class="fas fa-envelope"></i></template> - <span>{{ $ts.maintainerEmail }}</span> - </FormInput> + <FormInput v-model="maintainerEmail" type="email" class="_formBlock"> + <template #prefix><i class="fas fa-envelope"></i></template> + <template #label>{{ $ts.maintainerEmail }}</template> + </FormInput> + </FormSplit> - <FormTextarea v-model="pinnedUsers"> - <span>{{ $ts.pinnedUsers }}</span> - <template #desc>{{ $ts.pinnedUsersDescription }}</template> - </FormTextarea> + <FormTextarea v-model="pinnedUsers" class="_formBlock"> + <template #label>{{ $ts.pinnedUsers }}</template> + <template #caption>{{ $ts.pinnedUsersDescription }}</template> + </FormTextarea> - <FormInput v-model="maxNoteTextLength" type="number"> - <template #prefix><i class="fas fa-pencil-alt"></i></template> - <span>{{ $ts.maxNoteTextLength }}</span> - </FormInput> + <FormInput v-model="maxNoteTextLength" type="number" class="_formBlock"> + <template #prefix><i class="fas fa-pencil-alt"></i></template> + <template #label>{{ $ts.maxNoteTextLength }}</template> + </FormInput> - <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch> - <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo> + <FormSection> + <FormSwitch v-model="enableRegistration" class="_formBlock"> + <template #label>{{ $ts.enableRegistration }}</template> + </FormSwitch> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSwitch v-model="emailRequiredForSignup" class="_formBlock"> + <template #label>{{ $ts.emailRequiredForSignup }}</template> + </FormSwitch> + </FormSection> + + <FormSection> + <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch> + <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo> + </FormSection> + + <FormSection> + <template #label>{{ $ts.files }}</template> + + <FormSwitch v-model="cacheRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.cacheRemoteFiles }}</template> + <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template> + </FormSwitch> + + <FormSwitch v-model="proxyRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.proxyRemoteFiles }}</template> + <template #caption>{{ $ts.proxyRemoteFilesDescription }}</template> + </FormSwitch> + + <FormSplit :min-width="280"> + <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock"> + <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template> + <template #suffix>MB</template> + <template #caption>{{ $ts.inMb }}</template> + </FormInput> + + <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template> + <template #suffix>MB</template> + <template #caption>{{ $ts.inMb }}</template> + </FormInput> + </FormSplit> + </FormSection> + + <FormSection> + <template #label>ServiceWorker</template> + + <FormSwitch v-model="enableServiceWorker" class="_formBlock"> + <template #label>{{ $ts.enableServiceworker }}</template> + <template #caption>{{ $ts.serviceworkerInfo }}</template> + </FormSwitch> + + <template v-if="enableServiceWorker"> + <FormInput v-model="swPublicKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Public key</template> + </FormInput> + + <FormInput v-model="swPrivateKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Private key</template> + </FormInput> + </template> + </FormSection> + + <FormSection> + <template #label>DeepL Translation</template> + + <FormInput v-model="deeplAuthKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>DeepL Auth Key</template> + </FormInput> + <FormSwitch v-model="deeplIsPro" class="_formBlock"> + <template #label>Pro account</template> + </FormSwitch> + </FormSection> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -75,12 +149,11 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSuspense, FormTextarea, FormInfo, - FormSuspense, + FormSection, + FormSplit, }, emits: ['info'], @@ -91,6 +164,12 @@ export default defineComponent({ title: this.$ts.general, icon: 'fas fa-cog', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, name: null, description: null, @@ -104,13 +183,20 @@ export default defineComponent({ enableLocalTimeline: false, enableGlobalTimeline: false, pinnedUsers: '', + cacheRemoteFiles: false, + proxyRemoteFiles: false, + localDriveCapacityMb: 0, + remoteDriveCapacityMb: 0, + enableRegistration: false, + emailRequiredForSignup: false, + enableServiceWorker: false, + swPublicKey: null, + swPrivateKey: null, + deeplAuthKey: '', + deeplIsPro: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); @@ -126,6 +212,17 @@ export default defineComponent({ this.enableLocalTimeline = !meta.disableLocalTimeline; this.enableGlobalTimeline = !meta.disableGlobalTimeline; this.pinnedUsers = meta.pinnedUsers.join('\n'); + this.cacheRemoteFiles = meta.cacheRemoteFiles; + this.proxyRemoteFiles = meta.proxyRemoteFiles; + this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; + this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; + this.enableRegistration = !meta.disableRegistration; + this.emailRequiredForSignup = meta.emailRequiredForSignup; + this.enableServiceWorker = meta.enableServiceWorker; + this.swPublicKey = meta.swPublickey; + this.swPrivateKey = meta.swPrivateKey; + this.deeplAuthKey = meta.deeplAuthKey; + this.deeplIsPro = meta.deeplIsPro; }, save() { @@ -142,6 +239,17 @@ export default defineComponent({ disableLocalTimeline: !this.enableLocalTimeline, disableGlobalTimeline: !this.enableGlobalTimeline, pinnedUsers: this.pinnedUsers.split('\n'), + cacheRemoteFiles: this.cacheRemoteFiles, + proxyRemoteFiles: this.proxyRemoteFiles, + localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), + remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), + disableRegistration: !this.enableRegistration, + emailRequiredForSignup: this.emailRequiredForSignup, + enableServiceWorker: this.enableServiceWorker, + swPublicKey: this.swPublicKey, + swPrivateKey: this.swPrivateKey, + deeplAuthKey: this.deeplAuthKey, + deeplIsPro: this.deeplIsPro, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index e7a3437167..03e155ddcf 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -30,7 +30,7 @@ <template #prefix>@</template> <template #label>{{ $ts.username }}</template> </MkInput> - <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'" @update:modelValue="$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>{{ $ts.host }}</template> </MkInput> @@ -62,7 +62,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -110,36 +110,20 @@ export default defineComponent({ searchUsername: '', searchHost: '', pagination: { - endpoint: 'admin/show-users', + endpoint: 'admin/show-users' as const, limit: 10, - params: () => ({ + params: computed(() => ({ sort: this.sort, state: this.state, origin: this.origin, username: this.searchUsername, hostname: this.searchHost, - }), + })), offsetMode: true }, } }, - watch: { - sort() { - this.$refs.users.reload(); - }, - state() { - this.$refs.users.reload(); - }, - origin() { - this.$refs.users.reload(); - }, - }, - - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { lookupUser, diff --git a/packages/client/src/pages/advanced-theme-editor.vue b/packages/client/src/pages/advanced-theme-editor.vue deleted file mode 100644 index 9c2423131c..0000000000 --- a/packages/client/src/pages/advanced-theme-editor.vue +++ /dev/null @@ -1,349 +0,0 @@ -<template> -<div class="t9makv94"> - <section class="_section"> - <div class="_content"> - <details> - <summary>{{ $ts.import }}</summary> - <MkTextarea v-model="themeToImport"> - {{ $ts._theme.importInfo }} - </MkTextarea> - <MkButton :disabled="!themeToImport.trim()" @click="importTheme">{{ $ts.import }}</MkButton> - </details> - </div> - </section> - <section class="_section"> - <div class="_content _card _gap"> - <div class="_content"> - <MkInput v-model="name" required><span>{{ $ts.name }}</span></MkInput> - <MkInput v-model="author" required><span>{{ $ts.author }}</span></MkInput> - <MkTextarea v-model="description"><span>{{ $ts.description }}</span></MkTextarea> - <div class="_inputs"> - <div v-text="$ts._theme.base" /> - <MkRadio v-model="baseTheme" value="light">{{ $ts.light }}</MkRadio> - <MkRadio v-model="baseTheme" value="dark">{{ $ts.dark }}</MkRadio> - </div> - </div> - </div> - <div class="_content _card _gap"> - <div class="list-view _content"> - <div v-for="([ k, v ], i) in theme" :key="k" class="item"> - <div class="_inputs"> - <div> - {{ k.startsWith('$') ? `${k} (${$ts._theme.constant})` : $t('_theme.keys.' + k) }} - <button v-if="k.startsWith('$')" class="_button _link" @click="del(i)" v-text="$ts.delete" /> - </div> - <div> - <div class="type" @click="chooseType($event, i)"> - {{ getTypeOf(v) }} <i class="fas fa-chevron-down"></i> - </div> - <!-- default --> - <div v-if="v === null" class="default-value" v-text="baseProps[k]" /> - <!-- color --> - <div v-else-if="typeof v === 'string'" class="color"> - <input type="color" :value="v" @input="colorChanged($event.target.value, i)"/> - <MkInput class="select" :value="v" @update:modelValue="colorChanged($event, i)"/> - </div> - <!-- ref const --> - <MkInput v-else-if="v.type === 'refConst'" v-model="v.key"> - <template #prefix>$</template> - <span>{{ $ts.name }}</span> - </MkInput> - <!-- ref props --> - <MkSelect v-else-if="v.type === 'refProp'" v-model="v.key" class="select"> - <option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option> - </MkSelect> - <!-- func --> - <template v-else-if="v.type === 'func'"> - <MkSelect v-model="v.name" class="select"> - <template #label>{{ $ts._theme.funcKind }}</template> - <option v-for="n in ['alpha', 'darken', 'lighten']" :key="n" :value="n">{{ $t('_theme.' + n) }}</option> - </MkSelect> - <MkInput v-model="v.arg" type="number"><span>{{ $ts._theme.argument }}</span></MkInput> - <MkSelect v-model="v.value" class="select"> - <template #label>{{ $ts._theme.basedProp }}</template> - <option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option> - </MkSelect> - </template> - <!-- CSS --> - <MkInput v-else-if="v.type === 'css'" v-model="v.value"> - <span>CSS</span> - </MkInput> - </div> - </div> - </div> - <MkButton primary @click="addConst">{{ $ts._theme.addConstant }}</MkButton> - </div> - </div> - </section> - <section class="_section"> - <details class="_content"> - <summary>{{ $ts.sample }}</summary> - <MkSample/> - </details> - </section> - <section class="_section"> - <div class="_content"> - <MkButton inline @click="preview">{{ $ts.preview }}</MkButton> - <MkButton inline primary :disabled="!name || !author" @click="save">{{ $ts.save }}</MkButton> - </div> - </section> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import { toUnicode } from 'punycode/'; - -import MkRadio from '@/components/form/radio.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import MkTextarea from '@/components/form/textarea.vue'; -import MkSelect from '@/components/form/select.vue'; -import MkSample from '@/components/sample.vue'; - -import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@/scripts/theme-editor'; -import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } from '@/scripts/theme'; -import { host } from '@/config'; -import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; -import { addTheme } from '@/theme-store'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkRadio, - MkButton, - MkInput, - MkTextarea, - MkSelect, - MkSample, - }, - - async beforeRouteLeave(to, from, next) { - if (this.changed && !(await this.confirm())) { - next(false); - } else { - next(); - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.themeEditor, - icon: 'fas fa-palette', - }, - theme: [] as ThemeViewModel, - name: '', - description: '', - baseTheme: 'light' as 'dark' | 'light', - author: `@${this.$i.username}@${toUnicode(host)}`, - themeToImport: '', - changed: false, - lightTheme, darkTheme, themeProps, - } - }, - - computed: { - baseProps() { - return this.baseTheme === 'light' ? this.lightTheme.props : this.darkTheme.props; - }, - }, - - beforeUnmount() { - window.removeEventListener('beforeunload', this.beforeunload); - }, - - mounted() { - this.init(); - window.addEventListener('beforeunload', this.beforeunload); - const changed = () => this.changed = true; - this.$watch('name', changed); - this.$watch('description', changed); - this.$watch('baseTheme', changed); - this.$watch('author', changed); - this.$watch('theme', changed); - }, - - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, - - async confirm(): Promise<boolean> { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }); - return !canceled; - }, - - init() { - const t: ThemeViewModel = []; - for (const key of themeProps) { - t.push([ key, null ]); - } - this.theme = t; - }, - - async del(i: number) { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('_theme.deleteConstantConfirm', { const: this.theme[i][0] }), - }); - if (canceled) return; - Vue.delete(this.theme, i); - }, - - async addConst() { - const { canceled, result } = await os.inputText({ - title: this.$ts._theme.inputConstantName, - }); - if (canceled) return; - this.theme.push([ '$' + result, '#000000']); - }, - - save() { - const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme); - addTheme(theme); - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: theme.name }) - }); - this.changed = false; - }, - - preview() { - const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme); - try { - applyTheme(theme, false); - } catch (e) { - os.alert({ - type: 'error', - text: e.message - }); - } - }, - - async importTheme() { - if (this.changed && (!await this.confirm())) return; - - try { - const theme = JSON5.parse(this.themeToImport) as Theme; - if (!validateTheme(theme)) throw new Error(this.$ts._theme.invalid); - - this.name = theme.name; - this.description = theme.desc || ''; - this.author = theme.author; - this.baseTheme = theme.base || 'light'; - this.theme = convertToViewModel(theme); - this.themeToImport = ''; - } catch (e) { - os.alert({ - type: 'error', - text: e.message - }); - } - }, - - colorChanged(color: string, i: number) { - this.theme[i] = [this.theme[i][0], color]; - }, - - getTypeOf(v: ThemeValue) { - return v === null - ? this.$ts._theme.defaultValue - : typeof v === 'string' - ? this.$ts._theme.color - : this.$t('_theme.' + v.type); - }, - - async chooseType(e: MouseEvent, i: number) { - const newValue = await this.showTypeMenu(e); - this.theme[i] = [ this.theme[i][0], newValue ]; - }, - - showTypeMenu(e: MouseEvent) { - return new Promise<ThemeValue>((resolve) => { - os.popupMenu([{ - text: this.$ts._theme.defaultValue, - action: () => resolve(null), - }, { - text: this.$ts._theme.color, - action: () => resolve('#000000'), - }, { - text: this.$ts._theme.func, - action: () => resolve({ - type: 'func', name: 'alpha', arg: 1, value: 'accent' - }), - }, { - text: this.$ts._theme.refProp, - action: () => resolve({ - type: 'refProp', key: 'accent', - }), - }, { - text: this.$ts._theme.refConst, - action: () => resolve({ - type: 'refConst', key: '', - }), - }, { - text: 'CSS', - action: () => resolve({ - type: 'css', value: '', - }), - }], e.currentTarget || e.target); - }); - } - } -}); -</script> - -<style lang="scss" scoped> -.t9makv94 { - > ._section { - > ._content { - > .list-view { - > .item { - min-height: 48px; - word-break: break-all; - - &:not(:last-child) { - margin-bottom: 8px; - } - - .select { - margin: 24px 0; - } - - .type { - cursor: pointer; - } - - .default-value { - opacity: 0.6; - pointer-events: none; - user-select: none; - } - - .color { - > input { - display: inline-block; - width: 1.5em; - height: 1.5em; - } - - > div { - margin-left: 8px; - display: inline-block; - } - } - } - } - } - } -} -</style> diff --git a/packages/client/src/pages/announcements.vue b/packages/client/src/pages/announcements.vue index ca94640dda..53727823a4 100644 --- a/packages/client/src/pages/announcements.vue +++ b/packages/client/src/pages/announcements.vue @@ -36,7 +36,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'announcements', + endpoint: 'announcements' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue index 67ab2d8981..c9a8f36844 100644 --- a/packages/client/src/pages/channel.vue +++ b/packages/client/src/pages/channel.vue @@ -67,11 +67,11 @@ export default defineComponent({ channel: null, showBanner: true, pagination: { - endpoint: 'channels/timeline', + endpoint: 'channels/timeline' as const, limit: 10, - params: () => ({ + params: computed(() => ({ channelId: this.channelId, - }) + })) }, }; }, diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue index 48877ab3ec..4e538a6da3 100644 --- a/packages/client/src/pages/channels.vue +++ b/packages/client/src/pages/channels.vue @@ -60,15 +60,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagination: { - endpoint: 'channels/featured', + endpoint: 'channels/featured' as const, noPaging: true, }, followingPagination: { - endpoint: 'channels/followed', + endpoint: 'channels/followed' as const, limit: 5, }, ownedPagination: { - endpoint: 'channels/owned', + endpoint: 'channels/owned' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue index 077a6ac8b5..6b49221d32 100644 --- a/packages/client/src/pages/clip.vue +++ b/packages/client/src/pages/clip.vue @@ -50,11 +50,11 @@ export default defineComponent({ } : null), clip: null, pagination: { - endpoint: 'clips/notes', + endpoint: 'clips/notes' as const, limit: 10, - params: () => ({ + params: computed(() => ({ clipId: this.clipId, - }) + })) }, }; }, diff --git a/packages/client/src/pages/drive.vue b/packages/client/src/pages/drive.vue index f30000367f..1e17bea0cc 100644 --- a/packages/client/src/pages/drive.vue +++ b/packages/client/src/pages/drive.vue @@ -4,27 +4,21 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XDrive from '@/components/drive.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive - }, +let folder = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: computed(() => this.folder ? this.folder.name : this.$ts.drive), - icon: 'fas fa-cloud', - bg: 'var(--bg)', - hideHeader: true, - }, - folder: null, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: folder ? folder.name : i18n.locale.drive, + icon: 'fas fa-cloud', + bg: 'var(--bg)', + hideHeader: true, + })), }); </script> diff --git a/packages/client/src/pages/emojis.emoji.vue b/packages/client/src/pages/emojis.emoji.vue index 5dab72daea..83539ce7a3 100644 --- a/packages/client/src/pages/emojis.emoji.vue +++ b/packages/client/src/pages/emojis.emoji.vue @@ -8,35 +8,29 @@ </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - emoji: { - type: Object, - required: true, - } - }, +const props = defineProps<{ + emoji: Record<string, unknown>; // TODO +}>(); - methods: { - menu(ev) { - os.popupMenu([{ - type: 'label', - text: ':' + this.emoji.name + ':', - }, { - text: this.$ts.copy, - icon: 'fas fa-copy', - action: () => { - copyToClipboard(`:${this.emoji.name}:`); - os.success(); - } - }], ev.currentTarget || ev.target); +function menu(ev) { + os.popupMenu([{ + type: 'label', + text: ':' + props.emoji.name + ':', + }, { + text: i18n.locale.copy, + icon: 'fas fa-copy', + action: () => { + copyToClipboard(`:${props.emoji.name}:`); + os.success(); } - } -}); + }], ev.currentTarget || ev.target); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 2adb5345e2..6577f5abd9 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -4,55 +4,47 @@ </div> </template> -<script lang="ts"> -import { defineComponent, computed } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import XCategory from './emojis.category.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XCategory, - }, +const tab = ref('category'); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.customEmojis, - icon: 'fas fa-laugh', - bg: 'var(--bg)', - actions: [{ - icon: 'fas fa-ellipsis-h', - handler: this.menu - }], - })), - tab: 'category', +function menu(ev) { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); } - }, + }], ev.currentTarget || ev.target); +} - methods: { - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + }, }); </script> diff --git a/packages/client/src/pages/explore.vue b/packages/client/src/pages/explore.vue index a3c3b771f2..04cc3662a7 100644 --- a/packages/client/src/pages/explore.vue +++ b/packages/client/src/pages/explore.vue @@ -156,7 +156,7 @@ export default defineComponent({ sort: '+createdAt', } }, searchPagination: { - endpoint: 'users/search', + endpoint: 'users/search' as const, limit: 10, params: computed(() => (this.searchQuery && this.searchQuery !== '') ? { query: this.searchQuery, @@ -178,7 +178,7 @@ export default defineComponent({ }, tagUsers(): any { return { - endpoint: 'hashtags/users', + endpoint: 'hashtags/users' as const, limit: 30, params: { tag: this.tag, diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue index faab864744..8965b30d60 100644 --- a/packages/client/src/pages/favorites.vue +++ b/packages/client/src/pages/favorites.vue @@ -1,49 +1,49 @@ <template> -<div class="jmelgwjh"> - <div class="body"> - <XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'"/> - </div> -</div> +<MkSpacer :content-max="800"> + <MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotes }}</div> + </div> + </template> + + <template #default="{ items }"> + <XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false"> + <XNote :key="item.id" :note="item.note" :class="$style.note"/> + </XList> + </template> + </MkPagination> +</MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNotes from '@/components/notes.vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { ref } from 'vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import XNote from '@/components/note.vue'; +import XList from '@/components/date-separated-list.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'i/favorites' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.favorites, - icon: 'fas fa-star', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/favorites', - limit: 10, - params: () => ({ - }) - }, - }; +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.favorites, + icon: 'fas fa-star', + bg: 'var(--bg)', }, }); </script> -<style lang="scss" scoped> -.jmelgwjh { - background: var(--bg); - - > .body { - box-sizing: border-box; - max-width: 800px; - margin: 0 auto; - padding: 16px; - } +<style lang="scss" module> +.note { + background: var(--panel); + border-radius: var(--radius); } </style> diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue index 0844c0952f..725c70f0f7 100644 --- a/packages/client/src/pages/featured.vue +++ b/packages/client/src/pages/featured.vue @@ -4,29 +4,22 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'notes/featured' as const, + limit: 10, + offsetMode: true, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.featured, - icon: 'fas fa-fire-alt', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'notes/featured', - limit: 10, - offsetMode: true, - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.featured, + icon: 'fas fa-fire-alt', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 4e5f428ff9..6a4a28b6b4 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -6,7 +6,7 @@ <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.host }}</template> </MkInput> - <div class="_inputSplit" style="margin-top: var(--margin);"> + <FormSplit style="margin-top: var(--margin);"> <MkSelect v-model="state"> <template #label>{{ $ts.state }}</template> <option value="all">{{ $ts.all }}</option> @@ -38,7 +38,7 @@ <option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option> <option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option> </MkSelect> - </div> + </FormSplit> </div> <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> @@ -95,75 +95,50 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; import MkPagination from '@/components/ui/pagination.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSelect, - MkPagination, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.federation, - icon: 'fas fa-globe', - bg: 'var(--bg)', - }, - host: '', - state: 'federating', - sort: '+pubSub', - pagination: { - endpoint: 'federation/instances', - limit: 10, - offsetMode: true, - params: () => ({ - sort: this.sort, - host: this.host != '' ? this.host : null, - ...( - this.state === 'federating' ? { federating: true } : - this.state === 'subscribing' ? { subscribing: true } : - this.state === 'publishing' ? { publishing: true } : - this.state === 'suspended' ? { suspended: true } : - this.state === 'blocked' ? { blocked: true } : - this.state === 'notResponding' ? { notResponding: true } : - {}) - }) - }, - } - }, +let host = $ref(''); +let state = $ref('federating'); +let sort = $ref('+pubSub'); +const pagination = { + endpoint: 'federation/instances' as const, + limit: 10, + offsetMode: true, + params: computed(() => ({ + sort: sort, + host: host != '' ? host : null, + ...( + state === 'federating' ? { federating: true } : + state === 'subscribing' ? { subscribing: true } : + state === 'publishing' ? { publishing: true } : + state === 'suspended' ? { suspended: true } : + state === 'blocked' ? { blocked: true } : + state === 'notResponding' ? { notResponding: true } : + {}) + })) +}; - watch: { - host() { - this.$refs.instances.reload(); - }, - state() { - this.$refs.instances.reload(); - } - }, +function getStatus(instance) { + if (instance.isSuspended) return 'suspended'; + if (instance.isNotResponding) return 'error'; + return 'alive'; +}; - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.federation, + icon: 'fas fa-globe', + bg: 'var(--bg)', }, - - methods: { - getStatus(instance) { - if (instance.isSuspended) return 'suspended'; - if (instance.isNotResponding) return 'error'; - return 'alive'; - }, - } }); </script> diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 54d695091d..764daa0d3e 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -1,6 +1,6 @@ <template> <div> - <MkPagination ref="list" :pagination="pagination" class="mk-follow-requests"> + <MkPagination ref="paginationComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> @@ -8,19 +8,21 @@ </div> </template> <template v-slot="{items}"> - <div v-for="req in items" :key="req.id" class="user _panel"> - <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> - <div class="body"> - <div class="name"> - <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> - <p class="acct">@{{ acct(req.follower) }}</p> - </div> - <div v-if="req.follower.description" class="description" :title="req.follower.description"> - <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> - </div> - <div class="actions"> - <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button> - <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button> + <div class="mk-follow-requests"> + <div v-for="req in items" :key="req.id" class="user _panel"> + <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> + <div class="body"> + <div class="name"> + <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> + <p class="acct">@{{ acct(req.follower) }}</p> + </div> + <div v-if="req.follower.description" class="description" :title="req.follower.description"> + <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> + </div> + <div class="actions"> + <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button> + <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button> + </div> </div> </div> </div> @@ -29,45 +31,39 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import { userPage, acct } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination - }, +const paginationComponent = ref<InstanceType<typeof MkPagination>>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.followRequests, - icon: 'fas fa-user-clock', - }, - pagination: { - endpoint: 'following/requests/list', - limit: 10, - }, - }; - }, +const pagination = { + endpoint: 'following/requests/list' as const, + limit: 10, +}; - methods: { - accept(user) { - os.api('following/requests/accept', { userId: user.id }).then(() => { - this.$refs.list.reload(); - }); - }, - reject(user) { - os.api('following/requests/reject', { userId: user.id }).then(() => { - this.$refs.list.reload(); - }); - }, - userPage, - acct - } +function accept(user) { + os.api('following/requests/accept', { userId: user.id }).then(() => { + paginationComponent.value.reload(); + }); +} + +function reject(user) { + os.api('following/requests/reject', { userId: user.id }).then(() => { + paginationComponent.value.reload(); + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.followRequests, + icon: 'fas fa-user-clock', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue index caca6aed4b..e3fa1a0fcd 100644 --- a/packages/client/src/pages/gallery/edit.vue +++ b/packages/client/src/pages/gallery/edit.vue @@ -1,16 +1,16 @@ <template> -<FormBase> +<div> <FormSuspense :p="init"> <FormInput v-model="title"> - <span>{{ $ts.title }}</span> + <template #label>{{ $ts.title }}</template> </FormInput> <FormTextarea v-model="description" :max="500"> - <span>{{ $ts.description }}</span> + <template #label>{{ $ts.description }}</template> </FormTextarea> <FormGroup> - <div v-for="file in files" :key="file.id" class="_debobigegoItem _debobigegoPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> + <div v-for="file in files" :key="file.id" class="_formGroup wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> <div class="name">{{ file.name }}</div> <button v-tooltip="$ts.remove" class="remove _button" @click="remove(file)"><i class="fas fa-times"></i></button> </div> @@ -24,19 +24,17 @@ <FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { computed, defineComponent } from 'vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormTuple from '@/components/debobigego/tuple.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInput from '@/components/form/input.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormGroup from '@/components/form/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import { selectFiles } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -47,7 +45,6 @@ export default defineComponent({ FormInput, FormTextarea, FormSwitch, - FormBase, FormGroup, FormSuspense, }, diff --git a/packages/client/src/pages/gallery/index.vue b/packages/client/src/pages/gallery/index.vue index cd0d2a40e4..a19d69d5c2 100644 --- a/packages/client/src/pages/gallery/index.vue +++ b/packages/client/src/pages/gallery/index.vue @@ -81,19 +81,19 @@ export default defineComponent({ }, tab: 'explore', recentPostsPagination: { - endpoint: 'gallery/posts', + endpoint: 'gallery/posts' as const, limit: 6, }, popularPostsPagination: { - endpoint: 'gallery/featured', + endpoint: 'gallery/featured' as const, limit: 5, }, myPostsPagination: { - endpoint: 'i/gallery/posts', + endpoint: 'i/gallery/posts' as const, limit: 5, }, likedPostsPagination: { - endpoint: 'i/gallery/likes', + endpoint: 'i/gallery/likes' as const, limit: 5, }, tags: [], @@ -106,7 +106,7 @@ export default defineComponent({ }, tagUsers(): any { return { - endpoint: 'hashtags/users', + endpoint: 'hashtags/users' as const, limit: 30, params: { tag: this.tag, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 096947e6f8..1755c23286 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -1,6 +1,6 @@ <template> <div class="_root"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="post" class="rkxwuolj"> <div class="files"> <div v-for="file in post.files" :key="file.id" class="file"> @@ -93,11 +93,11 @@ export default defineComponent({ }] } : null), otherPostsPagination: { - endpoint: 'users/gallery/posts', + endpoint: 'users/gallery/posts' as const, limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.post.user.id - }) + })), }, post: null, error: null, diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue index 85096d991a..fa36db0659 100644 --- a/packages/client/src/pages/instance-info.vue +++ b/packages/client/src/pages/instance-info.vue @@ -1,70 +1,71 @@ <template> -<FormBase> - <FormGroup v-if="instance"> - <template #label>{{ instance.host }}</template> - <FormGroup> - <div class="_debobigegoItem"> - <div class="_debobigegoPanel fnfelxur"> - <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> - </div> - </div> - <FormKeyValueView> - <template #key>Name</template> - <template #value><span class="_monospace">{{ instance.name || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - </FormGroup> - - <FormButton v-if="$i.isAdmin || $i.isModerator" primary @click="info">{{ $ts.settings }}</FormButton> +<MkSpacer :content-max="600" :margin-min="16" :margin-max="32"> + <div v-if="instance" class="_formRoot"> + <div class="fnfelxur"> + <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> + </div> + <MkKeyValue :copy="host" oneline style="margin: 1em 0;"> + <template #key>Host</template> + <template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>Name</template> + <template #value>{{ instance.name || `(${$ts.unknown})` }}</template> + </MkKeyValue> + <MkKeyValue> + <template #key>{{ $ts.description }}</template> + <template #value>{{ instance.description }}</template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.software }}</template> + <template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }} / {{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.administrator }}</template> + <template #value>{{ instance.maintainerName || `(${$ts.unknown})` }} ({{ instance.maintainerEmail || `(${$ts.unknown})` }})</template> + </MkKeyValue> - <FormTextarea readonly :value="instance.description"> - <span>{{ $ts.description }}</span> - </FormTextarea> + <FormSection v-if="iAmModerator"> + <template #label>Moderation</template> + <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.stopActivityDelivery }}</FormSwitch> + <FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ $ts.blockThisInstance }}</FormSwitch> + </FormSection> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.software }}</template> - <template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.version }}</template> - <template #value><span class="_monospace">{{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.administrator }}</template> - <template #value><span class="_monospace">{{ instance.maintainerName || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.contact }}</template> - <template #value><span class="_monospace">{{ instance.maintainerEmail || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <FormKeyValueView> + <FormSection> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.registeredAt }}</template> + <template #value><MkTime mode="detail" :time="instance.caughtAt"/></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.updatedAt }}</template> + <template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.latestRequestSentAt }}</template> <template #value><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.latestStatus }}</template> <template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.latestRequestReceivedAt }}</template> <template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <FormKeyValueView> + </MkKeyValue> + </FormSection> + + <FormSection> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>Open Registrations</template> <template #value>{{ instance.openRegistrations ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - </FormGroup> - <div class="_debobigegoItem"> - <div class="_debobigegoLabel">{{ $ts.statistics }}</div> - <div class="_debobigegoPanel cmhjzshl"> + </MkKeyValue> + </FormSection> + + <FormSection> + <template #label>{{ $ts.statistics }}</template> + <div class="cmhjzshl"> <div class="selects"> - <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> + <MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;"> <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option> <option value="instance-users">{{ $ts._instanceCharts.users }}</option> <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option> @@ -83,147 +84,100 @@ </MkSelect> </div> <div class="chart"> - <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart> + <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> </div> </div> - </div> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.registeredAt }}</template> - <template #value><MkTime mode="detail" :time="instance.caughtAt"/></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.updatedAt }}</template> - <template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template> - </FormKeyValueView> - </FormGroup> - <FormObjectView tall :value="instance"> - <span>Raw</span> - </FormObjectView> - <FormGroup> + </FormSection> + + <MkObjectView tall :value="instance"> + </MkObjectView> + + <FormSection> <template #label>Well-known resources</template> - <FormLink :to="`https://${host}/.well-known/host-meta`" external>host-meta</FormLink> - <FormLink :to="`https://${host}/.well-known/host-meta.json`" external>host-meta.json</FormLink> - <FormLink :to="`https://${host}/.well-known/nodeinfo`" external>nodeinfo</FormLink> - <FormLink :to="`https://${host}/robots.txt`" external>robots.txt</FormLink> - <FormLink :to="`https://${host}/manifest.json`" external>manifest.json</FormLink> - </FormGroup> - <FormSuspense v-slot="{ result: dns }" :p="dnsPromiseFactory"> - <FormGroup> - <template #label>DNS</template> - <FormKeyValueView v-for="record in dns.a" :key="record"> - <template #key>A</template> - <template #value><span class="_monospace">{{ record }}</span></template> - </FormKeyValueView> - <FormKeyValueView v-for="record in dns.aaaa" :key="record"> - <template #key>AAAA</template> - <template #value><span class="_monospace">{{ record }}</span></template> - </FormKeyValueView> - <FormKeyValueView v-for="record in dns.cname" :key="record"> - <template #key>CNAME</template> - <template #value><span class="_monospace">{{ record }}</span></template> - </FormKeyValueView> - <FormKeyValueView v-for="record in dns.txt"> - <template #key>TXT</template> - <template #value><span class="_monospace">{{ record[0] }}</span></template> - </FormKeyValueView> - </FormGroup> - </FormSuspense> - </FormGroup> -</FormBase> + <FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink> + <FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink> + <FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink> + <FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink> + <FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink> + </FormSection> + </div> +</MkSpacer> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkChart from '@/components/chart.vue'; -import FormObjectView from '@/components/debobigego/object-view.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkObjectView from '@/components/object-view.vue'; +import FormLink from '@/components/form/link.vue'; +import MkLink from '@/components/link.vue'; +import FormSection from '@/components/form/section.vue'; +import MkKeyValue from '@/components/key-value.vue'; import MkSelect from '@/components/form/select.vue'; +import FormSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; -import MkInstanceInfo from '@/pages/admin/instance.vue'; +import { iAmModerator } from '@/account'; -export default defineComponent({ - components: { - FormBase, - FormTextarea, - FormObjectView, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - FormSuspense, - MkSelect, - MkChart, - }, +const props = defineProps<{ + host: string; +}>(); - props: { - host: { - type: String, - required: true - } - }, +let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null); +let instance = $ref<misskey.entities.Instance | null>(null); +let suspended = $ref(false); +let isBlocked = $ref(false); +let chartSrc = $ref('instance-requests'); +let chartSpan = $ref('hour'); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceInfo, - icon: 'fas fa-info-circle', - actions: [{ - text: `https://${this.host}`, - icon: 'fas fa-external-link-alt', - handler: () => { - window.open(`https://${this.host}`, '_blank'); - } - }], - }, - instance: null, - dnsPromiseFactory: () => os.api('federation/dns', { - host: this.host - }), - chartSrc: 'instance-requests', - chartSpan: 'hour', - } - }, +async function fetch() { + meta = await os.api('meta', { detail: true }); + instance = await os.api('federation/show-instance', { + host: props.host, + }); + suspended = instance.isSuspended; + isBlocked = meta.blockedHosts.includes(instance.host); +} - mounted() { - this.fetch(); - }, +async function toggleBlock(ev) { + if (meta == null) return; + await os.api('admin/update-meta', { + blockedHosts: isBlocked ? meta.blockedHosts.concat([instance.host]) : meta.blockedHosts.filter(x => x !== instance.host) + }); +} - methods: { - number, - bytes, +async function toggleSuspend(v) { + await os.api('admin/federation/update-instance', { + host: instance.host, + isSuspended: suspended, + }); +} - async fetch() { - this.instance = await os.api('federation/show-instance', { - host: this.host - }); - }, +fetch(); - info() { - os.popup(MkInstanceInfo, { - instance: this.instance - }, {}, 'closed'); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: props.host, + icon: 'fas fa-info-circle', + bg: 'var(--bg)', + actions: [{ + text: `https://${props.host}`, + icon: 'fas fa-external-link-alt', + handler: () => { + window.open(`https://${props.host}`, '_blank'); + } + }], + }, }); </script> <style lang="scss" scoped> .fnfelxur { - padding: 16px; - > .icon { display: block; - margin: auto; + margin: 0; height: 64px; border-radius: 8px; } @@ -232,7 +186,7 @@ export default defineComponent({ .cmhjzshl { > .selects { display: flex; - padding: 16px; + margin: 0 0 16px 0; } } </style> diff --git a/packages/client/src/pages/mentions.vue b/packages/client/src/pages/mentions.vue index 691d3bd9aa..bda56fc729 100644 --- a/packages/client/src/pages/mentions.vue +++ b/packages/client/src/pages/mentions.vue @@ -4,28 +4,21 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.mentions, - icon: 'fas fa-at', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'notes/mentions', - limit: 10, - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.mentions, + icon: 'fas fa-at', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/messages.vue b/packages/client/src/pages/messages.vue index 9085af9489..8efdc55586 100644 --- a/packages/client/src/pages/messages.vue +++ b/packages/client/src/pages/messages.vue @@ -4,31 +4,24 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, + params: () => ({ + visibility: 'specified' + }), +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.directNotes, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'notes/mentions', - limit: 10, - params: () => ({ - visibility: 'specified' - }) - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.directNotes, + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue index 01f9d4518f..554ebc4b6b 100644 --- a/packages/client/src/pages/messaging/index.vue +++ b/packages/client/src/pages/messaging/index.vue @@ -44,6 +44,7 @@ import * as Acct from 'misskey-js/built/acct'; import MkButton from '@/components/ui/button.vue'; import { acct } from '@/filters/user'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ @@ -66,7 +67,7 @@ export default defineComponent({ }, mounted() { - this.connection = markRaw(os.stream.useChannel('messagingIndex')); + this.connection = markRaw(stream.useChannel('messagingIndex')); this.connection.on('message', this.onMessage); this.connection.on('read', this.onRead); diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue index 8d92c430f1..0fc7c8a5df 100644 --- a/packages/client/src/pages/messaging/messaging-room.form.vue +++ b/packages/client/src/pages/messaging/messaging-room.form.vue @@ -7,7 +7,7 @@ ref="text" v-model="text" :placeholder="$ts.inputMessageHere" - @keypress="onKeypress" + @keydown="onKeydown" @compositionupdate="onCompositionUpdate" @paste="onPaste" ></textarea> @@ -28,6 +28,7 @@ import * as autosize from 'autosize'; import { formatTimeString } from '@/scripts/format-time-string'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; +import { stream } from '@/stream'; import { Autocomplete } from '@/scripts/autocomplete'; import { throttle } from 'throttle-debounce'; @@ -48,7 +49,7 @@ export default defineComponent({ file: null, sending: false, typing: throttle(3000, () => { - os.stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id }); + stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id }); }), }; }, @@ -140,7 +141,7 @@ export default defineComponent({ //#endregion }, - onKeypress(e) { + onKeydown(e) { this.typing(); if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { this.send(); diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index ffc7f7bc0d..a715dad6de 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -24,7 +24,7 @@ </I18n> <MkEllipsis/> </div> - <transition name="fade"> + <transition :name="$store.state.animation ? 'fade' : ''"> <div v-show="showIndicator" class="new-message"> <button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ $ts.newMessageExists }}</button> </div> @@ -43,6 +43,7 @@ import XForm from './messaging-room.form.vue'; import * as Acct from 'misskey-js/built/acct'; import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll'; import * as os from '@/os'; +import { stream } from '@/stream'; import { popout } from '@/scripts/popout'; import * as sound from '@/scripts/sound'; import * as symbols from '@/symbols'; @@ -141,7 +142,7 @@ const Component = defineComponent({ this.group = group; } - this.connection = markRaw(os.stream.useChannel('messaging', { + this.connection = markRaw(stream.useChannel('messaging', { otherparty: this.user ? this.user.id : undefined, group: this.group ? this.group.id : undefined, })); @@ -161,7 +162,7 @@ const Component = defineComponent({ // ã‚‚ã£ã¨è¦‹ã‚‹ã®äº¤å·®æ¤œçŸ¥ã‚’発ç«ã•ã›ãªã„ãŸã‚ã«fetch㯠// スクãƒãƒ¼ãƒ«ãŒçµ‚ã‚ã‚‹ã¾ã§falseã«ã—ã¦ãŠã // scrollendã®ã‚ˆã†ãªã‚¤ãƒ™ãƒ³ãƒˆã¯ãªã„ã®ã§setTimeoutã§ - setTimeout(() => this.fetching = false, 300); + window.setTimeout(() => this.fetching = false, 300); }); }, @@ -299,9 +300,9 @@ const Component = defineComponent({ this.showIndicator = false; }); - if (this.timer) clearTimeout(this.timer); + if (this.timer) window.clearTimeout(this.timer); - this.timer = setTimeout(() => { + this.timer = window.setTimeout(() => { this.showIndicator = false; }, 4000); }, diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue index 173807475a..427c9935c3 100644 --- a/packages/client/src/pages/my-antennas/create.vue +++ b/packages/client/src/pages/my-antennas/create.vue @@ -4,45 +4,37 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { } from 'vue'; import XAntenna from './editor.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - MkButton, - XAntenna, - }, +let draft = $ref({ + name: '', + src: 'all', + userListId: null, + userGroupId: null, + users: [], + keywords: [], + excludeKeywords: [], + withReplies: false, + caseSensitive: false, + withFile: false, + notify: false +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - }, - draft: { - name: '', - src: 'all', - userListId: null, - userGroupId: null, - users: [], - keywords: [], - excludeKeywords: [], - withReplies: false, - caseSensitive: false, - withFile: false, - notify: false - }, - }; - }, +function onAntennaCreated() { + router.push('/my/antennas'); +} - methods: { - onAntennaCreated() { - this.$router.push('/my/antennas'); - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.manageAntennas, + icon: 'fas fa-satellite', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index d185e796c3..7138d269a9 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -38,7 +38,7 @@ export default defineComponent({ } }, pagination: { - endpoint: 'antennas/list', + endpoint: 'antennas/list' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue index a5bbc3fd2d..97b563f6f8 100644 --- a/packages/client/src/pages/my-clips/index.vue +++ b/packages/client/src/pages/my-clips/index.vue @@ -3,7 +3,7 @@ <div class="qtcaoidl"> <MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list"> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="list"> <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap"> <b>{{ item.name }}</b> <div v-if="item.description" class="description">{{ item.description }}</div> @@ -13,71 +13,64 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import i18n from '@/components/global/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.clip, - icon: 'fas fa-paperclip', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - } - }, - pagination: { - endpoint: 'clips/list', - limit: 10, - }, - draft: null, - }; - }, +const pagination = { + endpoint: 'clips/list' as const, + limit: 10, +}; - methods: { - async create() { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; +const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); - os.apiWithDialog('clips/create', result); +async function create() { + const { canceled, result } = await os.form(i18n.locale.createNewClip, { + name: { + type: 'string', + label: i18n.locale.name, }, - - onClipCreated() { - this.$refs.list.reload(); - this.draft = null; + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.locale.description, }, + isPublic: { + type: 'boolean', + label: i18n.locale.public, + default: false, + }, + }); + if (canceled) return; + + os.apiWithDialog('clips/create', result); + + pagingComponent.reload(); +} + +function onClipCreated() { + pagingComponent.reload(); +} - onClipDeleted() { - this.$refs.list.reload(); +function onClipDeleted() { + pagingComponent.reload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.clip, + icon: 'fas fa-paperclip', + bg: 'var(--bg)', + action: { + icon: 'fas fa-plus', + handler: create }, - } + }, }); </script> diff --git a/packages/client/src/pages/my-groups/group.vue b/packages/client/src/pages/my-groups/group.vue index c307f037a6..92c0483af9 100644 --- a/packages/client/src/pages/my-groups/group.vue +++ b/packages/client/src/pages/my-groups/group.vue @@ -1,6 +1,6 @@ <template> <div class="mk-group-page"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="group" class="_section"> <div class="_content" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> <MkButton inline @click="invite()">{{ $ts.invite }}</MkButton> @@ -11,7 +11,7 @@ </div> </transition> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="group" class="_section members _gap"> <div class="_title">{{ $ts.members }}</div> <div class="_content"> diff --git a/packages/client/src/pages/my-groups/index.vue b/packages/client/src/pages/my-groups/index.vue index db5ccde466..4b2b2963a8 100644 --- a/packages/client/src/pages/my-groups/index.vue +++ b/packages/client/src/pages/my-groups/index.vue @@ -87,15 +87,15 @@ export default defineComponent({ })), tab: 'owned', ownedPagination: { - endpoint: 'users/groups/owned', + endpoint: 'users/groups/owned' as const, limit: 10, }, joinedPagination: { - endpoint: 'users/groups/joined', + endpoint: 'users/groups/joined' as const, limit: 10, }, invitationPagination: { - endpoint: 'i/user-group-invites', + endpoint: 'i/user-group-invites' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue index 94a869b9ff..e6fcba1b34 100644 --- a/packages/client/src/pages/my-lists/index.vue +++ b/packages/client/src/pages/my-lists/index.vue @@ -3,7 +3,7 @@ <div class="qkcjvfiv"> <MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="lists _content"> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content"> <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"/> @@ -13,50 +13,41 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import MkAvatars from '@/components/avatars.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - MkAvatars, - }, +const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageLists, - icon: 'fas fa-list-ul', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - }, - }, - pagination: { - endpoint: 'users/lists/list', - limit: 10, - }, - }; - }, +const pagination = { + endpoint: 'users/lists/list' as const, + limit: 10, +}; + +async function create() { + const { canceled, result: name } = await os.inputText({ + title: i18n.locale.enterListName, + }); + if (canceled) return; + await os.apiWithDialog('users/lists/create', { name: name }); + pagingComponent.reload(); +} - methods: { - async create() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts.enterListName, - }); - if (canceled) return; - await os.api('users/lists/create', { name: name }); - this.$refs.list.reload(); - os.success(); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.manageLists, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', + action: { + icon: 'fas fa-plus', + handler: create, }, - } + }, }); </script> diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue index a25522f933..bc24f58431 100644 --- a/packages/client/src/pages/my-lists/list.vue +++ b/packages/client/src/pages/my-lists/list.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="700"> <div class="mk-list-page"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="list" class="_section"> <div class="_content"> <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton> @@ -11,7 +11,7 @@ </div> </transition> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="list" class="_section members _gap"> <div class="_title">{{ $ts.members }}</div> <div class="_content"> diff --git a/packages/client/src/pages/not-found.vue b/packages/client/src/pages/not-found.vue index 92d3f399f7..914fdb9297 100644 --- a/packages/client/src/pages/not-found.vue +++ b/packages/client/src/pages/not-found.vue @@ -7,19 +7,15 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.notFound, - icon: 'fas fa-exclamation-triangle' - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.notFound, + icon: 'fas fa-exclamation-triangle', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index d40082381c..efeea345dc 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="800"> <div class="fcuexfpr"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="note" class="note"> <div v-if="showNext" class="_gap"> <XNotes class="_content" :pagination="next" :no-gap="true"/> @@ -82,21 +82,21 @@ export default defineComponent({ showNext: false, error: null, prev: { - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, untilId: this.note.id, - }) + })), }, next: { reversed: true, - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, sinceId: this.note.id, - }) + })), }, }; }, diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue index 695c54a535..090e80f99a 100644 --- a/packages/client/src/pages/notifications.vue +++ b/packages/client/src/pages/notifications.vue @@ -6,70 +6,62 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { notificationTypes } from 'misskey-js'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotifications - }, +let tab = $ref('all'); +let includeTypes = $ref<string[] | null>(null); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.notifications, - icon: 'fas fa-bell', - bg: 'var(--bg)', - actions: [{ - text: this.$ts.filter, - icon: 'fas fa-filter', - highlighted: this.includeTypes != null, - handler: this.setFilter, - }, { - text: this.$ts.markAllAsRead, - icon: 'fas fa-check', - handler: () => { - os.apiWithDialog('notifications/mark-all-as-read'); - }, - }], - tabs: [{ - active: this.tab === 'all', - title: this.$ts.all, - onClick: () => { this.tab = 'all'; }, - }, { - active: this.tab === 'unread', - title: this.$ts.unread, - onClick: () => { this.tab = 'unread'; }, - },] - })), - tab: 'all', - includeTypes: null, - }; - }, - - methods: { - setFilter(ev) { - const typeItems = notificationTypes.map(t => ({ - text: this.$t(`_notification._types.${t}`), - active: this.includeTypes && this.includeTypes.includes(t), - action: () => { - this.includeTypes = [t]; - } - })); - const items = this.includeTypes != null ? [{ - icon: 'fas fa-times', - text: this.$ts.clear, - action: () => { - this.includeTypes = null; - } - }, null, ...typeItems] : typeItems; - os.popupMenu(items, ev.currentTarget || ev.target); +function setFilter(ev) { + const typeItems = notificationTypes.map(t => ({ + text: i18n.t(`_notification._types.${t}`), + active: includeTypes && includeTypes.includes(t), + action: () => { + includeTypes = [t]; + } + })); + const items = includeTypes != null ? [{ + icon: 'fas fa-times', + text: i18n.locale.clear, + action: () => { + includeTypes = null; } - } + }, null, ...typeItems] : typeItems; + os.popupMenu(items, ev.currentTarget || ev.target); +} + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', + actions: [{ + text: i18n.locale.filter, + icon: 'fas fa-filter', + highlighted: includeTypes != null, + handler: setFilter, + }, { + text: i18n.locale.markAllAsRead, + icon: 'fas fa-check', + handler: () => { + os.apiWithDialog('notifications/mark-all-as-read'); + }, + }], + tabs: [{ + active: tab === 'all', + title: i18n.locale.all, + onClick: () => { tab = 'all'; }, + }, { + active: tab === 'unread', + title: i18n.locale.unread, + onClick: () => { tab = 'unread'; }, + },] + })), }); </script> diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 3a4803c3a3..b2c039a269 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -1,6 +1,6 @@ <template> <MkSpacer :content-max="700"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh"> <div class="_block main"> <!-- @@ -106,11 +106,11 @@ export default defineComponent({ page: null, error: null, otherPostsPagination: { - endpoint: 'users/pages', + endpoint: 'users/pages' as const, limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.page.user.id - }) + })), }, }; }, diff --git a/packages/client/src/pages/pages.vue b/packages/client/src/pages/pages.vue index f1dd64f119..dcccf7f7c4 100644 --- a/packages/client/src/pages/pages.vue +++ b/packages/client/src/pages/pages.vue @@ -62,15 +62,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagesPagination: { - endpoint: 'pages/featured', + endpoint: 'pages/featured' as const, noPaging: true, }, myPagesPagination: { - endpoint: 'i/pages', + endpoint: 'i/pages' as const, limit: 5, }, likedPagesPagination: { - endpoint: 'i/page-likes', + endpoint: 'i/page-likes' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/preview.vue b/packages/client/src/pages/preview.vue index 9d1ebb74ed..8eb4549516 100644 --- a/packages/client/src/pages/preview.vue +++ b/packages/client/src/pages/preview.vue @@ -4,24 +4,18 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkSample from '@/components/sample.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkSample, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.preview, - icon: 'fas fa-eye', - }, - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.preview, + icon: 'fas fa-eye', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index f9a2500840..8ef73858f6 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -1,67 +1,53 @@ <template> -<FormBase v-if="token"> - <FormInput v-model="password" type="password"> - <template #prefix><i class="fas fa-lock"></i></template> - <span>{{ $ts.newPassword }}</span> - </FormInput> - - <FormButton primary @click="save">{{ $ts.save }}</FormButton> -</FormBase> +<MkSpacer v-if="token" :content-max="700" :margin-min="16" :margin-max="32"> + <div class="_formRoot"> + <FormInput v-model="password" type="password" class="_formBlock"> + <template #prefix><i class="fas fa-lock"></i></template> + <template #label>{{ i18n.locale.newPassword }}</template> + </FormInput> + + <FormButton primary class="_formBlock" @click="save">{{ i18n.locale.save }}</FormButton> + </div> +</MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - FormBase, - FormGroup, - FormLink, - FormInput, - FormButton, - }, - - props: { - token: { - type: String, - required: false - } - }, +const props = defineProps<{ + token?: string; +}>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.resetPassword, - icon: 'fas fa-lock' - }, - password: '', - } - }, +let password = $ref(''); - mounted() { - if (this.token == null) { - os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); - this.$router.push('/'); - } - }, +async function save() { + await os.apiWithDialog('reset-password', { + token: props.token, + password: password, + }); + router.push('/'); +} - methods: { - async save() { - await os.apiWithDialog('reset-password', { - token: this.token, - password: this.password, - }); - this.$router.push('/'); - } +onMounted(() => { + if (props.token == null) { + os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); + router.push('/'); } }); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.resetPassword, + icon: 'fas fa-lock', + bg: 'var(--bg)', + }, +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/reversi/game.board.vue b/packages/client/src/pages/reversi/game.board.vue deleted file mode 100644 index eb6fef2799..0000000000 --- a/packages/client/src/pages/reversi/game.board.vue +++ /dev/null @@ -1,528 +0,0 @@ -<template> -<div class="xqnhankfuuilcwvhgsopeqncafzsquya"> - <header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $ts._reversi.white }})</header> - - <div style="overflow: hidden; line-height: 28px;"> - <p v-if="!iAmPlayer && !game.isEnded" class="turn"> - <Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> - <MkEllipsis/> - </p> - <p v-if="logPos != logs.length" class="turn"> - <Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> - </p> - <p v-if="iAmPlayer && !game.isEnded && !isMyTurn()" class="turn1">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p> - <p v-if="iAmPlayer && !game.isEnded && isMyTurn()" class="turn2" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p> - <p v-if="game.isEnded && logPos == logs.length" class="result"> - <template v-if="game.winner"> - <Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/> - <span v-if="game.surrendered != null"> ({{ $ts._reversi.surrendered }})</span> - </template> - <template v-else>{{ $ts._reversi.drawn }}</template> - </p> - </div> - - <div class="board"> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> - <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> - </div> - <div class="flex"> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> - <div v-for="i in game.map.length">{{ i }}</div> - </div> - <div class="cells" :style="cellsStyle"> - <div v-for="(stone, i) in o.board" - :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }" - :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`" - @click="set(i)" - > - <template v-if="$store.state.gamesReversiUseAvatarStones || true"> - <img v-if="stone === true" :src="blackUser.avatarUrl" alt="black"> - <img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white"> - </template> - <template v-else> - <i v-if="stone === true" class="fas fa-circle"></i> - <i v-if="stone === false" class="far fa-circle"></i> - </template> - </div> - </div> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> - <div v-for="i in game.map.length">{{ i }}</div> - </div> - </div> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> - <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> - </div> - </div> - - <p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $ts._reversi.black }}:{{ o.blackCount }} {{ $ts._reversi.white }}:{{ o.whiteCount }} {{ $ts._reversi.total }}:{{ o.blackCount + o.whiteCount }}</p> - - <div v-if="!game.isEnded && iAmPlayer" class="actions"> - <MkButton inline @click="surrender">{{ $ts._reversi.surrender }}</MkButton> - </div> - - <div v-if="game.isEnded" class="player"> - <span>{{ logPos }} / {{ logs.length }}</span> - <div v-if="!autoplaying" class="buttons"> - <MkButton inline :disabled="logPos == 0" @click="logPos = 0"><i class="fas fa-angle-double-left"></i></MkButton> - <MkButton inline :disabled="logPos == 0" @click="logPos--"><i class="fas fa-angle-left"></i></MkButton> - <MkButton inline :disabled="logPos == logs.length" @click="logPos++"><i class="fas fa-angle-right"></i></MkButton> - <MkButton inline :disabled="logPos == logs.length" @click="logPos = logs.length"><i class="fas fa-angle-double-right"></i></MkButton> - </div> - <MkButton :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;" @click="autoplay()"><i class="fas fa-play"></i></MkButton> - </div> - - <div class="info"> - <p v-if="game.isLlotheo">{{ $ts._reversi.isLlotheo }}</p> - <p v-if="game.loopedBoard">{{ $ts._reversi.loopedMap }}</p> - <p v-if="game.canPutEverywhere">{{ $ts._reversi.canPutEverywhere }}</p> - </div> - - <div class="watchers"> - <MkAvatar v-for="user in watchers" :key="user.id" :user="user" class="avatar"/> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as CRC32 from 'crc-32'; -import Reversi, { Color } from '@/scripts/games/reversi/core'; -import { url } from '@/config'; -import MkButton from '@/components/ui/button.vue'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - initGame: { - type: Object, - require: true - }, - connection: { - type: Object, - require: true - }, - }, - - data() { - return { - game: JSON.parse(JSON.stringify(this.initGame)), - o: null as Reversi, - logs: [], - logPos: 0, - watchers: [], - pollingClock: null, - }; - }, - - computed: { - iAmPlayer(): boolean { - if (!this.$i) return false; - return this.game.user1Id == this.$i.id || this.game.user2Id == this.$i.id; - }, - - myColor(): Color { - if (!this.iAmPlayer) return null; - if (this.game.user1Id == this.$i.id && this.game.black == 1) return true; - if (this.game.user2Id == this.$i.id && this.game.black == 2) return true; - return false; - }, - - opColor(): Color { - if (!this.iAmPlayer) return null; - return this.myColor === true ? false : true; - }, - - blackUser(): any { - return this.game.black == 1 ? this.game.user1 : this.game.user2; - }, - - whiteUser(): any { - return this.game.black == 1 ? this.game.user2 : this.game.user1; - }, - - cellsStyle(): any { - return { - 'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`, - 'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)` - }; - } - }, - - watch: { - logPos(v) { - if (!this.game.isEnded) return; - const o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - for (const log of this.logs.slice(0, v)) { - o.put(log.color, log.pos); - } - this.o = o; - //this.$forceUpdate(); - } - }, - - created() { - this.o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - - for (const log of this.game.logs) { - this.o.put(log.color, log.pos); - } - - this.logs = this.game.logs; - this.logPos = this.logs.length; - - // 通信をå–りã“ã¼ã—ã¦ã‚‚ã„ã„よã†ã«å®šæœŸçš„ã«ãƒãƒ¼ãƒªãƒ³ã‚°ã•ã›ã‚‹ - if (this.game.isStarted && !this.game.isEnded) { - this.pollingClock = setInterval(() => { - if (this.game.isEnded) return; - const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join('')); - this.connection.send('check', { - crc32: crc32 - }); - }, 3000); - } - }, - - mounted() { - this.connection.on('set', this.onSet); - this.connection.on('rescue', this.onRescue); - this.connection.on('ended', this.onEnded); - this.connection.on('watchers', this.onWatchers); - }, - - beforeUnmount() { - this.connection.off('set', this.onSet); - this.connection.off('rescue', this.onRescue); - this.connection.off('ended', this.onEnded); - this.connection.off('watchers', this.onWatchers); - - clearInterval(this.pollingClock); - }, - - methods: { - userPage, - - // this.o ãŒãƒªã‚¢ã‚¯ãƒ†ã‚£ãƒ–ã«ãªã£ãŸæŠ˜ã«ã¯computedã«ã§ãã‚‹ - turnUser(): any { - if (this.o.turn === true) { - return this.game.black == 1 ? this.game.user1 : this.game.user2; - } else if (this.o.turn === false) { - return this.game.black == 1 ? this.game.user2 : this.game.user1; - } else { - return null; - } - }, - - // this.o ãŒãƒªã‚¢ã‚¯ãƒ†ã‚£ãƒ–ã«ãªã£ãŸæŠ˜ã«ã¯computedã«ã§ãã‚‹ - isMyTurn(): boolean { - if (!this.iAmPlayer) return false; - if (this.turnUser() == null) return false; - return this.turnUser().id == this.$i.id; - }, - - set(pos) { - if (this.game.isEnded) return; - if (!this.iAmPlayer) return; - if (!this.isMyTurn()) return; - if (!this.o.canPut(this.myColor, pos)) return; - - this.o.put(this.myColor, pos); - - // サウンドをå†ç”Ÿã™ã‚‹ - sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite'); - - this.connection.send('set', { - pos: pos - }); - - this.checkEnd(); - - this.$forceUpdate(); - }, - - onSet(x) { - this.logs.push(x); - this.logPos++; - this.o.put(x.color, x.pos); - this.checkEnd(); - this.$forceUpdate(); - - // サウンドをå†ç”Ÿã™ã‚‹ - if (x.color !== this.myColor) { - sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite'); - } - }, - - onEnded(x) { - this.game = JSON.parse(JSON.stringify(x.game)); - }, - - checkEnd() { - this.game.isEnded = this.o.isEnded; - if (this.game.isEnded) { - if (this.o.winner === true) { - this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id; - this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2; - } else if (this.o.winner === false) { - this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id; - this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1; - } else { - this.game.winnerId = null; - this.game.winner = null; - } - } - }, - - // æ£ã—ã„ã‚²ãƒ¼ãƒ æƒ…å ±ãŒé€ã‚‰ã‚Œã¦ããŸã¨ã - onRescue(game) { - this.game = JSON.parse(JSON.stringify(game)); - - this.o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - - for (const log of this.game.logs) { - this.o.put(log.color, log.pos, true); - } - - this.logs = this.game.logs; - this.logPos = this.logs.length; - - this.checkEnd(); - this.$forceUpdate(); - }, - - onWatchers(users) { - this.watchers = users; - }, - - surrender() { - os.api('games/reversi/games/surrender', { - gameId: this.game.id - }); - }, - - autoplay() { - this.autoplaying = true; - this.logPos = 0; - - setTimeout(() => { - this.logPos = 1; - - let i = 1; - let previousLog = this.game.logs[0]; - const tick = () => { - const log = this.game.logs[i]; - const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime() - setTimeout(() => { - i++; - this.logPos++; - previousLog = log; - - if (i < this.game.logs.length) { - tick(); - } else { - this.autoplaying = false; - } - }, time); - }; - - tick(); - }, 1000); - } - } -}); -</script> - -<style lang="scss" scoped> - -@use "sass:math"; - -.xqnhankfuuilcwvhgsopeqncafzsquya { - text-align: center; - - > .go-index { - position: absolute; - top: 0; - left: 0; - z-index: 1; - width: 42px; - height :42px; - } - - > header { - padding: 8px; - border-bottom: dashed 1px var(--divider); - } - - > .board { - width: calc(100% - 16px); - max-width: 500px; - margin: 0 auto; - - $label-size: 16px; - $gap: 4px; - - > .labels-x { - height: $label-size; - padding: 0 $label-size; - display: flex; - - > * { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.8em; - - &:first-child { - margin-left: -(math.div($gap, 2)); - } - - &:last-child { - margin-right: -(math.div($gap, 2)); - } - } - } - - > .flex { - display: flex; - - > .labels-y { - width: $label-size; - display: flex; - flex-direction: column; - - > * { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - - &:first-child { - margin-top: -(math.div($gap, 2)); - } - - &:last-child { - margin-bottom: -(math.div($gap, 2)); - } - } - } - - > .cells { - flex: 1; - display: grid; - grid-gap: $gap; - - > div { - background: transparent; - border-radius: 6px; - overflow: hidden; - - * { - pointer-events: none; - user-select: none; - } - - &.empty { - border: solid 2px var(--divider); - } - - &.empty.can { - border-color: var(--accent); - } - - &.empty.myTurn { - border-color: var(--divider); - - &.can { - border-color: var(--accent); - cursor: pointer; - - &:hover { - background: var(--accent); - } - } - } - - &.prev { - box-shadow: 0 0 0 4px var(--accent); - } - - &.isEnded { - border-color: var(--divider); - } - - &.none { - border-color: transparent !important; - } - - > svg, > img { - display: block; - width: 100%; - height: 100%; - } - } - } - } - } - - > .status { - margin: 0; - padding: 16px 0; - } - - > .actions { - padding-bottom: 16px; - } - - > .player { - padding: 0 16px 32px 16px; - margin: 0 auto; - max-width: 500px; - - > span { - display: inline-block; - margin: 0 8px; - min-width: 70px; - } - - > .buttons { - display: flex; - - > * { - flex: 1; - } - } - } - - > .watchers { - padding: 0 0 16px 0; - - &:empty { - display: none; - } - - > .avatar { - width: 32px; - height: 32px; - } - } -} -</style> diff --git a/packages/client/src/pages/reversi/game.setting.vue b/packages/client/src/pages/reversi/game.setting.vue deleted file mode 100644 index 28bc598cfd..0000000000 --- a/packages/client/src/pages/reversi/game.setting.vue +++ /dev/null @@ -1,390 +0,0 @@ -<template> -<div class="urbixznjwwuukfsckrwzwsqzsxornqij"> - <header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header> - - <div> - <p>{{ $ts._reversi.gameSettings }}</p> - - <div class="card map _panel"> - <header> - <select v-model="mapName" :placeholder="$ts._reversi.chooseBoard" @change="onMapChange"> - <option v-if="mapName == '-Custom-'" label="-Custom-" :value="mapName"/> - <option :label="$ts.random" :value="null"/> - <optgroup v-for="c in mapCategories" :key="c" :label="c"> - <option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option> - </optgroup> - </select> - </header> - - <div> - <div v-if="game.map == null" class="random"><i class="fas fa-dice"></i></div> - <div v-else class="board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }"> - <div v-for="(x, i) in game.map.join('')" :class="{ none: x == ' ' }" @click="onPixelClick(i, x)"> - <i v-if="x === 'b'" class="fas fa-circle"></i> - <i v-if="x === 'w'" class="far fa-circle"></i> - </div> - </div> - </div> - </div> - - <div class="card _panel"> - <header> - <span>{{ $ts._reversi.blackOrWhite }}</span> - </header> - - <div> - <MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $ts.random }}</MkRadio> - <MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')"> - <I18n :src="$ts._reversi.blackIs" tag="span"> - <template #name> - <b><MkUserName :user="game.user1"/></b> - </template> - </I18n> - </MkRadio> - <MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')"> - <I18n :src="$ts._reversi.blackIs" tag="span"> - <template #name> - <b><MkUserName :user="game.user2"/></b> - </template> - </I18n> - </MkRadio> - </div> - </div> - - <div class="card _panel"> - <header> - <span>{{ $ts._reversi.rules }}</span> - </header> - - <div> - <MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ $ts._reversi.isLlotheo }}</MkSwitch> - <MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ $ts._reversi.loopedMap }}</MkSwitch> - <MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ $ts._reversi.canPutEverywhere }}</MkSwitch> - </div> - </div> - - <div v-if="form" class="card form _panel"> - <header> - <span>{{ $ts._reversi.botSettings }}</span> - </header> - - <div> - <template v-for="item in form"> - <MkSwitch v-if="item.type == 'switch'" :key="item.id" v-model="item.value" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch> - - <div v-if="item.type == 'radio'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio> - </div> - </div> - - <div v-if="item.type == 'slider'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <input v-model="item.value" type="range" :min="item.min" :max="item.max" :step="item.step || 1" @change="onChangeForm(item)"/> - </div> - </div> - - <div v-if="item.type == 'textbox'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <input v-model="item.value" @change="onChangeForm(item)"/> - </div> - </div> - </template> - </div> - </div> - </div> - - <footer class="_acrylic"> - <p class="status"> - <template v-if="isAccepted && isOpAccepted">{{ $ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> - <template v-if="isAccepted && !isOpAccepted">{{ $ts._reversi.waitingForOther }}<MkEllipsis/></template> - <template v-if="!isAccepted && isOpAccepted">{{ $ts._reversi.waitingForMe }}</template> - <template v-if="!isAccepted && !isOpAccepted">{{ $ts._reversi.waitingBoth }}<MkEllipsis/></template> - </p> - - <div class="actions"> - <MkButton inline @click="exit">{{ $ts.cancel }}</MkButton> - <MkButton v-if="!isAccepted" inline primary @click="accept">{{ $ts._reversi.ready }}</MkButton> - <MkButton v-if="isAccepted" inline primary @click="cancel">{{ $ts._reversi.cancelReady }}</MkButton> - </div> - </footer> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as maps from '@/scripts/games/reversi/maps'; -import MkButton from '@/components/ui/button.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkRadio from '@/components/form/radio.vue'; - -export default defineComponent({ - components: { - MkButton, - MkSwitch, - MkRadio, - }, - - props: { - initGame: { - type: Object, - require: true - }, - connection: { - type: Object, - require: true - }, - }, - - data() { - return { - game: this.initGame, - o: null, - isLlotheo: false, - mapName: maps.eighteight.name, - maps: maps, - form: null, - messages: [], - }; - }, - - computed: { - mapCategories(): string[] { - const categories = Object.values(maps).map(x => x.category); - return categories.filter((item, pos) => categories.indexOf(item) == pos); - }, - isAccepted(): boolean { - if (this.game.user1Id == this.$i.id && this.game.user1Accepted) return true; - if (this.game.user2Id == this.$i.id && this.game.user2Accepted) return true; - return false; - }, - isOpAccepted(): boolean { - if (this.game.user1Id != this.$i.id && this.game.user1Accepted) return true; - if (this.game.user2Id != this.$i.id && this.game.user2Accepted) return true; - return false; - } - }, - - created() { - this.connection.on('changeAccepts', this.onChangeAccepts); - this.connection.on('updateSettings', this.onUpdateSettings); - this.connection.on('initForm', this.onInitForm); - this.connection.on('message', this.onMessage); - - if (this.game.user1Id != this.$i.id && this.game.form1) this.form = this.game.form1; - if (this.game.user2Id != this.$i.id && this.game.form2) this.form = this.game.form2; - }, - - beforeUnmount() { - this.connection.off('changeAccepts', this.onChangeAccepts); - this.connection.off('updateSettings', this.onUpdateSettings); - this.connection.off('initForm', this.onInitForm); - this.connection.off('message', this.onMessage); - }, - - methods: { - exit() { - - }, - - accept() { - this.connection.send('accept', {}); - }, - - cancel() { - this.connection.send('cancelAccept', {}); - }, - - onChangeAccepts(accepts) { - this.game.user1Accepted = accepts.user1; - this.game.user2Accepted = accepts.user2; - }, - - updateSettings(key: string) { - this.connection.send('updateSettings', { - key: key, - value: this.game[key] - }); - }, - - onUpdateSettings({ key, value }) { - this.game[key] = value; - if (this.game.map == null) { - this.mapName = null; - } else { - const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join('')); - this.mapName = found ? found.name : '-Custom-'; - } - }, - - onInitForm(x) { - if (x.userId == this.$i.id) return; - this.form = x.form; - }, - - onMessage(x) { - if (x.userId == this.$i.id) return; - this.messages.unshift(x.message); - }, - - onChangeForm(item) { - this.connection.send('updateForm', { - id: item.id, - value: item.value - }); - }, - - onMapChange() { - if (this.mapName == null) { - this.game.map = null; - } else { - this.game.map = Object.values(maps).find(x => x.name == this.mapName).data; - } - this.updateSettings('map'); - }, - - onPixelClick(pos, pixel) { - const x = pos % this.game.map[0].length; - const y = Math.floor(pos / this.game.map[0].length); - const newPixel = - pixel == ' ' ? '-' : - pixel == '-' ? 'b' : - pixel == 'b' ? 'w' : - ' '; - const line = this.game.map[y].split(''); - line[x] = newPixel; - this.game.map[y] = line.join(''); - this.updateSettings('map'); - } - } -}); -</script> - -<style lang="scss" scoped> -.urbixznjwwuukfsckrwzwsqzsxornqij { - text-align: center; - background: var(--bg); - - > header { - padding: 8px; - border-bottom: dashed 1px #c4cdd4; - } - - > div { - padding: 0 16px; - - > .card { - margin: 0 auto 16px auto; - - &.map { - > header { - > select { - width: 100%; - padding: 12px 14px; - background: var(--face); - border: 1px solid var(--inputBorder); - border-radius: 4px; - color: var(--fg); - cursor: pointer; - transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - - &:focus-visible, - &:active { - border-color: var(--accent); - } - } - } - - > div { - > .random { - padding: 32px 0; - font-size: 64px; - color: var(--fg); - opacity: 0.7; - } - - > .board { - display: grid; - grid-gap: 4px; - width: 300px; - height: 300px; - margin: 0 auto; - color: var(--fg); - - > div { - background: transparent; - border: solid 2px var(--divider); - border-radius: 6px; - overflow: hidden; - cursor: pointer; - - * { - pointer-events: none; - user-select: none; - width: 100%; - height: 100%; - } - - &.none { - border-color: transparent; - } - } - } - } - } - - &.form { - > div { - > .card + .card { - margin-top: 16px; - } - - input[type='range'] { - width: 100%; - } - } - } - } - - .card { - max-width: 400px; - - > header { - padding: 18px 20px; - border-bottom: 1px solid var(--divider); - } - - > div { - padding: 20px; - color: var(--fg); - } - } - } - - > footer { - position: sticky; - bottom: 0; - padding: 16px; - border-top: solid 1px var(--divider); - - > .status { - margin: 0 0 16px 0; - } - } -} -</style> diff --git a/packages/client/src/pages/reversi/game.vue b/packages/client/src/pages/reversi/game.vue deleted file mode 100644 index b1ed632904..0000000000 --- a/packages/client/src/pages/reversi/game.vue +++ /dev/null @@ -1,76 +0,0 @@ -<template> -<div v-if="game == null"><MkLoading/></div> -<GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/> -<GameBoard v-else :init-game="game" :connection="connection"/> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import GameSetting from './game.setting.vue'; -import GameBoard from './game.board.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - GameSetting, - GameBoard, - }, - - props: { - gameId: { - type: String, - required: true - }, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._reversi.reversi, - icon: 'fas fa-gamepad' - }, - game: null, - connection: null, - }; - }, - - watch: { - gameId() { - this.fetch(); - } - }, - - mounted() { - this.fetch(); - }, - - beforeUnmount() { - if (this.connection) { - this.connection.dispose(); - } - }, - - methods: { - fetch() { - os.api('games/reversi/games/show', { - gameId: this.gameId - }).then(game => { - this.game = game; - - if (this.connection) { - this.connection.dispose(); - } - this.connection = markRaw(os.stream.useChannel('gamesReversiGame', { - gameId: this.game.id - })); - this.connection.on('started', this.onStarted); - }); - }, - - onStarted(game) { - Object.assign(this.game, game); - }, - } -}); -</script> diff --git a/packages/client/src/pages/reversi/index.vue b/packages/client/src/pages/reversi/index.vue deleted file mode 100644 index 0b118531fc..0000000000 --- a/packages/client/src/pages/reversi/index.vue +++ /dev/null @@ -1,279 +0,0 @@ -<template> -<div v-if="!matching" class="bgvwxkhb"> - <h1>Misskey {{ $ts._reversi.reversi }}</h1> - - <div class="play"> - <MkButton primary round style="margin: var(--margin) auto 0 auto;" @click="match">{{ $ts.invite }}</MkButton> - </div> - - <div class="_section"> - <MkFolder v-if="invitations.length > 0"> - <template #header>{{ $ts.invitations }}</template> - <div class="nfcacttm"> - <button v-for="invitation in invitations" class="invitation _panel _button" tabindex="-1" @click="accept(invitation)"> - <MkAvatar class="avatar" :user="invitation.parent" :show-indicator="true"/> - <span class="name"><b><MkUserName :user="invitation.parent"/></b></span> - <span class="username">@{{ invitation.parent.username }}</span> - <MkTime :time="invitation.createdAt" class="time"/> - </button> - </div> - </MkFolder> - - <MkFolder v-if="myGames.length > 0"> - <template #header>{{ $ts._reversi.myGames }}</template> - <div class="knextgwz"> - <MkA v-for="g in myGames" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> - <div class="players"> - <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> - </div> - <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> - </MkA> - </div> - </MkFolder> - - <MkFolder v-if="games.length > 0"> - <template #header>{{ $ts._reversi.allGames }}</template> - <div class="knextgwz"> - <MkA v-for="g in games" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> - <div class="players"> - <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> - </div> - <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> - </MkA> - </div> - </MkFolder> - </div> -</div> -<div v-else class="sazhgisb"> - <h1> - <I18n :src="$ts.waitingFor" tag="span"> - <template #x> - <b><MkUserName :user="matching"/></b> - </template> - </I18n> - <MkEllipsis/> - </h1> - <div class="cancel"> - <MkButton inline round @click="cancel">{{ $ts.cancel }}</MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import * as os from '@/os'; -import MkButton from '@/components/ui/button.vue'; -import MkFolder from '@/components/ui/folder.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkButton, MkFolder, - }, - - inject: ['navHook'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._reversi.reversi, - icon: 'fas fa-gamepad' - }, - games: [], - gamesFetching: true, - gamesMoreFetching: false, - myGames: [], - matching: null, - invitations: [], - connection: null, - pingClock: null, - }; - }, - - mounted() { - if (this.$i) { - this.connection = markRaw(os.stream.useChannel('gamesReversi')); - - this.connection.on('invited', this.onInvited); - - this.connection.on('matched', this.onMatched); - - this.pingClock = setInterval(() => { - if (this.matching) { - this.connection.send('ping', { - id: this.matching.id - }); - } - }, 3000); - - os.api('games/reversi/games', { - my: true - }).then(games => { - this.myGames = games; - }); - - os.api('games/reversi/invitations').then(invitations => { - this.invitations = this.invitations.concat(invitations); - }); - } - - os.api('games/reversi/games').then(games => { - this.games = games; - this.gamesFetching = false; - }); - }, - - beforeUnmount() { - if (this.connection) { - this.connection.dispose(); - clearInterval(this.pingClock); - } - }, - - methods: { - go(game) { - const url = '/games/reversi/' + game.id; - if (this.navHook) { - this.navHook(url); - } else { - this.$router.push(url); - } - }, - - async match() { - const user = await os.selectUser({ local: true }); - if (user == null) return; - os.api('games/reversi/match', { - userId: user.id - }).then(res => { - if (res == null) { - this.matching = user; - } else { - this.go(res); - } - }); - }, - - cancel() { - this.matching = null; - os.api('games/reversi/match/cancel'); - }, - - accept(invitation) { - os.api('games/reversi/match', { - userId: invitation.parent.id - }).then(game => { - if (game) { - this.go(game); - } - }); - }, - - onMatched(game) { - this.go(game); - }, - - onInvited(invite) { - this.invitations.unshift(invite); - } - } -}); -</script> - -<style lang="scss" scoped> -.bgvwxkhb { - > h1 { - margin: 0; - padding: 24px; - text-align: center; - font-size: 1.5em; - background: linear-gradient(0deg, #43c583, #438881); - color: #fff; - } - - > .play { - text-align: center; - } -} - -.sazhgisb { - text-align: center; -} - -.nfcacttm { - > .invitation { - display: flex; - box-sizing: border-box; - width: 100%; - padding: 16px; - line-height: 32px; - text-align: left; - - > .avatar { - width: 32px; - height: 32px; - margin-right: 8px; - } - - > .name { - margin-right: 8px; - } - - > .username { - margin-right: 8px; - opacity: 0.7; - } - - > .time { - margin-left: auto; - opacity: 0.7; - } - } -} - -.knextgwz { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - - > .game { - > .players { - text-align: center; - padding: 16px; - line-height: 32px; - - > .avatar { - width: 32px; - height: 32px; - - &:first-child { - margin-right: 8px; - } - - &:last-child { - margin-left: 8px; - } - } - } - - > footer { - display: flex; - align-items: baseline; - border-top: solid 0.5px var(--divider); - padding: 6px 8px; - font-size: 0.9em; - - > .state { - &.playing { - color: var(--accent); - } - } - - > .time { - margin-left: auto; - opacity: 0.7; - } - } - } -} -</style> diff --git a/packages/client/src/pages/room/preview.vue b/packages/client/src/pages/room/preview.vue deleted file mode 100644 index b0e600d4fb..0000000000 --- a/packages/client/src/pages/room/preview.vue +++ /dev/null @@ -1,107 +0,0 @@ -<template> -<canvas width="224" height="128"></canvas> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as THREE from 'three'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - selected: null, - objectHeight: 0, - orbitRadius: 5 - }; - }, - - mounted() { - const canvas = this.$el; - - const width = canvas.width; - const height = canvas.height; - - const scene = new THREE.Scene(); - - const renderer = new THREE.WebGLRenderer({ - canvas: canvas, - antialias: true, - alpha: false - }); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.setSize(width, height); - renderer.setClearColor(0x000000); - renderer.autoClear = false; - renderer.shadowMap.enabled = true; - renderer.shadowMap.cullFace = THREE.CullFaceBack; - - const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100); - camera.zoom = 10; - camera.position.x = 0; - camera.position.y = 2; - camera.position.z = 0; - camera.updateProjectionMatrix(); - scene.add(camera); - - const ambientLight = new THREE.AmbientLight(0xffffff, 1); - ambientLight.castShadow = false; - scene.add(ambientLight); - - const light = new THREE.PointLight(0xffffff, 1, 100); - light.position.set(3, 3, 3); - scene.add(light); - - const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222); - scene.add(grid); - - const render = () => { - const timer = Date.now() * 0.0004; - requestAnimationFrame(render); - - camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius; // Math.PI / 6 => 30deg - camera.position.z = Math.cos(timer) * this.orbitRadius; - camera.position.x = Math.sin(timer) * this.orbitRadius; - camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0)); - renderer.render(scene, camera); - }; - - this.selected = selected => { - const obj = selected.clone(); - - // Remove current object - const current = scene.getObjectByName('obj'); - if (current != null) { - scene.remove(current); - } - - // Add new object - obj.name = 'obj'; - obj.position.x = 0; - obj.position.y = 0; - obj.position.z = 0; - obj.rotation.x = 0; - obj.rotation.y = 0; - obj.rotation.z = 0; - obj.traverse(child => { - if (child instanceof THREE.Mesh) { - child.material = child.material.clone(); - return child.material.emissive.setHex(0x000000); - } - }); - const objectBoundingBox = new THREE.Box3().setFromObject(obj); - this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y; - - const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x; - const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z; - - const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect; - this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180)); - - scene.add(obj); - }; - - render(); - }, -}); -</script> diff --git a/packages/client/src/pages/room/room.vue b/packages/client/src/pages/room/room.vue deleted file mode 100644 index eb85d39dc4..0000000000 --- a/packages/client/src/pages/room/room.vue +++ /dev/null @@ -1,279 +0,0 @@ -<template> -<div class="hveuntkp"> - <div v-if="objectSelected" class="controller _section"> - <div class="_content"> - <p class="name">{{ selectedFurnitureName }}</p> - <XPreview ref="preview"/> - <template v-if="selectedFurnitureInfo.props"> - <div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k"> - <p>{{ k }}</p> - <template v-if="selectedFurnitureInfo.props[k] === 'image'"> - <MkButton @click="chooseImage(k, $event)">{{ $ts._rooms.chooseImage }}</MkButton> - </template> - <template v-else-if="selectedFurnitureInfo.props[k] === 'color'"> - <input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/> - </template> - </div> - </template> - </div> - <div class="_content"> - <MkButton inline :primary="isTranslateMode" @click="translate()"><i class="fas fa-arrows-alt"></i> {{ $ts._rooms.translate }}</MkButton> - <MkButton inline :primary="isRotateMode" @click="rotate()"><i class="fas fa-undo"></i> {{ $ts._rooms.rotate }}</MkButton> - <MkButton v-if="isTranslateMode || isRotateMode" inline @click="exit()"><i class="fas fa-ban"></i> {{ $ts._rooms.exit }}</MkButton> - </div> - <div class="_content"> - <MkButton @click="remove()"><i class="fas fa-trash-alt"></i> {{ $ts._rooms.remove }}</MkButton> - </div> - </div> - - <div v-if="isMyRoom" class="menu _section"> - <div class="_content"> - <MkButton @click="add()"><i class="fas fa-box-open"></i> {{ $ts._rooms.addFurniture }}</MkButton> - </div> - <div class="_content"> - <MkSelect :model-value="roomType" @update:modelValue="updateRoomType($event)"> - <template #label>{{ $ts._rooms.roomType }}</template> - <option value="default">{{ $ts._rooms._roomType.default }}</option> - <option value="washitsu">{{ $ts._rooms._roomType.washitsu }}</option> - </MkSelect> - <label v-if="roomType === 'default'"> - <span>{{ $ts._rooms.carpetColor }}</span> - <input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/> - </label> - </div> - <div class="_content"> - <MkButton inline :disabled="!changed" primary @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton inline @click="clear()"><i class="fas fa-broom"></i> {{ $ts._rooms.clear }}</MkButton> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent } from 'vue'; -import { Room } from '@/scripts/room/room'; -import * as Acct from 'misskey-js/built/acct'; -import XPreview from './preview.vue'; -const storeItems = require('@/scripts/room/furnitures.json5'); -import { query as urlQuery } from '@/scripts/url'; -import MkButton from '@/components/ui/button.vue'; -import MkSelect from '@/components/form/select.vue'; -import { selectFile } from '@/scripts/select-file'; -import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; -import * as symbols from '@/symbols'; - -let room: Room; - -export default defineComponent({ - components: { - XPreview, - MkButton, - MkSelect, - }, - - beforeRouteLeave(to, from, next) { - if (this.changed) { - os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }).then(({ canceled }) => { - if (canceled) { - next(false); - } else { - next(); - } - }); - } else { - next(); - } - }, - - props: { - acct: { - type: String, - required: true - }, - }, - - data() { - return { - [symbols.PAGE_INFO]: computed(() => this.user ? { - title: this.$ts.room, - avatar: this.user, - } : null), - user: null, - objectSelected: false, - selectedFurnitureName: null, - selectedFurnitureInfo: null, - selectedFurnitureProps: null, - roomType: null, - carpetColor: null, - isTranslateMode: false, - isRotateMode: false, - isMyRoom: false, - changed: false, - }; - }, - - async mounted() { - window.addEventListener('beforeunload', this.beforeunload); - - this.user = await os.api('users/show', { - ...Acct.parse(this.acct) - }); - - this.isMyRoom = this.$i && (this.$i.id === this.user.id); - - const roomInfo = await os.api('room/show', { - userId: this.user.id - }); - - this.roomType = roomInfo.roomType; - this.carpetColor = roomInfo.carpetColor; - - room = new Room(this.user, this.isMyRoom, roomInfo, this.$el, { - graphicsQuality: ColdDeviceStorage.get('roomGraphicsQuality'), - onChangeSelect: obj => { - this.objectSelected = obj != null; - if (obj) { - const f = room.findFurnitureById(obj.name); - this.selectedFurnitureName = this.$t('_rooms._furnitures.' + f.type); - this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type); - this.selectedFurnitureProps = f.props - ? JSON.parse(JSON.stringify(f.props)) // Disable reactivity - : null; - this.$nextTick(() => { - this.$refs.preview.selected(obj); - }); - } - }, - useOrthographicCamera: ColdDeviceStorage.get('roomUseOrthographicCamera'), - }); - }, - - beforeUnmount() { - room.destroy(); - window.removeEventListener('beforeunload', this.beforeunload); - }, - - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, - - async add() { - const { canceled, result: id } = await os.select({ - title: this.$ts._rooms.addFurniture, - items: storeItems.map(item => ({ - value: item.id, text: this.$t('_rooms._furnitures.' + item.id) - })) - }); - if (canceled) return; - room.addFurniture(id); - this.changed = true; - }, - - remove() { - this.isTranslateMode = false; - this.isRotateMode = false; - room.removeFurniture(); - this.changed = true; - }, - - save() { - os.api('room/update', { - room: room.getRoomInfo() - }).then(() => { - this.changed = false; - os.success(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message - }); - }); - }, - - clear() { - os.confirm({ - type: 'warning', - text: this.$ts._rooms.clearConfirm, - }).then(({ canceled }) => { - if (canceled) return; - room.removeAllFurnitures(); - this.changed = true; - }); - }, - - chooseImage(key, e) { - selectFile(e.currentTarget || e.target, null).then(file => { - room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`); - this.$refs.preview.selected(room.getSelectedObject()); - this.changed = true; - }); - }, - - updateColor(key, ev) { - room.updateProp(key, ev.target.value); - this.$refs.preview.selected(room.getSelectedObject()); - this.changed = true; - }, - - updateCarpetColor(ev) { - room.updateCarpetColor(ev.target.value); - this.carpetColor = ev.target.value; - this.changed = true; - }, - - updateRoomType(type) { - room.changeRoomType(type); - this.roomType = type; - this.changed = true; - }, - - translate() { - if (this.isTranslateMode) { - this.exit(); - } else { - this.isRotateMode = false; - this.isTranslateMode = true; - room.enterTransformMode('translate'); - } - this.changed = true; - }, - - rotate() { - if (this.isRotateMode) { - this.exit(); - } else { - this.isTranslateMode = false; - this.isRotateMode = true; - room.enterTransformMode('rotate'); - } - this.changed = true; - }, - - exit() { - this.isTranslateMode = false; - this.isRotateMode = false; - room.exitTransformMode(); - this.changed = true; - } - } -}); -</script> - -<style lang="scss" scoped> -.hveuntkp { - position: relative; - min-height: 500px; - - > ::v-deep(canvas) { - display: block; - } -} -</style> diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue index 85d19bb255..ce2b7035da 100644 --- a/packages/client/src/pages/search.vue +++ b/packages/client/src/pages/search.vue @@ -6,37 +6,31 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + query: string; + channel?: string; +}>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: computed(() => this.$t('searchWith', { q: this.$route.query.q })), - icon: 'fas fa-search', - }, - pagination: { - endpoint: 'notes/search', - limit: 10, - params: () => ({ - query: this.$route.query.q, - channelId: this.$route.query.channel, - }) - }, - }; - }, +const pagination = { + endpoint: 'notes/search' as const, + limit: 10, + params: computed(() => ({ + query: props.query, + channelId: props.channel, + })) +}; - watch: { - $route() { - (this.$refs.notes as any).reload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.t('searchWith', { q: props.query }), + icon: 'fas fa-search', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue index cffd10a0ee..10599d99ff 100644 --- a/packages/client/src/pages/settings/2fa.vue +++ b/packages/client/src/pages/settings/2fa.vue @@ -71,9 +71,6 @@ import MkButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; import MkInput from '@/components/form/input.vue'; import MkSwitch from '@/components/form/switch.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index f3d5e2f2c3..c98ad056f6 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -1,144 +1,135 @@ <template> -<FormBase> - <FormKeyValueView> +<div class="_formRoot"> + <MkKeyValue> <template #key>ID</template> <template #value><span class="_monospace">{{ $i.id }}</span></template> - </FormKeyValueView> + </MkKeyValue> - <FormGroup> - <FormKeyValueView> + <FormSection> + <MkKeyValue> <template #key>{{ $ts.registeredDate }}</template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> - </FormKeyValueView> - </FormGroup> + </MkKeyValue> + </FormSection> - <FormGroup v-if="stats"> + <FormSection v-if="stats"> <template #label>{{ $ts.statistics }}</template> - <FormKeyValueView> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.notesCount }}</template> <template #value>{{ number(stats.notesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.repliesCount }}</template> <template #value>{{ number(stats.repliesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.renotesCount }}</template> <template #value>{{ number(stats.renotesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.repliedCount }}</template> <template #value>{{ number(stats.repliedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.renotedCount }}</template> <template #value>{{ number(stats.renotedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pollVotesCount }}</template> <template #value>{{ number(stats.pollVotesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pollVotedCount }}</template> <template #value>{{ number(stats.pollVotedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.sentReactionsCount }}</template> <template #value>{{ number(stats.sentReactionsCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.receivedReactionsCount }}</template> <template #value>{{ number(stats.receivedReactionsCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.noteFavoritesCount }}</template> <template #value>{{ number(stats.noteFavoritesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followingCount }}</template> <template #value>{{ number(stats.followingCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template> <template #value>{{ number(stats.localFollowingCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template> <template #value>{{ number(stats.remoteFollowingCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followersCount }}</template> <template #value>{{ number(stats.followersCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template> <template #value>{{ number(stats.localFollowersCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template> <template #value>{{ number(stats.remoteFollowersCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pageLikesCount }}</template> <template #value>{{ number(stats.pageLikesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pageLikedCount }}</template> <template #value>{{ number(stats.pageLikedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.driveFilesCount }}</template> <template #value>{{ number(stats.driveFilesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.driveUsage }}</template> <template #value>{{ bytes(stats.driveUsage) }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.reversiCount }}</template> - <template #value>{{ number(stats.reversiCount) }}</template> - </FormKeyValueView> - </FormGroup> + </MkKeyValue> + </FormSection> - <FormGroup> + <FormSection> <template #label>{{ $ts.other }}</template> - <FormKeyValueView> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>emailVerified</template> <template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>twoFactorEnabled</template> <template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>securityKeys</template> <template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>usePasswordLessLogin</template> <template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isModerator</template> <template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isAdmin</template> <template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - </FormGroup> -</FormBase> + </MkKeyValue> + </FormSection> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; +import FormSection from '@/components/form/section.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; @@ -146,13 +137,8 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, + FormSection, + MkKeyValue, }, emits: ['info'], @@ -168,8 +154,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('users/stats', { userId: this.$i.id }).then(stats => { diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index 2d1e0eff4e..c795ede8ac 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -1,41 +1,35 @@ <template> -<FormBase> +<div class="_formRoot"> <FormSuspense :p="init"> <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton> - <div v-for="account in accounts" :key="account.id" class="_debobigegoItem _button" @click="menu(account, $event)"> - <div class="_debobigegoPanel lcjjdxlm"> - <div class="avatar"> - <MkAvatar :user="account" class="avatar"/> + <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)"> + <div class="avatar"> + <MkAvatar :user="account" class="avatar"/> + </div> + <div class="body"> + <div class="name"> + <MkUserName :user="account"/> </div> - <div class="body"> - <div class="name"> - <MkUserName :user="account"/> - </div> - <div class="acct"> - <MkAcct :user="account"/> - </div> + <div class="acct"> + <MkAcct :user="account"/> </div> </div> </div> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { getAccounts, addAccount, login } from '@/account'; export default defineComponent({ components: { - FormBase, FormSuspense, FormButton, }, @@ -59,10 +53,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { menu(account, ev) { os.popupMenu([{ diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue index 30a4902a15..20ff2a8d96 100644 --- a/packages/client/src/pages/settings/api.vue +++ b/packages/client/src/pages/settings/api.vue @@ -1,25 +1,20 @@ <template> -<FormBase> - <FormButton primary @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> - <FormLink to="/settings/apps">{{ $ts.manageAccessTokens }}</FormLink> - <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink> -</FormBase> +<div class="_formRoot"> + <FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> + <FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink> + <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormLink from '@/components/form/link.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormButton, FormLink, }, @@ -37,10 +32,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { generateToken() { os.popup(import('@/components/token-generate-window.vue'), {}, { diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index b5fe4e0aed..9c0fa8a54d 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -1,5 +1,5 @@ <template> -<FormBase> +<div class="_formRoot"> <FormPagination ref="list" :pagination="pagination"> <template #empty> <div class="_fullinfo"> @@ -8,7 +8,7 @@ </div> </template> <template v-slot="{items}"> - <div v-for="token in items" :key="token.id" class="_debobigegoPanel bfomjevm"> + <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> @@ -34,23 +34,17 @@ </div> </template> </FormPagination> -</FormBase> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormPagination from '@/components/debobigego/pagination.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormPagination from '@/components/ui/pagination.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormPagination, }, @@ -64,7 +58,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'i/apps', + endpoint: 'i/apps' as const, limit: 100, params: { sort: '+lastUsedAt' @@ -73,10 +67,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { revoke(token) { os.api('i/revoke-token', { tokenId: token.id }).then(() => { diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue index 155956923c..556ee30c1d 100644 --- a/packages/client/src/pages/settings/custom-css.vue +++ b/packages/client/src/pages/settings/custom-css.vue @@ -1,25 +1,18 @@ <template> -<FormBase> - <FormInfo warn>{{ $ts.customCssWarn }}</FormInfo> +<div class="_formRoot"> + <FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo> - <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;"> - <span>{{ $ts.local }}</span> + <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;"> + <template #label>CSS</template> </FormTextarea> -</FormBase> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormInfo from '@/components/debobigego/info.vue'; +import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; import { defaultStore } from '@/store'; @@ -27,12 +20,6 @@ import { defaultStore } from '@/store'; export default defineComponent({ components: { FormTextarea, - FormSelect, - FormRadios, - FormBase, - FormGroup, - FormLink, - FormButton, FormInfo, }, @@ -50,8 +37,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$watch('localCustomCss', this.apply); }, diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue index bc82b0ca84..46b90d3d1a 100644 --- a/packages/client/src/pages/settings/deck.vue +++ b/packages/client/src/pages/settings/deck.vue @@ -1,42 +1,41 @@ <template> -<FormBase> +<div class="_formRoot"> <FormGroup> <template #label>{{ $ts.defaultNavigationBehaviour }}</template> <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch> </FormGroup> - <FormSwitch v-model="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> + <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> - <FormRadios v-model="columnAlign"> - <template #desc>{{ $ts._deck.columnAlign }}</template> + <FormRadios v-model="columnAlign" class="_formBlock"> + <template #label>{{ $ts._deck.columnAlign }}</template> <option value="left">{{ $ts.left }}</option> <option value="center">{{ $ts.center }}</option> </FormRadios> - <FormRadios v-model="columnHeaderHeight"> - <template #desc>{{ $ts._deck.columnHeaderHeight }}</template> + <FormRadios v-model="columnHeaderHeight" class="_formBlock"> + <template #label>{{ $ts._deck.columnHeaderHeight }}</template> <option :value="42">{{ $ts.narrow }}</option> <option :value="45">{{ $ts.medium }}</option> <option :value="48">{{ $ts.wide }}</option> </FormRadios> - <FormInput v-model="columnMargin" type="number"> - <span>{{ $ts._deck.columnMargin }}</span> + <FormInput v-model="columnMargin" type="number" class="_formBlock"> + <template #label>{{ $ts._deck.columnMargin }}</template> <template #suffix>px</template> </FormInput> - <FormLink @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> -</FormBase> + <FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormRadios from '@/components/debobigego/radios.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormLink from '@/components/form/link.vue'; +import FormRadios from '@/components/form/radios.vue'; +import FormInput from '@/components/form/input.vue'; +import FormGroup from '@/components/form/group.vue'; import { deckStore } from '@/ui/deck/deck-store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; @@ -48,7 +47,6 @@ export default defineComponent({ FormLink, FormInput, FormRadios, - FormBase, FormGroup, }, @@ -85,10 +83,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async setProfile() { const { canceled, result: name } = await os.inputText({ diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue index 6ce8d6509c..7edc81a309 100644 --- a/packages/client/src/pages/settings/delete-account.vue +++ b/packages/client/src/pages/settings/delete-account.vue @@ -1,28 +1,23 @@ <template> -<FormBase> - <FormInfo warn>{{ $ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo>{{ $ts._accountDelete.sendEmail }}</FormInfo> - <FormButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> +<div class="_formRoot"> + <FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo> + <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> <FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton> -</FormBase> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; -import { debug } from '@/config'; import { signout } from '@/account'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormButton, - FormGroup, FormInfo, }, @@ -35,14 +30,9 @@ export default defineComponent({ icon: 'fas fa-exclamation-triangle', bg: 'var(--bg)', }, - debug, } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async deleteAccount() { { diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index 9ab99c6efe..f1016ebd84 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -5,7 +5,7 @@ <div class="_formBlock uawsfosz"> <div class="meter"><div :style="meterStyle"></div></div> </div> - <div class="_inputSplit _formBlock"> + <FormSplit> <MkKeyValue class="_formBlock"> <template #key>{{ $ts.capacity }}</template> <template #value>{{ bytes(capacity, 1) }}</template> @@ -14,7 +14,7 @@ <template #key>{{ $ts.inUse }}</template> <template #value>{{ bytes(usage, 1) }}</template> </MkKeyValue> - </div> + </FormSplit> </FormSection> <FormSection> @@ -38,6 +38,7 @@ import * as tinycolor from 'tinycolor2'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkKeyValue from '@/components/key-value.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; @@ -49,6 +50,7 @@ export default defineComponent({ FormLink, FormSection, MkKeyValue, + FormSplit, }, emits: ['info'], @@ -97,10 +99,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { chooseUploadFolder() { os.selectDriveFolder(false).then(async folder => { diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index b04295cce0..54557f8773 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -41,8 +41,6 @@ <script lang="ts"> import { defineComponent, onMounted, ref, watch } from 'vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormLink from '@/components/debobigego/link.vue'; import FormSection from '@/components/form/section.vue'; import FormInput from '@/components/form/input.vue'; import FormSwitch from '@/components/form/switch.vue'; @@ -54,8 +52,6 @@ import { i18n } from '@/i18n'; export default defineComponent({ components: { FormSection, - FormLink, - FormButton, FormSwitch, FormInput, }, @@ -115,8 +111,6 @@ export default defineComponent({ }); onMounted(() => { - context.emit('info', INFO); - watch(emailAddress, () => { saveEmailAddress(); }); diff --git a/packages/client/src/pages/settings/experimental-features.vue b/packages/client/src/pages/settings/experimental-features.vue deleted file mode 100644 index 5a7bcb3b41..0000000000 --- a/packages/client/src/pages/settings/experimental-features.vue +++ /dev/null @@ -1,52 +0,0 @@ -<template> -<FormBase> - <FormButton @click="error()">error test</FormButton> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.experimentalFeatures, - icon: 'fas fa-flask' - }, - stats: null - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - error() { - throw new Error('Test error'); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index 734bc78442..2e159e56a9 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -195,10 +195,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async reloadAsk() { const { canceled } = await os.confirm({ diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index a1dd6a1539..21031c559e 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -133,10 +133,6 @@ export default defineComponent({ os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); }; - onMounted(() => { - context.emit('info', INFO); - }); - return { [symbols.PAGE_INFO]: INFO, excludeMutingUsers, diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 8ffff86705..66c8b147bb 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -14,7 +14,7 @@ </div> <div class="main"> <div class="bkzroven"> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </div> </div> </div> @@ -215,19 +215,9 @@ export default defineComponent({ case 'deck': return defineAsyncComponent(() => import('./deck.vue')); case 'plugin': return defineAsyncComponent(() => import('./plugin.vue')); case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue')); - case 'plugin/manage': return defineAsyncComponent(() => import('./plugin.manage.vue')); case 'import-export': return defineAsyncComponent(() => import('./import-export.vue')); case 'account-info': return defineAsyncComponent(() => import('./account-info.vue')); - case 'update': return defineAsyncComponent(() => import('./update.vue')); - case 'registry': return defineAsyncComponent(() => import('./registry.vue')); case 'delete-account': return defineAsyncComponent(() => import('./delete-account.vue')); - case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue')); - } - if (page.value.startsWith('registry/keys/system/')) { - return defineAsyncComponent(() => import('./registry.keys.vue')); - } - if (page.value.startsWith('registry/value/system/')) { - return defineAsyncComponent(() => import('./registry.value.vue')); } return null; }); @@ -235,17 +225,6 @@ export default defineComponent({ watch(component, () => { pageProps.value = {}; - if (page.value) { - if (page.value.startsWith('registry/keys/system/')) { - pageProps.value.scope = page.value.replace('registry/keys/system/', '').split('/'); - } - if (page.value.startsWith('registry/value/system/')) { - const path = page.value.replace('registry/value/system/', '').split('/'); - pageProps.value.xKey = path.pop(); - pageProps.value.scope = path; - } - } - nextTick(() => { scroll(el.value, { top: 0 }); }); @@ -271,8 +250,9 @@ export default defineComponent({ const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); - const onInfo = (info) => { - childInfo.value = info; + const pageChanged = (page) => { + if (page == null) return; + childInfo.value = page[symbols.PAGE_INFO]; }; return { @@ -285,7 +265,7 @@ export default defineComponent({ pageProps, component, emailNotConfigured, - onInfo, + pageChanged, childInfo, }; }, diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue index 584a21e4bd..f84a209b60 100644 --- a/packages/client/src/pages/settings/instance-mute.vue +++ b/packages/client/src/pages/settings/instance-mute.vue @@ -47,11 +47,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - async created() { this.instanceMutes = this.$i.mutedInstances.join('\n'); }, diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue index 3d8aaf8a6f..ca36c91665 100644 --- a/packages/client/src/pages/settings/integration.vue +++ b/packages/client/src/pages/settings/integration.vue @@ -1,45 +1,39 @@ <template> -<FormBase> - <div v-if="enableTwitterIntegration" class="_debobigegoItem"> - <div class="_debobigegoLabel"><i class="fab fa-twitter"></i> Twitter</div> - <div class="_debobigegoPanel" style="padding: 16px;"> - <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> - </div> - </div> +<div class="_formRoot"> + <FormSection v-if="enableTwitterIntegration"> + <template #label><i class="fab fa-twitter"></i> Twitter</template> + <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> + <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> + </FormSection> - <div v-if="enableDiscordIntegration" class="_debobigegoItem"> - <div class="_debobigegoLabel"><i class="fab fa-discord"></i> Discord</div> - <div class="_debobigegoPanel" style="padding: 16px;"> - <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> - </div> - </div> + <FormSection v-if="enableDiscordIntegration"> + <template #label><i class="fab fa-discord"></i> Discord</template> + <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> + <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> + </FormSection> - <div v-if="enableGithubIntegration" class="_debobigegoItem"> - <div class="_debobigegoLabel"><i class="fab fa-github"></i> GitHub</div> - <div class="_debobigegoPanel" style="padding: 16px;"> - <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> - </div> - </div> -</FormBase> + <FormSection v-if="enableGithubIntegration"> + <template #label><i class="fab fa-github"></i> GitHub</template> + <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> + <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> + </FormSection> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { apiUrl } from '@/config'; -import FormBase from '@/components/debobigego/base.vue'; +import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, + FormSection, MkButton }, @@ -79,8 +73,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - document.cookie = `igi=${this.$i.token}; path=/;` + ` max-age=31536000;` + (document.location.protocol.startsWith('https') ? ' secure' : ''); diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue index 19d26be89a..6e38cd5dfe 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/menu.vue @@ -21,7 +21,6 @@ import { defineComponent } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { menuDef } from '@/menu'; @@ -31,7 +30,6 @@ import { unisonReload } from '@/scripts/unison-reload'; export default defineComponent({ components: { - FormBase, FormButton, FormTextarea, FormRadios, @@ -69,10 +67,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addItem() { const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k)); diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue index 4f42d5e429..f4f9ebf8dd 100644 --- a/packages/client/src/pages/settings/mute-block.vue +++ b/packages/client/src/pages/settings/mute-block.vue @@ -1,5 +1,5 @@ <template> -<FormBase> +<div class="_formRoot"> <MkTab v-model="tab" style="margin-bottom: var(--margin);"> <option value="mute">{{ $ts.mutedUsers }}</option> <option value="block">{{ $ts.blockedUsers }}</option> @@ -8,11 +8,9 @@ <MkPagination :pagination="mutingPagination" class="muting"> <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template> <template v-slot="{items}"> - <FormGroup> - <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)"> - <MkAcct :user="mute.mutee"/> - </FormLink> - </FormGroup> + <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)"> + <MkAcct :user="mute.mutee"/> + </FormLink> </template> </MkPagination> </div> @@ -20,66 +18,43 @@ <MkPagination :pagination="blockingPagination" class="blocking"> <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template> <template v-slot="{items}"> - <FormGroup> - <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)"> - <MkAcct :user="block.blockee"/> - </FormLink> - </FormGroup> + <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)"> + <MkAcct :user="block.blockee"/> + </FormLink> </template> </MkPagination> </div> -</FormBase> +</div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormLink from '@/components/form/link.vue'; import { userPage } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkTab, - FormInfo, - FormBase, - FormGroup, - FormLink, - }, +let tab = $ref('mute'); - emits: ['info'], +const mutingPagination = { + endpoint: 'mute/list' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.muteAndBlock, - icon: 'fas fa-ban', - bg: 'var(--bg)', - }, - tab: 'mute', - mutingPagination: { - endpoint: 'mute/list', - limit: 10, - }, - blockingPagination: { - endpoint: 'blocking/list', - limit: 10, - }, - } - }, +const blockingPagination = { + endpoint: 'blocking/list' as const, + limit: 10, +}; - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.muteAndBlock, + icon: 'fas fa-ban', + bg: 'var(--bg)', }, - - methods: { - userPage - } }); </script> diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index d3ada0d7ef..12171530bb 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -13,7 +13,6 @@ import { defineComponent } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; import FormSection from '@/components/form/section.vue'; import { notificationTypes } from 'misskey-js'; import * as os from '@/os'; @@ -21,7 +20,6 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormLink, FormButton, FormSection, @@ -39,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { readAllUnreadNotes() { os.api('i/read-all-unread-notes'); diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue index 0d9e60e21d..6e48cb58a6 100644 --- a/packages/client/src/pages/settings/other.vue +++ b/packages/client/src/pages/settings/other.vue @@ -1,30 +1,12 @@ <template> <div class="_formRoot"> - <FormLink to="/settings/update" class="_formBlock">Misskey Update</FormLink> - - <FormSwitch :value="$i.injectFeaturedNote" @update:modelValue="onChangeInjectFeaturedNote" class="_formBlock"> + <FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> {{ $ts.showFeaturedNotesInTimeline }} </FormSwitch> - <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> + <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> <FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink> - <FormLink to="/settings/experimental-features" class="_formBlock">{{ $ts.experimentalFeatures }}</FormLink> - - <FormSection> - <template #label>{{ $ts.developer }}</template> - <FormSwitch v-model="debug" @update:modelValue="changeDebug" class="_formBlock"> - DEBUG MODE - </FormSwitch> - <template v-if="debug"> - <FormButton @click="taskmanager">Task Manager</FormButton> - </template> - </FormSection> - - <FormLink to="/settings/registry" class="_formBlock"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.registry }}</FormLink> - - <FormLink to="/bios" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink> - <FormLink to="/cli" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink> <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink> </div> @@ -33,10 +15,8 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; import FormSection from '@/components/form/section.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormLink from '@/components/form/link.vue'; import * as os from '@/os'; import { debug } from '@/config'; import { defaultStore } from '@/store'; @@ -45,10 +25,8 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormSelect, FormSection, FormSwitch, - FormButton, FormLink, }, @@ -69,10 +47,6 @@ export default defineComponent({ reportError: defaultStore.makeGetterSetter('reportError'), }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { changeDebug(v) { console.log(v); @@ -85,11 +59,6 @@ export default defineComponent({ injectFeaturedNote: v }); }, - - taskmanager() { - os.popup(import('@/components/taskmanager.vue'), { - }, {}, 'closed'); - }, } }); </script> diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue index af93ef2930..d35d20d17a 100644 --- a/packages/client/src/pages/settings/plugin.install.vue +++ b/packages/client/src/pages/settings/plugin.install.vue @@ -1,15 +1,15 @@ <template> -<FormBase> - <FormInfo warn>{{ $ts._plugin.installWarn }}</FormInfo> +<div class="_formRoot"> + <FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo> - <FormGroup> - <FormTextarea v-model="code" tall> - <span>{{ $ts.code }}</span> - </FormTextarea> - </FormGroup> + <FormTextarea v-model="code" tall class="_formBlock"> + <template #label>{{ $ts.code }}</template> + </FormTextarea> - <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> -</FormBase> + <div class="_formBlock"> + <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> + </div> +</div> </template> <script lang="ts"> @@ -18,13 +18,8 @@ import { AiScript, parse } from '@syuilo/aiscript'; import { serialize } from '@syuilo/aiscript/built/serializer'; import { v4 as uuid } from 'uuid'; import FormTextarea from '@/components/form/textarea.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormInfo from '@/components/debobigego/info.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; @@ -33,11 +28,6 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { FormTextarea, - FormSelect, - FormRadios, - FormBase, - FormGroup, - FormLink, FormButton, FormInfo, }, @@ -55,10 +45,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { installPlugin({ id, meta, ast, token }) { ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ diff --git a/packages/client/src/pages/settings/plugin.manage.vue b/packages/client/src/pages/settings/plugin.manage.vue deleted file mode 100644 index 8b9021dc3d..0000000000 --- a/packages/client/src/pages/settings/plugin.manage.vue +++ /dev/null @@ -1,116 +0,0 @@ -<template> -<FormBase> - <FormGroup v-for="plugin in plugins" :key="plugin.id"> - <template #label><span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span></template> - - <FormSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> - <div class="_debobigegoItem"> - <div class="_debobigegoPanel" style="padding: 16px;"> - <div class="_keyValue"> - <div>{{ $ts.author }}:</div> - <div>{{ plugin.author }}</div> - </div> - <div class="_keyValue"> - <div>{{ $ts.description }}:</div> - <div>{{ plugin.description }}</div> - </div> - <div class="_keyValue"> - <div>{{ $ts.permission }}:</div> - <div>{{ plugin.permissions }}</div> - </div> - </div> - </div> - <div class="_debobigegoItem"> - <div class="_debobigegoPanel" style="padding: 16px;"> - <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> - <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> - </div> - </div> - </FormGroup> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import MkTextarea from '@/components/form/textarea.vue'; -import MkSelect from '@/components/form/select.vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; -import * as symbols from '@/symbols'; -import { unisonReload } from '@/scripts/unison-reload'; - -export default defineComponent({ - components: { - MkButton, - MkTextarea, - MkSelect, - FormSwitch, - FormBase, - FormGroup, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._plugin.manage, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - plugins: ColdDeviceStorage.get('plugins'), - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - uninstall(plugin) { - ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); - os.success(); - this.$nextTick(() => { - unisonReload(); - }); - }, - - // TODO: ã“ã®å‡¦ç†ã‚’storeå´ã«actionã¨ã—ã¦ç§»å‹•ã—ã€è¨å®šç”»é¢ã‚’é–‹ãAiScriptAPIを実装ã§ãるよã†ã«ã™ã‚‹ - async config(plugin) { - const config = plugin.config; - for (const key in plugin.configData) { - config[key].default = plugin.configData[key]; - } - - const { canceled, result } = await os.form(plugin.name, config); - if (canceled) return; - - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).configData = result; - ColdDeviceStorage.set('plugins', plugins); - - this.$nextTick(() => { - location.reload(); - }); - }, - - changeActive(plugin, active) { - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).active = active; - ColdDeviceStorage.set('plugins', plugins); - - this.$nextTick(() => { - location.reload(); - }); - } - }, -}); -</script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index 50e53f459f..7a3ab9d152 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -1,23 +1,54 @@ <template> -<FormBase> +<div class="_formRoot"> <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink> - <FormLink to="/settings/plugin/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ $ts._plugin.manage }}<template #suffix>{{ plugins }}</template></FormLink> -</FormBase> + + <FormSection> + <template #label>{{ $ts.manage }}</template> + <div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;"> + <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> + + <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> + + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.author }}</template> + <template #value>{{ plugin.author }}</template> + </MkKeyValue> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.description }}</template> + <template #value>{{ plugin.description }}</template> + </MkKeyValue> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.permission }}</template> + <template #value>{{ plugin.permission }}</template> + </MkKeyValue> + + <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> + <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> + </div> + </div> + </FormSection> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; +import FormLink from '@/components/form/link.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormSection from '@/components/form/section.vue'; +import MkButton from '@/components/ui/button.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormLink, + FormSwitch, + FormSection, + MkButton, + MkKeyValue, }, emits: ['info'], @@ -29,12 +60,47 @@ export default defineComponent({ icon: 'fas fa-plug', bg: 'var(--bg)', }, - plugins: ColdDeviceStorage.get('plugins').length, + plugins: ColdDeviceStorage.get('plugins'), } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); + methods: { + uninstall(plugin) { + ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); + os.success(); + this.$nextTick(() => { + unisonReload(); + }); + }, + + // TODO: ã“ã®å‡¦ç†ã‚’storeå´ã«actionã¨ã—ã¦ç§»å‹•ã—ã€è¨å®šç”»é¢ã‚’é–‹ãAiScriptAPIを実装ã§ãるよã†ã«ã™ã‚‹ + async config(plugin) { + const config = plugin.config; + for (const key in plugin.configData) { + config[key].default = plugin.configData[key]; + } + + const { canceled, result } = await os.form(plugin.name, config); + if (canceled) return; + + const plugins = ColdDeviceStorage.get('plugins'); + plugins.find(p => p.id === plugin.id).configData = result; + ColdDeviceStorage.set('plugins', plugins); + + this.$nextTick(() => { + location.reload(); + }); + }, + + changeActive(plugin, active) { + const plugins = ColdDeviceStorage.get('plugins'); + plugins.find(p => p.id === plugin.id).active = active; + ColdDeviceStorage.set('plugins', plugins); + + this.$nextTick(() => { + location.reload(); + }); + } }, }); </script> diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index 78a0ea8b8d..dd13ba4bd0 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -47,8 +47,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormSection from '@/components/form/section.vue'; @@ -56,67 +56,39 @@ import FormGroup from '@/components/form/group.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - FormSelect, - FormSection, - FormGroup, - FormSwitch, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.privacy, - icon: 'fas fa-lock-open', - bg: 'var(--bg)', - }, - isLocked: false, - autoAcceptFollowed: false, - noCrawle: false, - isExplorable: false, - hideOnlineStatus: false, - publicReactions: false, - ffVisibility: 'public', - } - }, +let isLocked = $ref($i.isLocked); +let autoAcceptFollowed = $ref($i.autoAcceptFollowed); +let noCrawle = $ref($i.noCrawle); +let isExplorable = $ref($i.isExplorable); +let hideOnlineStatus = $ref($i.hideOnlineStatus); +let publicReactions = $ref($i.publicReactions); +let ffVisibility = $ref($i.ffVisibility); - computed: { - defaultNoteVisibility: defaultStore.makeGetterSetter('defaultNoteVisibility'), - defaultNoteLocalOnly: defaultStore.makeGetterSetter('defaultNoteLocalOnly'), - rememberNoteVisibility: defaultStore.makeGetterSetter('rememberNoteVisibility'), - keepCw: defaultStore.makeGetterSetter('keepCw'), - }, +let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility')); +let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly')); +let rememberNoteVisibility = $computed(defaultStore.makeGetterSetter('rememberNoteVisibility')); +let keepCw = $computed(defaultStore.makeGetterSetter('keepCw')); - created() { - this.isLocked = this.$i.isLocked; - this.autoAcceptFollowed = this.$i.autoAcceptFollowed; - this.noCrawle = this.$i.noCrawle; - this.isExplorable = this.$i.isExplorable; - this.hideOnlineStatus = this.$i.hideOnlineStatus; - this.publicReactions = this.$i.publicReactions; - this.ffVisibility = this.$i.ffVisibility; - }, +function save() { + os.api('i/update', { + isLocked: !!isLocked, + autoAcceptFollowed: !!autoAcceptFollowed, + noCrawle: !!noCrawle, + isExplorable: !!isExplorable, + hideOnlineStatus: !!hideOnlineStatus, + publicReactions: !!publicReactions, + ffVisibility: ffVisibility, + }); +} - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.privacy, + icon: 'fas fa-lock-open', + bg: 'var(--bg)', }, - - methods: { - save() { - os.api('i/update', { - isLocked: !!this.isLocked, - autoAcceptFollowed: !!this.autoAcceptFollowed, - noCrawle: !!this.noCrawle, - isExplorable: !!this.isExplorable, - hideOnlineStatus: !!this.hideOnlineStatus, - publicReactions: !!this.publicReactions, - ffVisibility: this.ffVisibility, - }); - } - } }); </script> diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 2eaf9a9f83..f875146a2c 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -3,50 +3,50 @@ <div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <div class="avatar _acrylic"> <MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/> - <MkButton primary class="avatarEdit" @click="changeAvatar">{{ $ts._profile.changeAvatar }}</MkButton> + <MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.locale._profile.changeAvatar }}</MkButton> </div> - <MkButton primary class="bannerEdit" @click="changeBanner">{{ $ts._profile.changeBanner }}</MkButton> + <MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.locale._profile.changeBanner }}</MkButton> </div> - <FormInput v-model="name" :max="30" manual-save class="_formBlock"> - <template #label>{{ $ts._profile.name }}</template> + <FormInput v-model="profile.name" :max="30" manual-save class="_formBlock"> + <template #label>{{ i18n.locale._profile.name }}</template> </FormInput> - <FormTextarea v-model="description" :max="500" tall manual-save class="_formBlock"> - <template #label>{{ $ts._profile.description }}</template> - <template #caption>{{ $ts._profile.youCanIncludeHashtags }}</template> + <FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock"> + <template #label>{{ i18n.locale._profile.description }}</template> + <template #caption>{{ i18n.locale._profile.youCanIncludeHashtags }}</template> </FormTextarea> - <FormInput v-model="location" manual-save class="_formBlock"> - <template #label>{{ $ts.location }}</template> + <FormInput v-model="profile.location" manual-save class="_formBlock"> + <template #label>{{ i18n.locale.location }}</template> <template #prefix><i class="fas fa-map-marker-alt"></i></template> </FormInput> - <FormInput v-model="birthday" type="date" manual-save class="_formBlock"> - <template #label>{{ $ts.birthday }}</template> + <FormInput v-model="profile.birthday" type="date" manual-save class="_formBlock"> + <template #label>{{ i18n.locale.birthday }}</template> <template #prefix><i class="fas fa-birthday-cake"></i></template> </FormInput> - <FormSelect v-model="lang" class="_formBlock"> - <template #label>{{ $ts.language }}</template> + <FormSelect v-model="profile.lang" class="_formBlock"> + <template #label>{{ i18n.locale.language }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> </FormSelect> <FormSlot> - <MkButton @click="editMetadata">{{ $ts._profile.metadataEdit }}</MkButton> - <template #caption>{{ $ts._profile.metadataDescription }}</template> + <MkButton @click="editMetadata">{{ i18n.locale._profile.metadataEdit }}</MkButton> + <template #caption>{{ i18n.locale._profile.metadataDescription }}</template> </FormSlot> - <FormSwitch v-model="isCat" class="_formBlock">{{ $ts.flagAsCat }}<template #caption>{{ $ts.flagAsCatDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.locale.flagAsCat }}<template #caption>{{ i18n.locale.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model="isBot" class="_formBlock">{{ $ts.flagAsBot }}<template #caption>{{ $ts.flagAsBotDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.locale.flagAsBot }}<template #caption>{{ i18n.locale.flagAsBotDescription }}</template></FormSwitch> - <FormSwitch v-model="alwaysMarkNsfw" class="_formBlock">{{ $ts.alwaysMarkSensitive }}</FormSwitch> + <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.locale.alwaysMarkSensitive }}</FormSwitch> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineComponent, reactive, watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -57,198 +57,149 @@ import { host, langs } from '@/config'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - MkButton, - FormInput, - FormTextarea, - FormSwitch, - FormSelect, - FormSlot, - }, - - emits: ['info'], +const profile = reactive({ + name: $i.name, + description: $i.description, + location: $i.location, + birthday: $i.birthday, + lang: $i.lang, + isBot: $i.isBot, + isCat: $i.isCat, + alwaysMarkNsfw: $i.alwaysMarkNsfw, +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.profile, - icon: 'fas fa-user', - bg: 'var(--bg)', - }, - host, - langs, - name: null, - description: null, - birthday: null, - lang: null, - location: null, - fieldName0: null, - fieldValue0: null, - fieldName1: null, - fieldValue1: null, - fieldName2: null, - fieldValue2: null, - fieldName3: null, - fieldValue3: null, - avatarId: null, - bannerId: null, - isBot: false, - isCat: false, - alwaysMarkNsfw: false, - saving: false, - } - }, +const additionalFields = reactive({ + fieldName0: $i.fields[0] ? $i.fields[0].name : null, + fieldValue0: $i.fields[0] ? $i.fields[0].value : null, + fieldName1: $i.fields[1] ? $i.fields[1].name : null, + fieldValue1: $i.fields[1] ? $i.fields[1].value : null, + fieldName2: $i.fields[2] ? $i.fields[2].name : null, + fieldValue2: $i.fields[2] ? $i.fields[2].value : null, + fieldName3: $i.fields[3] ? $i.fields[3].name : null, + fieldValue3: $i.fields[3] ? $i.fields[3].value : null, +}); - created() { - this.name = this.$i.name; - this.description = this.$i.description; - this.location = this.$i.location; - this.birthday = this.$i.birthday; - this.lang = this.$i.lang; - this.avatarId = this.$i.avatarId; - this.bannerId = this.$i.bannerId; - this.isBot = this.$i.isBot; - this.isCat = this.$i.isCat; - this.alwaysMarkNsfw = this.$i.alwaysMarkNsfw; +watch(() => profile, () => { + save(); +}, { + deep: true, +}); - this.fieldName0 = this.$i.fields[0] ? this.$i.fields[0].name : null; - this.fieldValue0 = this.$i.fields[0] ? this.$i.fields[0].value : null; - this.fieldName1 = this.$i.fields[1] ? this.$i.fields[1].name : null; - this.fieldValue1 = this.$i.fields[1] ? this.$i.fields[1].value : null; - this.fieldName2 = this.$i.fields[2] ? this.$i.fields[2].name : null; - this.fieldValue2 = this.$i.fields[2] ? this.$i.fields[2].value : null; - this.fieldName3 = this.$i.fields[3] ? this.$i.fields[3].name : null; - this.fieldValue3 = this.$i.fields[3] ? this.$i.fields[3].value : null; +function save() { + os.apiWithDialog('i/update', { + name: profile.name || null, + description: profile.description || null, + location: profile.location || null, + birthday: profile.birthday || null, + lang: profile.lang || null, + isBot: !!profile.isBot, + isCat: !!profile.isCat, + alwaysMarkNsfw: !!profile.alwaysMarkNsfw, + }); +} - this.$watch('name', this.save); - this.$watch('description', this.save); - this.$watch('location', this.save); - this.$watch('birthday', this.save); - this.$watch('lang', this.save); - this.$watch('isBot', this.save); - this.$watch('isCat', this.save); - this.$watch('alwaysMarkNsfw', this.save); - }, +function changeAvatar(ev) { + selectFile(ev.currentTarget || ev.target, i18n.locale.avatar).then(async (file) => { + const i = await os.apiWithDialog('i/update', { + avatarId: file.id, + }); + $i.avatarId = i.avatarId; + $i.avatarUrl = i.avatarUrl; + }); +} - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, +function changeBanner(ev) { + selectFile(ev.currentTarget || ev.target, i18n.locale.banner).then(async (file) => { + const i = await os.apiWithDialog('i/update', { + bannerId: file.id, + }); + $i.bannerId = i.bannerId; + $i.bannerUrl = i.bannerUrl; + }); +} - methods: { - changeAvatar(e) { - selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => { - os.api('i/update', { - avatarId: file.id, - }); - }); +async function editMetadata() { + const { canceled, result } = await os.form(i18n.locale._profile.metadata, { + fieldName0: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 1', + default: additionalFields.fieldName0, }, - - changeBanner(e) { - selectFile(e.currentTarget || e.target, this.$ts.banner).then(file => { - os.api('i/update', { - bannerId: file.id, - }); - }); + fieldValue0: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 1', + default: additionalFields.fieldValue0, }, + fieldName1: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 2', + default: additionalFields.fieldName1, + }, + fieldValue1: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 2', + default: additionalFields.fieldValue1, + }, + fieldName2: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 3', + default: additionalFields.fieldName2, + }, + fieldValue2: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 3', + default: additionalFields.fieldValue2, + }, + fieldName3: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 4', + default: additionalFields.fieldName3, + }, + fieldValue3: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 4', + default: additionalFields.fieldValue3, + }, + }); + if (canceled) return; - async editMetadata() { - const { canceled, result } = await os.form(this.$ts._profile.metadata, { - fieldName0: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 1', - default: this.fieldName0, - }, - fieldValue0: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 1', - default: this.fieldValue0, - }, - fieldName1: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 2', - default: this.fieldName1, - }, - fieldValue1: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 2', - default: this.fieldValue1, - }, - fieldName2: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 3', - default: this.fieldName2, - }, - fieldValue2: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 3', - default: this.fieldValue2, - }, - fieldName3: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 4', - default: this.fieldName3, - }, - fieldValue3: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 4', - default: this.fieldValue3, - }, - }); - if (canceled) return; - - this.fieldName0 = result.fieldName0; - this.fieldValue0 = result.fieldValue0; - this.fieldName1 = result.fieldName1; - this.fieldValue1 = result.fieldValue1; - this.fieldName2 = result.fieldName2; - this.fieldValue2 = result.fieldValue2; - this.fieldName3 = result.fieldName3; - this.fieldValue3 = result.fieldValue3; - - const fields = [ - { name: this.fieldName0, value: this.fieldValue0 }, - { name: this.fieldName1, value: this.fieldValue1 }, - { name: this.fieldName2, value: this.fieldValue2 }, - { name: this.fieldName3, value: this.fieldValue3 }, - ]; + additionalFields.fieldName0 = result.fieldName0; + additionalFields.fieldValue0 = result.fieldValue0; + additionalFields.fieldName1 = result.fieldName1; + additionalFields.fieldValue1 = result.fieldValue1; + additionalFields.fieldName2 = result.fieldName2; + additionalFields.fieldValue2 = result.fieldValue2; + additionalFields.fieldName3 = result.fieldName3; + additionalFields.fieldValue3 = result.fieldValue3; - os.api('i/update', { - fields, - }).then(i => { - os.success(); - }).catch(err => { - os.alert({ - type: 'error', - text: err.id - }); - }); - }, + const fields = [ + { name: additionalFields.fieldName0, value: additionalFields.fieldValue0 }, + { name: additionalFields.fieldName1, value: additionalFields.fieldValue1 }, + { name: additionalFields.fieldName2, value: additionalFields.fieldValue2 }, + { name: additionalFields.fieldName3, value: additionalFields.fieldValue3 }, + ]; - save() { - this.saving = true; + os.api('i/update', { + fields, + }).then(i => { + os.success(); + }).catch(err => { + os.alert({ + type: 'error', + text: err.id + }); + }); +} - os.apiWithDialog('i/update', { - name: this.name || null, - description: this.description || null, - location: this.location || null, - birthday: this.birthday || null, - lang: this.lang || null, - isBot: !!this.isBot, - isCat: !!this.isCat, - alwaysMarkNsfw: !!this.alwaysMarkNsfw, - }).then(i => { - this.saving = false; - this.$i.avatarId = i.avatarId; - this.$i.avatarUrl = i.avatarUrl; - this.$i.bannerId = i.bannerId; - this.$i.bannerUrl = i.bannerUrl; - }).catch(err => { - this.saving = false; - }); - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.profile, + icon: 'fas fa-user', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index 0d4db46936..e5b1189947 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -100,10 +100,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { save() { this.$store.set('reactions', this.reactions); diff --git a/packages/client/src/pages/settings/registry.keys.vue b/packages/client/src/pages/settings/registry.keys.vue deleted file mode 100644 index 89953ebea1..0000000000 --- a/packages/client/src/pages/settings/registry.keys.vue +++ /dev/null @@ -1,114 +0,0 @@ -<template> -<FormBase> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts._registry.domain }}</template> - <template #value>{{ $ts.system }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts._registry.scope }}</template> - <template #value>{{ scope.join('/') }}</template> - </FormKeyValueView> - </FormGroup> - - <FormGroup v-if="keys"> - <template #label>{{ $ts._registry.keys }}</template> - <FormLink v-for="key in keys" :to="`/settings/registry/value/system/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink> - </FormGroup> - - <FormButton primary @click="createKey">{{ $ts._registry.createKey }}</FormButton> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - }, - - props: { - scope: { - required: true - } - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.registry, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - }, - keys: null, - } - }, - - watch: { - scope() { - this.fetch(); - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.fetch(); - }, - - methods: { - fetch() { - os.api('i/registry/keys-with-type', { - scope: this.scope - }).then(keys => { - this.keys = Object.entries(keys).sort((a, b) => a[0].localeCompare(b[0])); - }); - }, - - async createKey() { - const { canceled, result } = await os.form(this.$ts._registry.createKey, { - key: { - type: 'string', - label: this.$ts._registry.key, - }, - value: { - type: 'string', - multiline: true, - label: this.$ts.value, - }, - scope: { - type: 'string', - label: this.$ts._registry.scope, - default: this.scope.join('/') - } - }); - if (canceled) return; - os.apiWithDialog('i/registry/set', { - scope: result.scope.split('/'), - key: result.key, - value: JSON5.parse(result.value), - }).then(() => { - this.fetch(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/registry.value.vue b/packages/client/src/pages/settings/registry.value.vue deleted file mode 100644 index 6acd3f6048..0000000000 --- a/packages/client/src/pages/settings/registry.value.vue +++ /dev/null @@ -1,147 +0,0 @@ -<template> -<FormBase> - <FormInfo warn>{{ $ts.editTheseSettingsMayBreakAccount }}</FormInfo> - - <template v-if="value"> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts._registry.domain }}</template> - <template #value>{{ $ts.system }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts._registry.scope }}</template> - <template #value>{{ scope.join('/') }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts._registry.key }}</template> - <template #value>{{ xKey }}</template> - </FormKeyValueView> - </FormGroup> - - <FormGroup> - <FormTextarea v-model="valueForEditor" tall class="_monospace" style="tab-size: 2;"> - <span>{{ $ts.value }} (JSON)</span> - </FormTextarea> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormGroup> - - <FormKeyValueView> - <template #key>{{ $ts.updatedAt }}</template> - <template #value><MkTime :time="value.updatedAt" mode="detail"/></template> - </FormKeyValueView> - - <FormButton danger @click="del"><i class="fas fa-trash"></i> {{ $ts.delete }}</FormButton> - </template> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormTextarea from '@/components/form/textarea.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormInfo, - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormTextarea, - FormGroup, - FormKeyValueView, - }, - - props: { - scope: { - required: true - }, - xKey: { - required: true - }, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.registry, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - }, - value: null, - valueForEditor: null, - } - }, - - watch: { - key() { - this.fetch(); - }, - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.fetch(); - }, - - methods: { - fetch() { - os.api('i/registry/get-detail', { - scope: this.scope, - key: this.xKey - }).then(value => { - this.value = value; - this.valueForEditor = JSON5.stringify(this.value.value, null, '\t'); - }); - }, - - save() { - try { - JSON5.parse(this.valueForEditor); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts.invalidValue - }); - return; - } - - os.confirm({ - type: 'warning', - text: this.$ts.saveConfirm, - }).then(({ canceled }) => { - if (canceled) return; - os.apiWithDialog('i/registry/set', { - scope: this.scope, - key: this.xKey, - value: JSON5.parse(this.valueForEditor) - }); - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - os.apiWithDialog('i/registry/remove', { - scope: this.scope, - key: this.xKey - }); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/registry.vue b/packages/client/src/pages/settings/registry.vue deleted file mode 100644 index 6faff5d2a4..0000000000 --- a/packages/client/src/pages/settings/registry.vue +++ /dev/null @@ -1,90 +0,0 @@ -<template> -<FormBase> - <FormGroup v-if="scopes"> - <template #label>{{ $ts.system }}</template> - <FormLink v-for="scope in scopes" :to="`/settings/registry/keys/system/${scope.join('/')}`" class="_monospace">{{ scope.join('/') }}</FormLink> - </FormGroup> - <FormButton primary @click="createKey">{{ $ts._registry.createKey }}</FormButton> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.registry, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - }, - scopes: null, - } - }, - - created() { - this.fetch(); - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - fetch() { - os.api('i/registry/scopes').then(scopes => { - this.scopes = scopes.slice().sort((a, b) => a.join('/').localeCompare(b.join('/'))); - }); - }, - - async createKey() { - const { canceled, result } = await os.form(this.$ts._registry.createKey, { - key: { - type: 'string', - label: this.$ts._registry.key, - }, - value: { - type: 'string', - multiline: true, - label: this.$ts.value, - }, - scope: { - type: 'string', - label: this.$ts._registry.scope, - } - }); - if (canceled) return; - os.apiWithDialog('i/registry/set', { - scope: result.scope.split('/'), - key: result.key, - value: JSON5.parse(result.value), - }).then(() => { - this.fetch(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 069f9d964d..6fb3f1c413 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -12,7 +12,7 @@ <FormSection> <template #label>{{ $ts.signinHistory }}</template> - <FormPagination :pagination="pagination"> + <MkPagination :pagination="pagination"> <template v-slot="{items}"> <div> <div v-for="item in items" :key="item.id" v-panel class="timnmucd"> @@ -25,7 +25,7 @@ </div> </div> </template> - </FormPagination> + </MkPagination> </FormSection> <FormSection> @@ -40,10 +40,9 @@ <script lang="ts"> import { defineComponent } from 'vue'; import FormSection from '@/components/form/section.vue'; -import FormLink from '@/components/debobigego/link.vue'; import FormSlot from '@/components/form/slot.vue'; import FormButton from '@/components/ui/button.vue'; -import FormPagination from '@/components/form/pagination.vue'; +import MkPagination from '@/components/ui/pagination.vue'; import X2fa from './2fa.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -51,9 +50,8 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { FormSection, - FormLink, FormButton, - FormPagination, + MkPagination, FormSlot, X2fa, }, @@ -68,16 +66,12 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'i/signin-history', + endpoint: 'i/signin-history' as const, limit: 5, }, } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async change() { const { canceled: canceled1, result: currentPassword } = await os.inputText({ diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index 0977dd8322..490a1b5514 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -94,12 +94,6 @@ export default defineComponent({ this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); this.sounds.channel = ColdDeviceStorage.get('sound_channel'); - this.sounds.reversiPutBlack = ColdDeviceStorage.get('sound_reversiPutBlack'); - this.sounds.reversiPutWhite = ColdDeviceStorage.get('sound_reversiPutWhite'); - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); }, methods: { diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index c3e531afb2..e2a3f042b9 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -1,105 +1,79 @@ <template> -<FormBase> - <FormGroup> - <FormTextarea v-model="installThemeCode"> - <span>{{ $ts._theme.code }}</span> - </FormTextarea> - <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton> - </FormGroup> +<div class="_formRoot"> + <FormTextarea v-model="installThemeCode" class="_formBlock"> + <template #label>{{ i18n.locale._theme.code }}</template> + </FormTextarea> - <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> -</FormBase> + <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ i18n.locale.preview }}</FormButton> + <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ i18n.locale.install }}</FormButton> + </div> +</div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormButton from '@/components/ui/button.vue'; import { applyTheme, validateTheme } from '@/scripts/theme'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { addTheme, getThemes } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormSelect, - FormRadios, - FormBase, - FormGroup, - FormLink, - FormButton, - }, - - emits: ['info'], +let installThemeCode = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._theme.install, - icon: 'fas fa-download', - bg: 'var(--bg)', - }, - installThemeCode: null, - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, +function parseThemeCode(code: string) { + let theme; - methods: { - parseThemeCode(code) { - let theme; + try { + theme = JSON5.parse(code); + } catch (e) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid + }); + return false; + } + if (!validateTheme(theme)) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid + }); + return false; + } + if (getThemes().some(t => t.id === theme.id)) { + os.alert({ + type: 'info', + text: i18n.locale._theme.alreadyInstalled + }); + return false; + } - try { - theme = JSON5.parse(code); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return false; - } - if (!validateTheme(theme)) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return false; - } - if (getThemes().some(t => t.id === theme.id)) { - os.alert({ - type: 'info', - text: this.$ts._theme.alreadyInstalled - }); - return false; - } + return theme; +} - return theme; - }, +function preview(code: string): void { + const theme = parseThemeCode(code); + if (theme) applyTheme(theme, false); +} - preview(code) { - const theme = this.parseThemeCode(code); - if (theme) applyTheme(theme, false); - }, +async function install(code: string): Promise<void> { + const theme = parseThemeCode(code); + if (!theme) return; + await addTheme(theme); + os.alert({ + type: 'success', + text: i18n.t('_theme.installed', { name: theme.name }) + }); +} - async install(code) { - const theme = this.parseThemeCode(code); - if (!theme) return; - await addTheme(theme); - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: theme.name }) - }); - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale._theme.install, + icon: 'fas fa-download', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index c605b1eb64..a1e849b540 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -30,9 +30,6 @@ import { defineComponent } from 'vue'; import * as JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import { Theme, builtinThemes } from '@/scripts/theme'; @@ -46,9 +43,6 @@ export default defineComponent({ components: { FormTextarea, FormSelect, - FormRadios, - FormBase, - FormGroup, FormInput, FormButton, }, @@ -84,10 +78,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { copyThemeCode() { copyToClipboard(this.selectedThemeCode); diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index 6c88b65699..658e36ec05 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -163,10 +163,6 @@ export default defineComponent({ location.reload(); }); - onMounted(() => { - emit('info', INFO); - }); - onActivated(() => { fetchThemes().then(() => { installedThemes.value = getThemes(); diff --git a/packages/client/src/pages/settings/update.vue b/packages/client/src/pages/settings/update.vue deleted file mode 100644 index e0d8f6d15c..0000000000 --- a/packages/client/src/pages/settings/update.vue +++ /dev/null @@ -1,95 +0,0 @@ -<template> -<FormBase> - <template v-if="meta"> - <FormInfo v-if="version === meta.version">{{ $ts.youAreRunningUpToDateClient }}</FormInfo> - <FormInfo v-else warn>{{ $ts.newVersionOfClientAvailable }}</FormInfo> - </template> - <FormGroup> - <template #label>{{ instanceName }}</template> - <FormKeyValueView> - <template #key>{{ $ts.currentVersion }}</template> - <template #value>{{ version }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.latestVersion }}</template> - <template v-if="meta" #value>{{ meta.version }}</template> - <template v-else #value><MkEllipsis/></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <template #label>Misskey</template> - <FormKeyValueView> - <template #key>{{ $ts.latestVersion }}</template> - <template v-if="releases" #value>{{ releases[0].tag_name }}</template> - <template v-else #value><MkEllipsis/></template> - </FormKeyValueView> - <template v-if="releases" #caption><MkTime :time="releases[0].published_at" mode="detail"/></template> - </FormGroup> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import * as os from '@/os'; -import { version, instanceName } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - FormInfo, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Misskey Update', - icon: 'fas fa-sync-alt', - bg: 'var(--bg)', - }, - version, - instanceName, - releases: null, - meta: null - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - - os.api('meta', { - detail: false - }).then(meta => { - this.meta = meta; - localStorage.setItem('v', meta.version); - }); - - fetch('https://api.github.com/repos/misskey-dev/misskey/releases', { - method: 'GET', - }) - .then(res => res.json()) - .then(res => { - this.releases = res; - }); - }, - - methods: { - } -}); -</script> diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index 068f88740a..19980dea14 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -31,7 +31,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; -import FormBase from '@/components/debobigego/base.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -42,7 +41,6 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, MkButton, FormTextarea, MkKeyValue, @@ -89,10 +87,6 @@ export default defineComponent({ this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async save() { this.$store.set('mutedWords', this.softMutedWords.trim().split('\n').map(x => x.trim().split(' '))); diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index bdd8500ee4..5df6256fb2 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -169,7 +169,7 @@ export default defineComponent({ window.close(); // é–‰ã˜ãªã‘れã°100ms後タイムライン㫠- setTimeout(() => { + window.setTimeout(() => { this.$router.push('/'); }, 100); } diff --git a/packages/client/src/pages/signup-complete.vue b/packages/client/src/pages/signup-complete.vue index 89375e05d2..a10af1a4cc 100644 --- a/packages/client/src/pages/signup-complete.vue +++ b/packages/client/src/pages/signup-complete.vue @@ -1,50 +1,36 @@ <template> <div> - {{ $ts.processing }} + {{ i18n.locale.processing }} </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { login } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { +const props = defineProps<{ + code: string; +}>(); - }, - - props: { - code: { - type: String, - required: true - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.signup, - icon: 'fas fa-user' - }, - } - }, +onMounted(async () => { + await os.alert({ + type: 'info', + text: i18n.t('clickToFinishEmailVerification', { ok: i18n.locale.gotIt }), + }); + const res = await os.apiWithDialog('signup-pending', { + code: props.code, + }); + login(res.i, '/'); +}); - async mounted() { - await os.alert({ - type: 'info', - text: this.$t('clickToFinishEmailVerification', { ok: this.$ts.gotIt }), - }); - const res = await os.apiWithDialog('signup-pending', { - code: this.code, - }); - login(res.i, '/'); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.signup, + icon: 'fas fa-user', }, - - methods: { - - } }); </script> diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index a0c8367849..045f1ef259 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -1,46 +1,31 @@ <template> <div class="_section"> - <XNotes ref="notes" class="_content" :pagination="pagination"/> + <XNotes class="_content" :pagination="pagination"/> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + tag: string; +}>(); - props: { - tag: { - type: String, - required: true - } - }, +const pagination = { + endpoint: 'notes/search-by-tag' as const, + limit: 10, + params: computed(() => ({ + tag: props.tag, + })), +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.tag, - icon: 'fas fa-hashtag' - }, - pagination: { - endpoint: 'notes/search-by-tag', - limit: 10, - params: () => ({ - tag: this.tag, - }) - }, - }; - }, - - watch: { - tag() { - (this.$refs.notes as any).reload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: props.tag, + icon: 'fas fa-hashtag', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/test.vue b/packages/client/src/pages/test.vue deleted file mode 100644 index d05e00d374..0000000000 --- a/packages/client/src/pages/test.vue +++ /dev/null @@ -1,260 +0,0 @@ -<template> -<div class="_section"> - <div class="_content"> - <div class="_card _gap"> - <div class="_title">Dialog</div> - <div class="_content"> - <MkInput v-model="dialogTitle"> - <template #label>Title</template> - </MkInput> - <MkInput v-model="dialogBody"> - <template #label>Body</template> - </MkInput> - <MkRadio v-model="dialogType" value="info">Info</MkRadio> - <MkRadio v-model="dialogType" value="success">Success</MkRadio> - <MkRadio v-model="dialogType" value="warning">Warn</MkRadio> - <MkRadio v-model="dialogType" value="error">Error</MkRadio> - <MkSwitch v-model="dialogCancel"> - <span>With cancel button</span> - </MkSwitch> - <MkSwitch v-model="dialogCancelByBgClick"> - <span>Can cancel by modal bg click</span> - </MkSwitch> - <MkSwitch v-model="dialogInput"> - <span>With input field</span> - </MkSwitch> - <MkButton @click="showDialog()">Show</MkButton> - </div> - <div class="_content"> - <code>Result: {{ dialogResult }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Form</div> - <div class="_content"> - <MkInput v-model="formTitle"> - <template #label>Title</template> - </MkInput> - <MkTextarea v-model="formForm"> - <template #label>Form</template> - </MkTextarea> - <MkButton @click="form()">Show</MkButton> - </div> - <div class="_content"> - <code>Result: {{ formResult }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">MFM</div> - <div class="_content"> - <MkTextarea v-model="mfm"> - <template #label>MFM</template> - </MkTextarea> - </div> - <div class="_content"> - <Mfm :text="mfm"/> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">selectDriveFile</div> - <div class="_content"> - <MkSwitch v-model="selectDriveFileMultiple"> - <span>Multiple</span> - </MkSwitch> - <MkButton @click="selectDriveFile()">selectDriveFile</MkButton> - </div> - <div class="_content"> - <code>Result: {{ JSON.stringify(selectDriveFileResult) }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">selectDriveFolder</div> - <div class="_content"> - <MkSwitch v-model="selectDriveFolderMultiple"> - <span>Multiple</span> - </MkSwitch> - <MkButton @click="selectDriveFolder()">selectDriveFolder</MkButton> - </div> - <div class="_content"> - <code>Result: {{ JSON.stringify(selectDriveFolderResult) }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">selectUser</div> - <div class="_content"> - <MkButton @click="selectUser()">selectUser</MkButton> - </div> - <div class="_content"> - <code>Result: {{ user }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Notification</div> - <div class="_content"> - <MkInput v-model="notificationIconUrl"> - <template #label>Icon URL</template> - </MkInput> - <MkInput v-model="notificationHeader"> - <template #label>Header</template> - </MkInput> - <MkTextarea v-model="notificationBody"> - <template #label>Body</template> - </MkTextarea> - <MkButton @click="createNotification()">createNotification</MkButton> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Waiting dialog</div> - <div class="_content"> - <MkButton inline @click="openWaitingDialog()">icon only</MkButton> - <MkButton inline @click="openWaitingDialog('Doing')">with text</MkButton> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Messaging window</div> - <div class="_content"> - <MkButton @click="messagingWindowOpen()">open</MkButton> - </div> - </div> - - <MkButton @click="resetTutorial()">Reset tutorial</MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkTextarea from '@/components/form/textarea.vue'; -import MkRadio from '@/components/form/radio.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSwitch, - MkTextarea, - MkRadio, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: 'TEST', - icon: 'fas fa-exclamation-triangle' - }, - dialogTitle: 'Hello', - dialogBody: 'World!', - dialogType: 'info', - dialogCancel: false, - dialogCancelByBgClick: true, - dialogInput: false, - dialogResult: null, - formTitle: 'Test form', - formForm: JSON.stringify({ - 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' - }, - qux: { - type: 'string', - multiline: true, - default: 'Misskey makes\nyou happy.', - label: 'Multiline string' - }, - }, null, '\t'), - formResult: null, - mfm: '', - selectDriveFileMultiple: false, - selectDriveFolderMultiple: false, - selectDriveFileResult: null, - selectDriveFolderResult: null, - user: null, - notificationIconUrl: null, - notificationHeader: '', - notificationBody: '', - } - }, - - methods: { - async showDialog() { - this.dialogResult = null; - /* - this.dialogResult = await os.dialog({ - type: this.dialogType, - title: this.dialogTitle, - text: this.dialogBody, - showCancelButton: this.dialogCancel, - cancelableByBgClick: this.dialogCancelByBgClick, - input: this.dialogInput ? {} : null - });*/ - }, - - async form() { - this.formResult = null; - this.formResult = await os.form(this.formTitle, JSON.parse(this.formForm)); - }, - - async selectDriveFile() { - this.selectDriveFileResult = null; - this.selectDriveFileResult = await os.selectDriveFile(this.selectDriveFileMultiple); - }, - - async selectDriveFolder() { - this.selectDriveFolderResult = null; - this.selectDriveFolderResult = await os.selectDriveFolder(this.selectDriveFolderMultiple); - }, - - async selectUser() { - this.user = null; - this.user = await os.selectUser(); - }, - - async createNotification() { - os.api('notifications/create', { - header: this.notificationHeader, - body: this.notificationBody, - icon: this.notificationIconUrl, - }); - }, - - messagingWindowOpen() { - os.pageWindow('/my/messaging'); - }, - - openWaitingDialog(text?) { - const promise = new Promise((resolve, reject) => { - setTimeout(resolve, 2000); - }); - os.promiseDialog(promise, null, null, text); - }, - - resetTutorial() { - this.$store.set('tutorial', 0); - }, - } -}); -</script> diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index f023653425..80b8c7806c 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -1,300 +1,274 @@ <template> -<FormBase class="cwepdizn"> - <div class="_debobigegoItem colorPicker"> - <div class="_debobigegoLabel">{{ $ts.backgroundColor }}</div> - <div class="_debobigegoPanel colors"> - <div class="row"> - <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> - <div class="preview" :style="{ background: color.forPreview }"></div> - </button> +<MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> + <div class="cwepdizn _formRoot"> + <FormFolder :default-open="true" class="_formBlock"> + <template #label>{{ i18n.locale.backgroundColor }}</template> + <div class="cwepdizn-colors"> + <div class="row"> + <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> + <div class="preview" :style="{ background: color.forPreview }"></div> + </button> + </div> + <div class="row"> + <button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> + <div class="preview" :style="{ background: color.forPreview }"></div> + </button> + </div> </div> - <div class="row"> - <button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> - <div class="preview" :style="{ background: color.forPreview }"></div> - </button> - </div> - </div> - </div> - <div class="_debobigegoItem colorPicker"> - <div class="_debobigegoLabel">{{ $ts.accentColor }}</div> - <div class="_debobigegoPanel colors"> - <div class="row"> - <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)"> - <div class="preview" :style="{ background: color }"></div> - </button> + </FormFolder> + + <FormFolder :default-open="true" class="_formBlock"> + <template #label>{{ i18n.locale.accentColor }}</template> + <div class="cwepdizn-colors"> + <div class="row"> + <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)"> + <div class="preview" :style="{ background: color }"></div> + </button> + </div> </div> - </div> - </div> - <div class="_debobigegoItem colorPicker"> - <div class="_debobigegoLabel">{{ $ts.textColor }}</div> - <div class="_debobigegoPanel colors"> - <div class="row"> - <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)"> - <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div> - </button> + </FormFolder> + + <FormFolder :default-open="true" class="_formBlock"> + <template #label>{{ i18n.locale.textColor }}</template> + <div class="cwepdizn-colors"> + <div class="row"> + <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)"> + <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div> + </button> + </div> </div> - </div> - </div> + </FormFolder> - <FormGroup v-if="codeEnabled"> - <FormTextarea v-model="themeCode" tall> - <span>{{ $ts._theme.code }}</span> - </FormTextarea> - <FormButton primary @click="applyThemeCode">{{ $ts.apply }}</FormButton> - </FormGroup> - <FormButton v-else @click="codeEnabled = true"><i class="fas fa-code"></i> {{ $ts.editCode }}</FormButton> + <FormFolder :default-open="false" class="_formBlock"> + <template #icon><i class="fas fa-code"></i></template> + <template #label>{{ i18n.locale.editCode }}</template> - <FormGroup v-if="descriptionEnabled"> - <FormTextarea v-model="description"> - <span>{{ $ts._theme.description }}</span> - </FormTextarea> - </FormGroup> - <FormButton v-else @click="descriptionEnabled = true">{{ $ts.addDescription }}</FormButton> + <div class="_formRoot"> + <FormTextarea v-model="themeCode" tall class="_formBlock"> + <template #label>{{ i18n.locale._theme.code }}</template> + </FormTextarea> + <FormButton primary class="_formBlock" @click="applyThemeCode">{{ i18n.locale.apply }}</FormButton> + </div> + </FormFolder> - <FormGroup> - <FormButton @click="showPreview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton> - <FormButton primary @click="saveAs"><i class="fas fa-save"></i> {{ $ts.saveAs }}</FormButton> - </FormGroup> -</FormBase> + <FormFolder :default-open="false" class="_formBlock"> + <template #label>{{ i18n.locale.addDescription }}</template> + + <div class="_formRoot"> + <FormTextarea v-model="description"> + <template #label>{{ i18n.locale._theme.description }}</template> + </FormTextarea> + </div> + </FormFolder> + </div> +</MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import { toUnicode } from 'punycode/'; import * as tinycolor from 'tinycolor2'; import { v4 as uuid} from 'uuid'; import * as JSON5 from 'json5'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormFolder from '@/components/form/folder.vue'; -import { Theme, applyTheme, validateTheme, darkTheme, lightTheme } from '@/scripts/theme'; +import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme'; import { host } from '@/config'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; +import { ColdDeviceStorage, defaultStore } from '@/store'; import { addTheme } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { useLeaveGuard } from '@/scripts/use-leave-guard'; -export default defineComponent({ - components: { - FormBase, - FormButton, - FormTextarea, - FormGroup, - }, +const bgColors = [ + { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, + { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' }, + { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' }, + { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' }, + { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' }, + { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' }, + { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' }, + { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' }, + { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' }, + { color: '#362e29', kind: 'dark', forPreview: '#735c4d' }, + { color: '#303629', kind: 'dark', forPreview: '#506d2f' }, + { color: '#293436', kind: 'dark', forPreview: '#258192' }, + { color: '#2e2936', kind: 'dark', forPreview: '#504069' }, + { color: '#252722', kind: 'dark', forPreview: '#3c462f' }, + { color: '#212525', kind: 'dark', forPreview: '#303e3e' }, + { color: '#191919', kind: 'dark', forPreview: '#272727' }, +] as const; +const accentColors = ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83']; +const fgColors = [ + { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null }, + { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' }, + { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' }, + { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' }, + { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' }, + { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' }, + { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, +]; - async beforeRouteLeave(to, from) { - if (this.changed && !(await this.leaveConfirm())) { - return false; - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.themeEditor, - icon: 'fas fa-palette', - }, - theme: { - base: 'light', - props: lightTheme.props - } as Theme, - codeEnabled: false, - descriptionEnabled: false, - description: null, - themeCode: null, - bgColors: [ - { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, - { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' }, - { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' }, - { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' }, - { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' }, - { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' }, - { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' }, - { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' }, - { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' }, - { color: '#362e29', kind: 'dark', forPreview: '#735c4d' }, - { color: '#303629', kind: 'dark', forPreview: '#506d2f' }, - { color: '#293436', kind: 'dark', forPreview: '#258192' }, - { color: '#2e2936', kind: 'dark', forPreview: '#504069' }, - { color: '#252722', kind: 'dark', forPreview: '#3c462f' }, - { color: '#212525', kind: 'dark', forPreview: '#303e3e' }, - { color: '#191919', kind: 'dark', forPreview: '#272727' }, - ], - accentColors: ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83'], - fgColors: [ - { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null }, - { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' }, - { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' }, - { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' }, - { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' }, - { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' }, - { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, - ], - changed: false, - } - }, - - created() { - this.$watch('theme', this.apply, { deep: true }); - window.addEventListener('beforeunload', this.beforeunload); - }, +const theme = $ref<Partial<Theme>>({ + base: 'light', + props: lightTheme.props, +}); +let description = $ref<string | null>(null); +let themeCode = $ref<string | null>(null); +let changed = $ref(false); - beforeUnmount() { - window.removeEventListener('beforeunload', this.beforeunload); - }, +useLeaveGuard($$(changed)); - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, +function showPreview() { + os.pageWindow('preview'); +} - async leaveConfirm(): Promise<boolean> { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }); - return !canceled; - }, +function setBgColor(color: typeof bgColors[number]) { + if (theme.base != color.kind) { + const base = color.kind === 'dark' ? darkTheme : lightTheme; + for (const prop of Object.keys(base.props)) { + if (prop === 'accent') continue; + if (prop === 'fg') continue; + theme.props[prop] = base.props[prop]; + } + } + theme.base = color.kind; + theme.props.bg = color.color; - showPreview() { - os.pageWindow('preview'); - }, + if (theme.props.fg) { + const matchedFgColor = fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(theme.props.fg).toRgbString())); + if (matchedFgColor) setFgColor(matchedFgColor); + } +} - setBgColor(color) { - if (this.theme.base != color.kind) { - const base = color.kind === 'dark' ? darkTheme : lightTheme; - for (const prop of Object.keys(base.props)) { - if (prop === 'accent') continue; - if (prop === 'fg') continue; - this.theme.props[prop] = base.props[prop]; - } - } - this.theme.base = color.kind; - this.theme.props.bg = color.color; +function setAccentColor(color) { + theme.props.accent = color; +} - if (this.theme.props.fg) { - const matchedFgColor = this.fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(this.theme.props.fg).toRgbString())); - if (matchedFgColor) this.setFgColor(matchedFgColor); - } - }, +function setFgColor(color) { + theme.props.fg = theme.base === 'light' ? color.forLight : color.forDark; +} - setAccentColor(color) { - this.theme.props.accent = color; - }, +function apply() { + themeCode = JSON5.stringify(theme, null, '\t'); + applyTheme(theme, false); + changed = true; +} - setFgColor(color) { - this.theme.props.fg = this.theme.base === 'light' ? color.forLight : color.forDark; - }, +function applyThemeCode() { + let parsed; - apply() { - this.themeCode = JSON5.stringify(this.theme, null, '\t'); - applyTheme(this.theme, false); - this.changed = true; - }, + try { + parsed = JSON5.parse(themeCode); + } catch (err) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid, + }); + return; + } - applyThemeCode() { - let parsed; + theme = parsed; +} - try { - parsed = JSON5.parse(this.themeCode); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return; - } +async function saveAs() { + const { canceled, result: name } = await os.inputText({ + title: i18n.locale.name, + allowEmpty: false, + }); + if (canceled) return; - this.theme = parsed; - }, + theme.id = uuid(); + theme.name = name; + theme.author = `@${$i.username}@${toUnicode(host)}`; + if (description) theme.desc = description; + addTheme(theme); + applyTheme(theme); + if (defaultStore.state.darkMode) { + ColdDeviceStorage.set('darkTheme', theme); + } else { + ColdDeviceStorage.set('lightTheme', theme); + } + changed = false; + os.alert({ + type: 'success', + text: i18n.t('_theme.installed', { name: theme.name }), + }); +} - async saveAs() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts.name, - allowEmpty: false - }); - if (canceled) return; +watch($$(theme), apply, { deep: true }); - this.theme.id = uuid(); - this.theme.name = name; - this.theme.author = `@${this.$i.username}@${toUnicode(host)}`; - if (this.description) this.theme.desc = this.description; - addTheme(this.theme); - applyTheme(this.theme); - if (this.$store.state.darkMode) { - ColdDeviceStorage.set('darkTheme', this.theme); - } else { - ColdDeviceStorage.set('lightTheme', this.theme); - } - this.changed = false; - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: this.theme.name }) - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.themeEditor, + icon: 'fas fa-palette', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-eye', + text: i18n.locale.preview, + handler: showPreview, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: i18n.locale.saveAs, + handler: saveAs, + }], + }, }); </script> <style lang="scss" scoped> .cwepdizn { - max-width: 800px; - margin: 0 auto; + ::v-deep(.cwepdizn-colors) { + text-align: center; - > .colorPicker { - > .colors { - padding: 32px; - text-align: center; + > .row { + > .color { + display: inline-block; + position: relative; + width: 64px; + height: 64px; + border-radius: 8px; - > .row { - > .color { - display: inline-block; - position: relative; - width: 64px; - height: 64px; - border-radius: 8px; + > .preview { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 42px; + height: 42px; + border-radius: 4px; + box-shadow: 0 2px 4px rgb(0 0 0 / 30%); + transition: transform 0.15s ease; + } + &:hover { > .preview { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: 42px; - height: 42px; - border-radius: 4px; - box-shadow: 0 2px 4px rgb(0 0 0 / 30%); - transition: transform 0.15s ease; + transform: scale(1.1); } + } - &:hover { - > .preview { - transform: scale(1.1); - } - } + &.active { + box-shadow: 0 0 0 2px var(--divider) inset; + } - &.active { - box-shadow: 0 0 0 2px var(--divider) inset; - } + &.rounded { + border-radius: 999px; - &.rounded { + > .preview { border-radius: 999px; - - > .preview { - border-radius: 999px; - } } + } - &.char { - line-height: 42px; - } + &.char { + line-height: 42px; } } } diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue index 3775796940..432d28c60b 100644 --- a/packages/client/src/pages/timeline.tutorial.vue +++ b/packages/client/src/pages/timeline.tutorial.vue @@ -65,26 +65,14 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkButton from '@/components/ui/button.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkButton, - }, - - data() { - return { - } - }, - - computed: { - tutorial: { - get() { return this.$store.reactiveState.tutorial.value || 0; }, - set(value) { this.$store.set('tutorial', value); } - }, - }, +const tutorial = computed({ + get() { return defaultStore.reactiveState.tutorial.value || 0; }, + set(value) { defaultStore.set('tutorial', value); } }); </script> diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 216b3c34ea..aabb953aec 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -1,6 +1,6 @@ <template> <MkSpacer :content-max="800"> - <div v-hotkey.global="keymap" class="cmuxhskf"> + <div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf"> <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/> <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/> @@ -18,162 +18,144 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent, computed } from 'vue'; +export default { + name: 'MkTimelinePage', +} +</script> + +<script lang="ts" setup> +import { defineAsyncComponent, computed, watch } from 'vue'; import XTimeline from '@/components/timeline.vue'; import XPostForm from '@/components/post-form.vue'; import { scroll } from '@/scripts/scroll'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { $i } from '@/account'; -export default defineComponent({ - name: 'timeline', - - components: { - XTimeline, - XTutorial: defineAsyncComponent(() => import('./timeline.tutorial.vue')), - XPostForm, - }, +const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue')); - data() { - return { - src: 'home', - queue: 0, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.timeline, - icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home', - bg: 'var(--bg)', - actions: [{ - icon: 'fas fa-list-ul', - text: this.$ts.lists, - handler: this.chooseList - }, { - icon: 'fas fa-satellite', - text: this.$ts.antennas, - handler: this.chooseAntenna - }, { - icon: 'fas fa-satellite-dish', - text: this.$ts.channel, - handler: this.chooseChannel - }, { - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }], - tabs: [{ - active: this.src === 'home', - title: this.$ts._timelines.home, - icon: 'fas fa-home', - iconOnly: true, - onClick: () => { this.src = 'home'; this.saveSrc(); }, - }, ...(this.isLocalTimelineAvailable ? [{ - active: this.src === 'local', - title: this.$ts._timelines.local, - icon: 'fas fa-comments', - iconOnly: true, - onClick: () => { this.src = 'local'; this.saveSrc(); }, - }, { - active: this.src === 'social', - title: this.$ts._timelines.social, - icon: 'fas fa-share-alt', - iconOnly: true, - onClick: () => { this.src = 'social'; this.saveSrc(); }, - }] : []), ...(this.isGlobalTimelineAvailable ? [{ - active: this.src === 'global', - title: this.$ts._timelines.global, - icon: 'fas fa-globe', - iconOnly: true, - onClick: () => { this.src = 'global'; this.saveSrc(); }, - }] : [])], - })), - }; - }, +const isLocalTimelineAvailable = !instance.disableLocalTimeline || ($i != null && ($i.isModerator || $i.isAdmin)); +const isGlobalTimelineAvailable = !instance.disableGlobalTimeline || ($i != null && ($i.isModerator || $i.isAdmin)); +const keymap = { + 't': focus, +}; - computed: { - keymap(): any { - return { - 't': this.focus - }; - }, +const tlComponent = $ref<InstanceType<typeof XTimeline>>(); +const rootEl = $ref<HTMLElement>(); - isLocalTimelineAvailable(): boolean { - return !this.$instance.disableLocalTimeline || this.$i.isModerator || this.$i.isAdmin; - }, +let src = $ref<'home' | 'local' | 'social' | 'global'>(defaultStore.state.tl.src); +let queue = $ref(0); - isGlobalTimelineAvailable(): boolean { - return !this.$instance.disableGlobalTimeline || this.$i.isModerator || this.$i.isAdmin; - }, - }, - - watch: { - src() { - this.showNav = false; - }, - }, - - created() { - this.src = this.$store.state.tl.src; - }, +function queueUpdated(q: number): void { + queue = q; +} - methods: { - queueUpdated(q) { - this.queue = q; - }, +function top(): void { + scroll(rootEl, { top: 0 }); +} - top() { - scroll(this.$el, { top: 0 }); - }, +async function chooseList(ev: MouseEvent): Promise<void> { + const lists = await os.api('users/lists/list'); + const items = lists.map(list => ({ + type: 'link', + text: list.name, + to: `/timeline/list/${list.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - async chooseList(ev) { - const lists = await os.api('users/lists/list'); - const items = lists.map(list => ({ - type: 'link', - text: list.name, - to: `/timeline/list/${list.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +async function chooseAntenna(ev: MouseEvent): Promise<void> { + const antennas = await os.api('antennas/list'); + const items = antennas.map(antenna => ({ + type: 'link', + text: antenna.name, + indicate: antenna.hasUnreadNote, + to: `/timeline/antenna/${antenna.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - async chooseAntenna(ev) { - const antennas = await os.api('antennas/list'); - const items = antennas.map(antenna => ({ - type: 'link', - text: antenna.name, - indicate: antenna.hasUnreadNote, - to: `/timeline/antenna/${antenna.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +async function chooseChannel(ev: MouseEvent): Promise<void> { + const channels = await os.api('channels/followed'); + const items = channels.map(channel => ({ + type: 'link', + text: channel.name, + indicate: channel.hasUnreadNote, + to: `/channels/${channel.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - async chooseChannel(ev) { - const channels = await os.api('channels/followed'); - const items = channels.map(channel => ({ - type: 'link', - text: channel.name, - indicate: channel.hasUnreadNote, - to: `/channels/${channel.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +function saveSrc(): void { + defaultStore.set('tl', { + src: src, + }); +} - saveSrc() { - this.$store.set('tl', { - src: this.src, - }); - }, +async function timetravel(): Promise<void> { + const { canceled, result: date } = await os.inputDate({ + title: i18n.locale.date, + }); + if (canceled) return; - async timetravel() { - const { canceled, result: date } = await os.inputDate({ - title: this.$ts.date, - }); - if (canceled) return; + tlComponent.timetravel(date); +} - this.$refs.tl.timetravel(date); - }, +function focus(): void { + tlComponent.focus(); +} - focus() { - (this.$refs.tl as any).focus(); - } - } +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.timeline, + icon: src === 'local' ? 'fas fa-comments' : src === 'social' ? 'fas fa-share-alt' : src === 'global' ? 'fas fa-globe' : 'fas fa-home', + bg: 'var(--bg)', + actions: [{ + icon: 'fas fa-list-ul', + text: i18n.locale.lists, + handler: chooseList, + }, { + icon: 'fas fa-satellite', + text: i18n.locale.antennas, + handler: chooseAntenna, + }, { + icon: 'fas fa-satellite-dish', + text: i18n.locale.channel, + handler: chooseChannel, + }, { + icon: 'fas fa-calendar-alt', + text: i18n.locale.jumpToSpecifiedDate, + handler: timetravel, + }], + tabs: [{ + active: src === 'home', + title: i18n.locale._timelines.home, + icon: 'fas fa-home', + iconOnly: true, + onClick: () => { src = 'home'; saveSrc(); }, + }, ...(isLocalTimelineAvailable ? [{ + active: src === 'local', + title: i18n.locale._timelines.local, + icon: 'fas fa-comments', + iconOnly: true, + onClick: () => { src = 'local'; saveSrc(); }, + }, { + active: src === 'social', + title: i18n.locale._timelines.social, + icon: 'fas fa-share-alt', + iconOnly: true, + onClick: () => { src = 'social'; saveSrc(); }, + }] : []), ...(isGlobalTimelineAvailable ? [{ + active: src === 'global', + title: i18n.locale._timelines.global, + icon: 'fas fa-globe', + iconOnly: true, + onClick: () => { src = 'global'; saveSrc(); }, + }] : [])], + })), }); </script> diff --git a/packages/client/src/pages/user-ap-info.vue b/packages/client/src/pages/user-ap-info.vue deleted file mode 100644 index 0027381f53..0000000000 --- a/packages/client/src/pages/user-ap-info.vue +++ /dev/null @@ -1,124 +0,0 @@ -<template> -<FormBase> - <FormSuspense v-slot="{ result: ap }" :p="apPromiseFactory"> - <FormGroup> - <template #label>ActivityPub</template> - <FormKeyValueView> - <template #key>Type</template> - <template #value><span class="_monospace">{{ ap.type }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>URI</template> - <template #value><span class="_monospace">{{ ap.id }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>URL</template> - <template #value><span class="_monospace">{{ ap.url }}</span></template> - </FormKeyValueView> - <FormGroup> - <FormKeyValueView> - <template #key>Inbox</template> - <template #value><span class="_monospace">{{ ap.inbox }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>Shared Inbox</template> - <template #value><span class="_monospace">{{ ap.sharedInbox || ap.endpoints.sharedInbox }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>Outbox</template> - <template #value><span class="_monospace">{{ ap.outbox }}</span></template> - </FormKeyValueView> - </FormGroup> - <FormTextarea readonly tall code pre :value="ap.publicKey.publicKeyPem"> - <span>Public Key</span> - </FormTextarea> - <FormKeyValueView> - <template #key>Discoverable</template> - <template #value>{{ ap.discoverable ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>ManuallyApprovesFollowers</template> - <template #value>{{ ap.manuallyApprovesFollowers ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormObjectView tall :value="ap"> - <span>Raw</span> - </FormObjectView> - <FormGroup> - <FormLink :to="`https://${user.host}/.well-known/webfinger?resource=acct:${user.username}`" external>WebFinger</FormLink> - </FormGroup> - <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink> - <FormKeyValueView v-else> - <template #key>{{ $ts.instanceInfo }}</template> - <template #value>(Local user)</template> - </FormKeyValueView> - </FormGroup> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import FormObjectView from '@/components/debobigego/object-view.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import number from '@/filters/number'; -import bytes from '@/filters/bytes'; -import * as symbols from '@/symbols'; -import { url } from '@/config'; - -export default defineComponent({ - components: { - FormBase, - FormTextarea, - FormObjectView, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - FormSuspense, - }, - - props: { - userId: { - type: String, - required: true - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.userInfo, - icon: 'fas fa-info-circle' - }, - user: null, - apPromiseFactory: null, - } - }, - - mounted() { - this.fetch(); - }, - - methods: { - number, - bytes, - - async fetch() { - this.user = await os.api('users/show', { - userId: this.userId - }); - - this.apPromiseFactory = () => os.api('ap/get', { - uri: this.user.uri || `${url}/users/${this.user.id}` - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 0fd208a64a..4bdc82f601 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -1,70 +1,75 @@ <template> -<FormBase> +<MkSpacer :content-max="500" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <div class="_debobigegoItem aeakzknw"> - <MkAvatar class="avatar" :user="user" :show-indicator="true"/> - </div> - - <FormLink :to="userPage(user)">Profile</FormLink> + <div class="_formRoot"> + <div class="_formBlock aeakzknw"> + <MkAvatar class="avatar" :user="user" :show-indicator="true"/> + </div> - <FormGroup> - <FormKeyValueView> - <template #key>Acct</template> - <template #value><span class="_monospace">{{ acct(user) }}</span></template> - </FormKeyValueView> + <FormLink :to="userPage(user)">Profile</FormLink> - <FormKeyValueView> - <template #key>ID</template> - <template #value><span class="_monospace">{{ user.id }}</span></template> - </FormKeyValueView> - </FormGroup> + <div class="_formBlock"> + <MkKeyValue :copy="acct(user)" oneline style="margin: 1em 0;"> + <template #key>Acct</template> + <template #value><span class="_monospace">{{ acct(user) }}</span></template> + </MkKeyValue> - <FormGroup v-if="iAmModerator"> - <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> - <FormSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> - <FormSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> - </FormGroup> + <MkKeyValue :copy="user.id" oneline style="margin: 1em 0;"> + <template #key>ID</template> + <template #value><span class="_monospace">{{ user.id }}</span></template> + </MkKeyValue> + </div> - <FormGroup> - <FormButton v-if="user.host != null" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> - <FormButton v-if="user.host == null && iAmModerator" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> - </FormGroup> + <FormSection v-if="iAmModerator"> + <template #label>Moderation</template> + <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> + <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> + <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> + <FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> + </FormSection> - <FormGroup> - <FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink> + <FormSection> + <template #label>ActivityPub</template> - <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink> - <FormKeyValueView v-else> - <template #key>{{ $ts.instanceInfo }}</template> - <template #value>(Local user)</template> - </FormKeyValueView> - </FormGroup> + <div class="_formBlock"> + <MkKeyValue v-if="user.host" oneline style="margin: 1em 0;"> + <template #key>{{ $ts.instanceInfo }}</template> + <template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="fas fa-angle-right"></i></MkA></template> + </MkKeyValue> + <MkKeyValue v-else oneline style="margin: 1em 0;"> + <template #key>{{ $ts.instanceInfo }}</template> + <template #value>(Local user)</template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.updatedAt }}</template> + <template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template> + </MkKeyValue> + <MkKeyValue v-if="ap" oneline style="margin: 1em 0;"> + <template #key>Type</template> + <template #value><span class="_monospace">{{ ap.type }}</span></template> + </MkKeyValue> + </div> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.updatedAt }}</template> - <template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template> - </FormKeyValueView> - </FormGroup> + <FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> + </FormSection> - <FormObjectView tall :value="user"> - <span>Raw</span> - </FormObjectView> + <MkObjectView tall :value="user"> + </MkObjectView> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { computed, defineAsyncComponent, defineComponent } from 'vue'; -import FormObjectView from '@/components/debobigego/object-view.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkObjectView from '@/components/object-view.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormLink from '@/components/form/link.vue'; +import FormSection from '@/components/form/section.vue'; +import FormButton from '@/components/ui/button.vue'; +import MkKeyValue from '@/components/key-value.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; @@ -74,14 +79,13 @@ import { userPage, acct } from '@/filters/user'; export default defineComponent({ components: { - FormBase, + FormSection, FormTextarea, FormSwitch, - FormObjectView, + MkObjectView, FormButton, FormLink, - FormGroup, - FormKeyValueView, + MkKeyValue, FormSuspense, }, @@ -97,6 +101,7 @@ export default defineComponent({ [symbols.PAGE_INFO]: computed(() => ({ title: this.user ? acct(this.user) : this.$ts.userInfo, icon: 'fas fa-info-circle', + bg: 'var(--bg)', actions: this.user ? [this.user.url ? { text: this.user.url, icon: 'fas fa-external-link-alt', @@ -108,6 +113,7 @@ export default defineComponent({ init: null, user: null, info: null, + ap: null, moderator: false, silenced: false, suspended: false, @@ -126,6 +132,13 @@ export default defineComponent({ this.init = this.createFetcher(); }, immediate: true + }, + user() { + os.api('ap/get', { + uri: this.user.uri || `${url}/users/${this.user.id}` + }).then(res => { + this.ap = res; + }); } }, @@ -234,7 +247,6 @@ export default defineComponent({ .aeakzknw { > .avatar { display: block; - margin: 0 auto; width: 64px; height: 64px; } diff --git a/packages/client/src/pages/user/clips.vue b/packages/client/src/pages/user/clips.vue index aad5317ce0..870e6f7174 100644 --- a/packages/client/src/pages/user/clips.vue +++ b/packages/client/src/pages/user/clips.vue @@ -28,7 +28,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/clips', + endpoint: 'users/clips' as const, limit: 20, params: { userId: this.user.id, diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue index 9fb8943fb8..98a1fc0f86 100644 --- a/packages/client/src/pages/user/follow-list.vue +++ b/packages/client/src/pages/user/follow-list.vue @@ -1,6 +1,6 @@ <template> <div> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="mk-following-or-followers"> + <MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination" class="mk-following-or-followers"> <div class="users _isolated"> <MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/> </div> @@ -8,50 +8,32 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkUserInfo from '@/components/user-info.vue'; import MkPagination from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - MkPagination, - MkUserInfo, - }, +const props = defineProps<{ + user: misskey.entities.User; + type: 'following' | 'followers'; +}>(); - props: { - user: { - type: Object, - required: true - }, - type: { - type: String, - required: true - }, - }, +const followingPagination = { + endpoint: 'users/following' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; - data() { - return { - pagination: { - endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers', - limit: 20, - params: { - userId: this.user.id, - } - }, - }; - }, - - watch: { - type() { - this.$refs.list.reload(); - }, - - user() { - this.$refs.list.reload(); - } - } -}); +const followersPagination = { + endpoint: 'users/followers' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue index 860aa9f44f..07dda4a292 100644 --- a/packages/client/src/pages/user/gallery.vue +++ b/packages/client/src/pages/user/gallery.vue @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkGalleryPostPreview from '@/components/gallery-post-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -29,20 +29,14 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/gallery/posts', + endpoint: 'users/gallery/posts' as const, limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.user.id - }) + })), }, }; }, - - watch: { - user() { - this.$refs.list.reload(); - } - } }); </script> diff --git a/packages/client/src/pages/user/index.activity.vue b/packages/client/src/pages/user/index.activity.vue index e51d6c6090..43a4f476f1 100644 --- a/packages/client/src/pages/user/index.activity.vue +++ b/packages/client/src/pages/user/index.activity.vue @@ -8,27 +8,16 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkContainer from '@/components/ui/container.vue'; import MkChart from '@/components/chart.vue'; -export default defineComponent({ - components: { - MkContainer, - MkChart, - }, - props: { - user: { - type: Object, - required: true - }, - limit: { - type: Number, - required: false, - default: 40 - } - }, +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + limit?: number; +}>(), { + limit: 40, }); </script> diff --git a/packages/client/src/pages/user/index.timeline.vue b/packages/client/src/pages/user/index.timeline.vue index 2ffa496979..a1329a7411 100644 --- a/packages/client/src/pages/user/index.timeline.vue +++ b/packages/client/src/pages/user/index.timeline.vue @@ -1,60 +1,36 @@ <template> <div v-sticky-container class="yrzkoczt"> - <MkTab v-model="with_" class="tab"> + <MkTab v-model="include" class="tab"> <option :value="null">{{ $ts.notes }}</option> <option value="replies">{{ $ts.notesAndReplies }}</option> <option value="files">{{ $ts.withFiles }}</option> </MkTab> - <XNotes ref="timeline" :no-gap="true" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> + <XNotes :no-gap="true" :pagination="pagination"/> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; +import * as misskey from 'misskey-js'; import XNotes from '@/components/notes.vue'; import MkTab from '@/components/tab.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XNotes, - MkTab, - }, +const props = defineProps<{ + user: misskey.entities.UserDetailed; +}>(); - props: { - user: { - type: Object, - required: true, - }, - }, +const include = ref<string | null>(null); - data() { - return { - date: null, - with_: null, - pagination: { - endpoint: 'users/notes', - limit: 10, - params: init => ({ - userId: this.user.id, - includeReplies: this.with_ === 'replies', - withFiles: this.with_ === 'files', - untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined), - }) - } - }; - }, - - watch: { - user() { - this.$refs.timeline.reload(); - }, - - with_() { - this.$refs.timeline.reload(); - }, - }, -}); +const pagination = { + endpoint: 'users/notes' as const, + limit: 10, + params: computed(() => ({ + userId: props.user.id, + includeReplies: include.value === 'replies', + withFiles: include.value === 'files', + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 0b96368587..599e24d81c 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -1,196 +1,125 @@ <template> <div> -<transition name="fade" mode="out-in"> - <div v-if="user && narrow === false" class="ftskorzw wide"> - <MkRemoteCaution v-if="user.host != null" :href="user.url"/> + <transition name="fade" mode="out-in"> + <MkSpacer v-if="user" :content-max="narrow ? 800 : 1100"> + <div v-size="{ max: [500] }" class="ftskorzw" :class="{ wide: !narrow }"> + <div class="main"> + <!-- TODO --> + <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> + <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> - <div class="banner-container" :style="style"> - <div ref="banner" class="banner" :style="style"></div> - </div> - <div class="contents"> - <div class="side _forceContainerFull_"> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="name"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <MkAcct :user="user" :detail="true" class="acct"/> - </div> - <div v-if="$i && $i.id != user.id && user.isFollowed" class="followed"><span>{{ $ts.followsYou }}</span></div> - <div class="status"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl v-if="user.location" class="field"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl v-if="user.birthday" class="field"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div v-if="user.fields.length > 0" class="fields"> - <dl v-for="(field, i) in user.fields" :key="i" class="field"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <XActivity :key="user.id" :user="user" class="_gap"/> - <XPhotos :key="user.id" :user="user" class="_gap"/> - </div> - <div class="main"> - <div class="actions"> - <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> - </div> - <template v-if="page === 'index'"> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _gap" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/> - </div> - <div class="_gap"> - <XUserTimeline :user="user"/> - </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - </div> - </div> - </div> - <MkSpacer v-else-if="user && narrow === true" :content-max="800"> - <div v-size="{ max: [500] }" class="ftskorzw narrow"> - <!-- TODO --> - <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> - <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> + <div class="profile"> + <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - <div class="profile"> - <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - - <div :key="user.id" class="_block main"> - <div class="banner-container" :style="style"> - <div ref="banner" class="banner" :style="style"></div> - <div class="fade"></div> - <div class="title"> - <MkUserName class="name" :user="user" :nowrap="true"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + <div :key="user.id" class="_block main"> + <div class="banner-container" :style="style"> + <div ref="banner" class="banner" :style="style"></div> + <div class="fade"></div> + <div class="title"> + <MkUserName class="name" :user="user" :nowrap="true"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> + <div v-if="$i" class="actions"> + <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> + <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> + </div> + </div> + <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> + <div class="title"> + <MkUserName :user="user" :nowrap="false" class="name"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <div class="description"> + <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> + <p v-else class="empty">{{ $ts.noAccountDescription }}</p> + </div> + <div class="fields system"> + <dl v-if="user.location" class="field"> + <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> + <dd class="value">{{ user.location }}</dd> + </dl> + <dl v-if="user.birthday" class="field"> + <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> + <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> + </dl> + <dl class="field"> + <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> + <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> + </dl> + </div> + <div v-if="user.fields.length > 0" class="fields"> + <dl v-for="(field, i) in user.fields" :key="i" class="field"> + <dt class="name"> + <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> + </dt> + <dd class="value"> + <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> + </dd> + </dl> + </div> + <div class="status"> + <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }"> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $ts.notes }}</span> + </MkA> + <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $ts.following }}</span> + </MkA> + <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $ts.followers }}</span> + </MkA> </div> - </div> - <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> - <div v-if="$i" class="actions"> - <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> - </div> - </div> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="title"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> </div> </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl v-if="user.location" class="field"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl v-if="user.birthday" class="field"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div v-if="user.fields.length > 0" class="fields"> - <dl v-for="(field, i) in user.fields" :key="i" class="field"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <div class="status"> - <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - </div> - </div> - <div class="contents"> - <template v-if="page === 'index'"> - <div> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/> - </div> - <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> - <XPhotos :key="user.id" :user="user"/> - <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> - </div> - <div> - <XUserTimeline :user="user"/> + <div class="contents"> + <template v-if="page === 'index'"> + <div> + <div v-if="user.pinnedNotes.length > 0" class="_gap"> + <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true"/> + </div> + <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> + <template v-if="narrow"> + <XPhotos :key="user.id" :user="user"/> + <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> + </template> + </div> + <div> + <XUserTimeline :user="user"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> + <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> - <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> + </div> + <div v-if="!narrow" class="sub"> + <XPhotos :key="user.id" :user="user"/> + <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> + </div> </div> - </div> - </MkSpacer> - <MkError v-else-if="error" @retry="fetch()"/> - <MkLoading v-else/> -</transition> + </MkSpacer> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </transition> </div> </template> @@ -314,7 +243,7 @@ export default defineComponent({ mounted() { window.requestAnimationFrame(this.parallaxLoop); - this.narrow = true//this.$el.clientWidth < 1000; + this.narrow = this.$el.clientWidth < 1000; }, beforeUnmount() { @@ -356,11 +285,6 @@ export default defineComponent({ banner.style.backgroundPosition = `center calc(50% - ${pos}px)`; }, - pinnedNoteUpdated(oldValue, newValue) { - const i = this.user.pinnedNotes.findIndex(n => n === oldValue); - this.user.pinnedNotes[i] = newValue; - }, - number, userPage @@ -378,447 +302,289 @@ export default defineComponent({ opacity: 0; } -.ftskorzw.wide { +.ftskorzw { - > .banner-container { - position: relative; - height: 300px; - overflow: hidden; - background-size: cover; - background-position: center; + > .main { - > .banner { - height: 100%; - background-color: #4c5e6d; - background-size: cover; - background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; + > .punished { + font-size: 0.8em; + padding: 16px; } - } - - > .contents { - display: flex; - padding: 16px; - - > .side { - width: 360px; - > .avatar { - display: block; - width: 180px; - height: 180px; - margin: -130px auto 0 auto; - } - - > .name { - padding: 16px 0px 20px 0; - text-align: center; - - > .name { - display: block; - font-size: 1.75em; - font-weight: bold; - } - } - - > .followed { - text-align: center; - - > span { - display: inline-block; - font-size: 80%; - padding: 8px 12px; - margin-bottom: 20px; - border: solid 0.5px var(--divider); - border-radius: 999px; - } - } + > .profile { - > .status { - display: flex; - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; + > .main { + position: relative; + overflow: hidden; - > a { - flex: 1; - text-align: center; + > .banner-container { + position: relative; + height: 250px; + overflow: hidden; + background-size: cover; + background-position: center; - &.active { - color: var(--accent); + > .banner { + height: 100%; + background-color: #4c5e6d; + background-size: cover; + background-position: center; + box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; + will-change: background-position; } - &:hover { - text-decoration: none; + > .fade { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 78px; + background: linear-gradient(transparent, rgba(#000, 0.7)); } - > b { - display: block; - line-height: 16px; + > .followed { + position: absolute; + top: 12px; + left: 12px; + padding: 4px 8px; + color: #fff; + background: rgba(0, 0, 0, 0.7); + font-size: 0.7em; + border-radius: 6px; } - > span { - font-size: 75%; - } - } - } + > .actions { + position: absolute; + top: 12px; + right: 12px; + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + background: rgba(0, 0, 0, 0.2); + padding: 8px; + border-radius: 24px; - > .description { - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; - } + > .menu { + vertical-align: bottom; + height: 31px; + width: 31px; + color: #fff; + text-shadow: 0 0 8px #000; + font-size: 16px; + } - > .fields { - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; + > .koudoku { + margin-left: 4px; + vertical-align: bottom; + } + } - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + > .title { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 0 0 8px 154px; + box-sizing: border-box; + color: #fff; - &:not(:last-child) { - margin-bottom: 8px; - } + > .name { + display: block; + margin: 0; + line-height: 32px; + font-weight: bold; + font-size: 1.8em; + text-shadow: 0 0 8px #000; + } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - } + > .bottom { + > * { + display: inline-block; + margin-right: 16px; + line-height: 20px; + opacity: 0.8; - > .value { - width: 70%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + &.username { + font-weight: bold; + } + } + } } } - } - } - > .main { - flex: 1; - margin-left: var(--margin); - min-width: 0; - - > .nav { - display: flex; - align-items: center; - margin-top: var(--margin); - //font-size: 120%; - font-weight: bold; - - > .link { - display: inline-block; - padding: 15px 24px 12px 24px; + > .title { + display: none; text-align: center; - border-bottom: solid 3px transparent; - - &:hover { - text-decoration: none; - } - - &.active { - color: var(--accent); - border-bottom-color: var(--accent); - } + padding: 50px 8px 16px 8px; + font-weight: bold; + border-bottom: solid 0.5px var(--divider); - &:not(.active):hover { - color: var(--fgHighlighted); + > .bottom { + > * { + display: inline-block; + margin-right: 8px; + opacity: 0.8; + } } + } - > .icon { - margin-right: 6px; - } + > .avatar { + display: block; + position: absolute; + top: 170px; + left: 16px; + z-index: 2; + width: 120px; + height: 120px; + box-shadow: 1px 1px 3px rgba(#000, 0.2); } - > .actions { - display: flex; - align-items: center; - margin-left: auto; + > .description { + padding: 24px 24px 24px 154px; + font-size: 0.95em; - > .menu { - padding: 12px 16px; + > .empty { + margin: 0; + opacity: 0.5; } } - } - } - } -} - -.ftskorzw.narrow { - box-sizing: border-box; - overflow: clip; - background: var(--bg); - - > .punished { - font-size: 0.8em; - padding: 16px; - } - - > .profile { - - > .main { - position: relative; - overflow: hidden; - > .banner-container { - position: relative; - height: 250px; - overflow: hidden; - background-size: cover; - background-position: center; - - > .banner { - height: 100%; - background-color: #4c5e6d; - background-size: cover; - background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; - } + > .fields { + padding: 24px; + font-size: 0.9em; + border-top: solid 0.5px var(--divider); - > .fade { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 78px; - background: linear-gradient(transparent, rgba(#000, 0.7)); - } + > .field { + display: flex; + padding: 0; + margin: 0; + align-items: center; - > .followed { - position: absolute; - top: 12px; - left: 12px; - padding: 4px 8px; - color: #fff; - background: rgba(0, 0, 0, 0.7); - font-size: 0.7em; - border-radius: 6px; - } + &:not(:last-child) { + margin-bottom: 8px; + } - > .actions { - position: absolute; - top: 12px; - right: 12px; - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - background: rgba(0, 0, 0, 0.2); - padding: 8px; - border-radius: 24px; + > .name { + width: 30%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: bold; + text-align: center; + } - > .menu { - vertical-align: bottom; - height: 31px; - width: 31px; - color: #fff; - text-shadow: 0 0 8px #000; - font-size: 16px; + > .value { + width: 70%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0; + } } - > .koudoku { - margin-left: 4px; - vertical-align: bottom; + &.system > .field > .name { } } - > .title { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: 0 0 8px 154px; - box-sizing: border-box; - color: #fff; + > .status { + display: flex; + padding: 24px; + border-top: solid 0.5px var(--divider); - > .name { - display: block; - margin: 0; - line-height: 32px; - font-weight: bold; - font-size: 1.8em; - text-shadow: 0 0 8px #000; - } + > a { + flex: 1; + text-align: center; - > .bottom { - > * { - display: inline-block; - margin-right: 16px; - line-height: 20px; - opacity: 0.8; + &.active { + color: var(--accent); + } - &.username { - font-weight: bold; - } + &:hover { + text-decoration: none; } - } - } - } - > .title { - display: none; - text-align: center; - padding: 50px 8px 16px 8px; - font-weight: bold; - border-bottom: solid 0.5px var(--divider); + > b { + display: block; + line-height: 16px; + } - > .bottom { - > * { - display: inline-block; - margin-right: 8px; - opacity: 0.8; + > span { + font-size: 70%; + } } } } + } - > .avatar { - display: block; - position: absolute; - top: 170px; - left: 16px; - z-index: 2; - width: 120px; - height: 120px; - box-shadow: 1px 1px 3px rgba(#000, 0.2); - } - - > .description { - padding: 24px 24px 24px 154px; - font-size: 0.95em; - - > .empty { - margin: 0; - opacity: 0.5; - } + > .contents { + > .content { + margin-bottom: var(--margin); } + } + } - > .fields { - padding: 24px; - font-size: 0.9em; - border-top: solid 0.5px var(--divider); - - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + &.max-width_500px { + > .main { + > .profile > .main { + > .banner-container { + height: 140px; - &:not(:last-child) { - margin-bottom: 8px; + > .fade { + display: none; } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - text-align: center; - } - - > .value { - width: 70%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + > .title { + display: none; } } - &.system > .field > .name { + > .title { + display: block; } - } - > .status { - display: flex; - padding: 24px; - border-top: solid 0.5px var(--divider); + > .avatar { + top: 90px; + left: 0; + right: 0; + width: 92px; + height: 92px; + margin: auto; + } - > a { - flex: 1; + > .description { + padding: 16px; text-align: center; - - &.active { - color: var(--accent); - } - - &:hover { - text-decoration: none; - } - - > b { - display: block; - line-height: 16px; - } - - > span { - font-size: 70%; - } } - } - } - } - > .contents { - > .content { - margin-bottom: var(--margin); - } - } - - &.max-width_500px { - > .profile > .main { - > .banner-container { - height: 140px; - - > .fade { - display: none; + > .fields { + padding: 16px; } - > .title { - display: none; + > .status { + padding: 16px; } } - > .title { - display: block; - } - - > .avatar { - top: 90px; - left: 0; - right: 0; - width: 92px; - height: 92px; - margin: auto; - } - - > .description { - padding: 16px; - text-align: center; + > .contents { + > .nav { + font-size: 80%; + } } + } + } - > .fields { - padding: 16px; - } + &.wide { + display: flex; + width: 100%; - > .status { - padding: 16px; - } + > .main { + width: 100%; + min-width: 0; } - > .contents { - > .nav { - font-size: 80%; - } + > .sub { + max-width: 350px; + min-width: 350px; + margin-left: var(--margin); } } } diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue index 40d1fe3842..ad101158e0 100644 --- a/packages/client/src/pages/user/pages.vue +++ b/packages/client/src/pages/user/pages.vue @@ -6,42 +6,23 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkPagePreview from '@/components/page-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - MkPagination, - MkPagePreview, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - pagination: { - endpoint: 'users/pages', - limit: 20, - params: { - userId: this.user.id, - } - }, - }; - }, - - watch: { - user() { - this.$refs.list.reload(); - } - } -}); +const pagination = { + endpoint: 'users/pages' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue index 69c27de55b..d2c1f92ebb 100644 --- a/packages/client/src/pages/user/reactions.vue +++ b/packages/client/src/pages/user/reactions.vue @@ -7,50 +7,30 @@ <MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/> <MkTime :time="item.createdAt" class="createdAt"/> </div> - <MkNote :key="item.id" :note="item.note" @update:note="updated(note, $event)"/> + <MkNote :key="item.id" :note="item.note"/> </div> </MkPagination> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkPagination from '@/components/ui/pagination.vue'; import MkNote from '@/components/note.vue'; import MkReactionIcon from '@/components/reaction-icon.vue'; -export default defineComponent({ - components: { - MkPagination, - MkNote, - MkReactionIcon, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - pagination: { - endpoint: 'users/reactions', - limit: 20, - params: { - userId: this.user.id, - } - }, - }; - }, - - watch: { - user() { - this.$refs.list.reload(); - } - }, -}); +const pagination = { + endpoint: 'users/reactions' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/v.vue b/packages/client/src/pages/v.vue deleted file mode 100644 index 3b1bb20861..0000000000 --- a/packages/client/src/pages/v.vue +++ /dev/null @@ -1,29 +0,0 @@ -<template> -<div> - <section class="_section"> - <div class="_content" style="text-align: center;"> - <img src="/static-assets/icons/512.png" alt="" style="display: block; width: 100px; margin: 0 auto; border-radius: 16px;"/> - <div style="margin-top: 0.75em;">Misskey</div> - <div style="opacity: 0.5;">v{{ version }}</div> - </div> - </section> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { version } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Misskey', - icon: null - }, - version, - } - }, -}); -</script> diff --git a/packages/client/src/pizzax.ts b/packages/client/src/pizzax.ts index 396abc2418..fa35df5511 100644 --- a/packages/client/src/pizzax.ts +++ b/packages/client/src/pizzax.ts @@ -1,6 +1,7 @@ import { onUnmounted, Ref, ref, watch } from 'vue'; import { $i } from './account'; import { api } from './os'; +import { stream } from './stream'; type StateDef = Record<string, { where: 'account' | 'device' | 'deviceAccount'; @@ -9,6 +10,8 @@ type StateDef = Record<string, { type ArrayElement<A> = A extends readonly (infer T)[] ? T : never; +const connection = $i && stream.useChannel('main'); + export class Storage<T extends StateDef> { public readonly key: string; public readonly keyForLocalStorage: string; @@ -51,7 +54,7 @@ export class Storage<T extends StateDef> { if ($i) { // ãªãœã‹setTimeoutã—ãªã„ã¨api関数内ã§ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹(ãŠãらã循環å‚ç…§ã—ã¦ã‚‹ã“ã¨ã«åŽŸå› ãŒã‚りãã†) - setTimeout(() => { + window.setTimeout(() => { api('i/registry/get-all', { scope: ['client', this.key] }).then(kvs => { const cache = {}; for (const [k, v] of Object.entries(def)) { @@ -69,8 +72,19 @@ export class Storage<T extends StateDef> { localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); }); }, 1); + // streamingã®user storage updateイベントを監視ã—ã¦æ›´æ–° + connection?.on('registryUpdated', ({ scope, key, value }: { scope: string[], key: keyof T, value: T[typeof key]['default'] }) => { + if (scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return; + + this.state[key] = value; + this.reactiveState[key].value = value; - // TODO: streamingã®user storage updateイベントを監視ã—ã¦æ›´æ–° + const cache = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}'); + if (cache[key] !== value) { + cache[key] = value; + localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); + } + }); } } diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 9b4dd162f3..ec48b76fdf 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -20,7 +20,6 @@ const defaultRoutes = [ { path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, { path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, { path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, - { path: '/@:acct/room', props: true, component: page('room/room') }, { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) }, @@ -34,7 +33,7 @@ const defaultRoutes = [ { path: '/explore/tags/:tag', props: true, component: page('explore') }, { path: '/federation', component: page('federation') }, { path: '/emojis', component: page('emojis') }, - { path: '/search', component: page('search') }, + { path: '/search', component: page('search'), props: route => ({ query: route.query.q, channel: route.query.channel }) }, { path: '/pages', name: 'pages', component: page('pages') }, { path: '/pages/new', component: page('page-editor/page-editor') }, { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, @@ -73,10 +72,7 @@ const defaultRoutes = [ { path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) }, { path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, { path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, - { path: '/user-ap-info/:user', component: page('user-ap-info'), props: route => ({ userId: route.params.user }) }, { path: '/instance-info/:host', component: page('instance-info'), props: route => ({ host: route.params.host }) }, - { path: '/games/reversi', component: page('reversi/index') }, - { path: '/games/reversi/:gameId', component: page('reversi/game'), props: route => ({ gameId: route.params.gameId }) }, { path: '/mfm-cheat-sheet', component: page('mfm-cheat-sheet') }, { path: '/api-console', component: page('api-console') }, { path: '/preview', component: page('preview') }, @@ -119,11 +115,11 @@ export const router = createRouter({ window._scroll = () => { // ã•らã«Hacky if (to.name === 'index') { window.scroll({ top: indexScrollPos, behavior: 'instant' }); - const i = setInterval(() => { + const i = window.setInterval(() => { window.scroll({ top: indexScrollPos, behavior: 'instant' }); }, 10); - setTimeout(() => { - clearInterval(i); + window.setTimeout(() => { + window.clearInterval(i); }, 500); } else { window.scroll({ top: 0, behavior: 'instant' }); diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index f2d5806484..f4a3a4c0fc 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -1,4 +1,4 @@ -import { Ref, ref } from 'vue'; +import { nextTick, Ref, ref } from 'vue'; import * as getCaretCoordinates from 'textarea-caret'; import { toASCII } from 'punycode/'; import { popup } from '@/os'; @@ -10,26 +10,23 @@ export class Autocomplete { q: Ref<string | null>; close: Function; } | null; - private textarea: any; - private vm: any; + private textarea: HTMLInputElement | HTMLTextAreaElement; private currentType: string; - private opts: { - model: string; - }; + private textRef: Ref<string>; private opening: boolean; private get text(): string { - return this.vm[this.opts.model]; + return this.textRef.value; } private set text(text: string) { - this.vm[this.opts.model] = text; + this.textRef.value = text; } /** * 対象ã®ãƒ†ã‚ストエリアを与ãˆã¦ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‚’åˆæœŸåŒ–ã—ã¾ã™ã€‚ */ - constructor(textarea, vm, opts) { + constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) { //#region BIND this.onInput = this.onInput.bind(this); this.complete = this.complete.bind(this); @@ -38,8 +35,7 @@ export class Autocomplete { this.suggestion = null; this.textarea = textarea; - this.vm = vm; - this.opts = opts; + this.textRef = textRef; this.opening = false; this.attach(); @@ -218,7 +214,7 @@ export class Autocomplete { this.text = `${trimmedBefore}@${acct} ${after}`; // ã‚ャレットを戻㙠- this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); @@ -234,7 +230,7 @@ export class Autocomplete { this.text = `${trimmedBefore}#${value} ${after}`; // ã‚ャレットを戻㙠- this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (value.length + 2); this.textarea.setSelectionRange(pos, pos); @@ -250,7 +246,7 @@ export class Autocomplete { this.text = trimmedBefore + value + after; // ã‚ャレットを戻㙠- this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); @@ -266,7 +262,7 @@ export class Autocomplete { this.text = `${trimmedBefore}$[${value} ]${after}`; // ã‚ャレットを戻㙠- this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (value.length + 3); this.textarea.setSelectionRange(pos, pos); diff --git a/packages/client/src/scripts/check-word-mute.ts b/packages/client/src/scripts/check-word-mute.ts index 3b1fa75b1e..55637bb3b3 100644 --- a/packages/client/src/scripts/check-word-mute.ts +++ b/packages/client/src/scripts/check-word-mute.ts @@ -1,4 +1,4 @@ -export async function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): Promise<boolean> { +export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index de7591f5a0..bd8689e4f8 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -1,7 +1,11 @@ -// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as { +export const unicodeEmojiCategories = ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'] as const; + +export type UnicodeEmojiDef = { name: string; keywords: string[]; char: string; - category: 'people' | 'animals_and_nature' | 'food_and_drink' | 'activity' | 'travel_and_places' | 'objects' | 'symbols' | 'flags'; -}[]; + category: typeof unicodeEmojiCategories[number]; +} + +// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb +export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[]; diff --git a/packages/client/src/scripts/form.ts b/packages/client/src/scripts/form.ts index 7bf6cec452..7f321cc0ae 100644 --- a/packages/client/src/scripts/form.ts +++ b/packages/client/src/scripts/form.ts @@ -23,9 +23,37 @@ export type FormItem = { enum: string[]; } | { label?: string; + type: 'radio'; + default: unknown | null; + hidden?: boolean; + options: { + label: string; + value: unknown; + }[]; +} | { + label?: string; + type: 'object'; + default: Record<string, unknown> | null; + hidden: true; +} | { + label?: string; type: 'array'; default: unknown[] | null; - hidden?: boolean; + hidden: true; }; export type Form = Record<string, FormItem>; + +type GetItemType<Item extends FormItem> = + Item['type'] extends 'string' ? string : + Item['type'] extends 'number' ? number : + Item['type'] extends 'boolean' ? boolean : + Item['type'] extends 'radio' ? unknown : + Item['type'] extends 'enum' ? string : + Item['type'] extends 'array' ? unknown[] : + Item['type'] extends 'object' ? Record<string, unknown> + : never; + +export type GetFormResultType<F extends Form> = { + [P in keyof F]: GetItemType<F[P]>; +}; diff --git a/packages/client/src/scripts/games/reversi/core.ts b/packages/client/src/scripts/games/reversi/core.ts deleted file mode 100644 index 0cb8922e19..0000000000 --- a/packages/client/src/scripts/games/reversi/core.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { count, concat } from '@/scripts/array'; - -// MISSKEY REVERSI ENGINE - -/** - * true ... é»’ - * false ... 白 - */ -export type Color = boolean; -const BLACK = true; -const WHITE = false; - -export type MapPixel = 'null' | 'empty'; - -export type Options = { - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; -}; - -export type Undo = { - /** - * 色 - */ - color: Color; - - /** - * ã©ã“ã«æ‰“ã£ãŸã‹ - */ - pos: number; - - /** - * å転ã—ãŸçŸ³ã®ä½ç½®ã®é…列 - */ - effects: number[]; - - /** - * ターン - */ - turn: Color | null; -}; - -/** - * リãƒãƒ¼ã‚·ã‚¨ãƒ³ã‚¸ãƒ³ - */ -export default class Reversi { - public map: MapPixel[]; - public mapWidth: number; - public mapHeight: number; - public board: (Color | null | undefined)[]; - public turn: Color | null = BLACK; - public opts: Options; - - public prevPos = -1; - public prevColor: Color | null = null; - - private logs: Undo[] = []; - - /** - * ã‚²ãƒ¼ãƒ ã‚’åˆæœŸåŒ–ã—ã¾ã™ - */ - constructor(map: string[], opts: Options) { - //#region binds - this.put = this.put.bind(this); - //#endregion - - //#region Options - this.opts = opts; - if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; - if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; - if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; - //#endregion - - //#region Parse map data - this.mapWidth = map[0].length; - this.mapHeight = map.length; - const mapData = map.join(''); - - this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); - - this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); - //#endregion - - // ゲームãŒå§‹ã¾ã£ãŸæ™‚点ã§ç‰‡æ–¹ã®è‰²ã®çŸ³ã—ã‹ãªã„ã‹ã€å§‹ã¾ã£ãŸæ™‚点ã§å‹æ•—ãŒæ±ºå®šã™ã‚‹ã‚ˆã†ãªãƒžãƒƒãƒ—ã®å ´åˆãŒã‚ã‚‹ - if (!this.canPutSomewhere(BLACK)) - this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; - } - - /** - * é»’çŸ³ã®æ•° - */ - public get blackCount() { - return count(BLACK, this.board); - } - - /** - * ç™½çŸ³ã®æ•° - */ - public get whiteCount() { - return count(WHITE, this.board); - } - - public transformPosToXy(pos: number): number[] { - const x = pos % this.mapWidth; - const y = Math.floor(pos / this.mapWidth); - return [x, y]; - } - - public transformXyToPos(x: number, y: number): number { - return x + (y * this.mapWidth); - } - - /** - * 指定ã®ãƒžã‚¹ã«çŸ³ã‚’打ã¡ã¾ã™ - * @param color 石ã®è‰² - * @param pos ä½ç½® - */ - public put(color: Color, pos: number) { - this.prevPos = pos; - this.prevColor = color; - - this.board[pos] = color; - - // å転ã•ã›ã‚‰ã‚Œã‚‹çŸ³ã‚’å–å¾— - const effects = this.effects(color, pos); - - // å転ã•ã›ã‚‹ - for (const pos of effects) { - this.board[pos] = color; - } - - const turn = this.turn; - - this.logs.push({ - color, - pos, - effects, - turn - }); - - this.calcTurn(); - } - - private calcTurn() { - // ターン計算 - this.turn = - this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : - null; - } - - public undo() { - const undo = this.logs.pop()!; - this.prevColor = undo.color; - this.prevPos = undo.pos; - this.board[undo.pos] = null; - for (const pos of undo.effects) { - const color = this.board[pos]; - this.board[pos] = !color; - } - this.turn = undo.turn; - } - - /** - * 指定ã—ãŸä½ç½®ã®ãƒžãƒƒãƒ—データã®ãƒžã‚¹ã‚’å–å¾—ã—ã¾ã™ - * @param pos ä½ç½® - */ - public mapDataGet(pos: number): MapPixel { - const [x, y] = this.transformPosToXy(pos); - return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; - } - - /** - * 打ã¤ã“ã¨ãŒã§ãã‚‹å ´æ‰€ã‚’å–å¾—ã—ã¾ã™ - */ - public puttablePlaces(color: Color): number[] { - return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); - } - - /** - * 打ã¤ã“ã¨ãŒã§ãã‚‹å ´æ‰€ãŒã‚ã‚‹ã‹ã©ã†ã‹ã‚’å–å¾—ã—ã¾ã™ - */ - public canPutSomewhere(color: Color): boolean { - return this.puttablePlaces(color).length > 0; - } - - /** - * 指定ã®ãƒžã‚¹ã«çŸ³ã‚’打ã¤ã“ã¨ãŒã§ãã‚‹ã‹ã©ã†ã‹ã‚’å–å¾—ã—ã¾ã™ - * @param color 自分ã®è‰² - * @param pos ä½ç½® - */ - public canPut(color: Color, pos: number): boolean { - return ( - this.board[pos] !== null ? false : // æ—¢ã«çŸ³ãŒç½®ã„ã¦ã‚ã‚‹å ´æ‰€ã«ã¯æ‰“ã¦ãªã„ - this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んã§ãªãã¦ã‚‚ç½®ã‘るモード - this.effects(color, pos).length !== 0); // 相手ã®çŸ³ã‚’1ã¤ã§ã‚‚å転ã•ã›ã‚‰ã‚Œã‚‹ã‹ - } - - /** - * 指定ã®ãƒžã‚¹ã«çŸ³ã‚’ç½®ã„ãŸæ™‚ã®ã€å転ã•ã›ã‚‰ã‚Œã‚‹çŸ³ã‚’å–å¾—ã—ã¾ã™ - * @param color 自分ã®è‰² - * @param initPos ä½ç½® - */ - public effects(color: Color, initPos: number): number[] { - const enemyColor = !color; - - const diffVectors: [number, number][] = [ - [ 0, -1], // 上 - [ +1, -1], // å³ä¸Š - [ +1, 0], // å³ - [ +1, +1], // å³ä¸‹ - [ 0, +1], // 下 - [ -1, +1], // 左下 - [ -1, 0], // å·¦ - [ -1, -1] // 左上 - ]; - - const effectsInLine = ([dx, dy]: [number, number]): number[] => { - const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; - - const found: number[] = []; // 挟ã‚ã‚‹ã‹ã‚‚ã—れãªã„相手ã®çŸ³ã‚’入れã¦ãŠãé…列 - let [x, y] = this.transformPosToXy(initPos); - while (true) { - [x, y] = nextPos(x, y); - - // åº§æ¨™ãŒæŒ‡ã—示ã™ä½ç½®ãŒãƒœãƒ¼ãƒ‰å¤–ã«å‡ºãŸã¨ã - if (this.opts.loopedBoard && this.transformXyToPos( - (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), - (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) - // 盤é¢ã®å¢ƒç•Œã§ãƒ«ãƒ¼ãƒ—ã—ã€è‡ªåˆ†ãŒçŸ³ã‚’ç½®ãä½ç½®ã«æˆ»ã£ã¦ããŸã¨ãã€æŒŸã‚るよã†ã«ã—ã¦ã„ã‚‹ (ref: Test4ã®ãƒžãƒƒãƒ—) - return found; - else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) - return []; // 挟ã‚ãªã„ã“ã¨ãŒç¢ºå®š (盤é¢å¤–ã«åˆ°é”) - - const pos = this.transformXyToPos(x, y); - if (this.mapDataGet(pos) === 'null') return []; // 挟ã‚ãªã„ã“ã¨ãŒç¢ºå®š (é…ç½®ä¸å¯èƒ½ãªãƒžã‚¹ã«åˆ°é”) - const stone = this.board[pos]; - if (stone === null) return []; // 挟ã‚ãªã„ã“ã¨ãŒç¢ºå®š (石ãŒç½®ã‹ã‚Œã¦ã„ãªã„マスã«åˆ°é”) - if (stone === enemyColor) found.push(pos); // 挟ã‚ã‚‹ã‹ã‚‚ã—れãªã„ (相手ã®çŸ³ã‚’発見) - if (stone === color) return found; // 挟ã‚ã‚‹ã“ã¨ãŒç¢ºå®š (対ã¨ãªã‚‹è‡ªåˆ†ã®çŸ³ã‚’発見) - } - }; - - return concat(diffVectors.map(effectsInLine)); - } - - /** - * ゲームãŒçµ‚了ã—ãŸã‹å¦ã‹ - */ - public get isEnded(): boolean { - return this.turn === null; - } - - /** - * ゲームã®å‹è€… (null = 引ã分ã‘) - */ - public get winner(): Color | null { - return this.isEnded ? - this.blackCount == this.whiteCount ? null : - this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined as never; - } -} diff --git a/packages/client/src/scripts/games/reversi/maps.ts b/packages/client/src/scripts/games/reversi/maps.ts deleted file mode 100644 index dc0d1bf9d0..0000000000 --- a/packages/client/src/scripts/games/reversi/maps.ts +++ /dev/null @@ -1,896 +0,0 @@ -/** - * 組ã¿è¾¼ã¿ãƒžãƒƒãƒ—定義 - * - * データ値: - * (スペース) ... マス無㗠- * - ... マス - * b ... åˆæœŸé…ç½®ã•れる黒石 - * w ... åˆæœŸé…ç½®ã•れる白石 - */ - -export type Map = { - name?: string; - category?: string; - author?: string; - data: string[]; -}; - -export const fourfour: Map = { - name: '4x4', - category: '4x4', - data: [ - '----', - '-wb-', - '-bw-', - '----' - ] -}; - -export const sixsix: Map = { - name: '6x6', - category: '6x6', - data: [ - '------', - '------', - '--wb--', - '--bw--', - '------', - '------' - ] -}; - -export const roundedSixsix: Map = { - name: '6x6 rounded', - category: '6x6', - author: 'syuilo', - data: [ - ' ---- ', - '------', - '--wb--', - '--bw--', - '------', - ' ---- ' - ] -}; - -export const roundedSixsix2: Map = { - name: '6x6 rounded 2', - category: '6x6', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - '--wb--', - '--bw--', - ' ---- ', - ' -- ' - ] -}; - -export const eighteight: Map = { - name: '8x8', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const eighteightH1: Map = { - name: '8x8 handicap 1', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const eighteightH2: Map = { - name: '8x8 handicap 2', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b' - ] -}; - -export const eighteightH3: Map = { - name: '8x8 handicap 3', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b' - ] -}; - -export const eighteightH4: Map = { - name: '8x8 handicap 4', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------b' - ] -}; - -export const eighteightH28: Map = { - name: '8x8 handicap 28', - category: '8x8', - data: [ - 'bbbbbbbb', - 'b------b', - 'b------b', - 'b--wb--b', - 'b--bw--b', - 'b------b', - 'b------b', - 'bbbbbbbb' - ] -}; - -export const roundedEighteight: Map = { - name: '8x8 rounded', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - ' ------ ' - ] -}; - -export const roundedEighteight2: Map = { - name: '8x8 rounded 2', - category: '8x8', - author: 'syuilo', - data: [ - ' ---- ', - ' ------ ', - '--------', - '---wb---', - '---bw---', - '--------', - ' ------ ', - ' ---- ' - ] -}; - -export const roundedEighteight3: Map = { - name: '8x8 rounded 3', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ---- ', - ' -- ' - ] -}; - -export const eighteightWithNotch: Map = { - name: '8x8 with notch', - category: '8x8', - author: 'syuilo', - data: [ - '--- ---', - '--------', - '--------', - ' --wb-- ', - ' --bw-- ', - '--------', - '--------', - '--- ---' - ] -}; - -export const eighteightWithSomeHoles: Map = { - name: '8x8 with some holes', - category: '8x8', - author: 'syuilo', - data: [ - '--- ----', - '----- --', - '-- -----', - '---wb---', - '---bw- -', - ' -------', - '--- ----', - '--------' - ] -}; - -export const circle: Map = { - name: 'Circle', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ------ ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ------ ', - ' -- ' - ] -}; - -export const smile: Map = { - name: 'Smile', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '-- -- --', - '---wb---', - '-- bw --', - '--- ---', - '--------', - ' ------ ' - ] -}; - -export const window: Map = { - name: 'Window', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '- -- -', - '- -- -', - '---wb---', - '---bw---', - '- -- -', - '- -- -', - '--------' - ] -}; - -export const reserved: Map = { - name: 'Reserved', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------w' - ] -}; - -export const x: Map = { - name: 'X', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '-w----b-', - '--w--b--', - '---wb---', - '---bw---', - '--b--w--', - '-b----w-', - 'b------w' - ] -}; - -export const parallel: Map = { - name: 'Parallel', - category: '8x8', - author: 'Aya', - data: [ - '--------', - '--------', - '--------', - '---bb---', - '---ww---', - '--------', - '--------', - '--------' - ] -}; - -export const lackOfBlack: Map = { - name: 'Lack of Black', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---w----', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const squareParty: Map = { - name: 'Square Party', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '-wwwbbb-', - '-w-wb-b-', - '-wwwbbb-', - '-bbbwww-', - '-b-bw-w-', - '-bbbwww-', - '--------' - ] -}; - -export const minesweeper: Map = { - name: 'Minesweeper', - category: '8x8', - author: 'syuilo', - data: [ - 'b-b--w-w', - '-w-wb-b-', - 'w-b--w-b', - '-b-wb-w-', - '-w-bw-b-', - 'b-w--b-w', - '-b-bw-w-', - 'w-w--b-b' - ] -}; - -export const tenthtenth: Map = { - name: '10x10', - category: '10x10', - data: [ - '----------', - '----------', - '----------', - '----------', - '----wb----', - '----bw----', - '----------', - '----------', - '----------', - '----------' - ] -}; - -export const hole: Map = { - name: 'The Hole', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '----------', - '--wb--wb--', - '--bw--bw--', - '---- ----', - '---- ----', - '--wb--wb--', - '--bw--bw--', - '----------', - '----------' - ] -}; - -export const grid: Map = { - name: 'Grid', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '- - -- - -', - '----------', - '- - -- - -', - '----wb----', - '----bw----', - '- - -- - -', - '----------', - '- - -- - -', - '----------' - ] -}; - -export const cross: Map = { - name: 'Cross', - category: '10x10', - author: 'Aya', - data: [ - ' ---- ', - ' ---- ', - ' ---- ', - '----------', - '----wb----', - '----bw----', - '----------', - ' ---- ', - ' ---- ', - ' ---- ' - ] -}; - -export const charX: Map = { - name: 'Char X', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - '----------', - '---- ----', - '--- ---' - ] -}; - -export const charY: Map = { - name: 'Char Y', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' ------ ', - ' ------ ', - ' ------ ', - ' ------ ' - ] -}; - -export const walls: Map = { - name: 'Walls', - category: '10x10', - author: 'Aya', - data: [ - ' bbbbbbbb ', - 'w--------w', - 'w--------w', - 'w--------w', - 'w---wb---w', - 'w---bw---w', - 'w--------w', - 'w--------w', - 'w--------w', - ' bbbbbbbb ' - ] -}; - -export const cpu: Map = { - name: 'CPU', - category: '10x10', - author: 'syuilo', - data: [ - ' b b b b ', - 'w--------w', - ' -------- ', - 'w--------w', - ' ---wb--- ', - ' ---bw--- ', - 'w--------w', - ' -------- ', - 'w--------w', - ' b b b b ' - ] -}; - -export const checker: Map = { - name: 'Checker', - category: '10x10', - author: 'Aya', - data: [ - '----------', - '----------', - '----------', - '---wbwb---', - '---bwbw---', - '---wbwb---', - '---bwbw---', - '----------', - '----------', - '----------' - ] -}; - -export const japaneseCurry: Map = { - name: 'Japanese curry', - category: '10x10', - author: 'syuilo', - data: [ - 'w-b-b-b-b-', - '-w-b-b-b-b', - 'w-w-b-b-b-', - '-w-w-b-b-b', - 'w-w-wwb-b-', - '-w-wbb-b-b', - 'w-w-w-b-b-', - '-w-w-w-b-b', - 'w-w-w-w-b-', - '-w-w-w-w-b' - ] -}; - -export const mosaic: Map = { - name: 'Mosaic', - category: '10x10', - author: 'syuilo', - data: [ - '- - - - - ', - ' - - - - -', - '- - - - - ', - ' - w w - -', - '- - b b - ', - ' - w w - -', - '- - b b - ', - ' - - - - -', - '- - - - - ', - ' - - - - -', - ] -}; - -export const arena: Map = { - name: 'Arena', - category: '10x10', - author: 'syuilo', - data: [ - '- - -- - -', - ' - - - - ', - '- ------ -', - ' -------- ', - '- --wb-- -', - '- --bw-- -', - ' -------- ', - '- ------ -', - ' - - - - ', - '- - -- - -' - ] -}; - -export const reactor: Map = { - name: 'Reactor', - category: '10x10', - author: 'syuilo', - data: [ - '-w------b-', - 'b- - - -w', - '- --wb-- -', - '---b w---', - '- b wb w -', - '- w bw b -', - '---w b---', - '- --bw-- -', - 'w- - - -b', - '-b------w-' - ] -}; - -export const sixeight: Map = { - name: '6x8', - category: 'Special', - data: [ - '------', - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - '------' - ] -}; - -export const spark: Map = { - name: 'Spark', - category: 'Special', - author: 'syuilo', - data: [ - ' - - ', - '----------', - ' -------- ', - ' -------- ', - ' ---wb--- ', - ' ---bw--- ', - ' -------- ', - ' -------- ', - '----------', - ' - - ' - ] -}; - -export const islands: Map = { - name: 'Islands', - category: 'Special', - author: 'syuilo', - data: [ - '-------- ', - '---wb--- ', - '---bw--- ', - '-------- ', - ' - - ', - ' - - ', - ' --------', - ' --------', - ' --------', - ' --------' - ] -}; - -export const galaxy: Map = { - name: 'Galaxy', - category: 'Special', - author: 'syuilo', - data: [ - ' ------ ', - ' --www--- ', - ' ------w--- ', - '---bbb--w---', - '--b---b-w-b-', - '-b--wwb-w-b-', - '-b-w-bww--b-', - '-b-w-b---b--', - '---w--bbb---', - ' ---w------ ', - ' ---www-- ', - ' ------ ' - ] -}; - -export const triangle: Map = { - name: 'Triangle', - category: 'Special', - author: 'syuilo', - data: [ - ' -- ', - ' -- ', - ' ---- ', - ' ---- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - ' -------- ', - '----------', - '----------' - ] -}; - -export const iphonex: Map = { - name: 'iPhone X', - category: 'Special', - author: 'syuilo', - data: [ - ' -- -- ', - '--------', - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - '--------', - ' ------ ' - ] -}; - -export const dealWithIt: Map = { - name: 'Deal with it!', - category: 'Special', - author: 'syuilo', - data: [ - '------------', - '--w-b-------', - ' --b-w------', - ' --w-b---- ', - ' ------- ' - ] -}; - -export const experiment: Map = { - name: 'Let\'s experiment', - category: 'Special', - author: 'syuilo', - data: [ - ' ------------ ', - '------wb------', - '------bw------', - '--------------', - ' - - ', - '------ ------', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'wwwwww bbbbbb' - ] -}; - -export const bigBoard: Map = { - name: 'Big board', - category: 'Special', - data: [ - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '-------wb-------', - '-------bw-------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------' - ] -}; - -export const twoBoard: Map = { - name: 'Two board', - category: 'Special', - author: 'Aya', - data: [ - '-------- --------', - '-------- --------', - '-------- --------', - '---wb--- ---wb---', - '---bw--- ---bw---', - '-------- --------', - '-------- --------', - '-------- --------' - ] -}; - -export const test1: Map = { - name: 'Test1', - category: 'Test', - data: [ - '--------', - '---wb---', - '---bw---', - '--------' - ] -}; - -export const test2: Map = { - name: 'Test2', - category: 'Test', - data: [ - '------', - '------', - '-b--w-', - '-w--b-', - '-w--b-' - ] -}; - -export const test3: Map = { - name: 'Test3', - category: 'Test', - data: [ - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '---', - 'b--', - ] -}; - -export const test4: Map = { - name: 'Test4', - category: 'Test', - data: [ - '-w--b-', - '-w--b-', - '------', - '-w--b-', - '-w--b-' - ] -}; - -// 検証用: ã“ã®ç›¤é¢ã§è—(lv3)ãŒé»’ã§å§‹ã‚ã‚‹ã¨ä½•æ•…ã‹(?)A1ã«æ‰“ã£ã¦ã—ã¾ã† -export const test6: Map = { - name: 'Test6', - category: 'Test', - data: [ - '--wwwww-', - 'wwwwwwww', - 'wbbbwbwb', - 'wbbbbwbb', - 'wbwbbwbb', - 'wwbwbbbb', - '--wbbbbb', - '-wwwww--', - ] -}; - -// 検証用: ã“ã®ç›¤é¢ã§è—(lv3)ãŒé»’ã§å§‹ã‚ã‚‹ã¨ä½•æ•…ã‹(?)G7ã«æ‰“ã£ã¦ã—ã¾ã† -export const test7: Map = { - name: 'Test7', - category: 'Test', - data: [ - 'b--w----', - 'b-wwww--', - 'bwbwwwbb', - 'wbwwwwb-', - 'wwwwwww-', - '-wwbbwwb', - '--wwww--', - '--wwww--', - ] -}; - -// 検証用: ã“ã®ç›¤é¢ã§è—(lv5)ãŒé»’ã§å§‹ã‚ã‚‹ã¨ä½•æ•…ã‹(?)A1ã«æ‰“ã£ã¦ã—ã¾ã† -export const test8: Map = { - name: 'Test8', - category: 'Test', - data: [ - '--------', - '-----w--', - 'w--www--', - 'wwwwww--', - 'bbbbwww-', - 'wwwwww--', - '--www---', - '--ww----', - ] -}; diff --git a/packages/client/src/scripts/games/reversi/package.json b/packages/client/src/scripts/games/reversi/package.json deleted file mode 100644 index a4415ad141..0000000000 --- a/packages/client/src/scripts/games/reversi/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "misskey-reversi", - "version": "0.0.5", - "description": "Misskey reversi engine", - "keywords": [ - "misskey" - ], - "author": "syuilo <i@syuilo.com>", - "license": "MIT", - "repository": "https://github.com/misskey-dev/misskey.git", - "bugs": "https://github.com/misskey-dev/misskey/issues", - "main": "./built/core.js", - "types": "./built/core.d.ts", - "scripts": { - "build": "tsc" - }, - "dependencies": {} -} diff --git a/packages/client/src/scripts/games/reversi/tsconfig.json b/packages/client/src/scripts/games/reversi/tsconfig.json deleted file mode 100644 index 851fb6b7e4..0000000000 --- a/packages/client/src/scripts/games/reversi/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "declaration": true, - "sourceMap": false, - "target": "es2017", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "./built", - "rootDir": "./" - }, - "compileOnSave": false, - "include": [ - "./core.ts" - ] -} diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts new file mode 100644 index 0000000000..3634f39632 --- /dev/null +++ b/packages/client/src/scripts/get-note-menu.ts @@ -0,0 +1,310 @@ +import { Ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +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 { pleaseLogin } from './please-login'; + +export function getNoteMenu(props: { + note: misskey.entities.Note; + menuButton: Ref<HTMLElement>; + translation: Ref<any>; + translating: Ref<boolean>; +}) { + const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null + ); + + let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; + + function del(): void { + os.confirm({ + type: 'warning', + text: i18n.locale.noteDeleteConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id + }); + }); + } + + function delEdit(): void { + os.confirm({ + type: 'warning', + text: i18n.locale.deleteAndEditConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id + }); + + os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel }); + }); + } + + function toggleFavorite(favorite: boolean): void { + os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { + noteId: appearNote.id + }); + } + + function toggleWatch(watch: boolean): void { + os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { + noteId: appearNote.id + }); + } + + function toggleThreadMute(mute: boolean): void { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: appearNote.id + }); + } + + function copyContent(): void { + copyToClipboard(appearNote.text); + os.success(); + } + + function copyLink(): void { + copyToClipboard(`${url}/notes/${appearNote.id}`); + os.success(); + } + + function togglePin(pin: boolean): void { + os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { + noteId: appearNote.id + }, undefined, null, e => { + if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + os.alert({ + type: 'error', + text: i18n.locale.pinLimitExceeded + }); + } + }); + } + + async function clip(): Promise<void> { + const clips = await os.api('clips/list'); + os.popupMenu([{ + icon: 'fas fa-plus', + text: i18n.locale.createNew, + action: async () => { + const { canceled, result } = await os.form(i18n.locale.createNewClip, { + name: { + type: 'string', + label: i18n.locale.name + }, + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.locale.description + }, + isPublic: { + type: 'boolean', + label: i18n.locale.public, + default: false + } + }); + if (canceled) return; + + const clip = await os.apiWithDialog('clips/create', result); + + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + } + }, null, ...clips.map(clip => ({ + text: clip.name, + action: () => { + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + } + }))], props.menuButton.value, { + }).then(focus); + } + + async function promote(): Promise<void> { + const { canceled, result: days } = await os.inputNumber({ + title: i18n.locale.numberOfDays, + }); + + if (canceled) return; + + os.apiWithDialog('admin/promo/create', { + noteId: appearNote.id, + expiresAt: Date.now() + (86400000 * days), + }); + } + + function share(): void { + navigator.share({ + title: i18n.t('noteOf', { user: appearNote.user.name }), + text: appearNote.text, + url: `${url}/notes/${appearNote.id}`, + }); + } + + async function translate(): Promise<void> { + if (props.translation.value != null) return; + props.translating.value = true; + const res = await os.api('notes/translate', { + noteId: appearNote.id, + targetLang: localStorage.getItem('lang') || navigator.language, + }); + props.translating.value = false; + props.translation.value = res; + } + + let menu; + if ($i) { + const statePromise = os.api('notes/state', { + noteId: appearNote.id + }); + + menu = [{ + icon: 'fas fa-copy', + text: i18n.locale.copyContent, + action: copyContent + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: copyLink + }, (appearNote.url || appearNote.uri) ? { + icon: 'fas fa-external-link-square-alt', + text: i18n.locale.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + } + } : undefined, + { + icon: 'fas fa-share-alt', + text: i18n.locale.share, + action: share + }, + instance.translatorAvailable ? { + icon: 'fas fa-language', + text: i18n.locale.translate, + action: translate + } : undefined, + null, + statePromise.then(state => state.isFavorited ? { + icon: 'fas fa-star', + text: i18n.locale.unfavorite, + action: () => toggleFavorite(false) + } : { + icon: 'fas fa-star', + text: i18n.locale.favorite, + action: () => toggleFavorite(true) + }), + { + icon: 'fas fa-paperclip', + text: i18n.locale.clip, + action: () => clip() + }, + (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { + icon: 'fas fa-eye-slash', + text: i18n.locale.unwatch, + action: () => toggleWatch(false) + } : { + icon: 'fas fa-eye', + text: i18n.locale.watch, + action: () => toggleWatch(true) + }) : undefined, + statePromise.then(state => state.isMutedThread ? { + icon: 'fas fa-comment-slash', + text: i18n.locale.unmuteThread, + action: () => toggleThreadMute(false) + } : { + icon: 'fas fa-comment-slash', + text: i18n.locale.muteThread, + action: () => toggleThreadMute(true) + }), + appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + icon: 'fas fa-thumbtack', + text: i18n.locale.unpin, + action: () => togglePin(false) + } : { + icon: 'fas fa-thumbtack', + text: i18n.locale.pin, + action: () => togglePin(true) + } : undefined, + /* + ...($i.isModerator || $i.isAdmin ? [ + null, + { + icon: 'fas fa-bullhorn', + text: i18n.locale.promote, + action: promote + }] + : [] + ),*/ + ...(appearNote.userId != $i.id ? [ + null, + { + icon: 'fas fa-exclamation-circle', + text: i18n.locale.reportAbuse, + action: () => { + const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; + os.popup(import('@/components/abuse-report-window.vue'), { + user: appearNote.user, + initialComment: `Note: ${u}\n-----\n` + }, {}, 'closed'); + } + }] + : [] + ), + ...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [ + null, + appearNote.userId == $i.id ? { + icon: 'fas fa-edit', + text: i18n.locale.deleteAndEdit, + action: delEdit + } : undefined, + { + icon: 'fas fa-trash-alt', + text: i18n.locale.delete, + danger: true, + action: del + }] + : [] + )] + .filter(x => x !== undefined); + } else { + menu = [{ + icon: 'fas fa-copy', + text: i18n.locale.copyContent, + action: copyContent + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: copyLink + }, (appearNote.url || appearNote.uri) ? { + icon: 'fas fa-external-link-square-alt', + text: i18n.locale.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + } + } : undefined] + .filter(x => x !== undefined); + } + + if (noteActions.length > 0) { + menu = menu.concat([null, ...noteActions.map(action => ({ + icon: 'fas fa-plug', + text: action.title, + action: () => { + action.handler(appearNote); + } + }))]); + } + + return menu; +} diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index ebe101bc0f..7b910a0083 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -5,7 +5,7 @@ import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; import { userActions } from '@/store'; import { router } from '@/router'; -import { $i } from '@/account'; +import { $i, iAmModerator } from '@/account'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -175,7 +175,7 @@ export function getUserMenu(user) { action: reportAbuse }]); - if ($i && ($i.isAdmin || $i.isModerator)) { + if (iAmModerator) { menu = menu.concat([null, { icon: 'fas fa-microphone-slash', text: user.isSilenced ? i18n.locale.unsilence : i18n.locale.silence, diff --git a/packages/client/src/scripts/paging.ts b/packages/client/src/scripts/paging.ts deleted file mode 100644 index ef63ecc450..0000000000 --- a/packages/client/src/scripts/paging.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { markRaw } from 'vue'; -import * as os from '@/os'; -import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from './scroll'; - -const SECOND_FETCH_LIMIT = 30; - -// reversed: items é…列ã®ä¸èº«ã‚’é€†é †ã«ã™ã‚‹(æ–°ã—ã„æ–¹ãŒæœ€å¾Œ) - -export default (opts) => ({ - emits: ['queue'], - - data() { - return { - items: [], - queue: [], - offset: 0, - fetching: true, - moreFetching: false, - inited: false, - more: false, - backed: false, // é¡ã‚Šä¸ã‹å¦ã‹ - isBackTop: false, - }; - }, - - computed: { - empty(): boolean { - return this.items.length === 0 && !this.fetching && this.inited; - }, - - error(): boolean { - return !this.fetching && !this.inited; - }, - }, - - watch: { - pagination: { - handler() { - this.init(); - }, - deep: true - }, - - queue: { - handler(a, b) { - if (a.length === 0 && b.length === 0) return; - this.$emit('queue', this.queue.length); - }, - deep: true - } - }, - - created() { - opts.displayLimit = opts.displayLimit || 30; - this.init(); - }, - - activated() { - this.isBackTop = false; - }, - - deactivated() { - this.isBackTop = window.scrollY === 0; - }, - - methods: { - reload() { - this.items = []; - this.init(); - }, - - replaceItem(finder, data) { - const i = this.items.findIndex(finder); - this.items[i] = data; - }, - - removeItem(finder) { - const i = this.items.findIndex(finder); - this.items.splice(i, 1); - }, - - async init() { - this.queue = []; - this.fetching = true; - if (opts.before) opts.before(this); - let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; - if (params && params.then) params = await params; - if (params === null) return; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, - }).then(items => { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - markRaw(item); - if (this.pagination.reversed) { - if (i === items.length - 2) item._shouldInsertAd_ = true; - } else { - if (i === 3) item._shouldInsertAd_ = true; - } - } - if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse() : items; - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse() : items; - this.more = false; - } - this.offset = items.length; - this.inited = true; - this.fetching = false; - if (opts.after) opts.after(this, null); - }, e => { - this.fetching = false; - if (opts.after) opts.after(this, e); - }); - }, - - async fetchMore() { - if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - this.backed = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: SECOND_FETCH_LIMIT + 1, - ...(this.pagination.offsetMode ? { - offset: this.offset, - } : { - untilId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, - }), - }).then(items => { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - markRaw(item); - if (this.pagination.reversed) { - if (i === items.length - 9) item._shouldInsertAd_ = true; - } else { - if (i === 10) item._shouldInsertAd_ = true; - } - } - if (items.length > SECOND_FETCH_LIMIT) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = false; - } - this.offset += items.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - async fetchMoreFeature() { - if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: SECOND_FETCH_LIMIT + 1, - ...(this.pagination.offsetMode ? { - offset: this.offset, - } : { - sinceId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, - }), - }).then(items => { - for (const item of items) { - markRaw(item); - } - if (items.length > SECOND_FETCH_LIMIT) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = false; - } - this.offset += items.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - prepend(item) { - if (this.pagination.reversed) { - const container = getScrollContainer(this.$el); - const pos = getScrollPosition(this.$el); - const viewHeight = container.clientHeight; - const height = container.scrollHeight; - const isBottom = (pos + viewHeight > height - 32); - if (isBottom) { - // オーãƒãƒ¼ãƒ•ãƒãƒ¼ã—ãŸã‚‰å¤ã„ã‚¢ã‚¤ãƒ†ãƒ ã¯æ¨ã¦ã‚‹ - if (this.items.length >= opts.displayLimit) { - // ã“ã®ã‚„り方ã ã¨Vue 3.2以é™ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‹•ã‹ãªããªã‚‹ - //this.items = this.items.slice(-opts.displayLimit); - while (this.items.length >= opts.displayLimit) { - this.items.shift(); - } - this.more = true; - } - } - this.items.push(item); - // TODO - } else { - const isTop = this.isBackTop || (document.body.contains(this.$el) && isTopVisible(this.$el)); - - if (isTop) { - // Prepend the item - this.items.unshift(item); - - // オーãƒãƒ¼ãƒ•ãƒãƒ¼ã—ãŸã‚‰å¤ã„ã‚¢ã‚¤ãƒ†ãƒ ã¯æ¨ã¦ã‚‹ - if (this.items.length >= opts.displayLimit) { - // ã“ã®ã‚„り方ã ã¨Vue 3.2以é™ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‹•ã‹ãªããªã‚‹ - //this.items = this.items.slice(0, opts.displayLimit); - while (this.items.length >= opts.displayLimit) { - this.items.pop(); - } - this.more = true; - } - } else { - this.queue.push(item); - onScrollTop(this.$el, () => { - for (const item of this.queue) { - this.prepend(item); - } - this.queue = []; - }); - } - } - }, - - append(item) { - this.items.push(item); - }, - } -}); diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts index 445b6296eb..36e476b6f9 100644 --- a/packages/client/src/scripts/physics.ts +++ b/packages/client/src/scripts/physics.ts @@ -136,7 +136,7 @@ export function physics(container: HTMLElement) { } // 奈è½ã«è½ã¡ãŸã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆã¯æ¶ˆã™ - const intervalId = setInterval(() => { + const intervalId = window.setInterval(() => { for (const obj of objs) { if (obj.position.y > (containerHeight + 1024)) Matter.World.remove(world, obj); } @@ -146,7 +146,7 @@ export function physics(container: HTMLElement) { stop: () => { stop = true; Matter.Runner.stop(runner); - clearInterval(intervalId); + window.clearInterval(intervalId); } }; } diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts index 51b8d72868..b8286a2a76 100644 --- a/packages/client/src/scripts/popout.ts +++ b/packages/client/src/scripts/popout.ts @@ -1,8 +1,8 @@ import * as config from '@/config'; export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; - url += '?zen'; // TODO: ã¡ã‚ƒã‚“ã¨URLパースã—ã¦ã‚¯ã‚¨ãƒªä»˜ã‘ã‚‹ + let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path; + url += '?zen'; if (w) { const position = w.getBoundingClientRect(); const width = parseInt(getComputedStyle(w, '').width, 10); diff --git a/packages/client/src/scripts/room/furniture.ts b/packages/client/src/scripts/room/furniture.ts deleted file mode 100644 index 7734e32668..0000000000 --- a/packages/client/src/scripts/room/furniture.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type RoomInfo = { - roomType: string; - carpetColor: string; - furnitures: Furniture[]; -}; - -export type Furniture = { - id: string; // åŒã˜å®¶å…·ãŒè¤‡æ•°ã‚ã‚‹å ´åˆã«ãれãžã‚Œã‚’è˜åˆ¥ã™ã‚‹ãŸã‚ã®IDã§ã‚りã€å®¶å…·IDã§ã¯ãªã„ - type: string; // ã“ã£ã¡ãŒå®¶å…·ID(chairã¨ã‹) - position: { - x: number; - y: number; - z: number; - }; - rotation: { - x: number; - y: number; - z: number; - }; - props?: Record<string, any>; -}; diff --git a/packages/client/src/scripts/room/furnitures.json5 b/packages/client/src/scripts/room/furnitures.json5 deleted file mode 100644 index 4a40994107..0000000000 --- a/packages/client/src/scripts/room/furnitures.json5 +++ /dev/null @@ -1,407 +0,0 @@ -// 家具メタデータ - -// å®¶å…·IDã¯glbファイルåŠã³ãã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªåã¨ä¸€è‡´ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ - -// å®¶å…·ã«ã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¨å®šã§ãるプãƒãƒ‘ティをè¨å®šå¯èƒ½ã§ã™: -// -// props: { -// <propname>: <proptype> -// } -// -// proptype一覧: -// * image ... ç”»åƒé¸æŠžãƒ€ã‚¤ã‚¢ãƒã‚°ã‚’出ã—ã€ãã®ç”»åƒã®URLãŒæ ¼ç´ã•れã¾ã™ -// * color ... è‰²é¸æŠžã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ã‚’出ã—ã€é¸æŠžã•れãŸè‰²ãŒæ ¼ç´ã•れã¾ã™ - -// å®¶å…·ã«ã‚«ã‚¹ã‚¿ãƒ テクスãƒãƒ£ã‚’é©ç”¨ã§ãるよã†ã«ã™ã‚‹ã«ã¯ã€textureプãƒãƒ‘ティã«ä»¥ä¸‹ã®è¿½åŠ ã®æƒ…å ±ã‚’å«ã‚ã¾ã™: -// 便宜上ãã®UVã®ã©ã®éƒ¨åˆ†ã«ã‚«ã‚¹ã‚¿ãƒ テクスãƒãƒ£ã‚’貼りåˆã‚ã›ã‚‹ã‹ã®ã‚¨ãƒªã‚¢ã‚’テクスãƒãƒ£ã‚¨ãƒªã‚¢ã¨å‘¼ã³ã¾ã™ã€‚ -// UVã¯1024*1024ã ã¨ä»®å®šã—ã¾ã™ã€‚ -// -// <key>: { -// prop: <プãƒãƒ‘ティå>, -// uv: { -// x: <テクスãƒãƒ£ã‚¨ãƒªã‚¢X座標>, -// y: <テクスãƒãƒ£ã‚¨ãƒªã‚¢Y座標>, -// width: <テクスãƒãƒ£ã‚¨ãƒªã‚¢ã®å¹…>, -// height: <テクスãƒãƒ£ã‚¨ãƒªã‚¢ã®é«˜ã•>, -// }, -// } -// -// <key>ã«ã¯ã€ã‚«ã‚¹ã‚¿ãƒ テクスãƒãƒ£ã‚’é©ç”¨ã—ãŸã„メッシュåを指定ã—ã¾ã™ -// <プãƒãƒ‘ティå>ã«ã¯ã€ã‚«ã‚¹ã‚¿ãƒ テクスãƒãƒ£ã¨ã—ã¦ä½¿ç”¨ã™ã‚‹ç”»åƒã‚’æ ¼ç´ã™ã‚‹ãƒ—ãƒãƒ‘ティ(å‰è¿°)åを指定ã—ã¾ã™ - -// å®¶å…·ã«ã‚«ã‚¹ã‚¿ãƒ カラーをé©ç”¨ã§ãるよã†ã«ã™ã‚‹ã«ã¯ã€colorプãƒãƒ‘ティã«ä»¥ä¸‹ã®è¿½åŠ ã®æƒ…å ±ã‚’å«ã‚ã¾ã™: -// -// <key>: <プãƒãƒ‘ティå> -// -// <key>ã«ã¯ã€ã‚«ã‚¹ã‚¿ãƒ カラーをé©ç”¨ã—ãŸã„マテリアルåを指定ã—ã¾ã™ -// <プãƒãƒ‘ティå>ã«ã¯ã€ã‚«ã‚¹ã‚¿ãƒ カラーã¨ã—ã¦ä½¿ç”¨ã™ã‚‹è‰²ã‚’æ ¼ç´ã™ã‚‹ãƒ—ãƒãƒ‘ティ(å‰è¿°)åを指定ã—ã¾ã™ - -[ - { - id: "milk", - place: "floor" - }, - { - id: "bed", - place: "floor" - }, - { - id: "low-table", - place: "floor", - props: { - color: 'color' - }, - color: { - Table: 'color' - } - }, - { - id: "desk", - place: "floor", - props: { - color: 'color' - }, - color: { - Board: 'color' - } - }, - { - id: "chair", - place: "floor", - props: { - color: 'color' - }, - color: { - Chair: 'color' - } - }, - { - id: "chair2", - place: "floor", - props: { - color1: 'color', - color2: 'color' - }, - color: { - Cushion: 'color1', - Leg: 'color2' - } - }, - { - id: "fan", - place: "wall" - }, - { - id: "pc", - place: "floor" - }, - { - id: "plant", - place: "floor" - }, - { - id: "plant2", - place: "floor" - }, - { - id: "eraser", - place: "floor" - }, - { - id: "pencil", - place: "floor" - }, - { - id: "pudding", - place: "floor" - }, - { - id: "cardboard-box", - place: "floor" - }, - { - id: "cardboard-box2", - place: "floor" - }, - { - id: "cardboard-box3", - place: "floor" - }, - { - id: "book", - place: "floor", - props: { - color: 'color' - }, - color: { - Cover: 'color' - } - }, - { - id: "book2", - place: "floor" - }, - { - id: "piano", - place: "floor" - }, - { - id: "facial-tissue", - place: "floor" - }, - { - id: "server", - place: "floor" - }, - { - id: "moon", - place: "floor" - }, - { - id: "corkboard", - place: "wall" - }, - { - id: "mousepad", - place: "floor", - props: { - color: 'color' - }, - color: { - Pad: 'color' - } - }, - { - id: "monitor", - place: "floor", - props: { - screen: 'image' - }, - texture: { - Screen: { - prop: 'screen', - uv: { - x: 0, - y: 434, - width: 1024, - height: 588, - }, - }, - }, - }, - { - id: "tv", - place: "floor", - props: { - screen: 'image' - }, - texture: { - Screen: { - prop: 'screen', - uv: { - x: 0, - y: 434, - width: 1024, - height: 588, - }, - }, - }, - }, - { - id: "keyboard", - place: "floor" - }, - { - id: "carpet-stripe", - place: "floor", - props: { - color1: 'color', - color2: 'color' - }, - color: { - CarpetAreaA: 'color1', - CarpetAreaB: 'color2' - }, - }, - { - id: "mat", - place: "floor", - props: { - color: 'color' - }, - color: { - Mat: 'color' - } - }, - { - id: "color-box", - place: "floor", - props: { - color: 'color' - }, - color: { - main: 'color' - } - }, - { - id: "wall-clock", - place: "wall" - }, - { - id: "cube", - place: "floor", - props: { - color: 'color' - }, - color: { - Cube: 'color' - } - }, - { - id: "photoframe", - place: "wall", - props: { - photo: 'image', - color: 'color' - }, - texture: { - Photo: { - prop: 'photo', - uv: { - x: 0, - y: 342, - width: 1024, - height: 683, - }, - }, - }, - color: { - Frame: 'color' - } - }, - { - id: "pinguin", - place: "floor", - props: { - body: 'color', - belly: 'color' - }, - color: { - Body: 'body', - Belly: 'belly', - } - }, - { - id: "rubik-cube", - place: "floor", - }, - { - id: "poster-h", - place: "wall", - props: { - picture: 'image' - }, - texture: { - Poster: { - prop: 'picture', - uv: { - x: 0, - y: 277, - width: 1024, - height: 745, - }, - }, - }, - }, - { - id: "poster-v", - place: "wall", - props: { - picture: 'image' - }, - texture: { - Poster: { - prop: 'picture', - uv: { - x: 0, - y: 0, - width: 745, - height: 1024, - }, - }, - }, - }, - { - id: "sofa", - place: "floor", - props: { - color: 'color' - }, - color: { - Sofa: 'color' - } - }, - { - id: "spiral", - place: "floor", - props: { - color: 'color' - }, - color: { - Step: 'color' - } - }, - { - id: "bin", - place: "floor", - props: { - color: 'color' - }, - color: { - Bin: 'color' - } - }, - { - id: "cup-noodle", - place: "floor" - }, - { - id: "holo-display", - place: "floor", - props: { - image: 'image' - }, - texture: { - Image_Front: { - prop: 'image', - uv: { - x: 0, - y: 0, - width: 1024, - height: 1024, - }, - }, - Image_Back: { - prop: 'image', - uv: { - x: 0, - y: 0, - width: 1024, - height: 1024, - }, - }, - }, - }, - { - id: 'energy-drink', - place: "floor", - }, - { - id: 'doll-ai', - place: "floor", - }, - { - id: 'banknote', - place: "floor", - }, -] diff --git a/packages/client/src/scripts/room/room.ts b/packages/client/src/scripts/room/room.ts deleted file mode 100644 index 7e04bec646..0000000000 --- a/packages/client/src/scripts/room/room.ts +++ /dev/null @@ -1,775 +0,0 @@ -import autobind from 'autobind-decorator'; -import { v4 as uuid } from 'uuid'; -import * as THREE from 'three'; -import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; -import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; -import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; -import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'; -import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; -import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'; -import { Furniture, RoomInfo } from './furniture'; -import { query as urlQuery } from '@/scripts/url'; -const furnitureDefs = require('./furnitures.json5'); - -THREE.ImageUtils.crossOrigin = ''; - -type Options = { - graphicsQuality: Room['graphicsQuality']; - onChangeSelect: Room['onChangeSelect']; - useOrthographicCamera: boolean; -}; - -/** - * MisskeyRoom Core Engine - */ -export class Room { - private clock: THREE.Clock; - private scene: THREE.Scene; - private renderer: THREE.WebGLRenderer; - private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera; - private controls: OrbitControls; - private composer: EffectComposer; - private mixers: THREE.AnimationMixer[] = []; - private furnitureControl: TransformControls; - private roomInfo: RoomInfo; - private graphicsQuality: 'cheep' | 'low' | 'medium' | 'high' | 'ultra'; - private roomObj: THREE.Object3D; - private objects: THREE.Object3D[] = []; - private selectedObject: THREE.Object3D = null; - private onChangeSelect: Function; - private isTransformMode = false; - private renderFrameRequestId: number; - - private get canvas(): HTMLCanvasElement { - return this.renderer.domElement; - } - - private get furnitures(): Furniture[] { - return this.roomInfo.furnitures; - } - - private set furnitures(furnitures: Furniture[]) { - this.roomInfo.furnitures = furnitures; - } - - private get enableShadow() { - return this.graphicsQuality != 'cheep'; - } - - private get usePostFXs() { - return this.graphicsQuality !== 'cheep' && this.graphicsQuality !== 'low'; - } - - private get shadowQuality() { - return ( - this.graphicsQuality === 'ultra' ? 16384 : - this.graphicsQuality === 'high' ? 8192 : - this.graphicsQuality === 'medium' ? 4096 : - this.graphicsQuality === 'low' ? 1024 : - 0); // cheep - } - - constructor(user, isMyRoom, roomInfo: RoomInfo, container: Element, options: Options) { - this.roomInfo = roomInfo; - this.graphicsQuality = options.graphicsQuality; - this.onChangeSelect = options.onChangeSelect; - - this.clock = new THREE.Clock(true); - - //#region Init a scene - this.scene = new THREE.Scene(); - - const width = container.clientWidth; - const height = container.clientHeight; - - //#region Init a renderer - this.renderer = new THREE.WebGLRenderer({ - antialias: false, - stencil: false, - alpha: false, - powerPreference: - this.graphicsQuality === 'ultra' ? 'high-performance' : - this.graphicsQuality === 'high' ? 'high-performance' : - this.graphicsQuality === 'medium' ? 'default' : - this.graphicsQuality === 'low' ? 'low-power' : - 'low-power' // cheep - }); - - this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setSize(width, height); - this.renderer.autoClear = false; - this.renderer.setClearColor(new THREE.Color(0x051f2d)); - this.renderer.shadowMap.enabled = this.enableShadow; - this.renderer.shadowMap.type = - this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap : - this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap : - this.graphicsQuality === 'medium' ? THREE.PCFShadowMap : - this.graphicsQuality === 'low' ? THREE.BasicShadowMap : - THREE.BasicShadowMap; // cheep - - container.insertBefore(this.canvas, container.firstChild); - //#endregion - - //#region Init a camera - this.camera = options.useOrthographicCamera - ? new THREE.OrthographicCamera( - width / - 2, width / 2, height / 2, height / - 2, -10, 10) - : new THREE.PerspectiveCamera(45, width / height); - - if (options.useOrthographicCamera) { - this.camera.position.x = 2; - this.camera.position.y = 2; - this.camera.position.z = 2; - this.camera.zoom = 100; - this.camera.updateProjectionMatrix(); - } else { - this.camera.position.x = 5; - this.camera.position.y = 2; - this.camera.position.z = 5; - } - - this.scene.add(this.camera); - //#endregion - - //#region AmbientLight - const ambientLight = new THREE.AmbientLight(0xffffff, 1); - this.scene.add(ambientLight); - //#endregion - - if (this.graphicsQuality !== 'cheep') { - //#region Room light - const roomLight = new THREE.SpotLight(0xffffff, 0.1); - - roomLight.position.set(0, 8, 0); - roomLight.castShadow = this.enableShadow; - roomLight.shadow.bias = -0.0001; - roomLight.shadow.mapSize.width = this.shadowQuality; - roomLight.shadow.mapSize.height = this.shadowQuality; - roomLight.shadow.camera.near = 0.1; - roomLight.shadow.camera.far = 9; - roomLight.shadow.camera.fov = 45; - - this.scene.add(roomLight); - //#endregion - } - - //#region Out light - const outLight1 = new THREE.SpotLight(0xffffff, 0.4); - outLight1.position.set(9, 3, -2); - outLight1.castShadow = this.enableShadow; - outLight1.shadow.bias = -0.001; // アクãƒã€ã‚¢ãƒ¼ãƒãƒ•ã‚¡ã‚¯ãƒˆå¯¾ç– ãã®ä»£ã‚りピーターパンãŒç™ºç”Ÿã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ - outLight1.shadow.mapSize.width = this.shadowQuality; - outLight1.shadow.mapSize.height = this.shadowQuality; - outLight1.shadow.camera.near = 6; - outLight1.shadow.camera.far = 15; - outLight1.shadow.camera.fov = 45; - this.scene.add(outLight1); - - const outLight2 = new THREE.SpotLight(0xffffff, 0.2); - outLight2.position.set(-2, 3, 9); - outLight2.castShadow = false; - outLight2.shadow.bias = -0.001; // アクãƒã€ã‚¢ãƒ¼ãƒãƒ•ã‚¡ã‚¯ãƒˆå¯¾ç– ãã®ä»£ã‚りピーターパンãŒç™ºç”Ÿã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ - outLight2.shadow.camera.near = 6; - outLight2.shadow.camera.far = 15; - outLight2.shadow.camera.fov = 45; - this.scene.add(outLight2); - //#endregion - - //#region Init a controller - this.controls = new OrbitControls(this.camera, this.canvas); - - this.controls.target.set(0, 1, 0); - this.controls.enableZoom = true; - this.controls.enablePan = isMyRoom; - this.controls.minPolarAngle = 0; - this.controls.maxPolarAngle = Math.PI / 2; - this.controls.minAzimuthAngle = 0; - this.controls.maxAzimuthAngle = Math.PI / 2; - this.controls.enableDamping = true; - this.controls.dampingFactor = 0.2; - //#endregion - - //#region POST FXs - if (!this.usePostFXs) { - this.composer = null; - } else { - const renderTarget = new THREE.WebGLRenderTarget(width, height, { - minFilter: THREE.LinearFilter, - magFilter: THREE.LinearFilter, - format: THREE.RGBFormat, - stencilBuffer: false, - }); - - const fxaa = new ShaderPass(FXAAShader); - fxaa.uniforms['resolution'].value = new THREE.Vector2(1 / width, 1 / height); - fxaa.renderToScreen = true; - - this.composer = new EffectComposer(this.renderer, renderTarget); - this.composer.addPass(new RenderPass(this.scene, this.camera)); - if (this.graphicsQuality === 'ultra') { - this.composer.addPass(new BloomPass(0.25, 30, 128.0, 512)); - } - this.composer.addPass(fxaa); - } - //#endregion - //#endregion - - //#region Label - //#region Avatar - const avatarUrl = `/proxy/?${urlQuery({ url: user.avatarUrl })}`; - - const textureLoader = new THREE.TextureLoader(); - textureLoader.crossOrigin = 'anonymous'; - - const iconTexture = textureLoader.load(avatarUrl); - iconTexture.wrapS = THREE.RepeatWrapping; - iconTexture.wrapT = THREE.RepeatWrapping; - iconTexture.anisotropy = 16; - - const avatarMaterial = new THREE.MeshBasicMaterial({ - map: iconTexture, - side: THREE.DoubleSide, - alphaTest: 0.5 - }); - - const iconGeometry = new THREE.PlaneGeometry(1, 1); - - const avatarObject = new THREE.Mesh(iconGeometry, avatarMaterial); - avatarObject.position.set(-3, 2.5, 2); - avatarObject.rotation.y = Math.PI / 2; - avatarObject.castShadow = false; - - this.scene.add(avatarObject); - //#endregion - - //#region Username - const name = user.username; - - new THREE.FontLoader().load('/assets/fonts/helvetiker_regular.typeface.json', font => { - const nameGeometry = new THREE.TextGeometry(name, { - size: 0.5, - height: 0, - curveSegments: 8, - font: font, - bevelThickness: 0, - bevelSize: 0, - bevelEnabled: false - }); - - const nameMaterial = new THREE.MeshLambertMaterial({ - color: 0xffffff - }); - - const nameObject = new THREE.Mesh(nameGeometry, nameMaterial); - nameObject.position.set(-3, 2.25, 1.25); - nameObject.rotation.y = Math.PI / 2; - nameObject.castShadow = false; - - this.scene.add(nameObject); - }); - //#endregion - //#endregion - - //#region Interaction - if (isMyRoom) { - this.furnitureControl = new TransformControls(this.camera, this.canvas); - this.scene.add(this.furnitureControl); - - // Hover highlight - this.canvas.onmousemove = this.onmousemove; - - // Click - this.canvas.onmousedown = this.onmousedown; - } - //#endregion - - //#region Init room - this.loadRoom(); - //#endregion - - //#region Load furnitures - for (const furniture of this.furnitures) { - this.loadFurniture(furniture).then(obj => { - this.scene.add(obj.scene); - this.objects.push(obj.scene); - }); - } - //#endregion - - // Start render - if (this.usePostFXs) { - this.renderWithPostFXs(); - } else { - this.renderWithoutPostFXs(); - } - } - - @autobind - private renderWithoutPostFXs() { - this.renderFrameRequestId = - window.requestAnimationFrame(this.renderWithoutPostFXs); - - // Update animations - const clock = this.clock.getDelta(); - for (const mixer of this.mixers) { - mixer.update(clock); - } - - this.controls.update(); - this.renderer.render(this.scene, this.camera); - } - - @autobind - private renderWithPostFXs() { - this.renderFrameRequestId = - window.requestAnimationFrame(this.renderWithPostFXs); - - // Update animations - const clock = this.clock.getDelta(); - for (const mixer of this.mixers) { - mixer.update(clock); - } - - this.controls.update(); - this.renderer.clear(); - this.composer.render(); - } - - @autobind - private loadRoom() { - const type = this.roomInfo.roomType; - new GLTFLoader().load(`/client-assets/room/rooms/${type}/${type}.glb`, gltf => { - gltf.scene.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - - child.receiveShadow = this.enableShadow; - - child.material = new THREE.MeshLambertMaterial({ - color: (child.material as THREE.MeshStandardMaterial).color, - map: (child.material as THREE.MeshStandardMaterial).map, - name: (child.material as THREE.MeshStandardMaterial).name, - }); - - // 異方性フィルタリング - if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') { - (child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshLambertMaterial).map.anisotropy = 8; - } - }); - - gltf.scene.position.set(0, 0, 0); - - this.scene.add(gltf.scene); - this.roomObj = gltf.scene; - if (this.roomInfo.roomType === 'default') { - this.applyCarpetColor(); - } - }); - } - - @autobind - private loadFurniture(furniture: Furniture) { - const def = furnitureDefs.find(d => d.id === furniture.type); - return new Promise<GLTF>((res, rej) => { - const loader = new GLTFLoader(); - loader.load(`/client-assets/room/furnitures/${furniture.type}/${furniture.type}.glb`, gltf => { - const model = gltf.scene; - - // Load animation - if (gltf.animations.length > 0) { - const mixer = new THREE.AnimationMixer(model); - this.mixers.push(mixer); - for (const clip of gltf.animations) { - mixer.clipAction(clip).play(); - } - } - - model.name = furniture.id; - model.position.x = furniture.position.x; - model.position.y = furniture.position.y; - model.position.z = furniture.position.z; - model.rotation.x = furniture.rotation.x; - model.rotation.y = furniture.rotation.y; - model.rotation.z = furniture.rotation.z; - - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - child.castShadow = this.enableShadow; - child.receiveShadow = this.enableShadow; - (child.material as THREE.MeshStandardMaterial).metalness = 0; - - // 異方性フィルタリング - if ((child.material as THREE.MeshStandardMaterial).map && this.graphicsQuality !== 'cheep') { - (child.material as THREE.MeshStandardMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshStandardMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshStandardMaterial).map.anisotropy = 8; - } - }); - - if (def.color) { // カスタムカラー - this.applyCustomColor(model); - } - - if (def.texture) { // カスタムテクスãƒãƒ£ - this.applyCustomTexture(model); - } - - res(gltf); - }, null, rej); - }); - } - - @autobind - private applyCarpetColor() { - this.roomObj.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - if (child.material && - (child.material as THREE.MeshStandardMaterial).name && - (child.material as THREE.MeshStandardMaterial).name === 'Carpet' - ) { - const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16); - (child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); - } - }); - } - - @autobind - private applyCustomColor(model: THREE.Object3D) { - const furniture = this.furnitures.find(furniture => furniture.id === model.name); - const def = furnitureDefs.find(d => d.id === furniture.type); - if (def.color == null) return; - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - for (const t of Object.keys(def.color)) { - if (!child.material || - !(child.material as THREE.MeshStandardMaterial).name || - (child.material as THREE.MeshStandardMaterial).name !== t - ) continue; - - const prop = def.color[t]; - const val = furniture.props ? furniture.props[prop] : undefined; - - if (val == null) continue; - - const colorHex = parseInt(val.substr(1), 16); - (child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); - } - }); - } - - @autobind - private applyCustomTexture(model: THREE.Object3D) { - const furniture = this.furnitures.find(furniture => furniture.id === model.name); - const def = furnitureDefs.find(d => d.id === furniture.type); - if (def.texture == null) return; - - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - for (const t of Object.keys(def.texture)) { - if (child.name !== t) continue; - - const prop = def.texture[t].prop; - const val = furniture.props ? furniture.props[prop] : undefined; - - if (val == null) continue; - - const canvas = document.createElement('canvas'); - canvas.height = 1024; - canvas.width = 1024; - - child.material = new THREE.MeshLambertMaterial({ - emissive: 0x111111, - side: THREE.DoubleSide, - alphaTest: 0.5, - }); - - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => { - const uvInfo = def.texture[t].uv; - - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, - 0, 0, img.width, img.height, - uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height); - - const texture = new THREE.Texture(canvas); - texture.wrapS = THREE.RepeatWrapping; - texture.wrapT = THREE.RepeatWrapping; - texture.anisotropy = 16; - texture.flipY = false; - - (child.material as THREE.MeshLambertMaterial).map = texture; - (child.material as THREE.MeshLambertMaterial).needsUpdate = true; - (child.material as THREE.MeshLambertMaterial).map.needsUpdate = true; - }; - img.src = val; - } - }); - } - - @autobind - private onmousemove(ev: MouseEvent) { - if (this.isTransformMode) return; - - const rect = (ev.target as HTMLElement).getBoundingClientRect(); - const x = ((ev.clientX - rect.left) / rect.width) * 2 - 1; - const y = -((ev.clientY - rect.top) / rect.height) * 2 + 1; - const pos = new THREE.Vector2(x, y); - - this.camera.updateMatrixWorld(); - - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(pos, this.camera); - - const intersects = raycaster.intersectObjects(this.objects, true); - - for (const object of this.objects) { - if (this.isSelectedObject(object)) continue; - object.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); - } - }); - } - - if (intersects.length > 0) { - const intersected = this.getRoot(intersects[0].object); - if (this.isSelectedObject(intersected)) return; - intersected.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919); - } - }); - } - } - - @autobind - private onmousedown(ev: MouseEvent) { - if (this.isTransformMode) return; - if (ev.target !== this.canvas || ev.button !== 0) return; - - const rect = (ev.target as HTMLElement).getBoundingClientRect(); - const x = ((ev.clientX - rect.left) / rect.width) * 2 - 1; - const y = -((ev.clientY - rect.top) / rect.height) * 2 + 1; - const pos = new THREE.Vector2(x, y); - - this.camera.updateMatrixWorld(); - - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(pos, this.camera); - - const intersects = raycaster.intersectObjects(this.objects, true); - - for (const object of this.objects) { - object.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); - } - }); - } - - if (intersects.length > 0) { - const selectedObj = this.getRoot(intersects[0].object); - this.selectFurniture(selectedObj); - } else { - this.selectedObject = null; - this.onChangeSelect(null); - } - } - - @autobind - private getRoot(obj: THREE.Object3D): THREE.Object3D { - let found = false; - let x = obj.parent; - while (!found) { - if (x.parent.parent == null) { - found = true; - } else { - x = x.parent; - } - } - return x; - } - - @autobind - private isSelectedObject(obj: THREE.Object3D): boolean { - if (this.selectedObject == null) { - return false; - } else { - return obj.name === this.selectedObject.name; - } - } - - @autobind - private selectFurniture(obj: THREE.Object3D) { - this.selectedObject = obj; - this.onChangeSelect(obj); - obj.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0xff0000); - } - }); - } - - /** - * å®¶å…·ã®ç§»å‹•/回転モードã«ã—ã¾ã™ - * @param type 移動ã‹å›žè»¢ã‹ - */ - @autobind - public enterTransformMode(type: 'translate' | 'rotate') { - this.isTransformMode = true; - this.furnitureControl.setMode(type); - this.furnitureControl.attach(this.selectedObject); - this.controls.enableRotate = false; - } - - /** - * å®¶å…·ã®ç§»å‹•/回転モードを終了ã—ã¾ã™ - */ - @autobind - public exitTransformMode() { - this.isTransformMode = false; - this.furnitureControl.detach(); - this.controls.enableRotate = true; - } - - /** - * 家具プãƒãƒ‘ティを更新ã—ã¾ã™ - * @param key プãƒãƒ‘ティå - * @param value 値 - */ - @autobind - public updateProp(key: string, value: any) { - const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name); - if (furniture.props == null) furniture.props = {}; - furniture.props[key] = value; - this.applyCustomColor(this.selectedObject); - this.applyCustomTexture(this.selectedObject); - } - - /** - * 部屋ã«å®¶å…·ã‚’è¿½åŠ ã—ã¾ã™ - * @param type å®¶å…·ã®ç¨®é¡ž - */ - @autobind - public addFurniture(type: string) { - const furniture = { - id: uuid(), - type: type, - position: { - x: 0, - y: 0, - z: 0, - }, - rotation: { - x: 0, - y: 0, - z: 0, - }, - }; - - this.furnitures.push(furniture); - - this.loadFurniture(furniture).then(obj => { - this.scene.add(obj.scene); - this.objects.push(obj.scene); - }); - } - - /** - * ç¾åœ¨é¸æŠžã•れã¦ã„る家具を部屋ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ - */ - @autobind - public removeFurniture() { - this.exitTransformMode(); - const obj = this.selectedObject; - this.scene.remove(obj); - this.objects = this.objects.filter(object => object.name !== obj.name); - this.furnitures = this.furnitures.filter(furniture => furniture.id !== obj.name); - this.selectedObject = null; - this.onChangeSelect(null); - } - - /** - * å…¨ã¦ã®å®¶å…·ã‚’部屋ã‹ã‚‰å‰Šé™¤ã—ã¾ã™ - */ - @autobind - public removeAllFurnitures() { - this.exitTransformMode(); - for (const obj of this.objects) { - this.scene.remove(obj); - } - this.objects = []; - this.furnitures = []; - this.selectedObject = null; - this.onChangeSelect(null); - } - - /** - * 部屋ã®åºŠã®è‰²ã‚’変更ã—ã¾ã™ - * @param color 色 - */ - @autobind - public updateCarpetColor(color: string) { - this.roomInfo.carpetColor = color; - this.applyCarpetColor(); - } - - /** - * 部屋ã®ç¨®é¡žã‚’変更ã—ã¾ã™ - * @param type 種類 - */ - @autobind - public changeRoomType(type: string) { - this.roomInfo.roomType = type; - this.scene.remove(this.roomObj); - this.loadRoom(); - } - - /** - * 部屋データをå–å¾—ã—ã¾ã™ - */ - @autobind - public getRoomInfo() { - for (const obj of this.objects) { - const furniture = this.furnitures.find(f => f.id === obj.name); - furniture.position.x = obj.position.x; - furniture.position.y = obj.position.y; - furniture.position.z = obj.position.z; - furniture.rotation.x = obj.rotation.x; - furniture.rotation.y = obj.rotation.y; - furniture.rotation.z = obj.rotation.z; - } - - return this.roomInfo; - } - - /** - * é¸æŠžã•れã¦ã„ã‚‹å®¶å…·ã‚’å–å¾—ã—ã¾ã™ - */ - @autobind - public getSelectedObject() { - return this.selectedObject; - } - - @autobind - public findFurnitureById(id: string) { - return this.furnitures.find(furniture => furniture.id === id); - } - - /** - * レンダリングを終了ã—ã¾ã™ - */ - @autobind - public destroy() { - // Stop render loop - window.cancelAnimationFrame(this.renderFrameRequestId); - - this.controls.dispose(); - this.scene.dispose(); - } -} diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 6019890444..6bb3f8bf8a 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -1,4 +1,5 @@ import * as os from '@/os'; +import { stream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { DriveFile } from 'misskey-js/built/entities'; @@ -48,7 +49,7 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv const marker = Math.random().toString(); // TODO: UUIDã¨ã‹ä½¿ã† - const connection = os.stream.useChannel('main'); + const connection = stream.useChannel('main'); connection.on('urlUploadFinished', data => { if (data.marker === marker) { res(multiple ? [data.file] : data.file); diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 3b7f003d0f..85c087331b 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -34,11 +34,11 @@ export const builtinThemes = [ let timeout = null; export function applyTheme(theme: Theme, persist = true) { - if (timeout) clearTimeout(timeout); + if (timeout) window.clearTimeout(timeout); document.documentElement.classList.add('_themeChanging_'); - timeout = setTimeout(() => { + timeout = window.setTimeout(() => { document.documentElement.classList.remove('_themeChanging_'); }, 1000); diff --git a/packages/client/src/scripts/touch.ts b/packages/client/src/scripts/touch.ts index 06b4f8b2ed..5251bc2e27 100644 --- a/packages/client/src/scripts/touch.ts +++ b/packages/client/src/scripts/touch.ts @@ -14,6 +14,10 @@ if (isTouchSupported) { }, { passive: true }); window.addEventListener('touchend', () => { + // åè¦ç´ ã®touchstartイベントã§stopPropagation()ãŒå‘¼ã°ã‚Œã‚‹ã¨è¦ªè¦ç´ ã«ä¼æ¬ã•れãšã‚¿ãƒƒãƒã•れãŸã¨åˆ¤å®šã•れãªã„ãŸã‚〠+ // touchendイベントã§ã‚‚touchstartイベントã¨åŒæ§˜ã«trueã«ã™ã‚‹ + isTouchUsing = true; + isScreenTouching = false; }, { passive: true }); } diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts new file mode 100644 index 0000000000..3984256251 --- /dev/null +++ b/packages/client/src/scripts/use-leave-guard.ts @@ -0,0 +1,46 @@ +import { inject, onUnmounted, Ref } from 'vue'; +import { onBeforeRouteLeave } from 'vue-router'; +import { i18n } from '@/i18n'; +import * as os from '@/os'; + +export function useLeaveGuard(enabled: Ref<boolean>) { + const setLeaveGuard = inject('setLeaveGuard'); + + if (setLeaveGuard) { + setLeaveGuard(async () => { + if (!enabled.value) return false; + + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.leaveConfirm, + }); + + return canceled; + }); + } else { + onBeforeRouteLeave(async (to, from) => { + if (!enabled.value) return true; + + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.leaveConfirm, + }); + + return !canceled; + }); + } + + /* + function onBeforeLeave(ev: BeforeUnloadEvent) { + if (enabled.value) { + ev.preventDefault(); + ev.returnValue = ''; + } + } + + window.addEventListener('beforeunload', onBeforeLeave); + onUnmounted(() => { + window.removeEventListener('beforeunload', onBeforeLeave); + }); + */ +} diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts new file mode 100644 index 0000000000..bb00e464e3 --- /dev/null +++ b/packages/client/src/scripts/use-note-capture.ts @@ -0,0 +1,123 @@ +import { onUnmounted, Ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { stream } from '@/stream'; +import { $i } from '@/account'; + +export function useNoteCapture(props: { + rootEl: Ref<HTMLElement>; + appearNote: Ref<misskey.entities.Note>; +}) { + const appearNote = props.appearNote; + const connection = $i ? stream : null; + + function onStreamNoteUpdated(data): void { + const { type, id, body } = data; + + if (id !== appearNote.value.id) return; + + switch (type) { + case 'reacted': { + const reaction = body.reaction; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + if (body.emoji) { + const emojis = appearNote.value.emojis || []; + if (!emojis.includes(body.emoji)) { + updated.emojis = [...emojis, body.emoji]; + } + } + + // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ + const currentCount = (appearNote.value.reactions || {})[reaction] || 0; + + updated.reactions[reaction] = currentCount + 1; + + if ($i && (body.userId === $i.id)) { + updated.myReaction = reaction; + } + + appearNote.value = updated; + break; + } + + case 'unreacted': { + const reaction = body.reaction; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ + const currentCount = (appearNote.value.reactions || {})[reaction] || 0; + + updated.reactions[reaction] = Math.max(0, currentCount - 1); + + if ($i && (body.userId === $i.id)) { + updated.myReaction = null; + } + + appearNote.value = updated; + break; + } + + case 'pollVoted': { + const choice = body.choice; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + const choices = [...appearNote.value.poll.choices]; + choices[choice] = { + ...choices[choice], + votes: choices[choice].votes + 1, + ...($i && (body.userId === $i.id) ? { + isVoted: true + } : {}) + }; + + updated.poll.choices = choices; + + appearNote.value = updated; + break; + } + + case 'deleted': { + const updated = JSON.parse(JSON.stringify(appearNote.value)); + updated.value = true; + appearNote.value = updated; + break; + } + } + } + + function capture(withHandler = false): void { + if (connection) { + // TODO: ã“ã®ãƒŽãƒ¼ãƒˆãŒã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°çµŒç”±ã§æµã‚Œã¦ããŸå ´åˆã®ã¿ sr ã™ã‚‹ + connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: appearNote.value.id }); + if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); + } + } + + function decapture(withHandler = false): void { + if (connection) { + connection.send('un', { + id: appearNote.value.id, + }); + if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated); + } + } + + function onStreamConnected() { + capture(false); + } + + capture(true); + if (connection) { + connection.on('_connected_', onStreamConnected); + } + + onUnmounted(() => { + decapture(true); + if (connection) { + connection.off('_connected_', onStreamConnected); + } + }); +} diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index dc9c3b7b9e..cd358d29d0 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -97,7 +97,7 @@ export const defaultStore = markRaw(new Storage('base', { tl: { where: 'deviceAccount', default: { - src: 'home', + src: 'home' as 'home' | 'local' | 'social' | 'global', arg: null } }, @@ -160,7 +160,7 @@ export const defaultStore = markRaw(new Storage('base', { }, useReactionPickerForContextMenu: { where: 'device', - default: true + default: false }, showGapBetweenNotesInTimeline: { where: 'device', @@ -255,10 +255,6 @@ export class ColdDeviceStorage { sound_chatBg: { type: 'syuilo/waon', volume: 1 }, sound_antenna: { type: 'syuilo/triple', volume: 1 }, sound_channel: { type: 'syuilo/square-pico', volume: 1 }, - sound_reversiPutBlack: { type: 'syuilo/kick', volume: 0.3 }, - sound_reversiPutWhite: { type: 'syuilo/snare', volume: 0.3 }, - roomGraphicsQuality: 'medium' as 'cheep' | 'low' | 'medium' | 'high' | 'ultra', - roomUseOrthographicCamera: true, }; public static watchers = []; diff --git a/packages/client/src/stream.ts b/packages/client/src/stream.ts new file mode 100644 index 0000000000..dea3459b86 --- /dev/null +++ b/packages/client/src/stream.ts @@ -0,0 +1,8 @@ +import * as Misskey from 'misskey-js'; +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)); diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss index 181521b4f5..c1d47ffd08 100644 --- a/packages/client/src/style.scss +++ b/packages/client/src/style.scss @@ -26,6 +26,7 @@ html { background-size: cover; background-position: center; color: var(--fg); + accent-color: var(--accent); overflow: auto; overflow-wrap: break-word; font-family: "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; @@ -386,16 +387,6 @@ hr { backdrop-filter: var(--blur, blur(15px)); } -._inputSplit { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); - grid-gap: 12px; - - > * { - margin: 0 !important; - } -} - ._formBlock { margin: 1.5em 0; } diff --git a/packages/client/src/themes/_dark.json5 b/packages/client/src/themes/_dark.json5 index d8be16f60a..1d87788794 100644 --- a/packages/client/src/themes/_dark.json5 +++ b/packages/client/src/themes/_dark.json5 @@ -69,6 +69,9 @@ success: '#86b300', error: '#ec4137', warn: '#ecb637', + codeString: '#ffb675', + codeNumber: '#cfff9e', + codeBoolean: '#c59eff', htmlThemeColor: '@bg', X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', diff --git a/packages/client/src/themes/_light.json5 b/packages/client/src/themes/_light.json5 index 251aa36c7a..359b560688 100644 --- a/packages/client/src/themes/_light.json5 +++ b/packages/client/src/themes/_light.json5 @@ -69,6 +69,9 @@ success: '#86b300', error: '#ec4137', warn: '#ecb637', + codeString: '#b98710', + codeNumber: '#0fbbbb', + codeBoolean: '#62b70c', htmlThemeColor: '@bg', X2: ':darken<2<@panel', X3: 'rgba(0, 0, 0, 0.05)', diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue index 956ec556c1..98069258d9 100644 --- a/packages/client/src/ui/_common_/common.vue +++ b/packages/client/src/ui/_common_/common.vue @@ -15,9 +15,10 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import { stream, popup, popups, uploads, pendingApiRequestsCount } from '@/os'; +import { popup, popups, uploads, pendingApiRequestsCount } from '@/os'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; +import { stream } from '@/stream'; export default defineComponent({ components: { diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue index 5babdb98a8..afcc50725b 100644 --- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue +++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue @@ -61,7 +61,11 @@ export default defineComponent({ otherMenuItemIndicated, post: os.post, search, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, more: () => { os.popup(import('@/components/launch-pad.vue'), {}, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue index fa712ba45d..94baacbee9 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/sidebar.vue @@ -76,7 +76,11 @@ export default defineComponent({ iconOnly, post: os.post, search, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, more: () => { os.popup(import('@/components/launch-pad.vue'), {}, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/stream-indicator.vue b/packages/client/src/ui/_common_/stream-indicator.vue index 3f86f94549..5e811e1b88 100644 --- a/packages/client/src/ui/_common_/stream-indicator.vue +++ b/packages/client/src/ui/_common_/stream-indicator.vue @@ -8,38 +8,28 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onUnmounted } from 'vue'; +import { stream } from '@/stream'; -export default defineComponent({ - data() { - return { - hasDisconnected: false, - } - }, - computed: { - stream() { - return os.stream; - }, - }, - created() { - os.stream.on('_disconnected_', this.onDisconnected); - }, - beforeUnmount() { - os.stream.off('_disconnected_', this.onDisconnected); - }, - methods: { - onDisconnected() { - this.hasDisconnected = true; - }, - resetDisconnected() { - this.hasDisconnected = false; - }, - reload() { - location.reload(); - }, - } +let hasDisconnected = $ref(false); + +function onDisconnected() { + hasDisconnected = true; +} + +function resetDisconnected() { + hasDisconnected = false; +} + +function reload() { + location.reload(); +} + +stream.on('_disconnected_', onDisconnected); + +onUnmounted(() => { + stream.off('_disconnected_', onDisconnected); }); </script> diff --git a/packages/client/src/ui/_common_/upload.vue b/packages/client/src/ui/_common_/upload.vue index a1c5dcdecc..ab7678a505 100644 --- a/packages/client/src/ui/_common_/upload.vue +++ b/packages/client/src/ui/_common_/upload.vue @@ -17,18 +17,12 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - data() { - return { - uploads: os.uploads, - zIndex: os.claimZIndex('high'), - }; - }, -}); +const uploads = os.uploads; +const zIndex = os.claimZIndex('high'); </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/chat/date-separated-list.vue b/packages/client/src/ui/chat/date-separated-list.vue deleted file mode 100644 index 1a36aca6dd..0000000000 --- a/packages/client/src/ui/chat/date-separated-list.vue +++ /dev/null @@ -1,157 +0,0 @@ -<script lang="ts"> -import { defineComponent, h, PropType, TransitionGroup } from 'vue'; -import MkAd from '@/components/global/ad.vue'; - -export default defineComponent({ - props: { - items: { - type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>, - required: true, - }, - reversed: { - type: Boolean, - required: false, - default: false - }, - ad: { - type: Boolean, - required: false, - default: false - }, - }, - - render() { - const getDateText = (time: string) => { - const date = new Date(time).getDate(); - const month = new Date(time).getMonth() + 1; - return this.$t('monthAndDay', { - month: month.toString(), - day: date.toString() - }); - } - - return h(this.reversed ? 'div' : TransitionGroup, { - class: 'hmjzthxl', - name: this.reversed ? 'list-reversed' : 'list', - tag: 'div', - }, this.items.map((item, i) => { - const el = this.$slots.default({ - item: item - })[0]; - if (el.key == null && item.id) el.key = item.id; - - if ( - i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() - ) { - const separator = h('div', { - class: 'separator', - key: item.id + ':separator', - }, h('p', { - class: 'date' - }, [ - h('span', [ - h('i', { - class: 'fas fa-angle-up icon', - }), - getDateText(item.createdAt) - ]), - h('span', [ - getDateText(this.items[i + 1].createdAt), - h('i', { - class: 'fas fa-angle-down icon', - }) - ]) - ])); - - return [el, separator]; - } else { - if (this.ad && item._shouldInsertAd_) { - return [h(MkAd, { - class: 'a', // advertiseã®æ„(ブãƒãƒƒã‚«ãƒ¼å¯¾ç–) - key: item.id + ':ad', - prefer: ['horizontal', 'horizontal-big'], - }), el]; - } else { - return el; - } - } - })); - }, -}); -</script> - -<style lang="scss"> -.hmjzthxl { - > .list-move { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-enter-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-enter-from { - opacity: 0; - transform: translateY(-64px); - } - - > .list-reversed-enter-active, > .list-reversed-leave-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-reversed-enter-from { - opacity: 0; - transform: translateY(64px); - } -} -</style> - -<style lang="scss"> -.hmjzthxl { - > .separator { - text-align: center; - position: relative; - - &:before { - content: ""; - display: block; - position: absolute; - top: 50%; - left: 0; - right: 0; - margin: auto; - width: calc(100% - 32px); - height: 1px; - background: var(--divider); - } - - > .date { - display: inline-block; - position: relative; - margin: 0; - padding: 0 16px; - line-height: 32px; - text-align: center; - font-size: 12px; - color: var(--dateLabelFg); - background: var(--panel); - - > span { - &:first-child { - margin-right: 8px; - - > .icon { - margin-right: 8px; - } - } - - &:last-child { - margin-left: 8px; - - > .icon { - margin-left: 8px; - } - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/header-clock.vue b/packages/client/src/ui/chat/header-clock.vue deleted file mode 100644 index 3488289c21..0000000000 --- a/packages/client/src/ui/chat/header-clock.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="acemodlh _monospace"> - <div> - <span v-text="y"></span>/<span v-text="m"></span>/<span v-text="d"></span> - </div> - <div> - <span v-text="hh"></span> - <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-text="mm"></span> - <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-text="ss"></span> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - clock: null, - y: null, - m: null, - d: null, - hh: null, - mm: null, - ss: null, - showColon: true, - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - this.y = now.getFullYear().toString(); - this.m = (now.getMonth() + 1).toString().padStart(2, '0'); - this.d = now.getDate().toString().padStart(2, '0'); - this.hh = now.getHours().toString().padStart(2, '0'); - this.mm = now.getMinutes().toString().padStart(2, '0'); - this.ss = now.getSeconds().toString().padStart(2, '0'); - this.showColon = now.getSeconds() % 2 === 0; - } - } -}); -</script> - -<style lang="scss" scoped> -.acemodlh { - opacity: 0.7; - font-size: 0.85em; - line-height: 1em; - text-align: center; -} -</style> diff --git a/packages/client/src/ui/chat/index.vue b/packages/client/src/ui/chat/index.vue deleted file mode 100644 index f66ab4dcee..0000000000 --- a/packages/client/src/ui/chat/index.vue +++ /dev/null @@ -1,463 +0,0 @@ -<template> -<div class="mk-app" @contextmenu.self.prevent="onContextmenu"> - <XSidebar ref="menu" class="menu" :default-hidden="true"/> - - <div class="nav"> - <header class="header"> - <div class="left"> - <button class="_button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><!--<MkAcct class="text" :user="$i"/>--> - </button> - </div> - <div class="right"> - <MkA v-tooltip="$ts.messaging" class="item" to="/my/messaging"><i class="fas fa-comments icon"></i><span v-if="$i.hasUnreadMessagingMessage" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.directNotes" class="item" to="/my/messages"><i class="fas fa-envelope icon"></i><span v-if="$i.hasUnreadSpecifiedNotes" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.mentions" class="item" to="/my/mentions"><i class="fas fa-at icon"></i><span v-if="$i.hasUnreadMentions" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.notifications" class="item" to="/my/notifications"><i class="fas fa-bell icon"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></MkA> - </div> - </header> - <div class="body"> - <div class="container"> - <div class="header">{{ $ts.timeline }}</div> - <div class="body"> - <MkA to="/timeline/home" class="item" :class="{ active: tl === 'home' }"><i class="fas fa-home icon"></i>{{ $ts._timelines.home }}</MkA> - <MkA to="/timeline/local" class="item" :class="{ active: tl === 'local' }"><i class="fas fa-comments icon"></i>{{ $ts._timelines.local }}</MkA> - <MkA to="/timeline/social" class="item" :class="{ active: tl === 'social' }"><i class="fas fa-share-alt icon"></i>{{ $ts._timelines.social }}</MkA> - <MkA to="/timeline/global" class="item" :class="{ active: tl === 'global' }"><i class="fas fa-globe icon"></i>{{ $ts._timelines.global }}</MkA> - </div> - </div> - <div v-if="followedChannels" class="container"> - <div class="header">{{ $ts.channel }} ({{ $ts.following }})<button class="_button add" @click="addChannel"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="channel in followedChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ active: tl === `channel:${ channel.id }`, read: !channel.hasUnreadNote }"><i class="fas fa-satellite-dish icon"></i>{{ channel.name }}</MkA> - </div> - </div> - <div v-if="featuredChannels" class="container"> - <div class="header">{{ $ts.channel }}<button class="_button add" @click="addChannel"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="channel in featuredChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ active: tl === `channel:${ channel.id }` }"><i class="fas fa-satellite-dish icon"></i>{{ channel.name }}</MkA> - </div> - </div> - <div v-if="lists" class="container"> - <div class="header">{{ $ts.lists }}<button class="_button add" @click="addList"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="list in lists" :key="list.id" :to="`/my/list/${ list.id }`" class="item" :class="{ active: tl === `list:${ list.id }` }"><i class="fas fa-list-ul icon"></i>{{ list.name }}</MkA> - </div> - </div> - <div v-if="antennas" class="container"> - <div class="header">{{ $ts.antennas }}<button class="_button add" @click="addAntenna"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="antenna in antennas" :key="antenna.id" :to="`/my/antenna/${ antenna.id }`" class="item" :class="{ active: tl === `antenna:${ antenna.id }` }"><i class="fas fa-satellite icon"></i>{{ antenna.name }}</MkA> - </div> - </div> - <div class="container"> - <div class="body"> - <MkA to="/my/favorites" class="item"><i class="fas fa-star icon"></i>{{ $ts.favorites }}</MkA> - </div> - </div> - <MkAd class="a" :prefer="['square']"/> - </div> - <footer class="footer"> - <div class="left"> - <button class="_button menu" @click="showMenu"> - <i class="fas fa-bars icon"></i> - </button> - </div> - <div class="right"> - <button v-tooltip="$ts.search" class="_button item search" @click="search"> - <i class="fas fa-search icon"></i> - </button> - <MkA v-tooltip="$ts.settings" class="item" to="/settings"><i class="fas fa-cog icon"></i></MkA> - </div> - </footer> - </div> - - <main class="main" @contextmenu.stop="onContextmenu"> - <header class="header"> - <MkHeader class="header" :info="pageInfo" :menu="menu" :center="false" @click="onHeaderClick"/> - </header> - <router-view v-slot="{ Component }"> - <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> - <component :is="Component" :ref="changePage" class="body"/> - </keep-alive> - </transition> - </router-view> - </main> - - <XSide ref="side" class="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/> - <div class="side widgets" :class="{ sideViewOpening }"> - <XWidgets/> - </div> - - <XCommon/> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import { instanceName, url } from '@/config'; -import XSidebar from '@/ui/_common_/sidebar.vue'; -import XWidgets from './widgets.vue'; -import XCommon from '../_common_/common.vue'; -import XSide from './side.vue'; -import XHeaderClock from './header-clock.vue'; -import * as os from '@/os'; -import { router } from '@/router'; -import { menuDef } from '@/menu'; -import { search } from '@/scripts/search'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { store } from './store'; -import * as symbols from '@/symbols'; -import { openAccountMenu } from '@/account'; - -export default defineComponent({ - components: { - XCommon, - XSidebar, - XWidgets, - XSide, // NOTE: dynamic importã™ã‚‹ã¨AsyncComponentWrapperãŒé–“ã«å…¥ã‚‹ã›ã„ã§refå–å¾—ã§ããªãã¦é¢å€’ã«ãªã‚‹ - XHeaderClock, - }, - - provide() { - return { - sideViewHook: (path) => { - this.$refs.side.navigate(path); - } - }; - }, - - data() { - return { - pageInfo: null, - lists: null, - antennas: null, - followedChannels: null, - featuredChannels: null, - currentChannel: null, - menuDef: menuDef, - sideViewOpening: false, - instanceName, - }; - }, - - computed: { - menu() { - return [{ - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(this.$route.path); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.$route.path); - } - }]; - } - }, - - created() { - if (window.innerWidth < 1024) { - localStorage.setItem('ui', 'default'); - location.reload(); - } - - os.api('users/lists/list').then(lists => { - this.lists = lists; - }); - - os.api('antennas/list').then(antennas => { - this.antennas = antennas; - }); - - os.api('channels/followed', { limit: 20 }).then(channels => { - this.followedChannels = channels; - }); - - // TODO: pagination - os.api('channels/featured', { limit: 20 }).then(channels => { - this.featuredChannels = channels; - }); - }, - - methods: { - changePage(page) { - console.log(page); - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - document.title = `${this.pageInfo.title} | ${instanceName}`; - } - }, - - showMenu() { - this.$refs.menu.show(); - }, - - post() { - os.post(); - }, - - search() { - search(); - }, - - back() { - history.back(); - }, - - top() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - onTransition() { - if (window._scroll) window._scroll(); - }, - - onHeaderClick() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; - if (window.getSelection().toString() !== '') return; - const path = this.$route.path; - os.contextMenu([{ - type: 'label', - text: path, - }, { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(path); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(path); - } - }], e); - }, - - openAccountMenu, - } -}); -</script> - -<style lang="scss" scoped> -.mk-app { - $header-height: 54px; // TODO: ã©ã“ã‹ã«é›†ç´„ã—ãŸã„ - $ui-font-size: 1em; // TODO: ã©ã“ã‹ã«é›†ç´„ã—ãŸã„ - - // ã»ã‚“ã¨ã¯å˜ã« 100vh ã¨æ›¸ããŸã„ã¨ã“ã‚ã ãŒ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - height: calc(var(--vh, 1vh) * 100); - display: flex; - - > .nav { - display: flex; - flex-direction: column; - width: 250px; - height: 100vh; - border-right: solid 4px var(--divider); - - > .header, > .footer { - $padding: 8px; - display: flex; - align-items: center; - z-index: 1000; - height: $header-height; - padding: $padding; - box-sizing: border-box; - user-select: none; - - &.header { - border-bottom: solid 0.5px var(--divider); - } - - &.footer { - border-top: solid 0.5px var(--divider); - } - - > .left, > .right { - > .item, > .menu { - display: inline-flex; - vertical-align: middle; - height: ($header-height - ($padding * 2)); - width: ($header-height - ($padding * 2)); - box-sizing: border-box; - //opacity: 0.6; - position: relative; - border-radius: 5px; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - > .icon { - margin: auto; - } - - > .indicator { - position: absolute; - top: 8px; - right: 8px; - color: var(--indicator); - font-size: 8px; - line-height: 8px; - animation: blink 1s infinite; - } - } - } - - > .left { - flex: 1; - min-width: 0; - - > .account { - display: flex; - align-items: center; - padding: 0 8px; - - > .avatar { - width: 26px; - height: 26px; - margin-right: 8px; - } - - > .text { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 0.9em; - } - } - } - - > .right { - margin-left: auto; - } - } - - > .body { - flex: 1; - min-width: 0; - overflow: auto; - - > .container { - margin-top: 8px; - margin-bottom: 8px; - - & + .container { - margin-top: 16px; - } - - > .header { - display: flex; - font-size: 0.9em; - padding: 8px 16px; - position: sticky; - top: 0; - background: var(--X17); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - z-index: 1; - color: var(--fgTransparentWeak); - - > .add { - margin-left: auto; - color: var(--fgTransparentWeak); - - &:hover { - color: var(--fg); - } - } - } - - > .body { - padding: 0 8px; - - > .item { - display: block; - padding: 6px 8px; - border-radius: 4px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &:hover { - text-decoration: none; - background: rgba(0, 0, 0, 0.05); - } - - &.active, &.active:hover { - background: var(--accent); - color: #fff !important; - } - - &.read { - color: var(--fgTransparent); - } - - > .icon { - margin-right: 8px; - opacity: 0.6; - } - } - } - } - - > .a { - margin: 12px; - } - } - } - - > .main { - display: flex; - flex: 1; - flex-direction: column; - min-width: 0; - height: 100vh; - position: relative; - background: var(--panel); - - > .header { - z-index: 1000; - height: $header-height; - background-color: var(--panel); - border-bottom: solid 0.5px var(--divider); - user-select: none; - } - - > .body { - width: 100%; - box-sizing: border-box; - overflow: auto; - } - } - - > .side { - width: 350px; - border-left: solid 4px var(--divider); - background: var(--panel); - - &.widgets.sideViewOpening { - @media (max-width: 1400px) { - display: none; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note-header.vue b/packages/client/src/ui/chat/note-header.vue deleted file mode 100644 index 5f87fdd14e..0000000000 --- a/packages/client/src/ui/chat/note-header.vue +++ /dev/null @@ -1,99 +0,0 @@ -<template> -<header class="dehvdgxo"> - <MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - <span v-if="note.user.isBot" class="is-bot">bot</span> - <span class="username"><MkAcct :user="note.user"/></span> - <div class="info"> - <MkA class="created-at" :to="notePage(note)"> - <MkTime :time="note.createdAt"/> - </MkA> - <span v-if="note.visibility !== 'public'" class="visibility"> - <i v-if="note.visibility === 'home'" class="fas fa-home"></i> - <i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i> - <i v-else-if="note.visibility === 'specified'" class="fas fa-envelope"></i> - </span> - <span v-if="note.localOnly" class="localOnly"><i class="fas fa-biohazard"></i></span> - </div> -</header> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { notePage } from '@/filters/note'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; - -export default defineComponent({ - props: { - note: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - notePage, - userPage - } -}); -</script> - -<style lang="scss" scoped> -.dehvdgxo { - display: flex; - align-items: baseline; - white-space: nowrap; - font-size: 0.9em; - - > .name { - display: block; - margin: 0 .5em 0 0; - padding: 0; - overflow: hidden; - font-size: 1em; - font-weight: bold; - text-decoration: none; - text-overflow: ellipsis; - - &:hover { - text-decoration: underline; - } - } - - > .is-bot { - flex-shrink: 0; - align-self: center; - margin: 0 .5em 0 0; - padding: 1px 6px; - font-size: 80%; - border: solid 0.5px var(--divider); - border-radius: 3px; - } - - > .username { - margin: 0 .5em 0 0; - overflow: hidden; - text-overflow: ellipsis; - } - - > .info { - font-size: 0.9em; - opacity: 0.7; - - > .visibility { - margin-left: 8px; - } - - > .localOnly { - margin-left: 8px; - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note-preview.vue b/packages/client/src/ui/chat/note-preview.vue deleted file mode 100644 index c28591815e..0000000000 --- a/packages/client/src/ui/chat/note-preview.vue +++ /dev/null @@ -1,112 +0,0 @@ -<template> -<div class="hduudsxk"> - <MkAvatar class="avatar" :user="note.user"/> - <div class="main"> - <XNoteHeader class="header" :note="note" :mini="true"/> - <div class="body"> - <p v-if="note.cw != null" class="cw"> - <span v-if="note.cw != ''" class="text">{{ note.cw }}</span> - <XCwButton v-model="showContent" :note="note"/> - </p> - <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> - </div> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from '@/components/cw-button.vue'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - } - }, - - data() { - return { - showContent: false - }; - } -}); -</script> - -<style lang="scss" scoped> -.hduudsxk { - display: flex; - margin: 0; - padding: 0; - overflow: hidden; - font-size: 0.95em; - - > .avatar { - - @media (min-width: 350px) { - margin: 0 10px 0 0; - width: 44px; - height: 44px; - } - - @media (min-width: 500px) { - margin: 0 12px 0 0; - width: 48px; - height: 48px; - } - } - - > .avatar { - flex-shrink: 0; - display: block; - margin: 0 10px 0 0; - width: 40px; - height: 40px; - border-radius: 8px; - } - - > .main { - flex: 1; - min-width: 0; - - > .header { - margin-bottom: 2px; - } - - > .body { - - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - > .text { - cursor: default; - margin: 0; - padding: 0; - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note.sub.vue b/packages/client/src/ui/chat/note.sub.vue deleted file mode 100644 index b61b7521a8..0000000000 --- a/packages/client/src/ui/chat/note.sub.vue +++ /dev/null @@ -1,137 +0,0 @@ -<template> -<div class="wrpstxzv" :class="{ children }"> - <div class="main"> - <MkAvatar class="avatar" :user="note.user"/> - <div class="body"> - <XNoteHeader class="header" :note="note" :mini="true"/> - <div class="body"> - <p v-if="note.cw != null" class="cw"> - <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> - <XCwButton v-model="showContent" :note="note"/> - </p> - <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> - </div> - </div> - </div> - </div> - <XSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :children="true"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from '@/components/cw-button.vue'; -import * as os from '@/os'; - -export default defineComponent({ - name: 'XSub', - - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - }, - detail: { - type: Boolean, - required: false, - default: false - }, - children: { - type: Boolean, - required: false, - default: false - }, - // TODO - truncate: { - type: Boolean, - default: true - } - }, - - data() { - return { - showContent: false, - replies: [], - }; - }, - - created() { - if (this.detail) { - os.api('notes/children', { - noteId: this.note.id, - limit: 5 - }).then(replies => { - this.replies = replies; - }); - } - }, -}); -</script> - -<style lang="scss" scoped> -.wrpstxzv { - padding: 16px 16px; - font-size: 0.8em; - - &.children { - padding: 10px 0 0 16px; - font-size: 1em; - } - - > .main { - display: flex; - - > .avatar { - flex-shrink: 0; - display: block; - margin: 0 8px 0 0; - width: 36px; - height: 36px; - } - - > .body { - flex: 1; - min-width: 0; - - > .header { - margin-bottom: 2px; - } - - > .body { - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - > .text { - margin: 0; - padding: 0; - } - } - } - } - } - - > .reply { - border-left: solid 0.5px var(--divider); - margin-top: 10px; - } -} -</style> diff --git a/packages/client/src/ui/chat/note.vue b/packages/client/src/ui/chat/note.vue deleted file mode 100644 index 6927dd0eaf..0000000000 --- a/packages/client/src/ui/chat/note.vue +++ /dev/null @@ -1,1142 +0,0 @@ -<template> -<div - v-if="!muted" - v-show="!isDeleted" - v-hotkey="keymap" - class="vfzoeqcg" - :tabindex="!isDeleted ? '-1' : null" - :class="{ renote: isRenote, highlighted: appearNote._prId_ || appearNote._featuredId_, operating }" -> - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ $ts.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ $ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ $ts.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ $ts.featured }}</div> - <div v-if="isRenote" class="renote"> - <MkAvatar class="avatar" :user="note.user"/> - <i class="fas fa-retweet"></i> - <I18n :src="$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="fas fa-ellipsis-h dropdownIcon"></i> - <MkTime :time="note.createdAt"/> - </button> - <span v-if="note.visibility !== 'public'" class="visibility"> - <i v-if="note.visibility === 'home'" class="fas fa-home"></i> - <i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i> - <i v-else-if="note.visibility === 'specified'" class="fas fa-envelope"></i> - </span> - <span v-if="note.localOnly" class="localOnly"><i class="fas fa-biohazard"></i></span> - </div> - </div> - <article class="article" @contextmenu.stop="onContextmenu"> - <MkAvatar class="avatar" :user="appearNote.user"/> - <div class="main"> - <XNoteHeader class="header" :note="appearNote" :mini="true"/> - <MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/> - <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" :custom-emojis="appearNote.emojis"/> - <XCwButton v-model="showContent" :note="appearNote"/> - </p> - <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> - <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> - <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> - <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <a v-if="appearNote.renote != null" class="rp">RN:</a> - </div> - <div v-if="appearNote.files.length > 0" class="files"> - <XMediaList :media-list="appearNote.files"/> - </div> - <XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> - <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> - <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ $ts.showMore }}</span> - </button> - </div> - <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> - </div> - <XReactionsViewer ref="reactionsViewer" :note="appearNote"/> - <footer class="footer _panel"> - <button v-tooltip="$ts.reply" class="button _button" @click="reply()"> - <template v-if="appearNote.reply"><i class="fas fa-reply-all"></i></template> - <template v-else><i class="fas fa-reply"></i></template> - <p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p> - </button> - <button v-if="canRenote" ref="renoteButton" v-tooltip="$ts.renote" class="button _button" @click="renote()"> - <i class="fas fa-retweet"></i><p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p> - </button> - <button v-else class="button _button"> - <i class="fas fa-ban"></i> - </button> - <button v-if="appearNote.myReaction == null" ref="reactButton" v-tooltip="$ts.reaction" class="button _button" @click="react()"> - <i class="fas fa-plus"></i> - </button> - <button v-if="appearNote.myReaction != null" ref="reactButton" v-tooltip="$ts.reaction" class="button _button reacted" @click="undoReact(appearNote)"> - <i class="fas fa-minus"></i> - </button> - <button ref="menuButton" class="button _button" @click="menu()"> - <i class="fas fa-ellipsis-h"></i> - </button> - </footer> - </div> - </article> -</div> -<div v-else class="muted" @click="muted = false"> - <I18n :src="$ts.userSaysSomething" tag="small"> - <template #name> - <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> - <MkUserName :user="appearNote.user"/> - </MkA> - </template> - </I18n> -</div> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; -import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; -import XNoteHeader from './note-header.vue'; -import XNoteSimple from './note-preview.vue'; -import XReactionsViewer from '@/components/reactions-viewer.vue'; -import XMediaList from '@/components/media-list.vue'; -import XCwButton from '@/components/cw-button.vue'; -import XPoll from '@/components/poll.vue'; -import { pleaseLogin } from '@/scripts/please-login'; -import { focusPrev, focusNext } from '@/scripts/focus'; -import { url } from '@/config'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { checkWordMute } from '@/scripts/check-word-mute'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import { noteActions, noteViewInterruptors } from '@/store'; -import { reactionPicker } from '@/scripts/reaction-picker'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; - -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, - - inject: { - inChannel: { - default: null - }, - }, - - props: { - note: { - type: Object, - required: true - }, - pinned: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['update:note'], - - data() { - return { - connection: null, - replies: [], - showContent: false, - collapsed: false, - isDeleted: false, - muted: false, - operating: false, - }; - }, - - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - canRenote(): boolean { - return ['public', 'home'].includes(this.appearNote.visibility) || this.isMyNote; - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = os.stream; - } - - this.collapsed = this.appearNote.cw == null && this.appearNote.text && ( - (this.appearNote.text.split('\n').length > 9) || - (this.appearNote.text.length > 500) - ); - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); - - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); - } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: ã“ã®ãƒŽãƒ¼ãƒˆãŒã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°çµŒç”±ã§æµã‚Œã¦ããŸå ´åˆã®ã¿ sr ã™ã‚‹ - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプãƒãƒ‘ティãŒãªã„å ´åˆã£ã¦ã‚ã£ãŸã£ã‘? ãªã‘れ㰠|| {} ã¯æ¶ˆã›ã‚‹ - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // Deepã§ã¯ãªãShallowコピーã§ã‚ã‚‹ã“ã¨ã«æ³¨æ„。n.reactions[reaction] = hogeã¨ã‹ã—ãªã„よã†ã«(親ã‹ã‚‰ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ã‚’ミューテートã™ã‚‹ã“ã¨ã«ãªã‚‹ã®ã§) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.operating = false; - this.focus(); - }); - }, - - renote(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - this.blur(); - os.popupMenu([{ - text: this.$ts.renote, - icon: 'fas fa-retweet', - action: () => { - os.api('notes/create', { - renoteId: this.appearNote.id - }); - } - }, { - text: this.$ts.quote, - icon: 'fas fa-quote-right', - action: () => { - os.post({ - renote: this.appearNote, - }); - } - }], this.$refs.renoteButton, { - viaKeyboard - }).then(() => { - this.operating = false; - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - async react(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.operating = false; - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /* - ...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - this.operating = true; - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(() => { - this.operating = false; - this.focus(); - }); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, - - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, - - focusBefore() { - focusPrev(this.$el); - }, - - focusAfter() { - focusNext(this.$el); - }, - - userPage - } -}); -</script> - -<style lang="scss" scoped> -.vfzoeqcg { - position: relative; - contain: content; - - // ã“ã‚Œã‚‰ã®æŒ‡å®šã¯ãƒ‘フォーマンスå‘上ã«ã¯æœ‰åйã ãŒã€ãƒŽãƒ¼ãƒˆã®é«˜ã•ã¯ä¸€å®šã§ãªã„ãŸã‚〠- // ä¸‹ã®æ–¹ã¾ã§ã‚¹ã‚¯ãƒãƒ¼ãƒ«ã™ã‚‹ã¨ä¸Šã®ãƒŽãƒ¼ãƒˆã®é«˜ã•ãŒã“ã“ã§æ±ºã‚打ã¡ã•れãŸã‚‚ã®ã«å¤‰åŒ–ã—ã€è¡¨ç¤ºã—ã¦ã„るノートã®ä½ç½®ãŒå¤‰ã‚ã£ã¦ã—ã¾ã† - // ノートãŒãƒžã‚¦ãƒ³ãƒˆã•れãŸã¨ãã«è‡ªèº«ã®é«˜ã•ã‚’å–å¾—ã— contain-intrinsic-size ã‚’è¨å®šã—ãªãŠã›ã°ã»ã¼è§£æ±ºã§ããã†ã ãŒã€ - // 今度ã¯ãã®å‡¦ç†è‡ªä½“ãŒãƒ‘フォーマンス低下ã®åŽŸå› ã«ãªã‚‰ãªã„ã‹æ‡¸å¿µã•れる。ã¾ãŸã€è¢«ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã§ã‚‚高ã•ã¯å¤‰åŒ–ã™ã‚‹ãŸã‚ã€ã‚„ã¯ã‚Šå¤šå°‘ã®ã‚ºãƒ¬ã¯ç”Ÿã˜ã‚‹ - // 一度レンダリングã•れãŸè¦ç´ ã¯ãƒ–ラウザãŒã‚ˆã—ãªã«ã‚µã‚¤ã‚ºã‚’覚ãˆã¦ãŠã„ã¦ãれるよã†ãªå®Ÿè£…ã«ãªã‚‹ã¾ã§å¾…ã£ãŸæ–¹ãŒè‰¯ã•ãã†(ãªã‚‹ã®ã‹ï¼Ÿ) - //content-visibility: auto; - //contain-intrinsic-size: 0 128px; - - &:focus-visible { - outline: none; - } - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - &:hover, &.operating { - > .article > .main > .footer { - display: block; - } - } - - &.renote { - background: rgba(128, 255, 0, 0.05); - } - - &.highlighted { - background: rgba(255, 128, 0, 0.05); - } - - > .info { - display: flex; - align-items: center; - padding: 12px 16px 4px 16px; - line-height: 24px; - font-size: 85%; - white-space: pre; - color: #d28a3f; - - > i { - margin-right: 4px; - } - - > .hide { - margin-left: 16px; - color: inherit; - opacity: 0.7; - } - } - - > .info + .article { - padding-top: 8px; - } - - > .reply-to { - opacity: 0.7; - padding-bottom: 0; - } - - > .renote { - display: flex; - align-items: center; - padding: 12px 16px 4px 16px; - line-height: 28px; - white-space: pre; - color: var(--renote); - font-size: 0.9em; - - > .avatar { - flex-shrink: 0; - display: inline-block; - width: 28px; - height: 28px; - margin: 0 8px 0 0; - border-radius: 6px; - } - - > i { - margin-right: 4px; - } - - > span { - overflow: hidden; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; - - > .name { - font-weight: bold; - } - } - - > .info { - margin-left: 8px; - font-size: 0.9em; - opacity: 0.7; - - > .time { - flex-shrink: 0; - color: inherit; - - > .dropdownIcon { - margin-right: 4px; - } - } - - > .visibility { - margin-left: 8px; - } - - > .localOnly { - margin-left: 8px; - } - } - } - - > .renote + .article { - padding-top: 8px; - } - - > .article { - display: flex; - padding: 12px 16px; - - > .avatar { - flex-shrink: 0; - display: block; - position: sticky; - top: 0; - margin: 0 14px 0 0; - width: 46px; - height: 46px; - } - - > .main { - flex: 1; - min-width: 0; - - > .body { - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - &.collapsed { - position: relative; - max-height: 9em; - overflow: hidden; - - > .fade { - display: block; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); - - > span { - display: inline-block; - background: var(--panel); - padding: 6px 10px; - font-size: 0.8em; - border-radius: 999px; - box-shadow: 0 2px 6px rgb(0 0 0 / 20%); - } - - &:hover { - > span { - background: var(--panelHighlight); - } - } - } - } - - > .text { - overflow-wrap: break-word; - - > .reply { - color: var(--accent); - margin-right: 0.5em; - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - } - - > .files { - max-width: 500px; - } - - > .url-preview { - margin-top: 8px; - max-width: 500px; - } - - > .poll { - font-size: 80%; - max-width: 500px; - } - - > .renote { - padding: 8px 0; - - > * { - padding: 16px; - border: dashed 1px var(--renote); - border-radius: 8px; - } - } - } - - > .channel { - opacity: 0.7; - font-size: 80%; - } - } - - > .footer { - display: none; - position: absolute; - top: 8px; - right: 8px; - padding: 0 6px; - opacity: 0.7; - - &:hover { - opacity: 1; - } - - > .button { - margin: 0; - padding: 8px; - opacity: 0.7; - - &:hover { - color: var(--accent); - } - - > .count { - display: inline; - margin: 0 0 0 8px; - opacity: 0.7; - } - - &.reacted { - color: var(--accent); - } - } - } - } - } - - > .reply { - border-top: solid 0.5px var(--divider); - } -} - -.muted { - padding: 8px 16px; - opacity: 0.7; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } -} -</style> diff --git a/packages/client/src/ui/chat/notes.vue b/packages/client/src/ui/chat/notes.vue deleted file mode 100644 index 51d4afcf54..0000000000 --- a/packages/client/src/ui/chat/notes.vue +++ /dev/null @@ -1,94 +0,0 @@ -<template> -<div class=""> - <div v-if="empty" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noNotes }}</div> - </div> - - <MkLoading v-if="fetching"/> - - <MkError v-if="error" @retry="init()"/> - - <div v-show="more && reversed" style="margin-bottom: var(--margin);"> - <MkButton style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> - - <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :ad="true"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" :note="note" @update:note="updated(note, $event)"/> - </XList> - - <div v-show="more && !reversed" style="margin-top: var(--margin);"> - <MkButton v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import XNote from './note.vue'; -import XList from './date-separated-list.vue'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNote, XList, MkButton, - }, - - mixins: [ - paging({ - before: (self) => { - self.$emit('before'); - }, - - after: (self, e) => { - self.$emit('after', e); - } - }), - ], - - props: { - pagination: { - required: true - }, - - prop: { - type: String, - required: false - } - }, - - emits: ['before', 'after'], - - computed: { - notes(): any[] { - return this.prop ? this.items.map(item => item[this.prop]) : this.items; - }, - - reversed(): boolean { - return this.pagination.reversed; - } - }, - - methods: { - updated(oldValue, newValue) { - const i = this.notes.findIndex(n => n === oldValue); - if (this.prop) { - this.items[i][this.prop] = newValue; - } else { - this.items[i] = newValue; - } - }, - - focus() { - this.$refs.notes.focus(); - } - } -}); -</script> diff --git a/packages/client/src/ui/chat/pages/channel.vue b/packages/client/src/ui/chat/pages/channel.vue deleted file mode 100644 index 13c735cd47..0000000000 --- a/packages/client/src/ui/chat/pages/channel.vue +++ /dev/null @@ -1,258 +0,0 @@ -<template> -<div v-if="channel" class="hhizbblb"> - <div v-if="date" class="info"> - <MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo> - </div> - <div ref="body" class="tl"> - <div v-if="queue > 0" class="new" :style="{ width: width + 'px', bottom: bottom + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> - <XNotes ref="tl" v-follow="true" class="tl" :pagination="pagination" @queue="queueUpdated"/> - </div> - <div class="bottom"> - <div v-if="typers.length > 0" class="typers"> - <I18n :src="$ts.typingUsers" text-tag="span" class="users"> - <template #users> - <b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b> - </template> - </I18n> - <MkEllipsis/> - </div> - <XPostForm :channel="channel"/> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, markRaw } from 'vue'; -import * as Misskey from 'misskey-js'; -import XNotes from '../notes.vue'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; -import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; -import follow from '@/directives/follow-append'; -import XPostForm from '../post-form.vue'; -import MkInfo from '@/components/ui/info.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - XNotes, - XPostForm, - MkInfo, - }, - - directives: { - follow - }, - - provide() { - return { - inChannel: true - }; - }, - - props: { - channelId: { - type: String, - required: true - }, - }, - - data() { - return { - channel: null as Misskey.entities.Channel | null, - connection: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - queue: 0, - width: 0, - top: 0, - bottom: 0, - typers: [], - date: null, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.channel ? this.channel.name : '-', - subtitle: this.channel ? this.channel.description : '-', - icon: 'fas fa-satellite-dish', - actions: [{ - icon: this.channel?.isFollowing ? 'fas fa-star' : 'far fa-star', - text: this.channel?.isFollowing ? this.$ts.unfollow : this.$ts.follow, - highlighted: this.channel?.isFollowing, - handler: this.toggleChannelFollow - }, { - icon: 'fas fa-search', - text: this.$ts.inChannelSearch, - handler: this.inChannelSearch - }, { - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }] - })), - }; - }, - - async created() { - this.channel = await os.api('channels/show', { channelId: this.channelId }); - - const prepend = note => { - (this.$refs.tl as any).prepend(note); - - this.$emit('note'); - - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - }; - - this.connection = markRaw(os.stream.useChannel('channel', { - channelId: this.channelId - })); - this.connection.on('note', prepend); - this.connection.on('typers', typers => { - this.typers = this.$i ? typers.filter(u => u.id !== this.$i.id) : typers; - }); - - this.pagination = { - endpoint: 'channels/timeline', - reversed: true, - limit: 10, - params: init => ({ - channelId: this.channelId, - untilDate: this.date?.getTime(), - ...this.baseQuery - }) - }; - }, - - mounted() { - - }, - - beforeUnmount() { - this.connection.dispose(); - }, - - methods: { - focus() { - this.$refs.body.focus(); - }, - - goTop() { - const container = getScrollContainer(this.$refs.body); - container.scrollTop = 0; - }, - - queueUpdated(q) { - if (this.$refs.body.offsetWidth !== 0) { - const rect = this.$refs.body.getBoundingClientRect(); - this.width = this.$refs.body.offsetWidth; - this.top = rect.top; - this.bottom = this.$refs.body.offsetHeight; - } - this.queue = q; - }, - - async inChannelSearch() { - const { canceled, result: query } = await os.inputText({ - title: this.$ts.inChannelSearch, - }); - if (canceled || query == null || query === '') return; - router.push(`/search?q=${encodeURIComponent(query)}&channel=${this.channelId}`); - }, - - async toggleChannelFollow() { - if (this.channel.isFollowing) { - await os.apiWithDialog('channels/unfollow', { - channelId: this.channel.id - }); - this.channel.isFollowing = false; - } else { - await os.apiWithDialog('channels/follow', { - channelId: this.channel.id - }); - this.channel.isFollowing = true; - } - }, - - openChannelMenu(ev) { - os.popupMenu([{ - text: this.$ts.copyUrl, - icon: 'fas fa-link', - action: () => { - copyToClipboard(`${url}/channels/${this.currentChannel.id}`); - } - }], ev.currentTarget || ev.target); - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } -}); -</script> - -<style lang="scss" scoped> -.hhizbblb { - display: flex; - flex-direction: column; - flex: 1; - overflow: auto; - - > .info { - padding: 16px 16px 0 16px; - } - - > .top { - padding: 16px 16px 0 16px; - } - - > .bottom { - padding: 0 16px 16px 16px; - position: relative; - - > .typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - background: var(--panel); - border-radius: 0 8px 0 0; - color: var(--fgTransparentWeak); - - > .users { - > .user + .user:before { - content: ", "; - font-weight: normal; - } - - > .user:last-of-type:after { - content: " "; - } - } - } - } - - > .tl { - position: relative; - padding: 16px 0; - flex: 1; - min-width: 0; - overflow: auto; - - > .new { - position: fixed; - z-index: 1000; - - > button { - display: block; - margin: 16px auto; - padding: 8px 16px; - border-radius: 32px; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/pages/timeline.vue b/packages/client/src/ui/chat/pages/timeline.vue deleted file mode 100644 index 07e847ad73..0000000000 --- a/packages/client/src/ui/chat/pages/timeline.vue +++ /dev/null @@ -1,221 +0,0 @@ -<template> -<div class="dbiokgaf"> - <div v-if="date" class="info"> - <MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo> - </div> - <div class="top"> - <XPostForm/> - </div> - <div ref="body" class="tl"> - <div v-if="queue > 0" class="new" :style="{ width: width + 'px', top: top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> - <XNotes ref="tl" class="tl" :pagination="pagination" @queue="queueUpdated"/> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, markRaw } from 'vue'; -import XNotes from '../notes.vue'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; -import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; -import follow from '@/directives/follow-append'; -import XPostForm from '../post-form.vue'; -import MkInfo from '@/components/ui/info.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - XNotes, - XPostForm, - MkInfo, - }, - - directives: { - follow - }, - - props: { - src: { - type: String, - required: true - }, - }, - - data() { - return { - connection: null, - connection2: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - query: {}, - queue: 0, - width: 0, - top: 0, - bottom: 0, - typers: [], - date: null, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.timeline, - icon: 'fas fa-home', - actions: [{ - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }] - })), - }; - }, - - created() { - const prepend = note => { - (this.$refs.tl as any).prepend(note); - - this.$emit('note'); - - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - }; - - const onChangeFollowing = () => { - if (!this.$refs.tl.backed) { - this.$refs.tl.reload(); - } - }; - - let endpoint; - - if (this.src == 'home') { - endpoint = 'notes/timeline'; - this.connection = markRaw(os.stream.useChannel('homeTimeline')); - this.connection.on('note', prepend); - - this.connection2 = markRaw(os.stream.useChannel('main')); - this.connection2.on('follow', onChangeFollowing); - this.connection2.on('unfollow', onChangeFollowing); - } else if (this.src == 'local') { - endpoint = 'notes/local-timeline'; - this.connection = markRaw(os.stream.useChannel('localTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'social') { - endpoint = 'notes/hybrid-timeline'; - this.connection = markRaw(os.stream.useChannel('hybridTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'global') { - endpoint = 'notes/global-timeline'; - this.connection = markRaw(os.stream.useChannel('globalTimeline')); - this.connection.on('note', prepend); - } - - this.pagination = { - endpoint: endpoint, - limit: 10, - params: init => ({ - untilDate: this.date?.getTime(), - ...this.baseQuery, ...this.query - }) - }; - }, - - mounted() { - - }, - - beforeUnmount() { - this.connection.dispose(); - if (this.connection2) this.connection2.dispose(); - }, - - methods: { - focus() { - this.$refs.body.focus(); - }, - - goTop() { - const container = getScrollContainer(this.$refs.body); - container.scrollTop = 0; - }, - - queueUpdated(q) { - if (this.$refs.body.offsetWidth !== 0) { - const rect = this.$refs.body.getBoundingClientRect(); - this.width = this.$refs.body.offsetWidth; - this.top = rect.top; - this.bottom = this.$refs.body.offsetHeight; - } - this.queue = q; - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } -}); -</script> - -<style lang="scss" scoped> -.dbiokgaf { - display: flex; - flex-direction: column; - flex: 1; - overflow: auto; - - > .info { - padding: 16px 16px 0 16px; - } - - > .top { - padding: 16px 16px 0 16px; - } - - > .bottom { - padding: 0 16px 16px 16px; - position: relative; - - > .typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - background: var(--panel); - border-radius: 0 8px 0 0; - color: var(--fgTransparentWeak); - - > .users { - > .user + .user:before { - content: ", "; - font-weight: normal; - } - - > .user:last-of-type:after { - content: " "; - } - } - } - } - - > .tl { - position: relative; - padding: 16px 0; - flex: 1; - min-width: 0; - overflow: auto; - - > .new { - position: fixed; - z-index: 1000; - - > button { - display: block; - margin: 16px auto; - padding: 8px 16px; - border-radius: 32px; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/post-form.vue b/packages/client/src/ui/chat/post-form.vue deleted file mode 100644 index 8c572e3b1c..0000000000 --- a/packages/client/src/ui/chat/post-form.vue +++ /dev/null @@ -1,769 +0,0 @@ -<template> -<div class="pxiwixjf" - @dragover.stop="onDragover" - @dragenter="onDragenter" - @dragleave="onDragleave" - @drop.stop="onDrop" -> - <div class="form"> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> - <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ $ts.recipient }}</span> - <div class="visibleUsers"> - <span v-for="u in visibleUsers" :key="u.id"> - <MkAcct :user="u"/> - <button class="_button" @click="removeVisibleUser(u)"><i class="fas fa-times"></i></button> - </span> - <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> - </div> - </div> - <input v-show="useCw" ref="cw" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> - <textarea ref="text" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> - <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> - <footer> - <div class="left"> - <button v-tooltip="$ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="$ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="$ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="$ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="$ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="$ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> - </div> - <div class="right"> - <span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span> - <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> - <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> - <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> - <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> - <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> - </button> - <button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> - </div> - </footer> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import insertTextAtCursor from 'insert-text-at-cursor'; -import { length } from 'stringz'; -import { toASCII } from 'punycode/'; -import * as mfm from 'mfm-js'; -import { host, url } from '@/config'; -import { erase, unique } from '@/scripts/array'; -import { extractMentions } from '@/scripts/extract-mentions'; -import * as Acct from 'misskey-js/built/acct'; -import { formatTimeString } from '@/scripts/format-time-string'; -import { Autocomplete } from '@/scripts/autocomplete'; -import * as os from '@/os'; -import { selectFiles } from '@/scripts/select-file'; -import { notePostInterruptors, postFormActions } from '@/store'; -import { throttle } from 'throttle-debounce'; - -export default defineComponent({ - components: { - XPostFormAttaches: defineAsyncComponent(() => import('@/components/post-form-attaches.vue')), - XPollEditor: defineAsyncComponent(() => import('@/components/poll-editor.vue')) - }, - - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - channel: { - type: String, - required: false - }, - mention: { - type: Object, - required: false - }, - specified: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialNote: { - type: Object, - required: false - }, - share: { - type: Boolean, - required: false, - default: false - }, - autofocus: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['posted', 'cancel', 'esc'], - - data() { - return { - posting: false, - text: '', - files: [], - poll: null, - useCw: false, - cw: null, - localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, - visibility: this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility, - visibleUsers: [], - autocomplete: null, - draghover: false, - quoteId: null, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - imeText: '', - typing: throttle(3000, () => { - if (this.channel) { - os.stream.send('typingOnChannel', { channel: this.channel }); - } - }), - postFormActions, - }; - }, - - computed: { - draftKey(): string { - let key = this.channel ? `channel:${this.channel}` : ''; - - if (this.renote) { - key += `renote:${this.renote.id}`; - } else if (this.reply) { - key += `reply:${this.reply.id}`; - } else { - key += 'note'; - } - - return key; - }, - - placeholder(): string { - if (this.renote) { - return this.$ts._postForm.quotePlaceholder; - } else if (this.reply) { - return this.$ts._postForm.replyPlaceholder; - } else if (this.channel) { - return this.$ts._postForm.channelPlaceholder; - } else { - const xs = [ - this.$ts._postForm._placeholders.a, - this.$ts._postForm._placeholders.b, - this.$ts._postForm._placeholders.c, - this.$ts._postForm._placeholders.d, - this.$ts._postForm._placeholders.e, - this.$ts._postForm._placeholders.f - ]; - return xs[Math.floor(Math.random() * xs.length)]; - } - }, - - submitText(): string { - return this.renote - ? this.$ts.quote - : this.reply - ? this.$ts.reply - : this.$ts.note; - }, - - textLength(): number { - return length((this.text + this.imeText).trim()); - }, - - canPost(): boolean { - return !this.posting && - (1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) && - (this.textLength <= this.max) && - (!this.poll || this.poll.choices.length >= 2); - }, - - max(): number { - return this.$instance ? this.$instance.maxNoteTextLength : 1000; - } - }, - - mounted() { - if (this.initialText) { - this.text = this.initialText; - } - - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } - - if (this.reply && (this.reply.user.username != this.$i.username || (this.reply.user.host != null && this.reply.user.host != host))) { - this.text = `@${this.reply.user.username}${this.reply.user.host != null ? '@' + toASCII(this.reply.user.host) : ''} `; - } - - if (this.reply && this.reply.text != null) { - const ast = mfm.parse(this.reply.text); - - for (const x of extractMentions(ast)) { - const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; - - // 自分ã¯é™¤å¤– - if (this.$i.username == x.username && x.host == null) continue; - if (this.$i.username == x.username && x.host == host) continue; - - // é‡è¤‡ã¯é™¤å¤– - if (this.text.indexOf(`${mention} `) != -1) continue; - - this.text += `${mention} `; - } - } - - if (this.channel) { - this.visibility = 'public'; - this.localOnly = true; // TODO: ãƒãƒ£ãƒ³ãƒãƒ«ãŒé€£åˆã™ã‚‹ã‚ˆã†ã«ãªã£ãŸæŠ˜ã«ã¯æ¶ˆã™ - } - - // 公開以外ã¸ã®ãƒªãƒ—ライ時ã¯å…ƒã®å…¬é–‹ç¯„囲を引ãç¶™ã - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - os.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); - - if (this.reply.userId !== this.$i.id) { - os.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } - - if (this.specified) { - this.visibility = 'specified'; - this.visibleUsers.push(this.specified); - } - - // keep cw when reply - if (this.$store.state.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; - } - - if (this.autofocus) { - this.focus(); - - this.$nextTick(() => { - this.focus(); - }); - } - - // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); - new Autocomplete(this.$refs.cw, this, { model: 'cw' }); - - this.$nextTick(() => { - // 書ãã‹ã‘ã®æŠ•ç¨¿ã‚’å¾©å…ƒ - if (!this.share && !this.mention && !this.specified) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey]; - if (draft) { - this.text = draft.data.text; - this.useCw = draft.data.useCw; - this.cw = draft.data.cw; - this.visibility = draft.data.visibility; - this.localOnly = draft.data.localOnly; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = draft.data.poll; - } - } - } - - // 削除ã—ã¦ç·¨é›† - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = init.poll; - } - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } - - this.$nextTick(() => this.watch()); - }); - }, - - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('useCw', () => this.saveDraft()); - this.$watch('cw', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft(), { deep: true }); - this.$watch('visibility', () => this.saveDraft()); - this.$watch('localOnly', () => this.saveDraft()); - }, - - togglePoll() { - if (this.poll) { - this.poll = null; - } else { - this.poll = { - choices: ['', ''], - multiple: false, - expiresAt: null, - expiredAfter: null, - }; - } - }, - - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, - - focus() { - (this.$refs.text as any).focus(); - }, - - chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, this.$ts.attachFile).then(files => { - for (const file of files) { - this.files.push(file); - } - }); - }, - - detachFile(id) { - this.files = this.files.filter(x => x.id != id); - }, - - updateFiles(files) { - this.files = files; - }, - - updateFileSensitive(file, sensitive) { - this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; - }, - - updateFileName(file, name) { - this.files[this.files.findIndex(x => x.id === file.id)].name = name; - }, - - upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { - this.files.push(res); - }); - }, - - onPollUpdate(poll) { - this.poll = poll; - this.saveDraft(); - }, - - setVisibility() { - if (this.channel) { - // TODO: information dialog - return; - } - - os.popup(import('@/components/visibility-picker.vue'), { - currentVisibility: this.visibility, - currentLocalOnly: this.localOnly, - src: this.$refs.visibilityButton - }, { - changeVisibility: visibility => { - this.visibility = visibility; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('visibility', visibility); - } - }, - changeLocalOnly: localOnly => { - this.localOnly = localOnly; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('localOnly', localOnly); - } - } - }, 'closed'); - }, - - addVisibleUser() { - os.selectUser().then(user => { - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, - - clear() { - this.text = ''; - this.files = []; - this.poll = null; - this.quoteId = null; - }, - - onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - if (e.which === 27) this.$emit('esc'); - this.typing(); - }, - - onCompositionUpdate(e: CompositionEvent) { - this.imeText = e.data; - this.typing(); - }, - - onCompositionEnd(e: CompositionEvent) { - this.imeText = ''; - }, - - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - this.upload(file, formatted); - } - } - - const paste = e.clipboardData.getData('text'); - - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); - - os.confirm({ - type: 'info', - text: this.$ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } - - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, - - onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, - - onDragenter(e) { - this.draghover = true; - }, - - onDragleave(e) { - this.draghover = false; - }, - - onDrop(e): void { - this.draghover = false; - - // ファイルã ã£ãŸã‚‰ - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } - - //#region ドライブã®ãƒ•ァイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - e.preventDefault(); - } - //#endregion - }, - - saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - data[this.draftKey] = { - updatedAt: new Date(), - data: { - text: this.text, - useCw: this.useCw, - cw: this.cw, - visibility: this.visibility, - localOnly: this.localOnly, - files: this.files, - poll: this.poll - } - }; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - delete data[this.draftKey]; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - async post() { - let data = { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - channelId: this.channel ? this.channel : undefined, - poll: this.poll, - cw: this.useCw ? this.cw || '' : undefined, - localOnly: this.localOnly, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - }; - - // plugin - if (notePostInterruptors.length > 0) { - for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); - } - } - - this.posting = true; - os.api('notes/create', data).then(() => { - this.clear(); - this.$nextTick(() => { - this.deleteDraft(); - this.$emit('posted'); - if (this.text && this.text != '') { - const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - this.posting = false; - }); - }).catch(err => { - this.posting = false; - os.alert({ - type: 'error', - text: err.message + '\n' + (err as any).id, - }); - }); - }, - - cancel() { - this.$emit('cancel'); - }, - - insertMention() { - os.selectUser().then(user => { - insertTextAtCursor(this.$refs.text, '@' + Acct.toString(user) + ' '); - }); - }, - - async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); - }, - - showActions(ev) { - os.popupMenu(postFormActions.map(action => ({ - text: action.title, - action: () => { - action.handler({ - text: this.text - }, (key, value) => { - if (key === 'text') { this.text = value; } - }); - } - })), ev.currentTarget || ev.target); - } - } -}); -</script> - -<style lang="scss" scoped> -.pxiwixjf { - position: relative; - border: solid 0.5px var(--divider); - border-radius: 8px; - - > .form { - > .preview { - padding: 16px; - } - - > .with-quote { - margin: 0 0 8px 0; - color: var(--accent); - - > button { - padding: 4px 8px; - color: var(--accentAlpha04); - - &:hover { - color: var(--accentAlpha06); - } - - &:active { - color: var(--accentDarken30); - } - } - } - - > .to-specified { - padding: 6px 24px; - margin-bottom: 8px; - overflow: auto; - white-space: nowrap; - - > .visibleUsers { - display: inline; - top: -1px; - font-size: 14px; - - > button { - padding: 4px; - border-radius: 8px; - } - - > span { - margin-right: 14px; - padding: 8px 0 8px 8px; - border-radius: 8px; - background: var(--X4); - - > button { - padding: 4px 8px; - } - } - } - } - - > .cw, - > .text { - display: block; - box-sizing: border-box; - padding: 16px; - margin: 0; - width: 100%; - font-size: 16px; - border: none; - border-radius: 0; - background: transparent; - color: var(--fg); - font-family: inherit; - - &:focus { - outline: none; - } - - &:disabled { - opacity: 0.5; - } - } - - > .cw { - z-index: 1; - padding-bottom: 8px; - border-bottom: solid 0.5px var(--divider); - } - - > .text { - max-width: 100%; - min-width: 100%; - min-height: 60px; - - &.withCw { - padding-top: 8px; - } - } - - > footer { - $height: 44px; - display: flex; - padding: 0 8px 8px 8px; - line-height: $height; - - > .left { - > button { - display: inline-block; - padding: 0; - margin: 0; - font-size: 16px; - width: $height; - height: $height; - border-radius: 6px; - - &:hover { - background: var(--X5); - } - - &.active { - color: var(--accent); - } - } - } - - > .right { - margin-left: auto; - - > .text-count { - opacity: 0.7; - } - - > .visibility { - width: $height; - margin: 0 8px; - - & + .localOnly { - margin-left: 0 !important; - } - } - - > .local-only { - margin: 0 0 0 12px; - opacity: 0.7; - } - - > .submit { - margin: 0; - padding: 0 12px; - line-height: 34px; - font-weight: bold; - border-radius: 4px; - - &:disabled { - opacity: 0.7; - } - - > i { - margin-left: 6px; - } - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/side.vue b/packages/client/src/ui/chat/side.vue deleted file mode 100644 index 548a46102b..0000000000 --- a/packages/client/src/ui/chat/side.vue +++ /dev/null @@ -1,157 +0,0 @@ -<template> -<div v-if="component" class="mrajymqm _narrow_"> - <header class="header" @contextmenu.prevent.stop="onContextmenu"> - <MkHeader class="title" :info="pageInfo" :center="false"/> - </header> - <component :is="component" v-bind="props" :ref="changePage" class="body"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { resolve } from '@/router'; -import { url } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - }, - - provide() { - return { - navHook: (path) => { - this.navigate(path); - } - }; - }, - - data() { - return { - path: null, - component: null, - props: {}, - pageInfo: null, - history: [], - }; - }, - - computed: { - url(): string { - return url + this.path; - } - }, - - methods: { - changePage(page) { - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - } - }, - - navigate(path, record = true) { - if (record && this.path) this.history.push(this.path); - this.path = path; - const { component, props } = resolve(path); - this.component = component; - this.props = props; - this.$emit('open'); - }, - - back() { - this.navigate(this.history.pop(), false); - }, - - close() { - this.path = null; - this.component = null; - this.props = {}; - this.$emit('close'); - }, - - onContextmenu(e) { - os.contextMenu([{ - type: 'label', - text: this.path, - }, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.path); - this.close(); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.path); - this.close(); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.url, '_blank'); - this.close(); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(this.url); - } - }], e); - } - } -}); -</script> - -<style lang="scss" scoped> -.mrajymqm { - $header-height: 54px; // TODO: ã©ã“ã‹ã«é›†ç´„ã—ãŸã„ - - --root-margin: 16px; - --margin: var(--marginHalf); - - height: 100%; - overflow: auto; - box-sizing: border-box; - - > .header { - display: flex; - position: sticky; - z-index: 1000; - top: 0; - height: $header-height; - width: 100%; - font-weight: bold; - //background-color: var(--panel); - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--header); - border-bottom: solid 0.5px var(--divider); - box-sizing: border-box; - - > ._button { - height: $header-height; - width: $header-height; - - &:hover { - color: var(--fgHighlighted); - } - } - - > .title { - flex: 1; - position: relative; - } - } - - > .body { - - } -} -</style> - diff --git a/packages/client/src/ui/chat/store.ts b/packages/client/src/ui/chat/store.ts deleted file mode 100644 index 389d56afb6..0000000000 --- a/packages/client/src/ui/chat/store.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { markRaw } from 'vue'; -import { Storage } from '../../pizzax'; - -export const store = markRaw(new Storage('chatUi', { - widgets: { - where: 'account', - default: [] as { - name: string; - id: string; - data: Record<string, any>; - }[] - }, - tl: { - where: 'deviceAccount', - default: 'home' - }, -})); diff --git a/packages/client/src/ui/chat/sub-note-content.vue b/packages/client/src/ui/chat/sub-note-content.vue deleted file mode 100644 index a85096ebc9..0000000000 --- a/packages/client/src/ui/chat/sub-note-content.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="wrmlmaau"> - <div class="body"> - <span v-if="note.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> - <span v-if="note.deletedAt" style="opacity: 0.5">({{ $ts.deleted }})</span> - <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="fas fa-reply"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> - <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> - </div> - <details v-if="note.files.length > 0"> - <summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary> - <XMediaList :media-list="note.files"/> - </details> - <details v-if="note.poll"> - <summary>{{ $ts.poll }}</summary> - <XPoll :note="note"/> - </details> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XPoll from '@/components/poll.vue'; -import XMediaList from '@/components/media-list.vue'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XPoll, - XMediaList, - }, - props: { - note: { - type: Object, - required: true - } - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.wrmlmaau { - overflow-wrap: break-word; - - > .body { - > .reply { - margin-right: 6px; - color: var(--accent); - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - } -} -</style> diff --git a/packages/client/src/ui/chat/widgets.vue b/packages/client/src/ui/chat/widgets.vue deleted file mode 100644 index 337d5a7b58..0000000000 --- a/packages/client/src/ui/chat/widgets.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="qydbhufi"> - <XWidgets :edit="edit" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> - - <button v-if="edit" class="_textButton" style="font-size: 0.9em;" @click="edit = false">{{ $ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="edit = true">{{ $ts.editWidgets }}</button> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import XWidgets from '@/components/widgets.vue'; -import { store } from './store'; - -export default defineComponent({ - components: { - XWidgets, - }, - - data() { - return { - edit: false, - widgets: store.reactiveState.widgets - }; - }, - - methods: { - addWidget(widget) { - store.set('widgets', [widget, ...store.state.widgets]); - }, - - removeWidget(widget) { - store.set('widgets', store.state.widgets.filter(w => w.id != widget.id)); - }, - - updateWidget({ id, data }) { - // TODO: throttleã—ãŸã„ - store.set('widgets', store.state.widgets.map(w => w.id === id ? { - ...w, - data: data - } : w)); - }, - - updateWidgets(widgets) { - store.set('widgets', widgets); - } - } -}); -</script> - -<style lang="scss" scoped> -.qydbhufi { - height: 100%; - box-sizing: border-box; - overflow: auto; - padding: var(--margin); - - ::v-deep(._panel) { - box-shadow: none; - } -} -</style> diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index 3563e8a888..699b992668 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -105,7 +105,11 @@ export default defineComponent({ }, 'closed'); }, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, } }); </script> diff --git a/packages/client/src/ui/classic.side.vue b/packages/client/src/ui/classic.side.vue index ede658626a..f816834141 100644 --- a/packages/client/src/ui/classic.side.vue +++ b/packages/client/src/ui/classic.side.vue @@ -72,7 +72,7 @@ export default defineComponent({ this.props = {}; }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { os.contextMenu([{ type: 'label', text: this.path, @@ -103,7 +103,7 @@ export default defineComponent({ action: () => { copyToClipboard(this.url); } - }], e); + }], ev); } } }); diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index cc9d7a9b48..afbca06c8e 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -125,7 +125,11 @@ export default defineComponent({ }, 'closed'); }, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, } }); </script> diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 41da973152..c61cbc433e 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -16,7 +16,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> @@ -30,7 +30,7 @@ </div> </div> - <transition name="tray-back"> + <transition :name="$store.state.animation ? 'tray-back' : ''"> <div v-if="widgetsShowing" class="tray-back _modalBg" @click="widgetsShowing = false" @@ -38,7 +38,7 @@ ></div> </transition> - <transition name="tray"> + <transition :name="$store.state.animation ? 'tray' : ''"> <XWidgets v-if="widgetsShowing" class="tray"/> </transition> @@ -167,15 +167,15 @@ export default defineComponent({ if (window._scroll) window._scroll(); }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (isLink(ev.target)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; const path = this.$route.path; os.contextMenu([{ @@ -193,7 +193,7 @@ export default defineComponent({ action: () => { os.pageWindow(path); } - }], e); + }], ev); }, onAiClick(ev) { diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 73dc83180f..51a4853e9d 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -29,7 +29,7 @@ <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> </div> - <transition name="menu-back"> + <transition :name="$store.state.animation ? 'menu-back' : ''"> <div v-if="drawerMenuShowing" class="menu-back _modalBg" @click="drawerMenuShowing = false" @@ -37,7 +37,7 @@ ></div> </transition> - <transition name="menu"> + <transition :name="$store.state.animation ? 'menu' : ''"> <XDrawerMenu v-if="drawerMenuShowing" class="menu"/> </transition> diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index d3c7cf8213..f1ce3ca838 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -207,8 +207,8 @@ export default defineComponent({ return items; }, - onContextmenu(e) { - os.contextMenu(this.getMenu(), e); + onContextmenu(ev: MouseEvent) { + os.contextMenu(this.getMenu(), ev); }, goTop() { @@ -224,7 +224,7 @@ export default defineComponent({ // Chromeã®ãƒã‚°ã§ã€Dragstartãƒãƒ³ãƒ‰ãƒ©å†…ã§ã™ãã«DOMを変更ã™ã‚‹(=リアクティブãªãƒ—ãƒãƒ‘ティを変更ã™ã‚‹)ã¨DragãŒçµ‚了ã—ã¦ã—ã¾ã† // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately - setTimeout(() => { + window.setTimeout(() => { this.dragging = true; }, 10); }, diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index 6ef733dfd0..ca70f693c3 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -2,43 +2,26 @@ <XColumn :column="column" :is-stacked="isStacked"> <template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <XNotes :pagination="pagination"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +const props = defineProps<{ + column: Record<string, unknown>; // TODO + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - pagination: { - endpoint: 'notes/mentions', - limit: 10, - params: () => ({ - visibility: 'specified' - }) - }, - } - }, -}); +const pagination = { + point: 'notes/mentions' as const, + limit: 10, + params: computed(() => ({ + visibility: 'specified' as const, + })), +}; </script> diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index 744056881c..cb045e9a46 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -11,7 +11,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/> </keep-alive> </transition> @@ -64,15 +64,15 @@ export default defineComponent({ history.back(); }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (isLink(ev.target)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; const path = this.$route.path; os.contextMenu([{ @@ -84,7 +84,7 @@ export default defineComponent({ action: () => { os.pageWindow(path); } - }], e); + }], ev); }, } }); diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index 4b8dc0c4ee..6822e7ef06 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -2,40 +2,23 @@ <XColumn :column="column" :is-stacked="isStacked"> <template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <XNotes :pagination="pagination"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +const props = defineProps<{ + column: Record<string, unknown>; // TODO + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - pagination: { - endpoint: 'notes/mentions', - limit: 10, - }, - } - }, -}); +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, +}; </script> diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 9fc2177ee0..16cc9a4f06 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -9,7 +9,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> @@ -36,7 +36,7 @@ <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> </div> - <transition name="menuDrawer-back"> + <transition :name="$store.state.animation ? 'menuDrawer-back' : ''"> <div v-if="drawerMenuShowing" class="menuDrawer-back _modalBg" @click="drawerMenuShowing = false" @@ -44,11 +44,11 @@ ></div> </transition> - <transition name="menuDrawer"> + <transition :name="$store.state.animation ? 'menuDrawer' : ''"> <XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/> </transition> - <transition name="widgetsDrawer-back"> + <transition :name="$store.state.animation ? 'widgetsDrawer-back' : ''"> <div v-if="widgetsShowing" class="widgetsDrawer-back _modalBg" @click="widgetsShowing = false" @@ -56,7 +56,7 @@ ></div> </transition> - <transition name="widgetsDrawer"> + <transition :name="$store.state.animation ? 'widgetsDrawer' : ''"> <XWidgets v-if="widgetsShowing" class="widgetsDrawer"/> </transition> diff --git a/packages/client/src/ui/visitor/b.vue b/packages/client/src/ui/visitor/b.vue index f6ad13d9a0..c9c0a1f72e 100644 --- a/packages/client/src/ui/visitor/b.vue +++ b/packages/client/src/ui/visitor/b.vue @@ -25,7 +25,7 @@ </div> </div> - <transition name="tray-back"> + <transition :name="$store.state.animation ? 'tray-back' : ''"> <div v-if="showMenu" class="menu-back _modalBg" @click="showMenu = false" @@ -33,7 +33,7 @@ ></div> </transition> - <transition name="tray"> + <transition :name="$store.state.animation ? 'tray' : ''"> <div v-if="showMenu" class="menu"> <MkA to="/" class="link" active-class="active"><i class="fas fa-home icon"></i>{{ $ts.home }}</MkA> <MkA to="/explore" class="link" active-class="active"><i class="fas fa-hashtag icon"></i>{{ $ts.explore }}</MkA> diff --git a/packages/client/src/ui/zen.vue b/packages/client/src/ui/zen.vue index 7c72232cfd..a7234f729b 100644 --- a/packages/client/src/ui/zen.vue +++ b/packages/client/src/ui/zen.vue @@ -8,7 +8,7 @@ <div class="content"> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue index d322f4758a..acbbb7a97a 100644 --- a/packages/client/src/widgets/activity.vue +++ b/packages/client/src/widgets/activity.vue @@ -1,82 +1,89 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div> <MkLoading v-if="fetching"/> <template v-else> - <XCalendar v-show="props.view === 0" :data="[].concat(activity)"/> - <XChart v-show="props.view === 1" :data="[].concat(activity)"/> + <XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/> + <XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/> </template> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import XCalendar from './activity.calendar.vue'; import XChart from './activity.chart.vue'; -import * as os from '@/os'; +import { $i } from '@/account'; -const widget = define({ - name: 'activity', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - view: { - type: 'number', - default: 0, - hidden: true, - }, - }) -}); +const name = 'activity'; -export default defineComponent({ - components: { - MkContainer, - XCalendar, - XChart, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - data() { - return { - fetching: true, - activity: null, - }; + transparent: { + type: 'boolean' as const, + default: false, }, - mounted() { - os.api('charts/user/notes', { - userId: this.$i.id, - span: 'day', - limit: 7 * 21 - }).then(activity => { - this.activity = activity.diffs.normal.map((_, i) => ({ - total: activity.diffs.normal[i] + activity.diffs.reply[i] + activity.diffs.renote[i], - notes: activity.diffs.normal[i], - replies: activity.diffs.reply[i], - renotes: activity.diffs.renote[i] - })); - this.fetching = false; - }); + view: { + type: 'number' as const, + default: 0, + hidden: true, }, - methods: { - toggleView() { - if (this.props.view === 1) { - this.props.view = 0; - } else { - this.props.view++; - } - this.save(); - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const activity = ref(null); +const fetching = ref(true); + +const toggleView = () => { + if (widgetProps.view === 1) { + widgetProps.view = 0; + } else { + widgetProps.view++; } + save(); +}; + +os.api('charts/user/notes', { + userId: $i.id, + span: 'day', + limit: 7 * 21, +}).then(res => { + activity.value = res.diffs.normal.map((_, i) => ({ + total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i], + notes: res.diffs.normal[i], + replies: res.diffs.reply[i], + renotes: res.diffs.renote[i] + })); + fetching.value = false; +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue index 891b7454d1..03e394b976 100644 --- a/packages/client/src/widgets/aichan.vue +++ b/packages/client/src/widgets/aichan.vue @@ -1,51 +1,65 @@ <template> -<MkContainer :naked="props.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false"> <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> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from './define'; -import MkContainer from '@/components/ui/container.vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; -const widget = define({ - name: 'ai', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'ai'; -export default defineComponent({ - components: { - MkContainer, - }, - extends: widget, - data() { - return { - }; - }, - mounted() { - window.addEventListener('mousemove', ev => { - const iframeRect = this.$refs.live2d.getBoundingClientRect(); - this.$refs.live2d.contentWindow.postMessage({ - type: 'moveCursor', - body: { - x: ev.clientX - iframeRect.left, - y: ev.clientY - iframeRect.top, - } - }, '*'); - }, { passive: true }); +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - methods: { - touched() { - //if (this.live2d) this.live2d.changeExpression('gurugurume'); - } - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const live2d = ref<HTMLIFrameElement>(); + +const touched = () => { + //if (this.live2d) this.live2d.changeExpression('gurugurume'); +}; + +onMounted(() => { + const onMousemove = (ev: MouseEvent) => { + const iframeRect = live2d.value.getBoundingClientRect(); + live2d.value.contentWindow.postMessage({ + type: 'moveCursor', + body: { + x: ev.clientX - iframeRect.left, + y: ev.clientY - iframeRect.top, + } + }, '*'); + }; + + window.addEventListener('mousemove', onMousemove, { passive: true }); + onUnmounted(() => { + window.removeEventListener('mousemove', onMousemove); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue index 46c5094ee9..0a5c0d614d 100644 --- a/packages/client/src/widgets/aiscript.vue +++ b/packages/client/src/widgets/aiscript.vue @@ -1,9 +1,9 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template> <div class="uylguesu _monospace"> - <textarea v-model="props.script" placeholder="(1 + 1)"></textarea> + <textarea v-model="widgetProps.script" placeholder="(1 + 1)"></textarea> <button class="_buttonPrimary" @click="run">RUN</button> <div class="logs"> <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div> @@ -12,97 +12,109 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; import { AiScript, parse, utils } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; +import { $i } from '@/account'; -const widget = define({ - name: 'aiscript', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - script: { - type: 'string', - multiline: true, - default: '(1 + 1)', - hidden: true, - }, - }) -}); +const name = 'aiscript'; -export default defineComponent({ - components: { - MkContainer +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - logs: [], - }; + script: { + type: 'string' as const, + multiline: true, + default: '(1 + 1)', + hidden: true, }, +}; - methods: { - async run() { - this.logs = []; - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'widget', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - this.logs.push({ - id: Math.random(), - text: value.type === 'str' ? value.value : utils.valToString(value), - print: true - }); - }, - log: (type, params) => { - switch (type) { - case 'end': this.logs.push({ - id: Math.random(), - text: utils.valToString(params.val, true), - print: false - }); break; - default: break; - } - } - }); +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - let ast; - try { - ast = parse(this.props.script); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e +// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const logs = ref<{ + id: string; + text: string; + print: boolean; +}[]>([]); + +const run = async () => { + logs.value = []; + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'widget', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); }); - } + }); + }, + out: (value) => { + logs.value.push({ + id: Math.random().toString(), + text: value.type === 'str' ? value.value : utils.valToString(value), + print: true, + }); }, + log: (type, params) => { + switch (type) { + case 'end': logs.value.push({ + id: Math.random().toString(), + text: utils.valToString(params.val, true), + print: false, + }); break; + default: break; + } + } + }); + + let ast; + try { + ast = parse(widgetProps.script); + } catch (e) { + os.alert({ + type: 'error', + text: 'Syntax error :(', + }); + return; } + try { + await aiscript.exec(ast); + } catch (e) { + os.alert({ + type: 'error', + text: e, + }); + } +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue index e98570862e..a33afd6e7a 100644 --- a/packages/client/src/widgets/button.vue +++ b/packages/client/src/widgets/button.vue @@ -1,90 +1,99 @@ <template> <div class="mkw-button"> - <MkButton :primary="props.colored" full @click="run"> - {{ props.label }} + <MkButton :primary="widgetProps.colored" full @click="run"> + {{ widgetProps.label }} </MkButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; import { AiScript, parse, utils } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; +import { $i } from '@/account'; +import MkButton from '@/components/ui/button.vue'; -const widget = define({ - name: 'button', - props: () => ({ - label: { - type: 'string', - default: 'BUTTON', - }, - colored: { - type: 'boolean', - default: true, - }, - script: { - type: 'string', - multiline: true, - default: 'Mk:dialog("hello" "world")', - }, - }) -}); +const name = 'button'; -export default defineComponent({ - components: { - MkButton +const widgetPropsDef = { + label: { + type: 'string' as const, + default: 'BUTTON', }, - extends: widget, - data() { - return { - }; + colored: { + type: 'boolean' as const, + default: true, }, - methods: { - async run() { - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'widget', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - // nop - }, - log: (type, params) => { - // nop - } - }); + script: { + type: 'string' as const, + multiline: true, + default: 'Mk:dialog("hello" "world")', + }, +}; - let ast; - try { - ast = parse(this.props.script); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const run = async () => { + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'widget', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); }); - } + }); }, + out: (value) => { + // nop + }, + log: (type, params) => { + // nop + } + }); + + let ast; + try { + ast = parse(widgetProps.script); + } catch (e) { + os.alert({ + type: 'error', + text: 'Syntax error :(', + }); + return; } + try { + await aiscript.exec(ast); + } catch (e) { + os.alert({ + type: 'error', + text: e, + }); + } +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index c8b52d7afc..b0e3edcb12 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -1,5 +1,5 @@ <template> -<div class="mkw-calendar" :class="{ _panel: !props.transparent }"> +<div class="mkw-calendar" :class="{ _panel: !widgetProps.transparent }"> <div class="calendar" :class="{ isHoliday }"> <p class="month-and-year"> <span class="year">{{ $t('yearX', { year }) }}</span> @@ -32,77 +32,87 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { i18n } from '@/i18n'; -const widget = define({ - name: 'calendar', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'calendar'; -export default defineComponent({ - extends: widget, - data() { - return { - now: new Date(), - year: null, - month: null, - day: null, - weekDay: null, - yearP: null, - dayP: null, - monthP: null, - isHoliday: null, - clock: null - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000); +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - const nd = now.getDate(); - const nm = now.getMonth(); - const ny = now.getFullYear(); +}; - this.year = ny; - this.month = nm + 1; - this.day = nd; - this.weekDay = [ - this.$ts._weekday.sunday, - this.$ts._weekday.monday, - this.$ts._weekday.tuesday, - this.$ts._weekday.wednesday, - this.$ts._weekday.thursday, - this.$ts._weekday.friday, - this.$ts._weekday.saturday - ][now.getDay()]; +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); - const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; - const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); - const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); - const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); - const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); +// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); - this.dayP = dayNumer / dayDenom * 100; - this.monthP = monthNumer / monthDenom * 100; - this.yearP = yearNumer / yearDenom * 100; +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - this.isHoliday = now.getDay() === 0 || now.getDay() === 6; - } - } +const year = ref(0); +const month = ref(0); +const day = ref(0); +const weekDay = ref(''); +const yearP = ref(0); +const monthP = ref(0); +const dayP = ref(0); +const isHoliday = ref(false); +const tick = () => { + const now = new Date(); + const nd = now.getDate(); + const nm = now.getMonth(); + const ny = now.getFullYear(); + + year.value = ny; + month.value = nm + 1; + day.value = nd; + weekDay.value = [ + i18n.locale._weekday.sunday, + i18n.locale._weekday.monday, + i18n.locale._weekday.tuesday, + i18n.locale._weekday.wednesday, + i18n.locale._weekday.thursday, + i18n.locale._weekday.friday, + i18n.locale._weekday.saturday + ][now.getDay()]; + + const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); + const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; + const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); + const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); + const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); + const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); + + dayP.value = dayNumer / dayDenom * 100; + monthP.value = monthNumer / monthDenom * 100; + yearP.value = yearNumer / yearDenom * 100; + + isHoliday.value = now.getDay() === 0 || now.getDay() === 6; +}; + +tick(); + +const intervalId = window.setInterval(tick, 1000); +onUnmounted(() => { + window.clearInterval(intervalId); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue index 6ca7ecd430..6acb10d74d 100644 --- a/packages/client/src/widgets/clock.vue +++ b/packages/client/src/widgets/clock.vue @@ -1,45 +1,56 @@ <template> -<MkContainer :naked="props.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false"> <div class="vubelbmv"> - <MkAnalogClock class="clock" :thickness="props.thickness"/> + <MkAnalogClock class="clock" :thickness="widgetProps.thickness"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; import MkAnalogClock from '@/components/analog-clock.vue'; -import * as os from '@/os'; -const widget = define({ - name: 'clock', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - thickness: { - type: 'radio', - default: 0.1, - options: [{ - value: 0.1, label: 'thin' - }, { - value: 0.2, label: 'medium' - }, { - value: 0.3, label: 'thick' - }] - } - }) -}); +const name = 'clock'; -export default defineComponent({ - components: { - MkContainer, - MkAnalogClock +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, + }, + thickness: { + type: 'radio' as const, + default: 0.1, + options: [{ + value: 0.1, label: 'thin' + }, { + value: 0.2, label: 'medium' + }, { + value: 0.3, label: 'thick' + }], }, - extends: widget, +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/define.ts b/packages/client/src/widgets/define.ts deleted file mode 100644 index 08a346d97c..0000000000 --- a/packages/client/src/widgets/define.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { defineComponent } from 'vue'; -import { throttle } from 'throttle-debounce'; -import { Form } from '@/scripts/form'; -import * as os from '@/os'; - -export default function <T extends Form>(data: { - name: string; - props?: () => T; -}) { - return defineComponent({ - props: { - widget: { - type: Object, - required: false - }, - settingCallback: { - required: false - } - }, - - emits: ['updateProps'], - - data() { - return { - props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {}, - save: throttle(3000, () => { - this.$emit('updateProps', this.props); - }), - }; - }, - - computed: { - id(): string { - return this.widget ? this.widget.id : null; - }, - }, - - created() { - this.mergeProps(); - - this.$watch('props', () => { - this.mergeProps(); - }, { deep: true }); - - if (this.settingCallback) this.settingCallback(this.setting); - }, - - methods: { - mergeProps() { - if (data.props) { - const defaultProps = data.props(); - for (const prop of Object.keys(defaultProps)) { - if (this.props.hasOwnProperty(prop)) continue; - this.props[prop] = defaultProps[prop].default; - } - } - }, - - async setting() { - const form = data.props(); - for (const item of Object.keys(form)) { - form[item].default = this.props[item]; - } - const { canceled, result } = await os.form(data.name, form); - if (canceled) return; - - for (const key of Object.keys(result)) { - this.props[key] = result[key]; - } - - this.save(); - }, - } - }); -} diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue index fbf632d2de..62f052a692 100644 --- a/packages/client/src/widgets/digital-clock.vue +++ b/packages/client/src/widgets/digital-clock.vue @@ -1,73 +1,84 @@ <template> -<div class="mkw-digitalClock _monospace" :class="{ _panel: !props.transparent }" :style="{ fontSize: `${props.fontSize}em` }"> +<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }"> <span> <span v-text="hh"></span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span v-text="mm"></span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span v-text="ss"></span> - <span v-if="props.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-if="props.showMs" v-text="ms"></span> + <span v-if="widgetProps.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> + <span v-if="widgetProps.showMs" v-text="ms"></span> </span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; -const widget = define({ - name: 'digitalClock', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - fontSize: { - type: 'number', - default: 1.5, - step: 0.1, - }, - showMs: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'digitalClock'; -export default defineComponent({ - extends: widget, - data() { - return { - clock: null, - hh: null, - mm: null, - ss: null, - ms: null, - showColon: true, - }; +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - created() { - this.tick(); - this.$watch(() => this.props.showMs, () => { - if (this.clock) clearInterval(this.clock); - this.clock = setInterval(this.tick, this.props.showMs ? 10 : 1000); - }, { immediate: true }); + fontSize: { + type: 'number' as const, + default: 1.5, + step: 0.1, }, - beforeUnmount() { - clearInterval(this.clock); + showMs: { + type: 'boolean' as const, + default: true, }, - methods: { - tick() { - const now = new Date(); - this.hh = now.getHours().toString().padStart(2, '0'); - this.mm = now.getMinutes().toString().padStart(2, '0'); - this.ss = now.getSeconds().toString().padStart(2, '0'); - this.ms = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0'); - this.showColon = now.getSeconds() % 2 === 0; - } - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +let intervalId; +const hh = ref(''); +const mm = ref(''); +const ss = ref(''); +const ms = ref(''); +const showColon = ref(true); +const tick = () => { + const now = new Date(); + hh.value = now.getHours().toString().padStart(2, '0'); + mm.value = now.getMinutes().toString().padStart(2, '0'); + ss.value = now.getSeconds().toString().padStart(2, '0'); + ms.value = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0'); + showColon.value = now.getSeconds() % 2 === 0; +}; + +tick(); + +watch(() => widgetProps.showMs, () => { + if (intervalId) window.clearInterval(intervalId); + intervalId = window.setInterval(tick, widgetProps.showMs ? 10 : 1000); +}, { immediate: true }); + +onUnmounted(() => { + window.clearInterval(intervalId); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index 736a91c52e..4c43117e48 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -1,10 +1,10 @@ <template> -<MkContainer :show-header="props.showHeader" :foldable="foldable" :scrollable="scrollable"> +<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable"> <template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template> <div class="wbrkwalb"> <MkLoading v-if="fetching"/> - <transition-group v-else tag="div" name="chart" class="instances"> + <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances"> <div v-for="(instance, i) in instances" :key="instance.id" class="instance"> <img v-if="instance.iconUrl" :src="instance.iconUrl" alt=""/> <div class="body"> @@ -18,66 +18,64 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; -const widget = define({ - name: 'federation', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'federation'; -export default defineComponent({ - components: { - MkContainer, MkMiniChart - }, - extends: widget, - props: { - foldable: { - type: Boolean, - required: false, - default: false - }, - scrollable: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - instances: [], - charts: [], - fetching: true, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 1000 * 60); +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - async fetch() { - const instances = await os.api('federation/instances', { - sort: '+lastCommunicatedAt', - limit: 5 - }); - const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); - this.instances = instances; - this.charts = charts; - this.fetching = false; - } - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const instances = ref([]); +const charts = ref([]); +const fetching = ref(true); + +const fetch = async () => { + const instances = await os.api('federation/instances', { + sort: '+lastCommunicatedAt', + limit: 5 + }); + const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); + instances.value = instances; + charts.value = charts; + fetching.value = false; +}; + +onMounted(() => { + fetch(); + const intervalId = window.setInterval(fetch, 1000 * 60); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue index ef440881e5..4a2a3cf233 100644 --- a/packages/client/src/widgets/job-queue.vue +++ b/packages/client/src/widgets/job-queue.vue @@ -1,133 +1,146 @@ <template> -<div class="mkw-jobQueue _monospace" :class="{ _panel: !props.transparent }"> +<div class="mkw-jobQueue _monospace" :class="{ _panel: !widgetProps.transparent }"> <div class="inbox"> - <div class="label">Inbox queue<i v-if="inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> + <div class="label">Inbox queue<i v-if="current.inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> <div class="values"> <div> <div>Process</div> - <div :class="{ inc: inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(inbox.activeSincePrevTick) }}</div> + <div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(current.inbox.activeSincePrevTick) }}</div> </div> <div> <div>Active</div> - <div :class="{ inc: inbox.active > prev.inbox.active, dec: inbox.active < prev.inbox.active }">{{ number(inbox.active) }}</div> + <div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }">{{ number(current.inbox.active) }}</div> </div> <div> <div>Delayed</div> - <div :class="{ inc: inbox.delayed > prev.inbox.delayed, dec: inbox.delayed < prev.inbox.delayed }">{{ number(inbox.delayed) }}</div> + <div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }">{{ number(current.inbox.delayed) }}</div> </div> <div> <div>Waiting</div> - <div :class="{ inc: inbox.waiting > prev.inbox.waiting, dec: inbox.waiting < prev.inbox.waiting }">{{ number(inbox.waiting) }}</div> + <div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }">{{ number(current.inbox.waiting) }}</div> </div> </div> </div> <div class="deliver"> - <div class="label">Deliver queue<i v-if="deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> + <div class="label">Deliver queue<i v-if="current.deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> <div class="values"> <div> <div>Process</div> - <div :class="{ inc: deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(deliver.activeSincePrevTick) }}</div> + <div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(current.deliver.activeSincePrevTick) }}</div> </div> <div> <div>Active</div> - <div :class="{ inc: deliver.active > prev.deliver.active, dec: deliver.active < prev.deliver.active }">{{ number(deliver.active) }}</div> + <div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }">{{ number(current.deliver.active) }}</div> </div> <div> <div>Delayed</div> - <div :class="{ inc: deliver.delayed > prev.deliver.delayed, dec: deliver.delayed < prev.deliver.delayed }">{{ number(deliver.delayed) }}</div> + <div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }">{{ number(current.deliver.delayed) }}</div> </div> <div> <div>Waiting</div> - <div :class="{ inc: deliver.waiting > prev.deliver.waiting, dec: deliver.waiting < prev.deliver.waiting }">{{ number(deliver.waiting) }}</div> + <div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }">{{ number(current.deliver.waiting) }}</div> </div> </div> </div> </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from './define'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { stream } from '@/stream'; import number from '@/filters/number'; import * as sound from '@/scripts/sound'; +import * as os from '@/os'; -const widget = define({ - name: 'jobQueue', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - sound: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'jobQueue'; -export default defineComponent({ - extends: widget, - data() { - return { - connection: markRaw(os.stream.useChannel('queueStats')), - inbox: { - activeSincePrevTick: 0, - active: 0, - waiting: 0, - delayed: 0, - }, - deliver: { - activeSincePrevTick: 0, - active: 0, - waiting: 0, - delayed: 0, - }, - prev: {}, - sound: sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1) - }; +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - created() { - for (const domain of ['inbox', 'deliver']) { - this.prev[domain] = JSON.parse(JSON.stringify(this[domain])); - } - - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); + sound: { + type: 'boolean' as const, + default: false, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 1 - }); +// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const connection = stream.useChannel('queueStats'); +const current = reactive({ + inbox: { + activeSincePrevTick: 0, + active: 0, + waiting: 0, + delayed: 0, }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - this.connection.dispose(); + deliver: { + activeSincePrevTick: 0, + active: 0, + waiting: 0, + delayed: 0, }, - methods: { - onStats(stats) { - for (const domain of ['inbox', 'deliver']) { - this.prev[domain] = JSON.parse(JSON.stringify(this[domain])); - this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; - this[domain].active = stats[domain].active; - this[domain].waiting = stats[domain].waiting; - this[domain].delayed = stats[domain].delayed; +}); +const prev = reactive({} as typeof current); +const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1); - if (this[domain].waiting > 0 && this.props.sound && this.sound.paused) { - this.sound.play(); - } - } - }, +for (const domain of ['inbox', 'deliver']) { + prev[domain] = JSON.parse(JSON.stringify(current[domain])); +} - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - }, +const onStats = (stats) => { + for (const domain of ['inbox', 'deliver']) { + prev[domain] = JSON.parse(JSON.stringify(current[domain])); + current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; + current[domain].active = stats[domain].active; + current[domain].waiting = stats[domain].waiting; + current[domain].delayed = stats[domain].delayed; + + if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) { + jammedSound.play(); + } + } +}; - number +const onStatsLog = (statsLog) => { + for (const stats of [...statsLog].reverse()) { + onStats(stats); } +}; + +connection.on('stats', onStats); +connection.on('statsLog', onStatsLog); + +connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 1, +}); + +onUnmounted(() => { + connection.off('stats', onStats); + connection.off('statsLog', onStatsLog); + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue index 9b51ada220..450598f65a 100644 --- a/packages/client/src/widgets/memo.vue +++ b/packages/client/src/widgets/memo.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template> <div class="otgbylcu"> @@ -9,56 +9,60 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; +import { defaultStore } from '@/store'; -const widget = define({ - name: 'memo', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'memo'; -export default defineComponent({ - components: { - MkContainer +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, +}; - data() { - return { - text: null, - changed: false, - timeoutId: null, - }; - }, +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - created() { - this.text = this.$store.state.memo; +// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); - this.$watch(() => this.$store.reactiveState.memo, text => { - this.text = text; - }); - }, +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - methods: { - onChange() { - this.changed = true; - clearTimeout(this.timeoutId); - this.timeoutId = setTimeout(this.saveMemo, 1000); - }, +const text = ref<string | null>(defaultStore.state.memo); +const changed = ref(false); +let timeoutId; - saveMemo() { - this.$store.set('memo', this.text); - this.changed = false; - } - } +const saveMemo = () => { + defaultStore.set('memo', text.value); + changed.value = false; +}; + +const onChange = () => { + changed.value = true; + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(saveMemo, 1000); +}; + +watch(() => defaultStore.reactiveState.memo, newText => { + text.value = newText.value; +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue index 568705b661..8cf29c9271 100644 --- a/packages/client/src/widgets/notifications.vue +++ b/packages/client/src/widgets/notifications.vue @@ -1,65 +1,68 @@ <template> -<MkContainer :style="`height: ${props.height}px;`" :show-header="props.showHeader" :scrollable="true"> +<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true"> <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template> - <template #func><button class="_button" @click="configure()"><i class="fas fa-cog"></i></button></template> + <template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template> <div> - <XNotifications :include-types="props.includingTypes"/> + <XNotifications :include-types="widgetProps.includingTypes"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; import XNotifications from '@/components/notifications.vue'; -import define from './define'; import * as os from '@/os'; -const widget = define({ - name: 'notifications', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - height: { - type: 'number', - default: 300, - }, - includingTypes: { - type: 'array', - hidden: true, - default: null, - }, - }) -}); - -export default defineComponent({ +const name = 'notifications'; - components: { - MkContainer, - XNotifications, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - }; + height: { + type: 'number' as const, + default: 300, + }, + includingTypes: { + type: 'array' as const, + hidden: true, + default: null, }, +}; - methods: { - configure() { - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes: this.props.includingTypes, - }, { - done: async (res) => { - const { includingTypes } = res; - this.props.includingTypes = includingTypes; - this.save(); - } - }, 'closed'); +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const configureNotification = () => { + os.popup(import('@/components/notification-setting-window.vue'), { + includingTypes: widgetProps.includingTypes, + }, { + done: async (res) => { + const { includingTypes } = res; + widgetProps.includingTypes = includingTypes; + save(); } - } + }, 'closed'); +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index 5b889f4816..1746a8314e 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -1,48 +1,60 @@ <template> -<div class="mkw-onlineUsers" :class="{ _panel: !props.transparent, pad: !props.transparent }"> +<div class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }"> <I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text"> <template #n><b>{{ onlineUsersCount }}</b></template> </I18n> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; -const widget = define({ - name: 'onlineUsers', - props: () => ({ - transparent: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'onlineUsers'; -export default defineComponent({ - extends: widget, - data() { - return { - onlineUsersCount: null, - clock: null, - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000 * 15); +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: true, }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - os.api('get-online-users-count').then(res => { - this.onlineUsersCount = res.count; - }); - } - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const onlineUsersCount = ref(0); + +const tick = () => { + os.api('get-online-users-count').then(res => { + onlineUsersCount.value = res.count; + }); +}; + +onMounted(() => { + tick(); + const intervalId = window.setInterval(tick, 1000 * 15); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue index a91d4f6c49..8f948dc643 100644 --- a/packages/client/src/widgets/photos.vue +++ b/packages/client/src/widgets/photos.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent" :class="$style.root" :data-transparent="props.transparent ? true : null"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null"> <template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template> <div class=""> @@ -14,69 +14,77 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { stream } from '@/stream'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; +import { defaultStore } from '@/store'; -const widget = define({ - name: 'photos', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'photos'; -export default defineComponent({ - components: { - MkContainer, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - data() { - return { - images: [], - fetching: true, - connection: null, - }; + transparent: { + type: 'boolean' as const, + default: false, }, - mounted() { - this.connection = markRaw(os.stream.useChannel('main')); +}; - this.connection.on('driveFileCreated', this.onDriveFileCreated); +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - os.api('drive/stream', { - type: 'image/*', - limit: 9 - }).then(images => { - this.images = images; - this.fetching = false; - }); - }, - beforeUnmount() { - this.connection.dispose(); - }, - methods: { - onDriveFileCreated(file) { - if (/^image\/.+$/.test(file.type)) { - this.images.unshift(file); - if (this.images.length > 9) this.images.pop(); - } - }, +// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const connection = stream.useChannel('main'); +const images = ref([]); +const fetching = ref(true); - thumbnail(image: any): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(image.thumbnailUrl) - : image.thumbnailUrl; - }, +const onDriveFileCreated = (file) => { + if (/^image\/.+$/.test(file.type)) { + images.value.unshift(file); + if (images.value.length > 9) images.value.pop(); } +}; + +const thumbnail = (image: any): string => { + return defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(image.thumbnailUrl) + : image.thumbnailUrl; +}; + +os.api('drive/stream', { + type: 'image/*', + limit: 9 +}).then(res => { + images.value = res; + fetching.value = false; +}); + +connection.on('driveFileCreated', onDriveFileCreated); +onUnmounted(() => { + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue index 6de0574cc1..51aa8fcf6b 100644 --- a/packages/client/src/widgets/post-form.vue +++ b/packages/client/src/widgets/post-form.vue @@ -2,22 +2,34 @@ <XPostForm class="_panel" :fixed="true" :autofocus="false"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import XPostForm from '@/components/post-form.vue'; -import define from './define'; -const widget = define({ - name: 'postForm', - props: () => ({ - }) -}); +const name = 'postForm'; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); -export default defineComponent({ +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - components: { - XPostForm, - }, - extends: widget, +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index b2dc77854e..9e2e503602 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -1,7 +1,7 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-rss-square"></i>RSS</template> - <template #func><button class="_button" @click="setting"><i class="fas fa-cog"></i></button></template> + <template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template> <div class="ekmkgxbj"> <MkLoading v-if="fetching"/> @@ -12,57 +12,66 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; -const widget = define({ - name: 'rss', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - url: { - type: 'string', - default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', - }, - }) -}); +const name = 'rss'; -export default defineComponent({ - components: { - MkContainer - }, - extends: widget, - data() { - return { - items: [], - fetching: true, - clock: null, - }; +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 60000); - this.$watch(() => this.props.url, this.fetch); + url: { + type: 'string' as const, + default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - fetch() { - fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, { - }).then(res => { - res.json().then(feed => { - this.items = feed.items; - this.fetching = false; - }); - }); - }, - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const items = ref([]); +const fetching = ref(true); + +const tick = () => { + fetch(`https://api.rss2json.com/v1/api.json?rss_url=${widgetProps.url}`, {}).then(res => { + res.json().then(feed => { + items.value = feed.items; + fetching.value = false; + }); + }); +}; + +watch(() => widgetProps.url, tick); + +onMounted(() => { + tick(); + const intervalId = window.setInterval(tick, 60000); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue index 650101b0ee..052991b554 100644 --- a/packages/client/src/widgets/server-metric/disk.vue +++ b/packages/client/src/widgets/server-metric/disk.vue @@ -10,32 +10,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XPie from './pie.vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - components: { - XPie - }, - props: { - meta: { - required: true, - } - }, - data() { - return { - usage: this.meta.fs.used / this.meta.fs.total, - total: this.meta.fs.total, - used: this.meta.fs.used, - available: this.meta.fs.total - this.meta.fs.used, - }; - }, - methods: { - bytes - } -}); +const props = defineProps<{ + meta: any; // TODO +}>(); + +const usage = $computed(() => props.meta.fs.used / props.meta.fs.total); +const total = $computed(() => props.meta.fs.total); +const used = $computed(() => props.meta.fs.used); +const available = $computed(() => props.meta.fs.total - props.meta.fs.used); </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue index 019e16fb33..2caa73fa74 100644 --- a/packages/client/src/widgets/server-metric/index.vue +++ b/packages/client/src/widgets/server-metric/index.vue @@ -1,21 +1,22 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #header><i class="fas fa-server"></i>{{ $ts._widgets.serverMetric }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div v-if="meta" class="mkw-serverMetric"> - <XCpuMemory v-if="props.view === 0" :connection="connection" :meta="meta"/> - <XNet v-if="props.view === 1" :connection="connection" :meta="meta"/> - <XCpu v-if="props.view === 2" :connection="connection" :meta="meta"/> - <XMemory v-if="props.view === 3" :connection="connection" :meta="meta"/> - <XDisk v-if="props.view === 4" :connection="connection" :meta="meta"/> + <XCpuMemory v-if="widgetProps.view === 0" :connection="connection" :meta="meta"/> + <XNet v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta"/> + <XCpu v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta"/> + <XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/> + <XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from '../define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget'; import MkContainer from '@/components/ui/container.vue'; import XCpuMemory from './cpu-mem.vue'; import XNet from './net.vue'; @@ -23,60 +24,63 @@ import XCpu from './cpu.vue'; import XMemory from './mem.vue'; import XDisk from './disk.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; -const widget = define({ - name: 'serverMetric', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - view: { - type: 'number', - default: 0, - hidden: true, - }, - }) -}); +const name = 'serverMetric'; -export default defineComponent({ - components: { - MkContainer, - XCpuMemory, - XNet, - XCpu, - XMemory, - XDisk, - }, - extends: widget, - data() { - return { - meta: null, - connection: null, - }; +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - created() { - os.api('server-info', {}).then(res => { - this.meta = res; - }); - this.connection = markRaw(os.stream.useChannel('serverStats')); + transparent: { + type: 'boolean' as const, + default: false, }, - unmounted() { - this.connection.dispose(); + view: { + type: 'number' as const, + default: 0, + hidden: true, }, - methods: { - toggleView() { - if (this.props.view == 4) { - this.props.view = 0; - } else { - this.props.view++; - } - this.save(); - }, +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const meta = ref(null); + +os.api('server-info', {}).then(res => { + meta.value = res; +}); + +const toggleView = () => { + if (widgetProps.view == 4) { + widgetProps.view = 0; + } else { + widgetProps.view++; } + save(); +}; + +const connection = stream.useChannel('serverStats'); +onUnmounted(() => { + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/server-metric/pie.vue b/packages/client/src/widgets/server-metric/pie.vue index 38dcf6fcd9..868dbc0484 100644 --- a/packages/client/src/widgets/server-metric/pie.vue +++ b/packages/client/src/widgets/server-metric/pie.vue @@ -20,30 +20,17 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - value: { - type: Number, - required: true - } - }, - data() { - return { - r: 0.45 - }; - }, - computed: { - color(): string { - return `hsl(${180 - (this.value * 180)}, 80%, 70%)`; - }, - strokeDashoffset(): number { - return (1 - this.value) * (Math.PI * (this.r * 2)); - } - } -}); +const props = defineProps<{ + value: number; +}>(); + +const r = 0.45; + +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> diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index 0909bda67c..7b2e539685 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -1,126 +1,116 @@ <template> -<div class="kvausudm _panel"> +<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }"> <div @click="choose"> - <p v-if="props.folderId == null"> - <template v-if="isCustomizeMode">{{ $t('folder-customize-mode') }}</template> - <template v-else>{{ $ts.folder }}</template> + <p v-if="widgetProps.folderId == null"> + {{ $ts.folder }} </p> - <p v-if="props.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p> + <p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p> <div ref="slideA" class="slide a"></div> <div ref="slideB" class="slide b"></div> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; -const widget = define({ - name: 'slideshow', - props: () => ({ - height: { - type: 'number', - default: 300, - }, - folderId: { - type: 'string', - default: null, - hidden: true, - }, - }) -}); +const name = 'slideshow'; -export default defineComponent({ - extends: widget, - data() { - return { - images: [], - fetching: true, - clock: null - }; +const widgetPropsDef = { + height: { + type: 'number' as const, + default: 300, + }, + folderId: { + type: 'string' as const, + default: null, + hidden: true, }, - mounted() { - this.$nextTick(() => { - this.applySize(); - }); +}; - if (this.props.folderId != null) { - this.fetch(); - } +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - this.clock = setInterval(this.change, 10000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - applySize() { - let h; +// ç¾æ™‚点ã§ã¯vueã®åˆ¶é™ã«ã‚ˆã‚Šimportã—ãŸtypeをジェãƒãƒªãƒƒã‚¯ã«æ¸¡ã›ãªã„ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); - if (this.props.size == 1) { - h = 250; - } else { - h = 170; - } +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - this.$el.style.height = `${h}px`; - }, - resize() { - if (this.props.size == 1) { - this.props.size = 0; - } else { - this.props.size++; - } - this.save(); +const images = ref([]); +const fetching = ref(true); +const slideA = ref<HTMLElement>(); +const slideB = ref<HTMLElement>(); - this.applySize(); - }, - change() { - if (this.images.length == 0) return; +const change = () => { + if (images.value.length == 0) return; - const index = Math.floor(Math.random() * this.images.length); - const img = `url(${ this.images[index].url })`; + const index = Math.floor(Math.random() * images.value.length); + const img = `url(${ images.value[index].url })`; - (this.$refs.slideB as any).style.backgroundImage = img; + slideB.value.style.backgroundImage = img; - this.$refs.slideB.classList.add('anime'); - setTimeout(() => { - // æ—¢ã«ã“ã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆãŒunmountã•れã¦ã„ãŸã‚‰è¦ç´ ãŒãªã„ - if ((this.$refs.slideA as any) == null) return; + slideB.value.classList.add('anime'); + window.setTimeout(() => { + // æ—¢ã«ã“ã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆãŒunmountã•れã¦ã„ãŸã‚‰è¦ç´ ãŒãªã„ + if (slideA.value == null) return; - (this.$refs.slideA as any).style.backgroundImage = img; + slideA.value.style.backgroundImage = img; - this.$refs.slideB.classList.remove('anime'); - }, 1000); - }, - fetch() { - this.fetching = true; + slideB.value.classList.remove('anime'); + }, 1000); +}; - os.api('drive/files', { - folderId: this.props.folderId, - type: 'image/*', - limit: 100 - }).then(images => { - this.images = images; - this.fetching = false; - (this.$refs.slideA as any).style.backgroundImage = ''; - (this.$refs.slideB as any).style.backgroundImage = ''; - this.change(); - }); - }, - choose() { - os.selectDriveFolder(false).then(folder => { - if (folder == null) { - return; - } - this.props.folderId = folder.id; - this.save(); - this.fetch(); - }); +const fetch = () => { + fetching.value = true; + + os.api('drive/files', { + folderId: widgetProps.folderId, + type: 'image/*', + limit: 100 + }).then(res => { + images.value = res; + fetching.value = false; + slideA.value.style.backgroundImage = ''; + slideB.value.style.backgroundImage = ''; + change(); + }); +}; + +const choose = () => { + os.selectDriveFolder(false).then(folder => { + if (folder == null) { + return; } + widgetProps.folderId = folder.id; + save(); + fetch(); + }); +}; + +onMounted(() => { + if (widgetProps.folderId != null) { + fetch(); } + + const intervalId = window.setInterval(change, 10000); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue index aee6a35b1d..fa700cc8ee 100644 --- a/packages/client/src/widgets/timeline.vue +++ b/packages/client/src/widgets/timeline.vue @@ -1,116 +1,129 @@ <template> -<MkContainer :show-header="props.showHeader" :style="`height: ${props.height}px;`" :scrollable="true"> +<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true"> <template #header> <button class="_button" @click="choose"> - <i v-if="props.src === 'home'" class="fas fa-home"></i> - <i v-else-if="props.src === 'local'" class="fas fa-comments"></i> - <i v-else-if="props.src === 'social'" class="fas fa-share-alt"></i> - <i v-else-if="props.src === 'global'" class="fas fa-globe"></i> - <i v-else-if="props.src === 'list'" class="fas fa-list-ul"></i> - <i v-else-if="props.src === 'antenna'" class="fas fa-satellite"></i> - <span style="margin-left: 8px;">{{ props.src === 'list' ? props.list.name : props.src === 'antenna' ? props.antenna.name : $t('_timelines.' + props.src) }}</span> + <i v-if="widgetProps.src === 'home'" class="fas fa-home"></i> + <i v-else-if="widgetProps.src === 'local'" class="fas fa-comments"></i> + <i v-else-if="widgetProps.src === 'social'" class="fas fa-share-alt"></i> + <i v-else-if="widgetProps.src === 'global'" class="fas fa-globe"></i> + <i v-else-if="widgetProps.src === 'list'" class="fas fa-list-ul"></i> + <i v-else-if="widgetProps.src === 'antenna'" class="fas fa-satellite"></i> + <span style="margin-left: 8px;">{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : $t('_timelines.' + widgetProps.src) }}</span> <i :class="menuOpened ? 'fas fa-angle-up' : 'fas fa-angle-down'" style="margin-left: 8px;"></i> </button> </template> <div> - <XTimeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list ? props.list.id : null" :antenna="props.antenna ? props.antenna.id : null"/> + <XTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; import XTimeline from '@/components/timeline.vue'; -import define from './define'; -import * as os from '@/os'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -const widget = define({ - name: 'timeline', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - height: { - type: 'number', - default: 300, - }, - src: { - type: 'string', - default: 'home', - hidden: true, - }, - list: { - type: 'object', - default: null, - hidden: true, - }, - }) -}); +const name = 'timeline'; -export default defineComponent({ - components: { - MkContainer, - XTimeline, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - menuOpened: false, - }; + height: { + type: 'number' as const, + default: 300, + }, + src: { + type: 'string' as const, + default: 'home', + hidden: true, + }, + antenna: { + type: 'object' as const, + default: null, + hidden: true, }, + list: { + type: 'object' as const, + default: null, + hidden: true, + }, +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const menuOpened = ref(false); + +const setSrc = (src) => { + widgetProps.src = src; + save(); +}; - methods: { - async choose(ev) { - this.menuOpened = true; - const [antennas, lists] = await Promise.all([ - os.api('antennas/list'), - os.api('users/lists/list') - ]); - const antennaItems = antennas.map(antenna => ({ - text: antenna.name, - icon: 'fas fa-satellite', - action: () => { - this.props.antenna = antenna; - this.setSrc('antenna'); - } - })); - const listItems = lists.map(list => ({ - text: list.name, - icon: 'fas fa-list-ul', - action: () => { - this.props.list = list; - this.setSrc('list'); - } - })); - os.popupMenu([{ - text: this.$ts._timelines.home, - icon: 'fas fa-home', - action: () => { this.setSrc('home') } - }, { - text: this.$ts._timelines.local, - icon: 'fas fa-comments', - action: () => { this.setSrc('local') } - }, { - text: this.$ts._timelines.social, - icon: 'fas fa-share-alt', - action: () => { this.setSrc('social') } - }, { - text: this.$ts._timelines.global, - icon: 'fas fa-globe', - action: () => { this.setSrc('global') } - }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { - this.menuOpened = false; - }); - }, +const choose = async (ev) => { + menuOpened.value = true; + const [antennas, lists] = await Promise.all([ + os.api('antennas/list'), + os.api('users/lists/list') + ]); + const antennaItems = antennas.map(antenna => ({ + text: antenna.name, + icon: 'fas fa-satellite', + action: () => { + widgetProps.antenna = antenna; + setSrc('antenna'); + } + })); + const listItems = lists.map(list => ({ + text: list.name, + icon: 'fas fa-list-ul', + action: () => { + widgetProps.list = list; + setSrc('list'); + } + })); + os.popupMenu([{ + text: i18n.locale._timelines.home, + icon: 'fas fa-home', + action: () => { setSrc('home') } + }, { + text: i18n.locale._timelines.local, + icon: 'fas fa-comments', + action: () => { setSrc('local') } + }, { + text: i18n.locale._timelines.social, + icon: 'fas fa-share-alt', + action: () => { setSrc('social') } + }, { + text: i18n.locale._timelines.global, + icon: 'fas fa-globe', + action: () => { setSrc('global') } + }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { + menuOpened.value = false; + }); +}; - setSrc(src) { - this.props.src = src; - this.save(); - }, - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index ffad93c02b..eb5eb4049f 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -1,10 +1,10 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template> <div class="wbrkwala"> <MkLoading v-if="fetching"/> - <transition-group v-else tag="div" name="chart" class="tags"> + <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="tags"> <div v-for="stat in stats" :key="stat.tag"> <div class="tag"> <MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA> @@ -17,49 +17,59 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; -const widget = define({ - name: 'hashtags', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'hashtags'; -export default defineComponent({ - components: { - MkContainer, MkMiniChart - }, - extends: widget, - data() { - return { - stats: [], - fetching: true, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 1000 * 60); - }, - beforeUnmount() { - clearInterval(this.clock); +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - methods: { - fetch() { - os.api('hashtags/trend').then(stats => { - this.stats = stats; - this.fetching = false; - }); - } - } +}; + +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<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const stats = ref([]); +const fetching = ref(true); + +const fetch = () => { + os.api('hashtags/trend').then(stats => { + stats.value = stats; + fetching.value = false; + }); +}; + +onMounted(() => { + fetch(); + const intervalId = window.setInterval(fetch, 1000 * 60); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts new file mode 100644 index 0000000000..81239bfb3b --- /dev/null +++ b/packages/client/src/widgets/widget.ts @@ -0,0 +1,71 @@ +import { reactive, watch } from 'vue'; +import { throttle } from 'throttle-debounce'; +import { Form, GetFormResultType } from '@/scripts/form'; +import * as os from '@/os'; + +export type Widget<P extends Record<string, unknown>> = { + id: string; + data: Partial<P>; +}; + +export type WidgetComponentProps<P extends Record<string, unknown>> = { + widget?: Widget<P>; +}; + +export type WidgetComponentEmits<P extends Record<string, unknown>> = { + (e: 'updateProps', props: P); +}; + +export type WidgetComponentExpose = { + name: string; + id: string | null; + configure: () => void; +}; + +export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>( + name: string, + propsDef: F, + props: Readonly<WidgetComponentProps<GetFormResultType<F>>>, + emit: WidgetComponentEmits<GetFormResultType<F>>, +): { + widgetProps: GetFormResultType<F>; + save: () => void; + configure: () => void; +} => { + const widgetProps = reactive(props.widget ? JSON.parse(JSON.stringify(props.widget.data)) : {}); + + const mergeProps = () => { + for (const prop of Object.keys(propsDef)) { + if (widgetProps.hasOwnProperty(prop)) continue; + widgetProps[prop] = propsDef[prop].default; + } + }; + watch(widgetProps, () => { + mergeProps(); + }, { deep: true, immediate: true, }); + + const save = throttle(3000, () => { + emit('updateProps', widgetProps) + }); + + const configure = async () => { + const form = JSON.parse(JSON.stringify(propsDef)); + for (const item of Object.keys(form)) { + form[item].default = widgetProps[item]; + } + const { canceled, result } = await os.form(name, form); + if (canceled) return; + + for (const key of Object.keys(result)) { + widgetProps[key] = result[key]; + } + + save(); + }; + + return { + widgetProps, + save, + configure, + }; +}; diff --git a/packages/client/webpack.config.js b/packages/client/webpack.config.js index 7bcfdcb15d..a50851e17f 100644 --- a/packages/client/webpack.config.js +++ b/packages/client/webpack.config.js @@ -47,6 +47,7 @@ module.exports = { loader: 'vue-loader', options: { cssSourceMap: false, + reactivityTransform: true, compilerOptions: { preserveWhitespace: false } diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 4f666c252b..a45a24ce0e 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -177,94 +177,32 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" -"@sentry/browser@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.29.2.tgz#51adb4005511b1a4a70e4571880fc6653d651f91" - integrity sha512-uxZ7y7rp85tJll+RZtXRhXPbnFnOaxZqJEv05vJlXBtBNLQtlczV5iCtU9mZRLVHDtmZ5VVKUV8IKXntEqqDpQ== - dependencies: - "@sentry/core" "5.29.2" - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/core@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.29.2.tgz#9e05fe197234161d57aabaf52fab336a7c520d81" - integrity sha512-7WYkoxB5IdlNEbwOwqSU64erUKH4laavPsM0/yQ+jojM76ErxlgEF0u//p5WaLPRzh3iDSt6BH+9TL45oNZeZw== - dependencies: - "@sentry/hub" "5.29.2" - "@sentry/minimal" "5.29.2" - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/hub@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.29.2.tgz#208f10fe6674695575ad74182a1151f71d6df00a" - integrity sha512-LaAIo2hwUk9ykeh9RF0cwLy6IRw+DjEee8l1HfEaDFUM6TPGlNNGObMJNXb9/95jzWp7jWwOpQjoIE3jepdQJQ== - dependencies: - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/minimal@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.29.2.tgz#420bebac8d03d30980fdb05c72d7b253d8aa541b" - integrity sha512-0aINSm8fGA1KyM7PavOBe1GDZDxrvnKt+oFnU0L+bTcw8Lr+of+v6Kwd97rkLRNOLw621xP076dL/7LSIzMuhw== - dependencies: - "@sentry/hub" "5.29.2" - "@sentry/types" "5.29.2" - tslib "^1.9.3" - -"@sentry/tracing@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.29.2.tgz#6012788547d2ab7893799d82c4941bda145dcd47" - integrity sha512-iumYbVRpvoU3BUuIooxibydeaOOjl5ysc+mzsqhRs2NGW/C3uKAsFXdvyNfqt3bxtRQwJEhwJByLP2u3pLThpw== - dependencies: - "@sentry/hub" "5.29.2" - "@sentry/minimal" "5.29.2" - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/types@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.29.2.tgz#ac87383df1222c2d9b9f8f9ed7a6b86ea41a098a" - integrity sha512-dM9wgt8wy4WRty75QkqQgrw9FV9F+BOMfmc0iaX13Qos7i6Qs2Q0dxtJ83SoR4YGtW8URaHzlDtWlGs5egBiMA== - -"@sentry/utils@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.29.2.tgz#99a5cdda2ea19d34a41932f138d470adcb3ee673" - integrity sha512-nEwQIDjtFkeE4k6yIk4Ka5XjGRklNLThWLs2xfXlL7uwrYOH2B9UBBOOIRUraBm/g/Xrra3xsam/kRxuiwtXZQ== - dependencies: - "@sentry/types" "5.29.2" - tslib "^1.9.3" - "@sideway/address@^4.1.0": version "4.1.2" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1" @@ -333,10 +271,10 @@ resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc" integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g== -"@types/escape-regexp@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3" - integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg== +"@types/escape-regexp@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" + integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -374,10 +312,10 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -478,11 +416,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@16.11.12": - version "16.11.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10" - integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw== - "@types/node@^14.11.8", "@types/node@^14.14.31": version "14.17.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.9.tgz#b97c057e6138adb7b720df2bd0264b03c9f504fd" @@ -539,10 +472,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" - integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -593,10 +526,10 @@ dependencies: "@types/undertaker-registry" "*" -"@types/uuid@8.3.3": - version "8.3.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.3.tgz#c6a60686d953dbd1b1d45e66f4ecdbd5d471b4d0" - integrity sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/vinyl-fs@*": version "2.4.11" @@ -681,13 +614,14 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz#97dfaa39f38e99f86801fdf34f9f1bed66704258" - integrity sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw== +"@typescript-eslint/eslint-plugin@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz#e90afea96dff8620892ad216b0e4ccdf8ee32d3a" + integrity sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ== dependencies: - "@typescript-eslint/experimental-utils" "5.8.1" - "@typescript-eslint/scope-manager" "5.8.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/type-utils" "5.10.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -695,60 +629,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz#01861eb2f0749f07d02db342b794145a66ed346f" - integrity sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.8.1" - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/typescript-estree" "5.8.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + debug "^4.3.2" -"@typescript-eslint/parser@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.8.1.tgz#380f5f1e596b540059998aa3fc80d78f0f9b0d0a" - integrity sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/scope-manager" "5.8.1" - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/typescript-estree" "5.8.1" - debug "^4.3.2" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/scope-manager@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz#7fc0604f7ade8833e4d42cebaa1e2debf8b932e4" - integrity sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q== +"@typescript-eslint/type-utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz#8524b9479c19c478347a7df216827e749e4a51e5" + integrity sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ== dependencies: - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/visitor-keys" "5.8.1" + "@typescript-eslint/utils" "5.10.0" + debug "^4.3.2" + tsutils "^3.21.0" -"@typescript-eslint/types@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.8.1.tgz#04c6b49ebc8c99238238a6b8b43f2fc613983b5a" - integrity sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA== +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== -"@typescript-eslint/typescript-estree@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz#a592855be688e7b729a1e9411d7d74ec992ed6ef" - integrity sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ== +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== dependencies: - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/visitor-keys" "5.8.1" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz#58a2c566265d5511224bc316149890451c1bbab0" - integrity sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg== +"@typescript-eslint/utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.0.tgz#c3d152a85da77c400e37281355561c72fb1b5a65" + integrity sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/types" "5.8.1" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -756,95 +699,95 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vue/compiler-core@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.26.tgz#9ab92ae624da51f7b6064f4679c2d4564f437cc8" - integrity sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw== +"@vue/compiler-core@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097" + integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.26" + "@vue/shared" "3.2.29" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz#c7a7b55d50a7b7981dd44fc28211df1450482667" - integrity sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg== +"@vue/compiler-dom@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715" + integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ== dependencies: - "@vue/compiler-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/compiler-sfc@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz#3ce76677e4aa58311655a3bea9eb1cb804d2273f" - integrity sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw== +"@vue/compiler-sfc@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead" + integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.26" - "@vue/compiler-dom" "3.2.26" - "@vue/compiler-ssr" "3.2.26" - "@vue/reactivity-transform" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.29" + "@vue/compiler-dom" "3.2.29" + "@vue/compiler-ssr" "3.2.29" + "@vue/reactivity-transform" "3.2.29" + "@vue/shared" "3.2.29" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz#fd049523341fbf4ab5e88e25eef566d862894ba7" - integrity sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag== +"@vue/compiler-ssr@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e" + integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA== dependencies: - "@vue/compiler-dom" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-dom" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/reactivity-transform@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz#6d8f20a4aa2d19728f25de99962addbe7c4d03e9" - integrity sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ== +"@vue/reactivity-transform@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354" + integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.29" + "@vue/shared" "3.2.29" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.26.tgz#d529191e581521c3c12e29ef986d4c8a933a0f83" - integrity sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ== +"@vue/reactivity@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.29.tgz#afdc9c111d4139b14600be17ad80267212af6052" + integrity sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g== dependencies: - "@vue/shared" "3.2.26" + "@vue/shared" "3.2.29" -"@vue/runtime-core@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.26.tgz#5c59cc440ed7a39b6dbd4c02e2d21c8d1988f0de" - integrity sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ== +"@vue/runtime-core@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417" + integrity sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig== dependencies: - "@vue/reactivity" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/reactivity" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/runtime-dom@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz#84d3ae2584488747717c2e072d5d9112c0d2e6c2" - integrity sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ== +"@vue/runtime-dom@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01" + integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA== dependencies: - "@vue/runtime-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/runtime-core" "3.2.29" + "@vue/shared" "3.2.29" csstype "^2.6.8" -"@vue/server-renderer@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.26.tgz#f16a4b9fbcc917417b4cea70c99afce2701341cf" - integrity sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w== +"@vue/server-renderer@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102" + integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw== dependencies: - "@vue/compiler-ssr" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-ssr" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/shared@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.26.tgz#7acd1621783571b9a82eca1f041b4a0a983481d9" - integrity sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA== +"@vue/shared@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925" + integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw== "@webassemblyjs/ast@1.11.0": version "1.11.0" @@ -1162,6 +1105,11 @@ acorn@^8.6.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1358,10 +1306,10 @@ autobind-decorator@2.4.0, autobind-decorator@^2.4.0: resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== -autosize@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.4.tgz#924f13853a466b633b9309330833936d8bccce03" - integrity sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ== +autosize@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/autosize/-/autosize-5.0.1.tgz#ed269b0fa9b7eb47627048a1bb3299e99e003a0f" + integrity sha512-UIWUlE4TOVPNNj2jjrU39wI4hEYbneUypEqcyRmRFIx5CC2gNdg3rQr+Zh7/3h6egbBvm33TDQjNQKtj9Tk1HA== autwh@0.1.0: version "0.1.0" @@ -1399,6 +1347,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -1426,7 +1379,7 @@ blob-util@^2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@3.7.2: +bluebird@3.7.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1528,6 +1481,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1681,15 +1642,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -1774,7 +1734,7 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.15.tgz#8e634aa0429b110d24be82eac4d42f5ea65ab2d5" integrity sha512-lIFQhufWaVvwi4wOlX9Gx5b0Nmw3XAZ8HzHNH9dfxhe+JaKNTmX6QLk4o7UHyI+tUY8ClvyfaHUm5bf61O3psA== -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -1806,7 +1766,7 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^8.0.0, commander@^8.2.0: +commander@^8.0.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -1816,10 +1776,10 @@ common-tags@^1.8.0: resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== -compare-versions@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +compare-versions@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== concat-map@0.0.1: version "0.0.1" @@ -1972,52 +1932,52 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" - integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== +cssnano-preset-default@^5.1.10: + version "5.1.10" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.10.tgz#9350765fdf3c49bf78fac7673354fa58fa95daa4" + integrity sha512-BcpSzUVygHMOnp9uG5rfPzTOCb0GAHQkqtUQx8j1oMNF9A1Q8hziOOhiM4bdICpmrBIU85BE64RD5XGYsVQZNA== dependencies: css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.2" + cssnano-utils "^3.0.0" + postcss-calc "^8.2.0" + postcss-colormin "^5.2.3" postcss-convert-values "^5.0.2" postcss-discard-comments "^5.0.1" postcss-discard-duplicates "^5.0.1" postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" + postcss-discard-overridden "^5.0.2" postcss-merge-longhand "^5.0.4" - postcss-merge-rules "^5.0.3" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.2" - postcss-minify-selectors "^5.1.0" + postcss-merge-rules "^5.0.4" + postcss-minify-font-values "^5.0.2" + postcss-minify-gradients "^5.0.4" + postcss-minify-params "^5.0.3" + postcss-minify-selectors "^5.1.1" postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" + postcss-normalize-display-values "^5.0.2" + postcss-normalize-positions "^5.0.2" + postcss-normalize-repeat-style "^5.0.2" + postcss-normalize-string "^5.0.2" + postcss-normalize-timing-functions "^5.0.2" + postcss-normalize-unicode "^5.0.2" postcss-normalize-url "^5.0.4" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" + postcss-normalize-whitespace "^5.0.2" + postcss-ordered-values "^5.0.3" postcss-reduce-initial "^5.0.2" - postcss-reduce-transforms "^5.0.1" + postcss-reduce-transforms "^5.0.2" postcss-svgo "^5.0.3" postcss-unique-selectors "^5.0.2" -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== +cssnano-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.0.0.tgz#c0b9fcd6e4f05c5155b07e9ab11bf94b97163057" + integrity sha512-Pzs7/BZ6OgT+tXXuF12DKR8SmSbzUeVYCtMBbS8lI0uAm3mrYmkyqCXXPsQESI6kmLfEVBppbdVY/el3hg3nAA== -cssnano@5.0.14: - version "5.0.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" - integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== +cssnano@5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.15.tgz#8779eaf60e3665e6a12687c814d375cc9f78db76" + integrity sha512-ppZsS7oPpi2sfiyV5+i+NbB/3GtQ+ab2Vs1azrZaXWujUSN4o+WdTxlCZIMcT9yLW3VO/5yX3vpyDaQ1nIn8CQ== dependencies: - cssnano-preset-default "^5.1.9" + cssnano-preset-default "^5.1.10" lilconfig "^2.0.3" yaml "^1.10.2" @@ -2040,24 +2000,25 @@ csstype@^2.6.8: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== -cypress@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.0.tgz#727c20b4662167890db81d5f6ba615231835b17d" - integrity sha512-Jn26Tprhfzh/a66Sdj9SoaYlnNX6Mjfmj5PHu2a7l3YHXhrgmavM368wjCmgrxC6KHTOv9SpMQGhAJn+upDViA== +cypress@9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.3.1.tgz#8116f52d49d6daf90a91e88f3eafd940234d2958" + integrity sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" - bluebird "3.7.2" + bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -2107,11 +2068,6 @@ date-fns@2.28.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== -dateformat@4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.5.1.tgz#c20e7a9ca77d147906b6dc2261a8be0a5bd2173c" - integrity sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q== - dayjs@^1.10.4: version "1.10.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" @@ -2367,7 +2323,7 @@ enhanced-resolve@^5.8.3: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5, enquirer@^2.3.6: +enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -2507,38 +2463,37 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" -eslint-plugin-import@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" + tsconfig-paths "^3.12.0" -eslint-plugin-vue@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.2.0.tgz#b404bc10e3f43b2b7aad4ebb3b38090a58040202" - integrity sha512-cLIdTuOAMXyHeQ4drYKcZfoyzdwdBpH279X8/N0DgmotEI9yFKb5O/cAgoie/CkQZCH/MOmh0xw/KEfS90zY2A== +eslint-plugin-vue@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.3.0.tgz#2ae4f915ed3541a58c4a4c1145c1e60b86aa7e85" + integrity sha512-IIuLHw4vQxGlHcoP2dG6t/2OVdQf2qoyAzEGAxreU1afZOHGA7y3TWq8I+r3ZA6Wjs6xpeUWGHlT31QGr9Rb5g== dependencies: eslint-utils "^3.0.0" natural-compare "^1.4.0" @@ -2591,10 +2546,15 @@ eslint-visitor-keys@^3.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== -eslint@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.5.0.tgz#ddd2c1afd8f412036f87ae2a063d2aa296d3175f" - integrity sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg== +eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" + integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== dependencies: "@eslint/eslintrc" "^1.0.5" "@humanwhocodes/config-array" "^0.9.2" @@ -2603,12 +2563,11 @@ eslint@8.5.0: cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" eslint-scope "^7.1.0" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.1.0" - espree "^9.2.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2616,7 +2575,7 @@ eslint@8.5.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -2627,9 +2586,7 @@ eslint@8.5.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" @@ -2653,6 +2610,15 @@ espree@^9.2.0: acorn-jsx "^5.3.1" eslint-visitor-keys "^3.1.0" +espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.1.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -3182,6 +3148,11 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -3299,12 +3270,17 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb-keyval@5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.3.tgz#6ef5dff371897c23f144322dc6374eadd6a345d9" - integrity sha512-N9HbCK/FaXSRVI+k6Xq4QgWxbcZRUv+SfG1y7HJ28JdV8yEJu6k+C/YLea7npGckX2DQJeEVuMc4bKOBeU/2LQ== +idb-keyval@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" + integrity sha512-u/qHZ75rlD3gH+Zah8dAJVJcGW/RfCnfNrFkElC5RpRCnpsCXXhqjVk+6MoVKJ3WhmNbRYdI6IIVP88e+5sxGw== dependencies: - safari-14-idb-fix "^1.0.4" + safari-14-idb-fix "^3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^4.0.6: version "4.0.6" @@ -3321,6 +3297,11 @@ ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" @@ -3727,7 +3708,7 @@ js-yaml@4.0.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1, js-yaml@^3.14.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -3877,10 +3858,10 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -katex@0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.1.tgz#cf4ce2fa1257c3279cc7a7fe0c8d1fab40800893" - integrity sha512-KIk+gizli0gl1XaJlCYS8/donGMbzXYTka6BbH3AgvDJTOwyDY4hJ+YmzJ1F0y/3XzX5B9ED8AqB2Hmn2AZ0uA== +katex@0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.2.tgz#c05ece41ab497597b17abca2cecde3e4c0127f9d" + integrity sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ== dependencies: commander "^8.0.0" @@ -4069,10 +4050,10 @@ map-stream@~0.1.0: resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= -matter-js@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.17.1.tgz#b30ac4c708116258fbcaacd2efd8a94e34a91c7f" - integrity sha512-pSquoENJgvSAlQGcA0s5UkmEohGXZaUww2g3B6qG87x0iEcVf+aigMXn5UkFPdnh6w3B+C4vXSLaYqhHwKrOLA== +matter-js@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.18.0.tgz#083ced04eb6768f7664dc7ca8948a10e46ad3ed6" + integrity sha512-/ZVem4WygUnbmo/iE4oHZpZS97btfBtYy5Iwn1396vUZU7YhgVEN8J4UWwfZwY1ZqoTYlPgjvSw9WXauuXL0mg== mdn-data@2.0.14: version "2.0.14" @@ -4104,10 +4085,10 @@ merge@^2.1.0: resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -mfm-js@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.20.0.tgz#3afdcd7959461fd825aa8af9b9e8a57cdbddc290" - integrity sha512-1+3tV3nWUKQNh/ztX3wXu5iLBtdsg6q3wUhl+XyOhc2H3sQdG+sih/w2c0nR9TIawjN+Z1/pvgGzxMJHfmKQmA== +mfm-js@0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" + integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== dependencies: twemoji-parser "13.1.x" @@ -4158,10 +4139,10 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -misskey-js@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.10.tgz#f305dd37cecfbaeb7a277d5e0c769ca12c6eb9a6" - integrity sha512-2rdqFrCOwggMKYitsUPyupesqCNpNooqEHQQRfdjttbhiqLbNFJE1UuWQ04ffmiJ08UJt+1ZN2kVAYNEN3HRsg== +misskey-js@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.13.tgz#03a4e469186e28752d599dc4093519eb64647970" + integrity sha512-kBdJdfe281gtykzzsrN3IAxWUQIimzPiJGyKWf863ggWJlWYVPmP9hTFlX2z8oPOaypgVBPEPHyw/jNUdc2DbQ== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" @@ -4220,7 +4201,7 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mylas@^2.1.4: +mylas@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800" integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ== @@ -4328,7 +4309,7 @@ oauth@0.9.15: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -4591,9 +4572,9 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -"photoswipe@git://github.com/dimsemenov/photoswipe#v5-beta": +"photoswipe@git+https://github.com/dimsemenov/photoswipe#v5-beta": version "5.1.7" - resolved "git://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7" + resolved "git+https://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7" picocolors@^1.0.0: version "1.0.0" @@ -4610,13 +4591,6 @@ pify@^2.2.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" - pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -4624,6 +4598,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + pngjs@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" @@ -4637,18 +4616,18 @@ portscanner@2.2.0: async "^2.6.0" is-number-like "^1.0.3" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== +postcss-calc@^8.2.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.2.tgz#9706e7399e8ec8b61a47830dcf1f21391af23373" + integrity sha512-B5R0UeB4zLJvxNt1FVCaDZULdzsKLPc6FhjFJ+xwFiq7VG4i9cuaJLxVjNtExNK8ocm3n2o4unXXLiVX1SCqxA== dependencies: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" -postcss-colormin@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" - integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== +postcss-colormin@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.3.tgz#da7fb80e81ad80d2867ea9e38672a892add5df15" + integrity sha512-dra4xoAjub2wha6RUXAgadHEn2lGxbj8drhFcIGLOMn914Eu7DkPUurugDXgstwttCYkJtZ/+PkWRWdp3UHRIA== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" @@ -4677,10 +4656,10 @@ postcss-discard-empty@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-discard-overridden@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.2.tgz#e6f51d83e66feffcf05ed94c4ad20b814d0aab5f" + integrity sha512-+56BLP6NSSUuWUXjRgAQuho1p5xs/hU5Sw7+xt9S3JSg+7R6+WMGnJW7Hre/6tTuZ2xiXMB42ObkiZJ2hy/Pew== postcss-loader@6.2.1: version "6.2.1" @@ -4699,46 +4678,46 @@ postcss-merge-longhand@^5.0.4: postcss-value-parser "^4.1.0" stylehacks "^5.0.1" -postcss-merge-rules@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" - integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== +postcss-merge-rules@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.4.tgz#a50640fd832380f322bd2861a9b33fbde4219f9b" + integrity sha512-yOj7bW3NxlQxaERBB0lEY1sH5y+RzevjbdH4DBJurjKERNpknRByFNdNe+V72i5pIZL12woM9uGdS5xbSB+kDQ== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" + cssnano-utils "^3.0.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== +postcss-minify-font-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.2.tgz#4603e956d85cd0719156e2b3eb68e3cd2f917092" + integrity sha512-R6MJZryq28Cw0AmnyhXrM7naqJZZLoa1paBltIzh2wM7yb4D45TLur+eubTQ4jCmZU9SGeZdWsc5KcSoqTMeTg== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== +postcss-minify-gradients@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.4.tgz#f13146950513f5a201015306914e3c76d10b591d" + integrity sha512-RVwZA7NC4R4J76u8X0Q0j+J7ItKUWAeBUJ8oEEZWmtv3Xoh19uNJaJwzNpsydQjk6PkuhRrK+YwwMf+c+68EYg== dependencies: colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" - integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== +postcss-minify-params@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.3.tgz#9f933d37098ef1dcf007e159a47bb2c1cf06989d" + integrity sha512-NY92FUikE+wralaiVexFd5gwb7oJTIDhgTNeIw89i1Ymsgt4RWiPXfz3bg7hDy4NL6gepcThJwOYNtZO/eNi7Q== dependencies: alphanum-sort "^1.0.2" browserslist "^4.16.6" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== +postcss-minify-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.1.tgz#20ae03b411f7fb397451e3d7d85b989f944b871c" + integrity sha512-TOzqOPXt91O2luJInaVPiivh90a2SIK5Nf1Ea7yEIM/5w+XA5BGrZGUSW8aEx9pJ/oNj7ZJBhjvigSiBV+bC1Q== dependencies: alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" @@ -4776,51 +4755,48 @@ postcss-normalize-charset@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== +postcss-normalize-display-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.2.tgz#8b5273c6c7d0a445e6ef226b8a5bb3204a55fb99" + integrity sha512-RxXoJPUR0shSjkMMzgEZDjGPrgXUVYyWA/YwQRicb48H15OClPuaDR7tYokLAlGZ2tCSENEN5WxjgxSD5m4cUw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== +postcss-normalize-positions@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.2.tgz#799fa494b352a5da183be8f050024af6d92fa29c" + integrity sha512-tqghWFVDp2btqFg1gYob1etPNxXLNh3uVeWgZE2AQGh6b2F8AK2Gj36v5Vhyh+APwIzNjmt6jwZ9pTBP+/OM8g== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== +postcss-normalize-repeat-style@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.2.tgz#fd9bddba3e6fd5f5d95c18dfb42a09ecd563adea" + integrity sha512-/rIZn8X9bBzC7KvY4iKUhXUGW3MmbXwfPF23jC9wT9xTi7kAvgj8sEgwxjixBmoL6MVa4WOgxNz2hAR6wTK8tw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== +postcss-normalize-string@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.2.tgz#1b2bbf91526f61266f28abf7f773e4136b2c4bd2" + integrity sha512-zaI1yzwL+a/FkIzUWMQoH25YwCYxi917J4pYm1nRXtdgiCdnlTkx5eRzqWEC64HtRa06WCJ9TIutpb6GmW4gFw== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== +postcss-normalize-timing-functions@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.2.tgz#db4f4f49721f47667afd1fdc5edb032f8d9cdb2e" + integrity sha512-Ao0PP6MoYsRU1LxeVUW740ioknvdIUmfr6uAA3xWlQJ9s69/Tupy8qwhuKG3xWfl+KvLMAP9p2WXF9cwuk/7Bg== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== +postcss-normalize-unicode@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.2.tgz#c4db89a0116066716b9e9fcb6444ce63178f5ced" + integrity sha512-3y/V+vjZ19HNcTizeqwrbZSUsE69ZMRHfiiyLAJb7C7hJtYmM4Gsbajy7gKagu97E8q5rlS9k8FhojA8cpGhWw== dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" postcss-normalize-url@^5.0.4: version "5.0.4" @@ -4830,20 +4806,20 @@ postcss-normalize-url@^5.0.4: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== +postcss-normalize-whitespace@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.2.tgz#92c5eaffe5255b5c43fca0baf19227e607c534db" + integrity sha512-CXBx+9fVlzSgbk0IXA/dcZn9lXixnQRndnsPC5ht3HxlQ1bVh77KQDL1GffJx1LTzzfae8ftMulsjYmO2yegxA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== +postcss-ordered-values@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.3.tgz#d80a8565f2e21efe8a06abacd60629a783bbcf54" + integrity sha512-T9pDS+P9bWeFvqivXd5ACzQmrCmHjv3ZP+djn8E1UZY7iK79pFSm7i3WbKw2VSmFmdbMm8sQ12OPcNpzBo3Z2w== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" postcss-reduce-initial@^5.0.2: version "5.0.2" @@ -4853,13 +4829,12 @@ postcss-reduce-initial@^5.0.2: browserslist "^4.16.6" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== +postcss-reduce-transforms@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.2.tgz#9242758629f9ad4d90312eadbc921259d15bee4d" + integrity sha512-25HeDeFsgiPSUx69jJXZn8I06tMxLQJJNF5h7i9gsUg8iP4KOOJ8EX8fj3seeoLt3SLU2YDD6UPnDYVGUO7DEA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.4" @@ -4947,10 +4922,10 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.26.0.tgz#16881b594828bb6b45296083a8cbab46b0accd47" + integrity sha512-HUoH9C5Z3jKkl3UunCyiD5jwk0+Hz0fIgQ2nbwU2Oo/ceuTAQAg+pPVnfdt2TJWRVLcxKh9iuoYDUSc8clb5UQ== private-ip@2.3.3: version "2.3.3" @@ -4962,11 +4937,6 @@ private-ip@2.3.3: is-ip "^3.1.0" netmask "^2.0.2" -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-limit@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/promise-limit/-/promise-limit-2.7.0.tgz#eb5737c33342a030eaeaecea9b3d3a93cb592b26" @@ -5323,10 +5293,10 @@ s-age@1.1.2: resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== -safari-14-idb-fix@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19" - integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA== +safari-14-idb-fix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" + integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== safe-buffer@5.2.1: version "5.2.1" @@ -5351,10 +5321,10 @@ sass-loader@12.4.0: klona "^2.0.4" neo-async "^2.6.2" -sass@1.45.1: - version "1.45.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.1.tgz#fa03951f924d1ba5762949567eaf660e608a1ab0" - integrity sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA== +sass@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.0.tgz#65ec1b1d9a6bc1bae8d2c9d4b392c13f5d32c078" + integrity sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -5393,7 +5363,7 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -5825,10 +5795,10 @@ textarea-caret@3.1.0: resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== -three@0.117.1: - version "0.117.1" - resolved "https://registry.yarnpkg.com/three/-/three-0.117.1.tgz#a49bcb1a6ddea2f250003e42585dc3e78e92b9d3" - integrity sha512-t4zeJhlNzUIj9+ub0l6nICVimSuRTZJOqvk3Rmlu+YGdTOJ49Wna8p7aumpkXJakJfITiybfpYE1XN1o1Z34UQ== +three@0.136.0: + version "0.136.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.136.0.tgz#b1504db021b46398ef468aa7849f3dcabb814f50" + integrity sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A== throttle-debounce@3.0.1: version "3.0.1" @@ -5915,19 +5885,19 @@ ts-node@10.4.0: make-error "^1.1.1" yn "3.1.1" -tsc-alias@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.4.2.tgz#80bac036f8d163a34a734a6772a57291313e084f" - integrity sha512-6OyipS3p1E7679vk9c55zfQm9JtBAMK1MfgoKXucT4oyTL7TdFgQRcRT/urdWC9JaAtahr3aYqyEI22T82UFuA== +tsc-alias@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.5.0.tgz#bc26f8dccf96e4ea350adc3f64ad3d2325cad967" + integrity sha512-Pb3y7ZjULKFHEV2US5dS58/hV76sE9Sn5iehiPjYqHcm/lx4eCGAJYoSmrVXQMPX+6baTnDFJD0MGOyqn94dIg== dependencies: chokidar "^3.5.2" - commander "^8.2.0" + commander "^8.3.0" find-node-modules "^2.1.2" globby "^11.0.4" - mylas "^2.1.4" + mylas "^2.1.6" normalize-path "^3.0.0" -tsconfig-paths@3.12.0: +tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== @@ -5937,26 +5907,11 @@ tsconfig-paths@3.12.0: minimist "^1.2.0" strip-bom "^3.0.0" -tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.0" - strip-bom "^3.0.0" - tslib@^1.8.1, tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslib@^1.9.3: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" @@ -6020,10 +5975,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== unbox-primitive@^1.0.1: version "1.0.1" @@ -6162,10 +6117,10 @@ vue-eslint-parser@^8.0.1: lodash "^4.17.21" semver "^7.3.5" -vue-loader@16.8.3: - version "16.8.3" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087" - integrity sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA== +vue-loader@17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.0.tgz#2eaa80aab125b19f00faa794b5bd867b17f85acb" + integrity sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg== dependencies: chalk "^4.1.0" hash-sum "^2.0.0" @@ -6198,16 +6153,16 @@ vue-svg-loader@0.17.0-beta.2: semver "^7.3.2" svgo "^1.3.2" -vue@3.2.26: - version "3.2.26" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.26.tgz#5db575583ecae495c7caa5c12fd590dffcbb763e" - integrity sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg== +vue@3.2.29: + version "3.2.29" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a" + integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q== dependencies: - "@vue/compiler-dom" "3.2.26" - "@vue/compiler-sfc" "3.2.26" - "@vue/runtime-dom" "3.2.26" - "@vue/server-renderer" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-dom" "3.2.29" + "@vue/compiler-sfc" "3.2.29" + "@vue/runtime-dom" "3.2.29" + "@vue/server-renderer" "3.2.29" + "@vue/shared" "3.2.29" vuedraggable@4.0.1: version "4.0.1" @@ -6294,10 +6249,10 @@ webpack-sources@^3.2.2: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== -webpack@5.65.0: - version "5.65.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be" - integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw== +webpack@5.66.0: + version "5.66.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.66.0.tgz#789bf36287f407fc92b3e2d6f978ddff1bfc2dbb" + integrity sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.50" @@ -6313,7 +6268,7 @@ webpack@5.65.0: eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -6450,10 +6405,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== +ws@8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== xml-js@^1.6.11: version "1.6.11" @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@cypress/request@^2.88.7": - version "2.88.7" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.7.tgz#386d960ab845a96953723348088525d5a75aaac4" - integrity sha512-FTULIP2rnDJvZDT9t6B4nSfYR40ue19tVmv3wUcY05R9/FPCoMl1nAPJkzWzBCo7ltVn5ThQTbxiMoGBN7k0ig== +"@cypress/request@^2.88.10": + version "2.88.10" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" + integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -14,8 +14,7 @@ extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" + http-signature "~1.3.6" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" @@ -68,29 +67,30 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" "@sideway/address@^4.1.0": @@ -125,10 +125,10 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -181,10 +181,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.9.tgz#b97c057e6138adb7b720df2bd0264b03c9f504fd" integrity sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" - integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -227,48 +227,48 @@ dependencies: "@types/node" "*" -"@typescript-eslint/parser@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.4.0.tgz#3aa83ce349d66e39b84151f6d5464928044ca9e3" - integrity sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@typescript-eslint/scope-manager" "5.4.0" - "@typescript-eslint/types" "5.4.0" - "@typescript-eslint/typescript-estree" "5.4.0" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz#aaab08415f4a9cf32b870c7750ae8ba4607126a1" - integrity sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/types" "5.4.0" - "@typescript-eslint/visitor-keys" "5.4.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/types@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.4.0.tgz#b1c130f4b381b77bec19696c6e3366f9781ce8f2" - integrity sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA== +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== -"@typescript-eslint/typescript-estree@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz#fe524fb308973c68ebeb7428f3b64499a6ba5fc0" - integrity sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA== +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== dependencies: - "@typescript-eslint/types" "5.4.0" - "@typescript-eslint/visitor-keys" "5.4.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz#09bc28efd3621f292fe88c86eef3bf4893364c8c" - integrity sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg== +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/types" "5.4.0" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" aggregate-error@^3.0.0: @@ -279,16 +279,6 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.5.5: - version "6.12.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" - integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -584,6 +574,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -631,7 +626,7 @@ blob-util@^2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@3.7.2: +bluebird@3.7.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -690,6 +685,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -827,15 +830,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -971,7 +973,7 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -1115,24 +1117,25 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" -cypress@9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.1.0.tgz#5d23c1b363b7d4853009c74a422a083a8ad2601c" - integrity sha512-fyXcCN51vixkPrz/vO/Qy6WL3hKYJzCQFeWofOpGOFewVVXrGfmfSOGFntXpzWBXsIwPn3wzW0HOFw51jZajNQ== +cypress@9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.3.1.tgz#8116f52d49d6daf90a91e88f3eafd940234d2958" + integrity sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q== dependencies: - "@cypress/request" "^2.88.7" + "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" - bluebird "3.7.2" + bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -1389,11 +1392,6 @@ esprima@^2.6.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - event-stream@=3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" @@ -1557,11 +1555,6 @@ fast-glob@^3.1.1: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -1978,19 +1971,6 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -2068,14 +2048,14 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== dependencies: assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + jsprim "^2.0.2" + sshpk "^1.14.1" human-signals@^1.1.1: version "1.1.1" @@ -2087,6 +2067,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.4: version "5.1.9" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" @@ -2436,21 +2421,13 @@ js-levenshtein@^1.1.6: resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== -js-yaml@4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -2464,20 +2441,15 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -2498,14 +2470,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" just-debounce@^1.0.0: @@ -3139,6 +3111,11 @@ plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -3947,10 +3924,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== +sshpk@^1.14.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -4283,10 +4260,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" - integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== unc-path-regex@^0.1.2: version "0.1.2" |