diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index a29b31a045bda18e89f20cdcdfeff1caf7276f0b..0000000000000000000000000000000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: 2.1 - -executors: - docker: - working_directory: /tmp/workspace - docker: - - image: docker:latest - -jobs: - docker: - parameters: - with_deploy: - type: boolean - default: false - executor: docker - steps: - - checkout - - setup_remote_docker: - version: 19.03.13 - - run: - name: Build - command: | - docker build -t misskey/misskey . - - when: - condition: <<parameters.with_deploy>> - steps: - - run: - name: Deploy - command: | - if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ] - then - apk update && apk add jq - docker tag misskey/misskey misskey/misskey:$(cat package.json | jq -r .version) - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD - docker push -a misskey/misskey - else - echo -e '\033[0;33mAborted deploying to Docker Hub\033[0;39m' - fi - -workflows: - version: 2 - docker: - jobs: - - docker: - name: auto-build - with_deploy: true - filters: - branches: - only: master diff --git a/.circleci/misskey/default.yml b/.circleci/misskey/default.yml deleted file mode 100644 index ae18a841bdff9b7f2690fd2f2ab9f768556a8cad..0000000000000000000000000000000000000000 --- a/.circleci/misskey/default.yml +++ /dev/null @@ -1,12 +0,0 @@ -url: 'http://misskey.local' -port: 8080 -db: - host: localhost - port: 5432 - db: test-misskey - user: postgres - pass: '' -redis: - host: localhost - port: 6379 -id: aid diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 2a41c12c7ecd2c9b37b2aeeb4417b38eeecad8e0..0000000000000000000000000000000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,38 +0,0 @@ -# PATH OWNERS -/.autogen/ @acid-chicken -/.circleci/ @syuilo @acid-chicken -/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki -# /.config/mongo_initdb_example.js @khws4v1 -/.github/ @syuilo @AyaMorisawa @acid-chicken -/.vscode/ @acid-chicken -/assets/ @syuilo # @tamaina -/docs/ @syuilo -/docs/*.en.md @AyaMorisawa # @skid9000 -# /docs/*.fr.md @BoFFire -# /docs/docker.*.md @khws4v1 -/locales/ @syuilo -/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki -# /src/crypto_key.cc @akihikodaki -# /src/crypto_key.d.ts @akihikodaki -/.dockerignore @syuilo # @khws4v1 -/.editorconfig @syuilo @AyaMorisawa -/.eslintrc @syuilo -/.gitattributes @syuilo -/.gitignore @syuilo -/.npmrc @syuilo -/.vsls.json @AyaMorisawa -/CHANGELOG.md @syuilo -/CODE_OF_CONDUCT.md @syuilo -/CONTRIBUTING.md @syuilo -/Dockerfile @syuilo @AyaMorisawa @acid-chicken # @khws4v1 -/LICENSE @syuilo -/README.md @syuilo @AyaMorisawa @acid-chicken # @nikhiljha -# /binding.gyp @akihikodaki -/crowdin.yml @syuilo -# /docker-compose.yml @khws4v1 -/gulpfile.ts @syuilo @AyaMorisawa -/jsconfig.json @syuilo @AyaMorisawa -/package.json @syuilo @AyaMorisawa -/tsconfig.json @syuilo @AyaMorisawa -/tslint.json @syuilo @AyaMorisawa -/webpack.config.ts @syuilo @AyaMorisawa diff --git a/.circleci/misskey/test.yml b/.github/misskey/test.yml similarity index 100% rename from .circleci/misskey/test.yml rename to .github/misskey/test.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000000000000000000000000000000000..0b3bbc186f6f87803ae57eb30ff45bf373f63f0b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +name: Lint + +on: + push: + branches: + - master + - develop + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: yarn install + - run: yarn lint diff --git a/.github/workflows/nodejs.yml b/.github/workflows/test.yml similarity index 55% rename from .github/workflows/nodejs.yml rename to .github/workflows/test.yml index a91572ad787003a3d39a980e7f8204458c71c374..045d2098025450cfdbf784e27193609a46109b49 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,5 @@ -name: Node.js CI +name: Test + on: push: branches: @@ -7,12 +8,12 @@ on: pull_request: jobs: - build_and_test: + mocha: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x, 16.x] + node-version: [16.x] services: postgres: @@ -44,16 +45,43 @@ jobs: - name: Build run: yarn build - name: Test - run: yarn test + run: yarn mocha - lint: + e2e: runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + services: + postgres: + image: postgres:12.2-alpine + ports: + - 54312:5432 + env: + POSTGRES_DB: test-misskey + POSTGRES_HOST_AUTH_METHOD: trust + redis: + image: redis:4.0-alpine + ports: + - 56312:6379 + steps: - uses: actions/checkout@v2 with: submodules: true - - uses: actions/setup-node@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 with: - node-version: 12.x - - run: yarn install - - run: yarn lint + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: yarn install + - name: Check yarn.lock + run: git diff --exit-code yarn.lock + - name: Copy Configure + run: cp test/test.yml .config + - name: Build + run: yarn build + - name: Test + run: yarn e2e diff --git a/CHANGELOG.md b/CHANGELOG.md index 13dccdc38b8ededd844eda02cae7c85c361b27a6..6847049bb55a6b0be959ff9637564918727b502b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ --> +## 12.95.0 (2021/10/31) + +### Improvements +- スレッドミュート機能 + +### Bugfixes +- リレーå‘ã‘ã®ActivityãŒä¸€éƒ¨å®Ÿè£…ã§é™¤å¤–ã•ã‚Œã¦ã—ã¾ã†ã“ã¨ãŒã‚ã‚‹ã®ã‚’ä¿®æ£ +- 削除ã—ãŸãƒŽãƒ¼ãƒˆã‚„ユーザーãŒãƒªãƒ¢ãƒ¼ãƒˆã‹ã‚‰å‚ç…§ã•ã‚Œã‚‹ã¨å¾©æ´»ã™ã‚‹ã“ã¨ãŒã‚ã‚‹ã®ã‚’ä¿®æ£ +- クライアント: ページ編集時ã®ãƒ‰ãƒãƒƒãƒ—ダウンメニューãªã©ãŒå‹•ä½œã—ãªã„å•é¡Œã‚’ä¿®æ£ +- クライアント: コントãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ«ã®ã‚«ã‚¹ã‚¿ãƒ 絵文å—タブãŒåˆ‡ã‚Šæ›¿ã‚らãªã„よã†ã«è¦‹ãˆã‚‹å•é¡Œã‚’ä¿®æ£ +- API: ãƒ¦ãƒ¼ã‚¶ãƒ¼æƒ…å ±ã® hasUnreadChannel ãŒå¸¸ã« false ã«ãªã£ã¦ã„ã‚‹å•é¡Œã‚’ä¿®æ£ + ## 12.94.1 (2021/10/25) ### Improvements diff --git a/README.md b/README.md index ce0aa0941750b8d5254eb21b03a9ed55e79ed0b8..a60f71cacfea14e65db72cc11f6aacb6c9ad3677 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ <div align="center"> -[](https://circleci.com/gh/misskey-dev/misskey) [](https://david-dm.org/misskey-dev/misskey) [](http://makeapullrequest.com) [](https://github.com/humanetech-community/awesome-humane-tech) diff --git a/cypress.json b/cypress.json index f2c02689e492ffcf3d0016f712336ad885611b37..e858e480b0a1bb5151873cd368e7d152b1ce47b2 100644 --- a/cypress.json +++ b/cypress.json @@ -1,3 +1,3 @@ { - "baseUrl": "http://localhost" + "baseUrl": "http://localhost:61812" } diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js index 182f70ff68dffe34abfb3195c80f2dd10dafbaba..a754f41b9832a8a0871fdf99e32b07d1d080eae6 100644 --- a/cypress/integration/basic.js +++ b/cypress/integration/basic.js @@ -128,7 +128,8 @@ describe('After user signup', () => { cy.get('[data-cy-signin-username] input').type('alice'); cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - cy.contains('アカウントãŒå‡çµã•ã‚Œã¦ã„ã¾ã™'); + // TODO: cypressã«ãƒ–ラウザã®è¨€èªžæŒ‡å®šã§ãる機能ãŒå®Ÿè£…ã•ã‚Œæ¬¡ç¬¬è‹±èªžã®ã¿ãƒ†ã‚¹ãƒˆã™ã‚‹ã‚ˆã†ã«ã™ã‚‹ + cy.contains(/アカウントãŒå‡çµã•ã‚Œã¦ã„ã¾ã™|This account has been suspended due to/gi); }); }); diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index d7f038393fc71e7d57853e455d9388e13d7a1b82..71626224a7c281835ccd17d85ba17c6b1aa5472e 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1,5 +1,6 @@ --- _lang_: "العربية" +headlineMisskey: "شبكة مرتبطة بالملاØظات" introMisskey: "اهلا بك! ميسكي هو منصة تدوين مصغر لا مركزية ومÙتوØØ© المصدر.\nيمكنك مشاركة \"ملاØظات\" عن ما يجري Øولك، وإخبار الجميع عن Ù†Ùسك 📡\nØªØ³Ù…Ø Ù„Ùƒ \"الانÙعالات\" بتعبير عن شعورك Øول ملاØظات الآخرين ðŸ‘\nاكتش٠عالمًا جديدًا 🚀" monthAndDay: "{day}/{month}" search: "البØØ«" @@ -12,6 +13,7 @@ ok: " Øسناً" gotIt: "ÙÙ‡Ùمت" cancel: " إلغاء" enterUsername: "أدخÙÙ„ إسم مسخدم" +renotedBy: "أعاد {user} نشر ملاØظة" noNotes: "لم يتم العثور على أية ملاØظات" noNotifications: "ليس هناك أية اشعارات" instance: "مثيل الخادم" @@ -26,7 +28,7 @@ login: "Ù„Ùج" loggingIn: "جار٠تسجيل الدخول" logout: "الخروج" signup: "أنشئ Øسابًا" -uploading: "عملية الإرسال جارية" +uploading: "يرÙع..." save: "ØÙظ" users: "المستخدمون" addUser: "اضاÙØ© مستخدم" @@ -37,7 +39,7 @@ favorited: "تمت الإضاÙØ© إلى المÙضلة." alreadyFavorited: "تمت إضاÙته بالÙعل إلى المÙضلة." cantFavorite: "تعذرت الإضاÙØ© إلى المÙضلة." pin: "دبّسها على الصÙØØ© الشخصية" -unpin: "ألغ تثبيتها من ملÙÙƒ الشخصي" +unpin: "ألغ تدبيسها من ملÙÙƒ الشخصي" copyContent: "انسخ المØتوى" copyLink: "انسخ الرابط" delete: "ØØ°Ù" @@ -63,13 +65,14 @@ files: "الملÙات" download: "تنزيل" driveFileDeleteConfirm: "أمتأكد من Øذ٠مل٠{name}ØŸ كل الملاØظات المÙرÙÙ‚ بها هذا المل٠ستØØ°Ù." unfollowConfirm: "أمتأكد من إلغاء متابعة {name}ØŸ" +exportRequested: "قد تستغرق عملية التصدير بعض الوقت. بمجرد الانتهاء ستتم إضاÙØ© المل٠الناتج إلى قرص التخزين." importRequested: "يستغرق الاستيراد بعض الوقت" lists: "القوائم" noLists: "ليس لديك أية قائمة" note: "ملاØظة" notes: "الملاØظات" following: "المتابَعون" -followers: "المتابÙعين" +followers: "المتابÙعون" followsYou: "يتابعك" createList: "إنشاء قائمة" manageLists: "إدارة القوائم" @@ -82,7 +85,7 @@ serverIsDead: "الخادم لا يستجيب، Øاول بعد قليل" youShouldUpgradeClient: "Øدّث الصÙØØ© لعرضها." enterListName: "اسم القائمة" privacy: "الخصوصية" -makeFollowManuallyApprove: "القبول يدويا طلبات الإشتراك" +makeFollowManuallyApprove: "قبول طلبات الإشتراك يدويا" defaultNoteVisibility: "مدى الرؤية الاÙتراضي" follow: "تابÙع" followRequest: "طلب اشتراك" @@ -90,7 +93,11 @@ followRequests: "طلبات الإشتراك" unfollow: "إلغاء الاشتراك" followRequestPending: "طلبات الإشتراك المعلّقة" enterEmoji: "أدخل إيموجي" +renote: "أعد النشر" unrenote: "إلغاء مشاركة الملاØظة" +renoted: "Ø£Ùعيد نشره" +cantRenote: "لا يمكن إعادة نشر الملاØظة" +cantReRenote: "لا يمكنك إعادة نشر ملاØظة معاد نشرها" quote: "اقتبس" pinnedNote: "ملاØظة مدبسة" pinned: "دبّسها على الصÙØØ© الشخصية" @@ -99,9 +106,12 @@ clickToShow: "اضغط للعرض" sensitive: "Ù…Øتوى Øساس" add: "إضاÙØ©" reaction: "تÙاعل" +reactionSettingDescription: "اختر التÙاعلات المÙضلة التي تريد تثبيتها ÙÙŠ منتقي التÙاعلات." +reactionSettingDescription2: "اسØب لإعادة التنظيم ØŒ انقر للØØ°Ù ØŒ استخدم \"+\" للإضاÙØ©." rememberNoteVisibility: "تذكر إعدادت مدى رؤية الملاØظات" attachCancel: "أزل المرÙÙ‚" markAsSensitive: "علّمه كمØتوى Øساس" +unmarkAsSensitive: "ألغ تعيينه كمØتوى Øساس" enterFileName: "ادخل اسم الملÙ" mute: "اكتم" unmute: "إلغاء الكتم" @@ -111,8 +121,12 @@ suspend: "علÙÙ‚" unsuspend: "ألغ التعليق" blockConfirm: "أمتأكد من Øجب هذا الØساب؟" unblockConfirm: "أمتأكد من إلغاء Øجب هذا الØساب؟" +suspendConfirm: "أمتأكد من تعليق الØساب؟" +unsuspendConfirm: "أمتأكد من إلغاء تعليق؟" selectList: "اختر قائمة" selectAntenna: "اختر هوائيًا" +selectWidget: "اختر ودجة" +editWidgets: "عدّل الودجات" editWidgetsExit: "تم" customEmojis: "إيموجي مخصص" emoji: "الوجوه التعبيرية" @@ -122,6 +136,10 @@ emojiUrl: "رابط الوجه التعبيري" addEmoji: "إضاÙØ© إيموجي" settingGuide: "الإعدادات المستØسنة" cacheRemoteFiles: "خزن مؤقتا الملÙات البعيدة" +flagAsBot: "علّمه ÙƒØساب آلي" +flagAsBotDescription: "Ùعّل هذا الخيار إذا كان هذا الØساب ÙŠÙدار عبر برمجية. إذا ÙÙعل Ùسيكون بمثابة علامة للمطورين الآخرين لتجنب سلاسل لا متناعية من التÙاعل بين Øسابات الآلية وضبط أنظمة ميسكي للتعامل مع هذا الØساب كروبوت." +flagAsCat: "علّم هذا الØساب ÙƒØساب قط" +flagAsCatDescription: "Ùعّل هذا الخيار لوضع علامة على الØساب Ù„ØªÙˆØ¶ÙŠØ Ø£Ù†Ù‡ Øساب قط." autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الØسابات المتابَعة" addAccount: "أض٠Øساباً" loginFailed: "Ùشل الولوج" @@ -165,6 +183,9 @@ statistics: "الإØصائيات" clearQueue: "تÙريغ قائمة الإنتظار" clearQueueConfirmTitle: "أتريد Ù…Ø³Ø Ø§Ù„Ø·Ø§Ø¨ÙˆØ±ØŸ" clearCachedFiles: "Ø§Ù…Ø³Ø Ø§Ù„ØªØ®Ø²ÙŠÙ† المؤقت" +clearCachedFilesConfirm: "أتريد Øذ٠التخزين المؤقت للملÙات البعيدة؟" +blockedInstances: "المثلاء المØجوبون" +blockedInstancesDescription: "قائمة بالمثلاء التي تريد Øظرها بØيث كل نطاق ÙÙŠ سطر لوØده. بعد إدراجهم لن يتمكنوا من التÙاعل مع هذا المثيل." muteAndBlock: "تم كتمها / تم Øجبها" mutedUsers: "الØسابات التي تم كتمها" blockedUsers: "الØسابات التي تم Øظرها" @@ -191,7 +212,7 @@ usernameOrUserId: "اسم المستخدم أو معرّÙÙ‡" noSuchUser: "لم ÙŠÙعثَر على المستخدم" lookup: "البØØ«" announcements: "الإعلانات" -imageUrl: "عنوان URL للصورة" +imageUrl: "رابط الصورة" remove: "ØØ°Ù" removed: "تم ØØ°ÙÙ‡ بنجاØ" removeAreYouSure: "متأكد من أنك تريد ØØ°Ù {x}ØŸ" @@ -199,11 +220,11 @@ deleteAreYouSure: "متأكد من أنك تريد ØØ°Ù {x}ØŸ" resetAreYouSure: "هل تريد إعادة التعيين؟" saved: "تم ØÙظه" messaging: "المØادثة" -upload: "تØميل" +upload: "ارÙع" fromDrive: "من المخزن" -fromUrl: "من عنوان URL" -uploadFromUrl: "التØميل عبر URL" -uploadFromUrlDescription: "رابط المل٠المراد تØميله " +fromUrl: "عبر رابط" +uploadFromUrl: "ارÙع عبر رابط" +uploadFromUrlDescription: "رابط المل٠المراد رÙعه" uploadFromUrlRequested: "الرÙع مطلوب" uploadFromUrlMayTakeTime: "سيستغرق بعض الوقت لاتمام الرÙع " explore: "استكشاÙ" @@ -248,7 +269,7 @@ unableToDelete: "لا يمكن ØØ°ÙÙ‡" inputNewFileName: "ادخل الإسم الجديد للملÙ" inputNewFolderName: "ادخل الإسم الجديد للمجلد" hasChildFilesOrFolders: "الان المل٠غير Ùارغ. لا يمكن ØØ°ÙÙ‡" -copyUrl: "انسخ عنوان URL" +copyUrl: "انسخ الرابط" rename: "إعادة التسمية" avatar: "الصورة الرمزية" banner: "الصورة الرأسية" @@ -267,7 +288,7 @@ instanceName: "اسم مثيل الخادم" instanceDescription: "وص٠مثيل الخادم" maintainerName: "المدير" maintainerEmail: "عنوان بريد المدير الإلكتروني" -tosUrl: "عنوان URL لشروط الخدمة" +tosUrl: "رابط صÙØØ© شروط الخدمة" thisYear: "هذا العام" thisMonth: "هذا الشهر" today: "اليوم" @@ -289,10 +310,10 @@ iconUrl: "رابط الأيقونة" bannerUrl: "رابط صورة اللاÙتة" backgroundImageUrl: "رابط صورة الخلÙية" basicInfo: "المعلومات الأساسية " -pinnedUsers: "المستخدمون المثبتون" -pinnedUsersDescription: "قائمة المستخدمين المثبتين ÙÙŠ لسان \"استكشÙ\" ØŒ اجعل كل اسم مستخدم ÙÙŠ سطر لوØده." -pinnedPages: "الصÙØات المثبتة" -pinnedPagesDescription: "أدخل مسار الصÙØات التي تريد تثبيتها ÙÙŠ أعلى هذا الموقع، اجعل كل مسار ÙÙŠ سطر لوØده." +pinnedUsers: "المستخدمون المدبسون" +pinnedUsersDescription: "قائمة المستخدمين المدبسين ÙÙŠ لسان \"استكشÙ\" ØŒ اجعل كل اسم مستخدم ÙÙŠ سطر لوØده." +pinnedPages: "الصÙØات المدبسة" +pinnedPagesDescription: "أدخل مسار الصÙØات التي تريد تدبيسها ÙÙŠ أعلى هذا الموقع، اجعل كل مسار ÙÙŠ سطر لوØده." pinnedNotes: "ملاØظة مدبسة" hcaptchaSiteKey: "Ù…ÙØªØ§Ø Ø§Ù„Ù…ÙˆÙ‚Ø¹" hcaptchaSecretKey: "المÙØªØ§Ø Ø§Ù„Ø³Ø±ÙŠ" @@ -310,9 +331,11 @@ withFileAntenna: "ملاØظات تØوي ملÙات Ùقط" caseSensitive: "Øساسية Øالة الأØرÙ" withReplies: "بالردود" notesAndReplies: "الملاØظات والردود" -withFiles: "بالمرÙقات" +withFiles: "ذات مرÙقات" silence: "اكتم" +silenceConfirm: "أمتأكد من كتم هذا المستخدم؟" unsilence: "إلغاء الكتم" +unsilenceConfirm: "أمتأكد من إلغاء كتم هذا المستخدم؟" popularUsers: "المستخدمون الشائعون" recentlyUpdatedUsers: "أصØاب النشاطات الأخيرة" recentlyRegisteredUsers: "المستخدمون المنضمون Øديثًا" @@ -339,14 +362,20 @@ newPasswordIs: "كلمتك السرية الجديدة هي {password}" reduceUiAnimation: "قلص تأثيرات الواجهة" share: "شارÙÙƒ" notFound: "غير موجود" +notFoundDescription: "تعذر العثور على صÙØØ© يقود إليها هذا الرابط." +uploadFolder: "المجلد الاÙتراضي للرÙع" cacheClear: "Ù…Ø³Ø Ø°Ø§ÙƒØ±Ø© التخزين المؤقت" markAsReadAllNotifications: "وضع جميع الإشعارات كأنها مقروءة" +markAsReadAllUnreadNotes: "علّم جميع الملاØظات كمقروءة" +markAsReadAllTalkMessages: "علّم جميع الرسائل كمقروءة" help: "المساعدة" inputMessageHere: "اكتب رسالتك هنا" close: "اغلق" group: "الÙريق" groups: "الÙÙرَق" createGroup: "انشئ Ùريقًا" +ownedGroups: "مجموعات المالك" +joinedGroups: "المجموعات المنضم إليها" invites: "دعوة" groupName: "اسم الÙريق" members: "الأعضاء" @@ -360,13 +389,18 @@ next: "التالية" retype: "أعد الكتابة" noteOf: "ملاØظات {user}" inviteToGroup: "دعوة إلى Ùريق" +maxNoteTextLength: "Øد عدد المØار٠لكل ملاØظة" +quoteAttached: "اÙقتÙبسَ" noMessagesYet: "ليس هناك رسائل بعد" newMessageExists: "لقد تلقيت رسالة جديدة" +onlyOneFileCanBeAttached: "يمكنك إرÙاق مل٠واØد بالرسالة" +signinRequired: "رجاءً Ù„Ùج" invitations: "دعوة" invitationCode: "رمز الدعوة" checking: "التØقق جارÙ" available: "متوÙر" unavailable: "غير متوÙر" +usernameInvalidFormat: "يمكنك استخدام A-zØŒ a-zØŒ 0-9ØŒ _" tooShort: "قصير جدًا" tooLong: "طويل جدًا" weakPassword: "الكلمة السرية ضعيÙØ©" @@ -375,11 +409,15 @@ strongPassword: "الكلمة السرية قوية" passwordMatched: "التطابق صØÙŠØ!" passwordNotMatched: "غير متطابقتان" signinWith: "الولوج عبر {x}" +signinFailed: "Ùشل الولوج، خطأ ÙÙŠ اسم المستخدم أو كلمة المرور." or: "أو" +language: "اللغة" uiLanguage: "لغة واجهة المستخدم" +groupInvited: "دÙعيت إلى مجموعة" aboutX: "عن {x}" useOsNativeEmojis: "استخدم الإيموجيات الخاصة بنظام التشغيل" youHaveNoGroups: "لا تمتلك أية ÙÙرَق" +joinOrCreateGroup: "اØصل على دعوة لمجموعة أو أنشئ واØدة." noHistory: "السجل Ùارغ" signinHistory: "تاريخ تسجيل الدخول" doing: "انتظر Ù„Øظة" @@ -387,8 +425,10 @@ category: "الÙئات" tags: "الوسوم" docSource: "مصدر هذا المستند" createAccount: "أنشئ Øسابًا" +existingAccount: "الØسابات الموجودة" regenerate: "أعÙد التوليد" fontSize: "Øجم الخط" +noFollowRequests: "ليس لديك طلبات متابعة معلقة" openImageInNewTab: "Ø¥ÙØªØ Ø§Ù„ØµÙˆØ±Ø© بصÙØØ© جديدة" dashboard: "لوØØ© التØكم" local: "المØلي" @@ -429,13 +469,17 @@ nothing: "لا يوجد شيء هنا" lastUsedDate: "آخر استخدام" state: "الØالة" sort: "ترتيب Øسب" +ascendingOrder: "تصاعدي" +descendingOrder: "تنازلي" output: "الخارجة" updateRemoteUser: "تØديث المعلومات عن المستخدم البعيد" deleteAllFiles: "Øذ٠كاÙØ© الملÙات" deleteAllFilesConfirm: "أتريد Øذ٠كل الملÙات؟" -removeAllFollowing: "ألغ متابعة كل المتابÙعين" +removeAllFollowing: "ألغ متابعة كل المتابَعين" userSuspended: "تم تعليق هذا المستخدم." userSilenced: "تم إسكات هذا المستخدم." +yourAccountSuspendedTitle: "هذا الØساب معلق" +menu: "القائمة" addItem: "إضاÙØ© عنصر" rooms: "الغرÙØ©" relays: "المÙرَØلات" @@ -443,9 +487,15 @@ addRelay: "إضاÙØ© Ù…ÙرØّل" addedRelays: "المرØلات التي تم إضاÙتها" deletedNote: "ملاØظة Ù…ØذوÙØ©" invisibleNote: "ملاØظة مخÙية" +enableInfiniteScroll: "Ùعّل التمرير المتواصل" +visibility: "الظهور" poll: "استطلاع رأي" useCw: "إخÙاء المØتوى" +enablePlayer: "اÙØªØ Ù…Ø´ØºÙ„ الÙيديو" +disablePlayer: "أغلق مشغل الÙيديو" themeEditor: "مصمم القوالب" +description: "الوصÙ" +leaveConfirm: "لديك تغييرات غير Ù…ØÙوظة. أتريد المتابعة دون ØÙظها؟" manage: "إدارة " plugins: "الإضاÙات" width: "العرض" @@ -453,12 +503,14 @@ height: "الإرتÙاع" large: "كبير" medium: "متوسط" small: "صغير" +generateAccessToken: "ولّد رمز الوصول" permission: "أذونات" enableAll: "تشغيل الكل" disableAll: "تعطيل الكل" tokenRequested: "Ù…Ù†Ø ØÙ‚ الوصول إلى الØساب" notificationType: "أنواع الإشعارات" edit: "التعديل" +emailServer: "خادم البريد الإلكتروني" email: "البريد الإلكتروني " emailAddress: "عنوان البريد الالكتروني" smtpHost: "المضيÙ" @@ -469,6 +521,12 @@ makeActive: "تÙعيل" display: "المظهر" copy: "نسخ" metrics: "المقاييس" +channel: "القنوات" +create: "أنشئ" +notificationSetting: "إعدادات التنبيهات" +notificationSettingDesc: "اختر نوع التنبيهات المراد عرضها" +other: "منوعات" +regenerateLoginToken: "أعد توليد الرمز" fileIdOrUrl: "معر٠المل٠أو رابط" chatOpenBehavior: "سلوك Ù†Ùاذة المØادثة عند ÙتØها" behavior: "السلوك" @@ -476,7 +534,7 @@ sample: "مثال" abuseReports: "البلاغات" reportAbuse: "البلاغات" reportAbuseOf: "أبلغ عن {name}" -fillAbuseReportDescription: "أكتب بالتÙصيل سبب الإبلاغ، إذا كنت تبلغ عن ملاØظة أرÙÙ‚ رابط لها." +fillAbuseReportDescription: "أكتب بالتÙصيل سبب البلاغ، إذا كنت تبلغ عن ملاØظة أرÙÙ‚ رابط لها." abuseReported: "Ø£Ùرسل البلاغ، شكرًا لك" send: "أرسل" abuseMarkAsResolved: "علّم البلاغ كمØلول" @@ -494,7 +552,9 @@ manageAccessTokens: "إدارة رموز الوصول" accountInfo: "معلومات الØساب" notesCount: "عدد الملاØظات" repliesCount: "عدد الردود المرسلة" +renotesCount: "عدد الملاØظات المعاد نشرها (المرسلة)" repliedCount: "عدد الردود المستلمة" +renotedCount: "عدد الملاØظات المعاد نشرها (المستلمة)" followingCount: "عدد الØسابات المتابَعة" followersCount: "عدد المتابÙعين" sentReactionsCount: "عدد الانÙعالات المرسلة" @@ -503,33 +563,141 @@ pollVotesCount: "عدد الاستطلاعات المرسلة" pollVotedCount: "عدد الاستطلاعات المستلمة" yes: "نعم" no: "لا" +driveFilesCount: "عدد الملÙات ÙÙŠ قرص التخزين" +useSystemFont: "استخدم الخط الاÙتراضية للنظام" +experimentalFeatures: "ميّزات اختبارية" +developer: "المطور" clearCache: "Ø§Ù…Ø³Ø Ø§Ù„ØªØ®Ø²ÙŠÙ† المؤقت" currentVersion: "الإصدار الØالي" latestVersion: "آخر نسخة مستقرة" usageAmount: "الإستخدام" capacity: "السعة" inUse: "مستخدم" +useReactionPickerForContextMenu: "اÙØªØ Ù…Ù†ØªÙ‚ÙŠ التÙاعلات عند التقر بالزر الأيمن" +typingUsers: "{users} يكتب(ون)..." +jumpToSpecifiedDate: "انتقل إلى التاريخ المØدد" +showingPastTimeline: "أنت تستعرض Øاليًا خيطًا زمنيًا قديمًا" +markAllAsRead: "علّم الكل كمقروء" +goBack: "رجوع" +unlikeConfirm: "أتريد إلغاء إعجابك؟" +fullView: "ملء الشاشة" +quitFullView: "اخرج من وضع ملء للشاشة" +addDescription: "أض٠وصÙًا" info: "عن" +userInfo: "معلومات المستخدم" +unknown: "مجهول" +onlineStatus: "الØالة" +hideOnlineStatus: "اخ٠الØالة" +online: "متصل" +active: "نشط" +offline: "غير متصل" +notRecommended: "غير مستØسن" +botProtection: "الØماية من الØسابات الآلية" +instanceBlocking: "المثيلات المØجوبة" +selectAccount: "اختر Øسابًا" +enabled: "Ù…Ùعّل" +disabled: "معطّل" +quickAction: "الإجراءات السّريعة" user: "المستخدمون" administration: "إدارة " +accounts: "الØسابات" +switch: "بدّل" +noBotProtectionWarning: "لم تضبط الØماية من الØسابات الآلية" +configure: "اضبط" postToGallery: "انشر ÙÙŠ المعرض" gallery: "المعرض" +recentPosts: "المشاركات الØديثة" +shareWithNote: "شاركه ÙÙŠ ملاØظة" +ads: "الإعلانات" expiration: "ينتهي استطلاع الرأي ÙÙŠ" +priority: "الأولوية" +high: "عالية" middle: "متوسط" +low: "منخÙضة" +emailNotConfiguredWarning: "لم تعيّن بريدًا إلكترونيًا" +ratio: "النسبة" +previewNoteText: "اعرض معاينة" +customCss: "CSS مخصصة" global: "الشامل" +squareAvatars: "اعرض شكل الصور الرمزية كمربعات" sent: "أرسل" +received: "اÙستلم" +searchResult: "نتائج البØØ«" +hashtags: "الوسوم" +learnMore: "راجع المزيد" +misskeyUpdated: "ØÙدث ميسكي!" +whatIsNew: "اعرض التغييرات" +translate: "ترجم" +translatedFrom: "تÙرجم من {x}" +accountDeletionInProgress: "Øذ٠الØساب جارÙ" +usernameInfo: "الاسم الذي يميزك عن باÙÙŠ مستخدمي هذا الخادم، يمكنك استخدام الØرو٠اللاتينية (a~z, A~Z) والأرقام (0~9) والشرطة السÙلية (_). لا يمكنك تغييره بعد تسجيله." +lastCommunication: "آخر تواصل" +itsOn: "Ù…Ùعّل" +itsOff: "معطّل" +emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل" +filter: "رشّØ" +controlPanel: "لوØØ© التØكم" +manageAccounts: "إدارة الØسابات" +makeReactionsPublic: "اجعل سجل التÙاعلات علنيًا" +makeReactionsPublicDescription: "هذا سيجعل قائمة تÙاعلاتك مرئية للعلن." +classic: "تقليدي" _docs: admin: "إدارة " +_gallery: + unlike: "أزل الإعجاب" _email: _follow: title: "يتابعك" +_registry: + keys: "المÙاتيØ" + domain: "النّطاق" + createKey: "أنشئ Ù…ÙتاØًا" +_aboutMisskey: + about: "ميسكي هو برمجية Ù…ÙتوØØ© المصدر يطورها syuilo منذ 2014." + contributors: "المساهم الرئيسي" + allContributors: "كل المساهمين" + source: "الشÙرة المصدرية" + translation: "ترجم ميسكي" + donate: "تبرع لميسكي" + morePatrons: "Ù†ØÙ† نقدر الدعم الذي قدمه العديد من الأشخاص الذين لم نذكرهم. شكرًا لكم 🥰" + patrons: "الداعمون" +_nsfw: + force: "اخ٠كل الوسائط" _mfm: mention: "أشر الى" + hashtag: "الوسوم" + url: "الرابط" + urlDescription: "يمكن عرض الروابط" + link: "رابط" + bold: "عريض" + small: "صغير" quote: "اقتبس" emoji: "إيموجي مخصص" search: "البØØ«" _reversi: + gameSettings: "إعدادات اللعبة" + chooseBoard: "اختر اللوØ" + blackOrWhite: "أسود/أبيض" + blackIs: "{name} سيلعب بالأسود" + botSettings: "خيارات الØسابات الآلية" + waitingBoth: "استعد" + ready: "جاهز" + cancelReady: "ألغ الجهوزية" + opponentTurn: "دور الخصم" + myTurn: "دورك" + turnOf: "دور {name}" + pastTurnOf: "دور {name}" + surrender: "استسلم" + drawn: "تعادل" + won: "Ùاز {name}" + black: "أسود" + white: "أبيض" total: "المجموع" + turnCount: "الدور {count}" + myGames: "جولاتي" + allGames: "كل الجولات" + ended: "انتهت" + playing: "ÙŠÙلعب الآن" _channel: featured: "المتداوَلة" _menuDisplay: @@ -539,11 +707,15 @@ _theme: install: "تنصيب قالب" manage: "إدارة القوالب" code: "شيÙرة القالب" + description: "الوصÙ" installed: "تم تنصيب {name}" make: "إنشاء قالب" alpha: "الشÙاÙية" keys: + link: "رابط" + hashtag: "وسم" mention: "أشر الى" + renote: "أعد النشر" messageBg: "خلÙية المØادثة" _sfx: note: "الملاØظات" @@ -569,11 +741,35 @@ _time: _tutorial: title: "كي٠تستخدم Misskey" step1_1: "مرØبًا!" + step1_2: "تدعى هذه الصÙØØ© 'الخيط الزمني' وهي تØوي ملاØظات الأشخاص الذي تتابعهم مرتبة Øسب تاريخ نشرها." + step1_3: "خيطك الزمني Ùارغ Øاليًا بما أنك لا تتابع أي شخص ولم تنشر أي ملاØظة." + step2_1: "لننهي إعداد ملÙÙƒ الشخصي قبل كتابة ملاØظة أو متابعة أشخاص." + step3_1: "هل أنهيت إعداد Øسابك؟" + step3_2: "إذا تاليًا للنشر ملاØظة. أنقر على أيقونة القلم ÙÙŠ أعلى الشاشة" + step5_3: "لمتابعة مستخدمين ادخل ملÙهم الشخصي بالنقر على صورتهم الشخصية ثم اضغط زر 'تابع'." _2fa: registerKey: "تسجيل Ù…ÙØªØ§Ø Ø£Ù…Ø§Ù† جديد" _permissions: + "read:account": "اعرض معلومات Øسابك" "write:account": "تعديل معلومات Øسابك" + "read:blocks": "اعرض قائمة المستخدمين المØجوبين" + "write:blocks": "عدّل قائمة المستخدمين المØجوبين" + "read:drive": "تصÙØ Ù‚Ø±Øµ التخزين" + "write:drive": "اØذ٠أو عدّل Ù…Øتويات قرص التخزين" + "read:favorites": "اعرض المÙضلة" + "write:favorites": "عدّل المÙضلة" "read:notifications": "اظهر الإشعارات" + "read:reactions": "اعرض تÙاعلاتك" + "write:reactions": "عدّل تÙاعلاتك" + "write:votes": "صوّت" + "read:pages": "اعرض صÙØاتك" + "write:pages": "عدّل أو اØذ٠صÙØاتك" + "read:user-groups": "اعرض مجموعات المستخدمين" + "write:user-groups": "عدّل أو اØذ٠مجموعات المستخدمين" + "read:gallery": "اعرض المعرض" + "write:gallery": "عدّل المعرض" +_auth: + shareAccess: "أتريد التÙويض لـ \"{name}\" بالوصول Ù„Øسابك؟" _weekday: sunday: "الأØد" monday: "الإثنين" @@ -624,39 +820,74 @@ _poll: _visibility: public: "للعامة" home: "الرئيسي" - followers: "المتابÙعين" + followers: "المتابÙعون" specified: "مباشرة" localOnly: "المØلي Ùقط" _postForm: replyPlaceholder: "رد على هذه الملاØظة…" quotePlaceholder: "اقتبس هذه الملاØظة…" + channelPlaceholder: "انشر ÙÙŠ قناة..." + _placeholders: + c: "ما الذي تÙكر Ùيه؟" + d: "ما الذي تريد قوله؟" + e: "أكتب..." _profile: name: "الإسم" username: "اسم المستخدم" + description: "السيرة" youCanIncludeHashtags: "يمكنك أيضًا إضاÙØ© وسوم إلى نبذتك التعريÙية." + metadata: "معلومات إضاÙية" + metadataEdit: "عدّل المعلومات الإضاÙية" + metadataDescription: "ÙŠÙمكنك عرض 4 Øقول معلومات ÙÙŠ ملÙÙƒ الشخصي" + metadataLabel: "التسمية" + metadataContent: "المØتوى" + changeAvatar: "غيّر الصورة الرمزية" + changeBanner: "غيّر اللاÙتة" _exportOrImport: allNotes: "كل الملاØظات" followingList: "المتابَعون" - muteList: "اكتم" - blockingList: "اØجب" + muteList: "المستخدمون المكتومون" + blockingList: "المستخدمون المØجوبون" userLists: "القوائم" _charts: usersTotal: "مجموع عدد المستخدمين والمستخدمات" activeUsers: "المستخدمون النشطون" + notesTotal: "إجمالي الملاØظات" _timelines: home: "الرئيسي" local: "المØلي" social: "الاجتماعي" global: "الشامل" _rooms: + leaveConfirm: "لديك تغييرات غير Ù…ØÙوظة. أتريد المتابعة دون ØÙظها؟" + chooseImage: "اختر صورة" + roomType: "نوع الغرÙØ©" _roomType: default: "اÙتراضي" + washitsu: "الأسلوب الياباني" _furnitures: + milk: "علبة Øليب" + bed: "سرير" + low-table: "طاولة قصيرة" + desk: "مكتب" + chair: "كرسي" + chair2: "كرسي 2" + pc: "Øاسوب" monitor: "شاشة التØكم" banknote: "أوراق نقدية" _pages: + viewPage: "اعرض صÙØاتك" + like: "أعجبني" + unlike: "أزل الإعجاب" + my: "صÙØاتي" blocks: image: "الصور" + _post: + text: "المØتوى" + _button: + _action: + _dialog: + content: "المØتوى" script: categories: list: "القوائم" @@ -685,9 +916,11 @@ _notification: youGotMessagingMessageFromUser: "لقد تلقيت رسالة Ù…ÙÙ† {name}" youGotMessagingMessageFromGroup: "لقد أرسÙلَت رسالة إلى الÙريق {name}" youWereFollowed: "يتابعك" + youWereInvitedToGroup: "دÙعيت إلى مجموعة" _types: follow: "المتابَعون" mention: "أشر الى" + renote: "أعد النشر" quote: "اقتبس" reaction: "تÙاعل" _deck: diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 81fb263a7f7b0cf7656c2ff261009e252ec654e1..311500e85a3c64a56ce6d4087265858079a4279e 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -799,6 +799,8 @@ manageAccounts: "Benutzerkonten verwalten" makeReactionsPublic: "Reaktionsverlauf veröffentlichen" makeReactionsPublicDescription: "Jeder wird die Liste deiner gesendeten Reaktionen einsehen können." classic: "Classic" +muteThread: "Thread stummschalten" +unmuteThread: "Threadstummschaltung aufheben" _signup: almostThere: "Fast geschafft" emailAddressInfo: "Bitte gib deine Email-Adresse ein." diff --git a/locales/en-US.yml b/locales/en-US.yml index 6d2961ce3d833a9f8358beeaeaf344f9b2b0e696..36d0a48ea633656e18eff654f5b10982f9ca97a7 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -800,6 +800,8 @@ manageAccounts: "Manage Accounts" makeReactionsPublic: "Set reaction history to public" makeReactionsPublicDescription: "This will make the list of all your past reactions publicly visible." classic: "Classic" +muteThread: "Mute thread" +unmuteThread: "Unmute thread" _signup: almostThere: "Almost there" emailAddressInfo: "Please enter your email address." diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml index 0733f0e4758587fbcd1c5eb89131e85de6fc3279..88bf7ab2f45c0b6dc6936591958b03bdd8da4ba3 100644 --- a/locales/eo-UY.yml +++ b/locales/eo-UY.yml @@ -543,6 +543,7 @@ learnMore: "Lernu pli" translate: "Traduki" translatedFrom: "Tradukita el {x}" controlPanel: "Åœaltpodio" +classic: "Klasika" _docs: continueReading: "Legi plu" features: "Funkcioj" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index dbb0bf166498f945cf87bd64827231d3622fde61..1326369f83765d85a4e015f2f45de195136eb58a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -800,6 +800,8 @@ manageAccounts: "アカウントを管ç†" makeReactionsPublic: "リアクション一覧を公開ã™ã‚‹" makeReactionsPublicDescription: "ã‚ãªãŸãŒã—ãŸãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä¸€è¦§ã‚’誰ã§ã‚‚見れるよã†ã«ã—ã¾ã™ã€‚" classic: "クラシック" +muteThread: "スレッドをミュート" +unmuteThread: "スレッドã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除" _signup: almostThere: "ã»ã¨ã‚“ã©å®Œäº†ã§ã™" diff --git a/migration/1635500777168-note-thread-mute.ts b/migration/1635500777168-note-thread-mute.ts new file mode 100644 index 0000000000000000000000000000000000000000..aed10d18d79bd2b159bc8a7547455cde27069912 --- /dev/null +++ b/migration/1635500777168-note-thread-mute.ts @@ -0,0 +1,26 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class noteThreadMute1635500777168 implements MigrationInterface { + name = 'noteThreadMute1635500777168' + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`CREATE TABLE "note_thread_muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "threadId" character varying(256) NOT NULL, CONSTRAINT "PK_ec5936d94d1a0369646d12a3a47" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_29c11c7deb06615076f8c95b80" ON "note_thread_muting" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_c426394644267453e76f036926" ON "note_thread_muting" ("threadId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ae7aab18a2641d3e5f25e0c4ea" ON "note_thread_muting" ("userId", "threadId") `); + await queryRunner.query(`ALTER TABLE "note" ADD "threadId" character varying(256)`); + await queryRunner.query(`CREATE INDEX "IDX_d4ebdef929896d6dc4a3c5bb48" ON "note" ("threadId") `); + await queryRunner.query(`ALTER TABLE "note_thread_muting" ADD CONSTRAINT "FK_29c11c7deb06615076f8c95b80a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "note_thread_muting" DROP CONSTRAINT "FK_29c11c7deb06615076f8c95b80a"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d4ebdef929896d6dc4a3c5bb48"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "threadId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_ae7aab18a2641d3e5f25e0c4ea"`); + await queryRunner.query(`DROP INDEX "public"."IDX_c426394644267453e76f036926"`); + await queryRunner.query(`DROP INDEX "public"."IDX_29c11c7deb06615076f8c95b80"`); + await queryRunner.query(`DROP TABLE "note_thread_muting"`); + } + +} diff --git a/package.json b/package.json index f0bdd0049da95e9e9f84eafe9350420bae927642..86872239233562b60f58ed5f1b5db6e1b79f2de8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.94.1", + "version": "12.95.0", "codename": "indigo", "repository": { "type": "git", @@ -28,8 +28,9 @@ "lint": "tslint 'src/**/*.ts'", "cy:open": "cypress open", "cy:run": "cypress run", - "e2e": "start-server-and-test start:test http://localhost cy:run", - "test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "e2e": "start-server-and-test start:test http://localhost:61812 cy:run", + "mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "test": "npm run mocha", "format": "gulp format" }, "resolutions": { diff --git a/src/client/components/forgot-password.vue b/src/client/components/forgot-password.vue index cb2380f483a764473258b8f4f707ac6c7dcab89d..7fcf9aa7205bdfba786cc8957d321d13a2ab5e85 100644 --- a/src/client/components/forgot-password.vue +++ b/src/client/components/forgot-password.vue @@ -7,21 +7,21 @@ > <template #header>{{ $ts.forgotPassword }}</template> - <form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail"> - <div class="_section"> - <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> + <form class="bafeceda" @submit.prevent="onSubmit" v-if="$instance.enableEmail"> + <div class="main _formRoot"> + <MkInput class="_formBlock" v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> <template #label>{{ $ts.username }}</template> <template #prefix>@</template> </MkInput> - <MkInput v-model="email" type="email" spellcheck="false" required> + <MkInput class="_formBlock" v-model="email" type="email" spellcheck="false" required> <template #label>{{ $ts.emailAddress }}</template> <template #caption>{{ $ts._forgotPassword.enterEmail }}</template> </MkInput> - <MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> + <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> </div> - <div class="_section"> + <div class="sub"> <MkA to="/about" class="_link">{{ $ts._forgotPassword.ifNoEmail }}</MkA> </div> </form> @@ -69,3 +69,16 @@ export default defineComponent({ } }); </script> + +<style lang="scss" scoped> +.bafeceda { + > .main { + padding: 24px; + } + + > .sub { + border-top: solid 0.5px var(--divider); + padding: 24px; + } +} +</style> diff --git a/src/client/components/form/select.vue b/src/client/components/form/select.vue index 9efaf026973a994c969440e9f192c5c5a38d5737..363b3515fad349cc84c6248c0c223265fd5f9d7f 100644 --- a/src/client/components/form/select.vue +++ b/src/client/components/form/select.vue @@ -150,26 +150,26 @@ export default defineComponent({ }); }; - for (const optionOrOptgroup of options) { - if (optionOrOptgroup.type === 'optgroup') { - const optgroup = optionOrOptgroup; - menu.push({ - type: 'label', - text: optgroup.props.label, - }); - for (const option of optgroup.children) { + const scanOptions = (options: VNode[]) => { + for (const vnode of options) { + if (vnode.type === 'optgroup') { + const optgroup = vnode; + menu.push({ + type: 'label', + text: optgroup.props.label, + }); + scanOptions(optgroup.children); + } else if (Array.isArray(vnode.children)) { // 何故ã‹ãƒ•ãƒ©ã‚°ãƒ¡ãƒ³ãƒˆã«ãªã£ã¦ãã‚‹ã“ã¨ãŒã‚ã‚‹ + const fragment = vnode; + scanOptions(fragment.children); + } else { + const option = vnode; pushOption(option); } - } else if (Array.isArray(optionOrOptgroup.children)) { // 何故ã‹ãƒ•ãƒ©ã‚°ãƒ¡ãƒ³ãƒˆã«ãªã£ã¦ãã‚‹ã“ã¨ãŒã‚ã‚‹ - const fragment = optionOrOptgroup; - for (const option of fragment.children) { - pushOption(option); - } - } else { - const option = optionOrOptgroup; - pushOption(option); } - } + }; + + scanOptions(options); os.popupMenu(menu, container.value, { width: container.value.offsetWidth, diff --git a/src/client/components/google.vue b/src/client/components/google.vue index 6d8ae0b5bf6ebbfaf8509bca695c0c728da30e49..be724f038dfef873e1d66a33ee0623d91ded9c24 100644 --- a/src/client/components/google.vue +++ b/src/client/components/google.vue @@ -10,7 +10,12 @@ import { defineComponent } from 'vue'; import * as os from '@client/os'; export default defineComponent({ - props: ['q'], + props: { + q: { + type: String, + required: true, + } + }, data() { return { query: null, @@ -21,10 +26,7 @@ export default defineComponent({ }, methods: { search() { - const engine = this.$store.state.webSearchEngine || - 'https://www.google.com/search?q={{query}}'; - const url = engine.replace('{{query}}', this.query) - window.open(url, '_blank'); + window.open(`https://www.google.com/search?q=${this.query}`, '_blank'); } } }); diff --git a/src/client/components/media-video.vue b/src/client/components/media-video.vue index 44367ee999039c73d3e179e05aa782c1e19afe6c..4d4a551653a9a0c01597cba36bf43c00023ba335 100644 --- a/src/client/components/media-video.vue +++ b/src/client/components/media-video.vue @@ -11,6 +11,7 @@ :title="video.name" preload="none" controls + @contextmenu.stop > <source :src="video.url" diff --git a/src/client/components/mention.vue b/src/client/components/mention.vue index 101a9020ee85eb50c6ad63364d225bb974970469..da13dcddddfaa703294a912986a307d14f74dbe5 100644 --- a/src/client/components/mention.vue +++ b/src/client/components/mention.vue @@ -1,13 +1,12 @@ <template> -<MkA class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')"> - <span class="me" v-if="isMe">{{ $ts.you }}</span> +<MkA v-if="url.startsWith('/')" class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" :style="{ background: bg }"> <img class="icon" :src="`/avatar/@${username}@${host}`" alt=""> <span class="main"> <span class="username">@{{ username }}</span> <span class="host" v-if="(host != localHost) || $store.state.showFullAcct">@{{ toUnicode(host) }}</span> </span> </MkA> -<a class="ldlomzub" :href="url" target="_blank" rel="noopener" v-else> +<a v-else class="ldlomzub" :href="url" target="_blank" rel="noopener" :style="{ background: bg }"> <span class="main"> <span class="username">@{{ username }}</span> <span class="host">@{{ toUnicode(host) }}</span> @@ -17,10 +16,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import { toUnicode } from 'punycode/'; +import * as tinycolor from 'tinycolor2'; +import { toUnicode } from 'punycode'; import { host as localHost } from '@client/config'; import { wellKnownServices } from '../../well-known-services'; -import * as os from '@client/os'; +import { $i } from '@client/account'; export default defineComponent({ props: { @@ -33,53 +33,46 @@ export default defineComponent({ required: true } }, - data() { + + setup(props) { + const canonical = props.host === localHost ? `@${props.username}` : `@${props.username}@${toUnicode(props.host)}`; + + const wellKnown = wellKnownServices.find(x => x[0] === props.host); + const url = wellKnown ? wellKnown[1](props.username) : `/${canonical}`; + + const isMe = $i && ( + `@${props.username}@${toUnicode(props.host)}` === `@${$i.username}@${toUnicode(localHost)}`.toLowerCase() + ); + + const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention')); + bg.setAlpha(0.20); + return { - localHost + localHost, + isMe, + url, + canonical, + toUnicode, + bg: bg.toRgbString(), }; }, - computed: { - url(): string { - const wellKnown = wellKnownServices.find(x => x[0] === this.host); - if (wellKnown) { - return wellKnown[1](this.username); - } else { - return `/${this.canonical}`; - } - }, - canonical(): string { - return this.host === localHost ? `@${this.username}` : `@${this.username}@${toUnicode(this.host)}`; - }, - isMe(): boolean { - return this.$i && ( - `@${this.username}@${toUnicode(this.host)}` === `@${this.$i.username}@${toUnicode(localHost)}`.toLowerCase() - ); - } - }, - methods: { - toUnicode - } }); </script> <style lang="scss" scoped> .ldlomzub { + display: inline-block; + padding: 4px 8px 4px 4px; + border-radius: 999px; color: var(--mention); &.isMe { color: var(--mentionMe); } - - > .me { - pointer-events: none; - user-select: none; - font-size: 70%; - vertical-align: top; - } > .icon { width: 1.5em; - margin: 0 0.2em; + margin: 0 0.2em 0 0; vertical-align: bottom; border-radius: 100%; } diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue index 40b0a68c583391402643922bc68f36a2c5a59b2c..568a2360d167ae230c5ef288a9a686aab20502f6 100644 --- a/src/client/components/note-detailed.vue +++ b/src/client/components/note-detailed.vue @@ -601,6 +601,12 @@ export default defineComponent({ }); }, + toggleThreadMute(mute: boolean) { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: this.appearNote.id + }); + }, + getMenu() { let menu; if (this.$i) { @@ -657,6 +663,15 @@ export default defineComponent({ 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, diff --git a/src/client/components/note.vue b/src/client/components/note.vue index 91a3e3b87d6ead08f3407fdc46cfc18205a14189..681e819a225cf62081167fa5cf0763ac222b3506 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -576,6 +576,12 @@ export default defineComponent({ }); }, + toggleThreadMute(mute: boolean) { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: this.appearNote.id + }); + }, + getMenu() { let menu; if (this.$i) { @@ -632,6 +638,15 @@ export default defineComponent({ 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, diff --git a/src/client/init.ts b/src/client/init.ts index 123d4020e18b1e11d4fbbcde8f73a489e8f47f9d..654e176398ac3383130b8ed4cbf8f368b953682a 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -37,6 +37,8 @@ import { isMobile } from '@client/scripts/is-mobile'; import { initializeSw } from '@client/scripts/initialize-sw'; import { reloadChannel } from '@client/scripts/unison-reload'; import { reactionPicker } from '@client/scripts/reaction-picker'; +import { getUrlWithoutLoginId } from '@client/scripts/login-id'; +import { getAccountFromId } from '@client/scripts/get-account-from-id'; console.info(`Misskey v${version}`); @@ -116,6 +118,25 @@ const html = document.documentElement; html.setAttribute('lang', lang); //#endregion +//#region loginId +const params = new URLSearchParams(location.search); +const loginId = params.get('loginId'); + +if (loginId) { + const target = getUrlWithoutLoginId(location.href); + + if (!$i || $i.id !== loginId) { + const account = await getAccountFromId(loginId); + if (account) { + await login(account.token, target); + } + } + + history.replaceState({ misskey: 'loginId' }, '', target); +} + +//#endregion + //#region Fetch user if ($i && $i.token) { if (_DEV_) { diff --git a/src/client/pages/admin/emojis.vue b/src/client/pages/admin/emojis.vue index 80e0e00ba92b1c130bbee70b79620db36f1e4cbc..5876db349e7079977dd29d90b31de82ecddab6b3 100644 --- a/src/client/pages/admin/emojis.vue +++ b/src/client/pages/admin/emojis.vue @@ -48,7 +48,7 @@ </template> <script lang="ts"> -import { computed, defineComponent } from 'vue'; +import { computed, defineComponent, toRef } from 'vue'; import MkButton from '@client/components/ui/button.vue'; import MkInput from '@client/components/form/input.vue'; import MkPagination from '@client/components/ui/pagination.vue'; @@ -112,7 +112,7 @@ export default defineComponent({ }, async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); + this.$emit('info', toRef(this, symbols.PAGE_INFO)); }, methods: { @@ -168,6 +168,10 @@ export default defineComponent({ <style lang="scss" scoped> .ogwlenmc { > .local { + .empty { + margin: var(--margin); + } + .ldhfsamy { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); @@ -210,6 +214,10 @@ export default defineComponent({ } > .remote { + .empty { + margin: var(--margin); + } + .ldhfsamy { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); diff --git a/src/client/pages/admin/index.vue b/src/client/pages/admin/index.vue index cda3ab912a617789a41a7f7027458c2030266b20..28157ff05a2e78c57ab3a114afe12cbc418dda17 100644 --- a/src/client/pages/admin/index.vue +++ b/src/client/pages/admin/index.vue @@ -26,7 +26,7 @@ </template> <script lang="ts"> -import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { i18n } from '@client/i18n'; import MkSuperMenu from '@client/components/ui/super-menu.vue'; import FormGroup from '@client/components/debobigego/group.vue'; @@ -73,7 +73,13 @@ export default defineComponent({ const view = ref(null); const el = ref(null); const onInfo = (viewInfo) => { - childInfo.value = viewInfo; + if (isRef(viewInfo)) { + watch(viewInfo, () => { + childInfo.value = viewInfo.value; + }, { immediate: true }); + } else { + childInfo.value = viewInfo; + } }; const pageProps = ref({}); diff --git a/src/client/scripts/login-id.ts b/src/client/scripts/login-id.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f9c6be4a9b746bdc84183ef325708f21bae727a --- /dev/null +++ b/src/client/scripts/login-id.ts @@ -0,0 +1,11 @@ +export function getUrlWithLoginId(url: string, loginId: string) { + const u = new URL(url, origin); + u.searchParams.append('loginId', loginId); + return u.toString(); +} + +export function getUrlWithoutLoginId(url: string) { + const u = new URL(url); + u.searchParams.delete('loginId'); + return u.toString(); +} diff --git a/src/client/themes/l-light.json5 b/src/client/themes/l-light.json5 index 79176cdd628361b3ba8313231773e8195808c592..248355c9459d32588dad2cba8e829ec503bd78be 100644 --- a/src/client/themes/l-light.json5 +++ b/src/client/themes/l-light.json5 @@ -15,5 +15,6 @@ navBg: '#fff', panel: '#fff', panelHeaderDivider: '@divider', + mentionMe: 'rgb(0, 179, 70)', }, } diff --git a/src/client/ui/_common_/sidebar.vue b/src/client/ui/_common_/sidebar.vue index ece80e60d90029d6b7c4296da8d45c76e67036df..cd78b6ae46eb83f3c00d1419d3a678dbff0915a3 100644 --- a/src/client/ui/_common_/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -35,7 +35,7 @@ <MkA class="item" active-class="active" to="/settings" v-click-anime> <i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span> </MkA> - <button class="item _button post" @click="post"> + <button class="item _button post" @click="post" data-cy-open-post-form> <i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span> </button> </div> diff --git a/src/db/postgre.ts b/src/db/postgre.ts index 4f4047b61360f0e7538ad0f27d57a4863b3e3669..f52c2ab72232f0d9783e8ab20ca507e1405c5ac4 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -17,6 +17,7 @@ import { PollVote } from '@/models/entities/poll-vote'; import { Note } from '@/models/entities/note'; import { NoteReaction } from '@/models/entities/note-reaction'; import { NoteWatching } from '@/models/entities/note-watching'; +import { NoteThreadMuting } from '@/models/entities/note-thread-muting'; import { NoteUnread } from '@/models/entities/note-unread'; import { Notification } from '@/models/entities/notification'; import { Meta } from '@/models/entities/meta'; @@ -138,6 +139,7 @@ export const entities = [ NoteFavorite, NoteReaction, NoteWatching, + NoteThreadMuting, NoteUnread, Page, PageLike, diff --git a/src/docs/ar-SA/advanced/share-page.md b/src/docs/ar-SA/advanced/share-page.md index b5b4edbebb078915ca33950475a01409a806013b..57485b66be0e54da486a4c308b08e8323e18c5c7 100644 --- a/src/docs/ar-SA/advanced/share-page.md +++ b/src/docs/ar-SA/advanced/share-page.md @@ -33,7 +33,7 @@ <dd>Renoteå…ˆã®Url(リモートã®ãƒŽãƒ¼ãƒˆã‚ªãƒ–ジェクトを指定)</dd> </dl> -### 公開範囲 +### الظهور ※specifiedã«ç›¸å½“ã™ã‚‹å€¤ã¯visibility=specifiedã¨visibleAccts/visibleUserIdsã§æŒ‡å®šã™ã‚‹ <dl> diff --git a/src/docs/ar-SA/advanced/stream.md b/src/docs/ar-SA/advanced/stream.md index 0e5edd2b0ca5341d9fa5c340fbcffef265258c45..a74aafb633efe1dc18994337919b9ad514bc3732 100644 --- a/src/docs/ar-SA/advanced/stream.md +++ b/src/docs/ar-SA/advanced/stream.md @@ -29,7 +29,7 @@ **ストリームã§ã®ã‚„ã‚Šå–ã‚Šã¯ã™ã¹ã¦JSONã§ã™ã€‚** -## ãƒãƒ£ãƒ³ãƒãƒ« +## القنوات Misskeyã®ã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°APIã«ã¯ãƒãƒ£ãƒ³ãƒãƒ«ã¨ã„ã†æ¦‚念ãŒã‚ã‚Šã¾ã™ã€‚ã“ã‚Œã¯ã€é€å—ä¿¡ã™ã‚‹æƒ…å ±ã‚’åˆ†é›¢ã™ã‚‹ãŸã‚ã®ä»•çµ„ã¿ã§ã™ã€‚ Misskeyã®ã‚¹ãƒˆãƒªãƒ¼ãƒ ã«æŽ¥ç¶šã—ãŸã ã‘ã§ã¯ã€ã¾ã リアルタイムã§ã‚¿ã‚¤ãƒ ラインã®æŠ•ç¨¿ã‚’å—ä¿¡ã—ãŸã‚Šã¯ã§ãã¾ã›ã‚“。 ストリーム上ã§ãƒãƒ£ãƒ³ãƒãƒ«ã«æŽ¥ç¶šã™ã‚‹ã“ã¨ã§ã€æ§˜ã€…ãªæƒ…å ±ã‚’å—ã‘å–ã£ãŸã‚Šæƒ…å ±ã‚’é€ä¿¡ã—ãŸã‚Šã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚ ã²ã¨ã¤ã®ã‚¹ãƒˆãƒªãƒ¼ãƒ 上ã§ã€åŒæ™‚ã«è¤‡æ•°ã®ãƒãƒ£ãƒ³ãƒãƒ«ã«æŽ¥ç¶šã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ diff --git a/src/docs/ar-SA/features/note.md b/src/docs/ar-SA/features/note.md index 93e9ee03197a1e69d60af07cb117f554d4323946..b9dcb13c33d14935b37e4af0e2224b7fe345dc76 100644 --- a/src/docs/ar-SA/features/note.md +++ b/src/docs/ar-SA/features/note.md @@ -12,7 +12,7 @@ <div class="info">â„¹ï¸ ã‚³ãƒ³ãƒ”ãƒ¥ãƒ¼ã‚¿ãƒ¼ã®ã‚¯ãƒªãƒƒãƒ—ボードã«ç”»åƒãƒ‡ãƒ¼ã‚¿ãŒã‚る状態ã§ã€ãƒ•ã‚©ãƒ¼ãƒ 内ã®ãƒ†ã‚ストボックスã«ãƒšãƒ¼ã‚¹ãƒˆã™ã‚‹ã¨ãã®ç”»åƒã‚’添付ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚</div> <div class="info">â„¹ï¸ ãƒ†ã‚ストボックス内ã§<kbd class="key">Ctrl + Enter</kbd>を押ã™ã“ã¨ã§ã‚‚投稿ã§ãã¾ã™ã€‚</div> -## Renote +## أعد النشر æ—¢ã«ã‚るノートを引用ã€ã‚‚ã—ãã¯ãã®ãƒŽãƒ¼ãƒˆã‚’æ–°ã—ã„ノートã¨ã—ã¦å…±æœ‰ã™ã‚‹è¡Œç‚ºã€ã¾ãŸãã‚Œã«ã‚ˆã£ã¦ä½œæˆã•ã‚ŒãŸãƒŽãƒ¼ãƒˆã‚’Renoteã¨å‘¼ã³ã¾ã™ã€‚ 自分ãŒãƒ•ã‚©ãƒãƒ¼ã—ã¦ã„るユーザーã®ã€æ°—ã«å…¥ã£ãŸãƒŽãƒ¼ãƒˆã‚’自分ã®ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã«å…±æœ‰ã—ãŸã„å ´åˆã‚„ã€éŽåŽ»ã®è‡ªåˆ†ã®ãƒŽãƒ¼ãƒˆã‚’å†åº¦å…±æœ‰ã—ãŸã„å ´åˆã«ä½¿ã„ã¾ã™ã€‚ åŒã˜ãƒŽãƒ¼ãƒˆã«å¯¾ã—ã¦ç„¡åˆ¶é™ã«Renoteã‚’è¡Œã†ã“ã¨ãŒã§ãã¾ã™ãŒã€ã‚ã¾ã‚Šé€£ç¶šã—ã¦ä½¿ç”¨ã™ã‚‹ã¨è¿·æƒ‘ã«ãªã‚‹å ´åˆã‚‚ã‚ã‚‹ã®ã§ã€æ³¨æ„ã—ã¾ã—ょã†ã€‚ <div class="warn">âš ï¸ å…¬é–‹ç¯„å›²ãŒãƒ•ã‚©ãƒãƒ¯ãƒ¼ã‚„ダイレクトã®ãƒŽãƒ¼ãƒˆã¯Renoteã§ãã¾ã›ã‚“</div> @@ -21,7 +21,7 @@ Renoteを削除ã™ã‚‹ã«ã¯ã€Renoteã®æ™‚刻表示ã®éš£ã«ã‚る「...ã€ã‚’ ## CW Contents Warningã®ç•¥ã§ã€ãƒŽãƒ¼ãƒˆã®å†…容をã€é–²è¦§è€…ã®æ“作ãªã—ã«ã¯è¡¨ç¤ºã—ãªã„よã†ã«ã§ãる機能ã§ã™ã€‚主ã«é•·å¤§ãªå†…å®¹ã‚’éš ã™ãŸã‚ã‚„ã€ãƒã‚¿ãƒãƒ¬é˜²æ¢ãªã©ã«ä½¿ã†ã“ã¨ãŒã§ãã¾ã™ã€‚ è¨å®šã™ã‚‹ã«ã¯ã€ãƒ•ã‚©ãƒ¼ãƒ ã®ã€Œå†…å®¹ã‚’éš ã™ã€ãƒœã‚¿ãƒ³(ç›®ã®ã‚¢ã‚¤ã‚³ãƒ³)を押ã—ã¾ã™ã€‚ã™ã‚‹ã¨æ–°ã—ã„入力エリアãŒè¡¨ã‚Œã‚‹ã®ã§ã€ãã“ã«å†…容ã®è¦ç´„を記入ã—ã¾ã™ã€‚ -## 公開範囲 +## الظهور ノートã”ã¨ã«ã€ãã®ãƒŽãƒ¼ãƒˆãŒå…¬é–‹ã•ã‚Œã‚‹ç¯„囲をè¨å®šã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚フォームã®ã€ŒãƒŽãƒ¼ãƒˆã€ãƒœã‚¿ãƒ³ã®å·¦ã«ã‚るアイコンを押ã™ã¨å…¬é–‹ç¯„囲を以下ã‹ã‚‰é¸æŠžã§ãã¾ã™ã€‚ ### للعامة diff --git a/src/docs/ar-SA/general/glossary.md b/src/docs/ar-SA/general/glossary.md index 7ca515cf1f2cddcb73ea1f07516dca80ea4fe767..0534c8d7c69f6125b52cf4ed867b55421a5339bc 100644 --- a/src/docs/ar-SA/general/glossary.md +++ b/src/docs/ar-SA/general/glossary.md @@ -34,7 +34,7 @@ Misskeyã«é–¢ã™ã‚‹ç”¨èªžé›†ã§ã™ã€‚ ## NSFW (èªã¿: ã®ã£ã¨ã›ãƒ¼ãµãµã‰ãƒ¼ã‚ーã) Not Safe For Workã®ç•¥ã€‚ç”»åƒã‚’「閲覧注æ„ã€æ‰±ã„ã«ã—ã€æ“作ãªã—ã«ã¯è¡¨ç¤ºã—ãªã„よã†ã«ã™ã‚‹ã“ã¨ãŒã§ãる機能。 -## Renote +## أعد النشر (èªã¿: ã‚Šã®ãƒ¼ã¨) æ—¢ã«ã‚るノートを引用ã€ã‚‚ã—ãã¯ãã®ãƒŽãƒ¼ãƒˆã‚’æ–°ã—ã„ノートã¨ã—ã¦å…±æœ‰ã™ã‚‹è¡Œç‚ºã€ã¾ãŸãã‚Œã«ã‚ˆã£ã¦ä½œæˆã•ã‚ŒãŸãƒŽãƒ¼ãƒˆã€‚詳細ã¯[ã“ã¡ã‚‰ã€‚](../features/note) ## STL diff --git a/src/docs/ar-SA/general/links.md b/src/docs/ar-SA/general/links.md index 5ce5e1ddbb29189e1d9699b44f6c21fbde1b2c88..2943015c92fde81ffc21ff3c49c47f9f29e6968e 100644 --- a/src/docs/ar-SA/general/links.md +++ b/src/docs/ar-SA/general/links.md @@ -4,7 +4,7 @@ - [Official Discord](https://discord.gg/Wp8gVStHW3) - Misskeyå…¬å¼Discordサーãƒãƒ¼ - [Misskey Forum](https://forum.misskey.io/) - Misskeyã«é–¢ã™ã‚‹è©±é¡Œã‚’扱ã†ãƒ•ã‚©ãƒ¼ãƒ©ãƒ -## アカウント +## الØسابات - [@repo@misskey.io](https://misskey.io/@repo) - Misskeyã®ãƒªãƒã‚¸ãƒˆãƒªã®æ›´æ–°ã‚’投稿ã™ã‚‹bot ## ライブラリ diff --git a/src/docs/en-US/features/keyboard-shortcut.md b/src/docs/en-US/features/keyboard-shortcut.md index d59c447647f3b5098abaea9d23e054c48f045792..75018e81f20ed695334ad40f471dda53b7beafc9 100644 --- a/src/docs/en-US/features/keyboard-shortcut.md +++ b/src/docs/en-US/features/keyboard-shortcut.md @@ -1,24 +1,24 @@ -# ã‚ーボードショートカット +# Keyboard shortcuts ## Global -ã“れらã®ã‚·ãƒ§ãƒ¼ãƒˆã‚«ãƒƒãƒˆã¯åŸºæœ¬çš„ã«ã©ã“ã§ã‚‚使ãˆã¾ã™ã€‚ +These shortcuts are usually available anywhere. <table> <thead> - <tr><th>ショートカット</th><th>効果</th><th>ç”±æ¥</th></tr> + <tr><th>Shortcut</th><th>Effect</th><th>Why this key?</th></tr> </thead> <tbody> - <tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>æ–°è¦æŠ•ç¨¿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr> - <tr><td><kbd class="key">T</kbd></td><td>タイムラインã®æœ€ã‚‚æ–°ã—ã„投稿ã«ãƒ•ã‚©ãƒ¼ã‚«ã‚¹</td><td><b>T</b>imeline, <b>T</b>op</td></tr> - <tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/éš ã™</td><td><b>N</b>otifications</td></tr> + <tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>Create a note</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr> + <tr><td><kbd class="key">T</kbd></td><td>Focus the latest note</td><td><b>T</b>imeline, <b>T</b>op</td></tr> + <tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>Show/hide notifications</td><td><b>N</b>otifications</td></tr> <tr><td><kbd class="key">S</kbd></td><td>Search</td><td><b>S</b>earch</td></tr> - <tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr> + <tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>Show help</td><td><b>H</b>elp</td></tr> </tbody> </table> ## 投稿ã«ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã•ã‚ŒãŸçŠ¶æ…‹ <table> <thead> - <tr><th>ショートカット</th><th>効果</th><th>ç”±æ¥</th></tr> + <tr><th>Shortcut</th><th>Effect</th><th>Why this key?</th></tr> </thead> <tbody> <tr><td><kbd class="key">↑</kbd>, <kbd class="key">K</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>上ã®æŠ•ç¨¿ã«ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã‚’移動</td><td>-</td></tr> @@ -39,12 +39,12 @@ ## Renoteフォーム<table> <thead> - <tr><th>ショートカット</th><th>効果</th><th>ç”±æ¥</th></tr> + <tr><th>Shortcut</th><th>Effect</th><th>Why this key?</th></tr> </thead> <tbody> - <tr><td><kbd class="key">Enter</kbd></td><td>Renoteã™ã‚‹</td><td>-</td></tr> - <tr><td><kbd class="key">Q</kbd></td><td>フォームを展開ã™ã‚‹</td><td><b>Q</b>uote</td></tr> - <tr><td><kbd class="key">Esc</kbd></td><td>フォームを閉ã˜ã‚‹</td><td>-</td></tr> + <tr><td><kbd class="key">Enter</kbd></td><td>Renote</td><td>-</td></tr> + <tr><td><kbd class="key">Q</kbd></td><td>Expand form</td><td><b>Q</b>uote</td></tr> + <tr><td><kbd class="key">Esc</kbd></td><td>Close form</td><td>-</td></tr> </tbody> </table> @@ -52,7 +52,7 @@ デフォルトã§ã€ŒðŸ‘ã€ã«ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ãŒå½“ãŸã£ã¦ã„る状態ã§ã™ã€‚ <table> <thead> - <tr><th>ショートカット</th><th>効果</th><th>ç”±æ¥</th></tr> + <tr><th>Shortcut</th><th>Effect</th><th>Why this key?</th></tr> </thead> <tbody> <tr><td><kbd class="key">↑</kbd>, <kbd class="key">K</kbd></td><td>上ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã«ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã‚’移動</td><td>-</td></tr> diff --git a/src/docs/en-US/features/pages.md b/src/docs/en-US/features/pages.md index 7fc6ee29c3947c65855438bd725e2d075a7866c1..dc5134b9401607a459a12ebb6cf623d0d24d81d2 100644 --- a/src/docs/en-US/features/pages.md +++ b/src/docs/en-US/features/pages.md @@ -1,9 +1,9 @@ # Pages ## Variables -変数を使ã†ã“ã¨ã§å‹•çš„ãªãƒšãƒ¼ã‚¸ã‚’作æˆã§ãã¾ã™ã€‚テã‚スト内㧠<b>{ 変数å }</b> ã¨æ›¸ãã¨ãã“ã«å¤‰æ•°ã®å€¤ã‚’埋ã‚è¾¼ã‚ã¾ã™ã€‚例ãˆã° <b>Hello { thing } world!</b> ã¨ã„ã†ãƒ†ã‚ストã§ã€å¤‰æ•°(thing)ã®å€¤ãŒ <b>ai</b> ã ã£ãŸå ´åˆã€ãƒ†ã‚スト㯠<b>Hello ai world!</b> ã«ãªã‚Šã¾ã™ã€‚ +Use variables to create dynamic pages. Put <b>{ variable-name }</b> in your content to embed the value. For example, if a variable named "thing" has the value <b>ai</b>, the string <b>Hello { thing } world!</b> turns into <b>Hello ai world!</b>. -変数ã®è©•ä¾¡(値を算出ã™ã‚‹ã“ã¨)ã¯ä¸Šã‹ã‚‰ä¸‹ã«è¡Œã‚れるã®ã§ã€ã‚る変数ã®ä¸ã§è‡ªåˆ†ã‚ˆã‚Šä¸‹ã®å¤‰æ•°ã‚’å‚ç…§ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。例ãˆã°ä¸Šã‹ã‚‰ <b>Aã€Bã€C</b> ã¨3ã¤ã®å¤‰æ•°ã‚’定義ã—ãŸã¨ãã€<b>C</b>ã®ä¸ã§<b>A</b>ã‚„<b>B</b>ã‚’å‚ç…§ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã™ãŒã€<b>A</b>ã®ä¸ã§<b>B</b>ã‚„<b>C</b>ã‚’å‚ç…§ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 +Variables are evaluated from top to bottom, so referencing variables before declaring is not possible. For example, when declaring the three variables <b>A, B, C</b> in this order, you can use <b>A</b> or <b>B</b> in <b>C</b>, but you cannot use <b>B</b> or <b>C</b> in <b>A</b>. ユーザーã‹ã‚‰ã®å…¥åŠ›ã‚’å—ã‘å–ã‚‹ã«ã¯ã€ãƒšãƒ¼ã‚¸ã«ã€Œãƒ¦ãƒ¼ã‚¶ãƒ¼å…¥åŠ›ã€ãƒ–ãƒãƒƒã‚¯ã‚’è¨ç½®ã—ã€ã€Œå¤‰æ•°åã€ã«å…¥åŠ›ã‚’æ ¼ç´ã—ãŸã„変数åã‚’è¨å®šã—ã¾ã™(変数ã¯è‡ªå‹•ã§ä½œæˆã•ã‚Œã¾ã™)。ãã®å¤‰æ•°ã‚’使ã£ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼å…¥åŠ›ã«å¿œã˜ãŸå‹•ä½œã‚’è¡Œãˆã¾ã™ã€‚ diff --git a/src/docs/en-US/features/theme.md b/src/docs/en-US/features/theme.md index a0a9b0d02fc82a919a8eeb7c0ccd90c828a45885..e9b3406c4ce3ad82c804561c136d96c7e6e1298b 100644 --- a/src/docs/en-US/features/theme.md +++ b/src/docs/en-US/features/theme.md @@ -2,10 +2,10 @@ テーマをè¨å®šã—ã¦ã€Misskeyクライアントã®è¦‹ãŸç›®ã‚’変更ã§ãã¾ã™ã€‚ -## テーマã®è¨å®š +## Configuring the theme è¨å®š > テーマ -## テーマを作æˆã™ã‚‹ +## Creating a theme テーマコードã¯JSON5ã§è¨˜è¿°ã•ã‚ŒãŸãƒ†ãƒ¼ãƒžã‚ªãƒ–ジェクトã§ã™ã€‚ テーマã¯ä»¥ä¸‹ã®ã‚ˆã†ãªã‚ªãƒ–ジェクトã§ã™ã€‚ ``` js { @@ -33,13 +33,13 @@ ``` -* `id` ... テーマã®ä¸€æ„ãªID。UUIDã‚’ãŠã™ã™ã‚ã—ã¾ã™ã€‚ -* `name` ... テーマå -* `author` ... テーマã®ä½œè€… -* `desc` ... テーマã®èª¬æ˜Ž(オプション) +* `id` ... A unique theme ID. UUID Recommended. +* `name` ... Theme name +* `author` ... The author of the theme (you!) +* `desc` ... The description of the theme (optional) * `base` ... 明るã„テーマã‹ã€æš—ã„テーマ㋠* `light`ã«ã™ã‚‹ã¨æ˜Žã‚‹ã„テーマã«ãªã‚Šã€`dark`ã«ã™ã‚‹ã¨æš—ã„テーマã«ãªã‚Šã¾ã™ã€‚ - * テーマã¯ã“ã“ã§è¨å®šã•ã‚ŒãŸãƒ™ãƒ¼ã‚¹ãƒ†ãƒ¼ãƒžã‚’継承ã—ã¾ã™ã€‚ + * The theme will be inheriting the default values of the theme specified here. * `props` ... テーマã®ã‚¹ã‚¿ã‚¤ãƒ«å®šç¾©ã€‚ã“ã‚Œã‹ã‚‰èª¬æ˜Žã—ã¾ã™ã€‚ ### テーマã®ã‚¹ã‚¿ã‚¤ãƒ«å®šç¾© diff --git a/src/docs/en-US/features/timeline.md b/src/docs/en-US/features/timeline.md index ed62a446673c46e904e46a25098b382b03a649a2..a98d2b8a2905ff9daa9cad44c6719aa28f63ae98 100644 --- a/src/docs/en-US/features/timeline.md +++ b/src/docs/en-US/features/timeline.md @@ -1,31 +1,31 @@ # Timeline -タイムラインã¯ã€[ノート](./note)ãŒæ™‚系列ã§è¡¨ç¤ºã•ã‚Œã‚‹æ©Ÿèƒ½ã§ã™ã€‚ タイムラインã«ã¯ä»¥ä¸‹ã§ç¤ºã™ç¨®é¡žãŒã‚ã‚Šã€ç¨®é¡žã«ã‚ˆã£ã¦è¡¨ç¤ºã•ã‚Œã‚‹ãƒŽãƒ¼ãƒˆã‚‚ç•°ãªã‚Šã¾ã™ã€‚ ãªãŠã€ã‚¿ã‚¤ãƒ ラインã®ç¨®é¡žã«ã‚ˆã£ã¦ã¯ã‚µãƒ¼ãƒãƒ¼ã«ã‚ˆã‚Šç„¡åŠ¹ã«ãªã£ã¦ã„ã‚‹å ´åˆãŒã‚ã‚Šã¾ã™ã€‚ +[Notes](./note) are shown in the timelines. There are several kinds of timelines as mentioned below and each of them displays the different set of notes. Servers might disable some of them. ## Home -自分ã®ãƒ•ã‚©ãƒãƒ¼ã—ã¦ã„るユーザーã®æŠ•ç¨¿ãŒæµã‚Œã¾ã™ã€‚HTLã¨ç•¥ã•ã‚Œã¾ã™ã€‚ +This is where you see posts from users you follow. Often abbreviated as HTL. ## Local -å…¨ã¦ã®ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã€Œãƒ›ãƒ¼ãƒ ã€æŒ‡å®šã•ã‚Œã¦ã„ãªã„投稿ãŒæµã‚Œã¾ã™ã€‚LTLã¨ç•¥ã•ã‚Œã¾ã™ã€‚ +This is where you see all the posts from the local users, except those with "Home" visibility. Often abbreviated as LTL. ## Social -自分ã®ãƒ•ã‚©ãƒãƒ¼ã—ã¦ã„るユーザーã®æŠ•ç¨¿ã¨ã€å…¨ã¦ã®ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã€Œãƒ›ãƒ¼ãƒ ã€æŒ‡å®šã•ã‚Œã¦ã„ãªã„投稿ãŒæµã‚Œã¾ã™ã€‚STLã¨ç•¥ã•ã‚Œã¾ã™ã€‚ +This is where you see the posts from users you follow AND all the posts from the local users, except those with "Home" visibility. Often abbreviated as STL. ## Global -å…¨ã¦ã®ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã€Œãƒ›ãƒ¼ãƒ ã€æŒ‡å®šã•ã‚Œã¦ã„ãªã„投稿ã¨ã€ã‚µãƒ¼ãƒãƒ¼ã«å±Šã„ãŸå…¨ã¦ã®ãƒªãƒ¢ãƒ¼ãƒˆãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã€Œãƒ›ãƒ¼ãƒ ã€æŒ‡å®šã•ã‚Œã¦ã„ãªã„投稿ãŒæµã‚Œã¾ã™ã€‚GTLã¨ç•¥ã•ã‚Œã¾ã™ã€‚ +This is where you see the posts from the local users and the remote users in federated servers, except those with "Home" visibility. Often abbreviated as GTL. ## Comparison -| ソース | | | Timeline | | | -| ------------ | ----------- | ---- | -------- | ------ | ------ | -| Users | Visiblility | Home | Local | Social | Global | -| ãƒãƒ¼ã‚«ãƒ« (フォãƒãƒ¼) | Publish | ✔ | ✔ | ✔ | ✔ | -| | Home | ✔ | | ✔ | | -| | Followers | ✔ | ✔ | ✔ | ✔ | -| リモート (フォãƒãƒ¼) | Publish | ✔ | | ✔ | ✔ | -| | Home | ✔ | | ✔ | | -| | Followers | ✔ | | ✔ | ✔ | -| ãƒãƒ¼ã‚«ãƒ« (未フォãƒãƒ¼) | Publish | | ✔ | ✔ | ✔ | -| | Home | | | | | -| | Followers | | | | | -| リモート (未フォãƒãƒ¼) | Publish | | | | ✔ | -| | Home | | | | | -| | Followers | | | | | +| Source | | | Timeline | | | +| ----------------------------- | ----------- | ---- | -------- | ------ | ------ | +| Users | Visiblility | Home | Local | Social | Global | +| Local users you follow | Publish | ✔ | ✔ | ✔ | ✔ | +| | Home | ✔ | | ✔ | | +| | Followers | ✔ | ✔ | ✔ | ✔ | +| Remote users you follow | Publish | ✔ | | ✔ | ✔ | +| | Home | ✔ | | ✔ | | +| | Followers | ✔ | | ✔ | ✔ | +| Local users you don't follow | Publish | | ✔ | ✔ | ✔ | +| | Home | | | | | +| | Followers | | | | | +| Remote users you don't follow | Publish | | | | ✔ | +| | Home | | | | | +| | Followers | | | | | diff --git a/src/docs/eo-UY/advanced/share-page.md b/src/docs/eo-UY/advanced/share-page.md index 8c5224f59fab3a07ac7304879c2db4f6025009b9..b1959b4d66426287c230a074f7d024fe5d476fda 100644 --- a/src/docs/eo-UY/advanced/share-page.md +++ b/src/docs/eo-UY/advanced/share-page.md @@ -1,19 +1,19 @@ -# シェアページ +# La paÄo nur por skribi novan noton `/share`ã‚’é–‹ãã¨ã€å…±æœ‰ç”¨ã®æŠ•ç¨¿ãƒ•ã‚©ãƒ¼ãƒ ã‚’é–‹ãã“ã¨ãŒã§ãã¾ã™ã€‚ ã“ã“ã§ã¯ã‚·ã‚§ã‚¢ãƒšãƒ¼ã‚¸ã§åˆ©ç”¨ã§ãるクエリ文å—列ã®ä¸€è¦§ã‚’示ã—ã¾ã™ã€‚ -## クエリ文å—列一覧 -### æ–‡å— +## La listo de la tekstoj por informpeti +### Teksto <dl> <dt>title</dt> -<dd>タイトルã§ã™ã€‚本文ã®å…ˆé ã«[ … ]ã¨æŒ¿å…¥ã•ã‚Œã¾ã™ã€‚</dd> +<dd>Tio estas titolo.本文ã®å…ˆé ã«[ … ]ã¨æŒ¿å…¥ã•ã‚Œã¾ã™ã€‚</dd> <dt>text</dt> -<dd>本文ã§ã™ã€‚</dd> +<dd>Tio estas teksto</dd> <dt>url</dt> -<dd>URLã§ã™ã€‚末尾ã«æŒ¿å…¥ã•ã‚Œã¾ã™ã€‚</dd> +<dd>Tio estas URL.末尾ã«æŒ¿å…¥ã•ã‚Œã¾ã™ã€‚</dd> </dl> -### ãƒªãƒ—ãƒ©ã‚¤æƒ…å ± +### La informo por respondi 以下ã®ã„ãšã‚Œã‹ <dl> @@ -23,14 +23,14 @@ <dd>リプライ先ã®Url(リモートã®ãƒŽãƒ¼ãƒˆã‚ªãƒ–ジェクトを指定)</dd> </dl> -### Renoteæƒ…å ± +### La informo por plusendi noton 以下ã®ã„ãšã‚Œã‹ <dl> <dt>renoteId</dt> -<dd>Renoteå…ˆã®ãƒŽãƒ¼ãƒˆid</dd> +<dd>la ID de la noto plusendota</dd> <dt>renoteUri</dt> -<dd>Renoteå…ˆã®Url(リモートã®ãƒŽãƒ¼ãƒˆã‚ªãƒ–ジェクトを指定)</dd> +<dd>la URL de la noto plusendota el fora nodo</dd> </dl> ### Videbleco @@ -50,5 +50,5 @@ ### Dosieroj <dl> <dt>fileIds</dt> -<dd>添付ã—ãŸã„ファイルã®id(カンマ区切りã§ï¼‰</dd> +<dd>La ID-oj de viaj aldonotaj dosieroj (devas esti apartigita de komoj)</dd> </dl> diff --git a/src/docs/pt-PT/features/custom-emoji.md b/src/docs/pt-PT/features/custom-emoji.md index ed2e92be16bb35ca0cf59fcd5fd9864f53297eb2..ba9a9743ff027af339bef5eca46d1a3217e104c0 100644 --- a/src/docs/pt-PT/features/custom-emoji.md +++ b/src/docs/pt-PT/features/custom-emoji.md @@ -1,2 +1,2 @@ -# ã‚«ã‚¹ã‚¿ãƒ çµµæ–‡å— +# Emojis Customizados カスタム絵文å—ã¯ã€ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã§ç”¨æ„ã•ã‚ŒãŸç”»åƒã‚’絵文å—ã®ã‚ˆã†ã«ä½¿ãˆã‚‹æ©Ÿèƒ½ã§ã™ã€‚ ノートã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€ãƒãƒ£ãƒƒãƒˆã€è‡ªå·±ç´¹ä»‹ã€åå‰ãªã©ã®å ´æ‰€ã§ä½¿ã†ã“ã¨ãŒã§ãã¾ã™ã€‚ カスタム絵文å—ã‚’ãれらã®å ´æ‰€ã§ä½¿ã†ã«ã¯ã€çµµæ–‡å—ピッカーボタン(ã‚ã‚‹å ´åˆ)を押ã™ã‹ã€`:`を入力ã—ã¦çµµæ–‡å—サジェストを表示ã—ã¾ã™ã€‚ テã‚スト内ã«`:foo:`ã®ã‚ˆã†ãªå½¢å¼ã®æ–‡å—列ãŒè¦‹ã¤ã‹ã‚‹ã¨ã€`foo`ã®éƒ¨åˆ†ãŒã‚«ã‚¹ã‚¿ãƒ 絵文å—åã¨è§£é‡ˆã•ã‚Œã€è¡¨ç¤ºæ™‚ã«ã¯å¯¾å¿œã—ãŸã‚«ã‚¹ã‚¿ãƒ 絵文å—ã«ç½®ãæ›ã‚ã‚Šã¾ã™ã€‚ diff --git a/src/docs/pt-PT/features/theme.md b/src/docs/pt-PT/features/theme.md index a406f3433ca0dbd4a4b412f543e36371d7f88868..1da9cbee710ff12ff955106e7138fb1d3afaab5d 100644 --- a/src/docs/pt-PT/features/theme.md +++ b/src/docs/pt-PT/features/theme.md @@ -1,9 +1,9 @@ -# テーマ +# Temas -テーマをè¨å®šã—ã¦ã€Misskeyクライアントã®è¦‹ãŸç›®ã‚’変更ã§ãã¾ã™ã€‚ +É possÃvel mudar a aparência do Misskey com as configurações de tema. -## テーマã®è¨å®š -è¨å®š > テーマ +## Configurações de tema +Configurações > Temas. ## テーマを作æˆã™ã‚‹ テーマコードã¯JSON5ã§è¨˜è¿°ã•ã‚ŒãŸãƒ†ãƒ¼ãƒžã‚ªãƒ–ジェクトã§ã™ã€‚ テーマã¯ä»¥ä¸‹ã®ã‚ˆã†ãªã‚ªãƒ–ジェクトã§ã™ã€‚ diff --git a/src/docs/pt-PT/features/widgets.md b/src/docs/pt-PT/features/widgets.md index a7c2c1d1d647140f3681f90c34ee0f1f4a69e2fb..7c5cacb39af436ec9bed47b29990ae94f27fdefb 100644 --- a/src/docs/pt-PT/features/widgets.md +++ b/src/docs/pt-PT/features/widgets.md @@ -1,4 +1,4 @@ -# ウィジェット +# Widgets ウィジェットã¯ã€Misskeyã®UI上ã«è¨ç½®ã§ãã‚‹å°åž‹ã®æƒ…å ±è¡¨ç¤ºã€æ“作ãŒè¡Œãˆã‚‹ãƒ‘ーツã§ã™ã€‚ ウィジェットを編集ã™ã‚‹ã«ã¯ã€ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆç·¨é›†ãƒ¢ãƒ¼ãƒ‰ã«åˆ‡ã‚Šæ›¿ãˆã¾ã™ã€‚切り替ãˆæ–¹æ³•ã¯UIã«ã‚ˆã£ã¦ç•°ãªã‚Šã¾ã™ã€‚ ウィジェット編集モードã§ã¯ã€ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã®è¿½åŠ ã€å‰Šé™¤ã€ä¸¦ã³æ›¿ãˆã€ãŠã‚ˆã³ãã‚Œãžã‚Œã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆã®è¨å®šã‚’è¡Œãˆã¾ã™ã€‚ diff --git a/src/models/entities/note-thread-muting.ts b/src/models/entities/note-thread-muting.ts new file mode 100644 index 0000000000000000000000000000000000000000..b438522a4ce14456bdca20f6cbd3ab898f30b1ea --- /dev/null +++ b/src/models/entities/note-thread-muting.ts @@ -0,0 +1,33 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { Note } from './note'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'threadId'], { unique: true }) +export class NoteThreadMuting { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column('varchar', { + length: 256, + }) + public threadId: string; +} diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 9a85532637bde8788834424190945ac8c16467fe..4a5411f93dfcce519bb0234ad83492f6c8a0baa2 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -47,6 +47,12 @@ export class Note { @JoinColumn() public renote: Note | null; + @Index() + @Column('varchar', { + length: 256, nullable: true + }) + public threadId: string | null; + @Column('varchar', { length: 8192, nullable: true }) diff --git a/src/models/index.ts b/src/models/index.ts index 4c6f19eaff5aadf6a94a646360c1a8eef032f378..7154cca550299030df3ca6fb11b8053d21cb6ce0 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -7,6 +7,7 @@ import { PollVote } from './entities/poll-vote'; import { Meta } from './entities/meta'; import { SwSubscription } from './entities/sw-subscription'; import { NoteWatching } from './entities/note-watching'; +import { NoteThreadMuting } from './entities/note-thread-muting'; import { NoteUnread } from './entities/note-unread'; import { RegistrationTicket } from './entities/registration-tickets'; import { UserRepository } from './repositories/user'; @@ -69,6 +70,7 @@ export const Apps = getCustomRepository(AppRepository); export const Notes = getCustomRepository(NoteRepository); export const NoteFavorites = getCustomRepository(NoteFavoriteRepository); export const NoteWatchings = getRepository(NoteWatching); +export const NoteThreadMutings = getRepository(NoteThreadMuting); export const NoteReactions = getCustomRepository(NoteReactionRepository); export const NoteUnreads = getRepository(NoteUnread); export const Polls = getRepository(Poll); diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 72cefbaac5d870695ed6b9c40f7eb39f18dcb4f1..9598e87191888fc4a67da75fb63f51eb6f37a4e9 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -112,7 +112,7 @@ export class UserRepository extends Repository<User> { const unread = channels.length > 0 ? await NoteUnreads.findOne({ userId: userId, - noteChannelId: In(channels.map(x => x.id)), + noteChannelId: In(channels.map(x => x.followeeId)), }) : null; return unread != null; diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index cf68f3005dae2d276196a3b959c0f68ae8ab754c..492dc05248d0bc9dc3ab030ea2fa7be6f615caec 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -288,6 +288,10 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): } //#endregion + if (uri.startsWith(config.url)) { + throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); + } + // リモートサーãƒãƒ¼ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒã—ã¦ãã¦ç™»éŒ² // ã“ã“ã§uriã®ä»£ã‚ã‚Šã«æ·»ä»˜ã•ã‚Œã¦ããŸNote ObjectãŒæŒ‡å®šã•ã‚Œã¦ã„ã‚‹ã¨ã€ã‚µãƒ¼ãƒãƒ¼ãƒ•ã‚§ãƒƒãƒã‚’経ãšã«ãƒŽãƒ¼ãƒˆãŒç”Ÿæˆã•ã‚Œã‚‹ãŒ // 添付ã•ã‚Œã¦ããŸNote Objectã¯å½è£…ã•ã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ãŸã‚ã€å¸¸ã«uriを指定ã—ã¦ã‚µãƒ¼ãƒãƒ¼ãƒ•ã‚§ãƒƒãƒã‚’è¡Œã†ã€‚ diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 84b2f0c51c9ab3fbdbd3dcd171e8244d91fdb958..eb8c00a10b7d8b097e4edb755cf8fb6b780cac3c 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -29,6 +29,7 @@ import { toArray } from '@/prelude/array'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata'; import { normalizeForSearch } from '@/misc/normalize-for-search'; import { truncate } from '@/misc/truncate'; +import { StatusError } from '@/misc/fetch'; const logger = apLogger; @@ -116,6 +117,10 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Use export async function createPerson(uri: string, resolver?: Resolver): Promise<User> { if (typeof uri !== 'string') throw new Error('uri is not string'); + if (uri.startsWith(config.url)) { + throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); + } + if (resolver == null) resolver = new Resolver(); const object = await resolver.resolve(uri) as any; diff --git a/src/remote/activitypub/renderer/delete.ts b/src/remote/activitypub/renderer/delete.ts index 83b27fa866ccb9a244dd2c411379357f4af8ec99..176a6f7e27dfbc9a40d64b32099eb5e00681fdc3 100644 --- a/src/remote/activitypub/renderer/delete.ts +++ b/src/remote/activitypub/renderer/delete.ts @@ -4,5 +4,6 @@ import { User } from '@/models/entities/user'; export default (object: any, user: { id: User['id']; host: null }) => ({ type: 'Delete', actor: `${config.url}/users/${user.id}`, - object + object, + published: new Date().toISOString(), }); diff --git a/src/remote/activitypub/renderer/undo.ts b/src/remote/activitypub/renderer/undo.ts index f9082ffdfcbb32a52021f1dcf337b775c0bbf771..14115b788d3aad55fc376aa9a128b453b29cae90 100644 --- a/src/remote/activitypub/renderer/undo.ts +++ b/src/remote/activitypub/renderer/undo.ts @@ -7,6 +7,7 @@ export default (object: any, user: { id: User['id'] }) => { return { type: 'Undo', actor: `${config.url}/users/${user.id}`, - object + object, + published: new Date().toISOString(), }; }; diff --git a/src/remote/activitypub/renderer/update.ts b/src/remote/activitypub/renderer/update.ts index d9a8149af37e4b1553387844d91f5ac7c46f9f21..8bb415d117c1fb6720ea215ba37f62e03417700c 100644 --- a/src/remote/activitypub/renderer/update.ts +++ b/src/remote/activitypub/renderer/update.ts @@ -7,7 +7,8 @@ export default (object: any, user: { id: User['id'] }) => { actor: `${config.url}/users/${user.id}`, type: 'Update', to: [ 'https://www.w3.org/ns/activitystreams#Public' ], - object + object, + published: new Date().toISOString(), } as any; return activity; diff --git a/src/server/api/common/generate-muted-note-thread-query.ts b/src/server/api/common/generate-muted-note-thread-query.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e2cbd498b8602946be478cf6d4a518dbf5b15ef --- /dev/null +++ b/src/server/api/common/generate-muted-note-thread-query.ts @@ -0,0 +1,17 @@ +import { User } from '@/models/entities/user'; +import { NoteThreadMutings } from '@/models/index'; +import { Brackets, SelectQueryBuilder } from 'typeorm'; + +export function generateMutedNoteThreadQuery(q: SelectQueryBuilder<any>, me: { id: User['id'] }) { + const mutedQuery = NoteThreadMutings.createQueryBuilder('threadMuted') + .select('threadMuted.threadId') + .where('threadMuted.userId = :userId', { userId: me.id }); + + q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); + q.andWhere(new Brackets(qb => { qb + .where(`note.threadId IS NULL`) + .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`); + })); + + q.setParameters(mutedQuery.getParameters()); +} diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 74f7911bfe215c09bf9a00940606d852ce8e6e9e..ffaebd6c95fd18550b44082ceb99e8a9dec7f474 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -8,6 +8,7 @@ import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Brackets } from 'typeorm'; import { generateBlockedUserQuery } from '../../common/generate-block-query'; +import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-thread-query'; export const meta = { tags: ['notes'], @@ -67,6 +68,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); + generateMutedNoteThreadQuery(query, user); generateBlockedUserQuery(query, user); if (ps.visibility) { diff --git a/src/server/api/endpoints/notes/state.ts b/src/server/api/endpoints/notes/state.ts index 489902435d66f10e0b873c7148160da6d414d13f..b3913a5e79e40668c89c9a530bca12ab69a2efa5 100644 --- a/src/server/api/endpoints/notes/state.ts +++ b/src/server/api/endpoints/notes/state.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; -import { NoteFavorites, NoteWatchings } from '@/models/index'; +import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index'; export const meta = { tags: ['notes'], @@ -25,31 +25,45 @@ export const meta = { isWatching: { type: 'boolean' as const, optional: false as const, nullable: false as const - } + }, + isMutedThread: { + type: 'boolean' as const, + optional: false as const, nullable: false as const + }, } } }; export default define(meta, async (ps, user) => { - const [favorite, watching] = await Promise.all([ + const note = await Notes.findOneOrFail(ps.noteId); + + const [favorite, watching, threadMuting] = await Promise.all([ NoteFavorites.count({ where: { userId: user.id, - noteId: ps.noteId + noteId: note.id, }, take: 1 }), NoteWatchings.count({ where: { userId: user.id, - noteId: ps.noteId + noteId: note.id, }, take: 1 - }) + }), + NoteThreadMutings.count({ + where: { + userId: user.id, + threadId: note.threadId || note.id, + }, + take: 1 + }), ]); return { isFavorited: favorite !== 0, - isWatching: watching !== 0 + isWatching: watching !== 0, + isMutedThread: threadMuting !== 0, }; }); diff --git a/src/server/api/endpoints/notes/thread-muting/create.ts b/src/server/api/endpoints/notes/thread-muting/create.ts new file mode 100644 index 0000000000000000000000000000000000000000..2010d54331ed7e36d6eec80b481cfecdfa299acc --- /dev/null +++ b/src/server/api/endpoints/notes/thread-muting/create.ts @@ -0,0 +1,54 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { getNote } from '../../../common/getters'; +import { ApiError } from '../../../error'; +import { Notes, NoteThreadMutings } from '@/models'; +import { genId } from '@/misc/gen-id'; +import readNote from '@/services/note/read'; + +export const meta = { + tags: ['notes'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + noteId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2' + } + } +}; + +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); + throw e; + }); + + const mutedNotes = await Notes.find({ + where: [{ + id: note.threadId || note.id, + }, { + threadId: note.threadId || note.id, + }], + }); + + await readNote(user.id, mutedNotes); + + await NoteThreadMutings.insert({ + id: genId(), + createdAt: new Date(), + threadId: note.threadId || note.id, + userId: user.id, + }); +}); diff --git a/src/server/api/endpoints/notes/thread-muting/delete.ts b/src/server/api/endpoints/notes/thread-muting/delete.ts new file mode 100644 index 0000000000000000000000000000000000000000..05d5691870c264e9afcb37018f3c982e7bdf140d --- /dev/null +++ b/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -0,0 +1,40 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { getNote } from '../../../common/getters'; +import { ApiError } from '../../../error'; +import { NoteThreadMutings } from '@/models'; + +export const meta = { + tags: ['notes'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + noteId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a' + } + } +}; + +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); + throw e; + }); + + await NoteThreadMutings.delete({ + threadId: note.threadId || note.id, + userId: user.id, + }); +}); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 8c996bdba661dd26512805076a13ebed3594d619..69d854ab1a45e033a8524fe0392758c1feea88d9 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -10,13 +10,13 @@ import { resolveUser } from '@/remote/resolve-user'; import config from '@/config/index'; import { updateHashtags } from '../update-hashtag'; import { concat } from '@/prelude/array'; -import insertNoteUnread from './unread'; +import { insertNoteUnread } from '@/services/note/unread'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import { extractMentions } from '@/misc/extract-mentions'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm'; import { extractHashtags } from '@/misc/extract-hashtags'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note'; -import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings } from '@/models/index'; +import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings, NoteThreadMutings } from '@/models/index'; import { DriveFile } from '@/models/entities/drive-file'; import { App } from '@/models/entities/app'; import { Not, getConnection, In } from 'typeorm'; @@ -344,8 +344,15 @@ export default async (user: { id: User['id']; username: User['username']; host: // 通知 if (data.reply.userHost === null) { - nm.push(data.reply.userId, 'reply'); - publishMainStream(data.reply.userId, 'reply', noteObj); + const threadMuted = await NoteThreadMutings.findOne({ + userId: data.reply.userId, + threadId: data.reply.threadId || data.reply.id, + }); + + if (!threadMuted) { + nm.push(data.reply.userId, 'reply'); + publishMainStream(data.reply.userId, 'reply', noteObj); + } } } @@ -459,6 +466,11 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, channelId: data.channel ? data.channel.id : null, + threadId: data.reply + ? data.reply.threadId + ? data.reply.threadId + : data.reply.id + : null, name: data.name, text: data.text, hasPoll: data.poll != null, @@ -581,6 +593,15 @@ async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { + const threadMuted = await NoteThreadMutings.findOne({ + userId: u.id, + threadId: note.threadId || note.id, + }); + + if (threadMuted) { + continue; + } + const detailPackedNote = await Notes.pack(note, u, { detail: true }); diff --git a/src/services/note/unread.ts b/src/services/note/unread.ts index 4a9df6083ccc2aedbe96516d32e191a9d348ef26..29d2b54af84f74303ca97860e94de356504aa22f 100644 --- a/src/services/note/unread.ts +++ b/src/services/note/unread.ts @@ -1,10 +1,10 @@ import { Note } from '@/models/entities/note'; import { publishMainStream } from '@/services/stream'; import { User } from '@/models/entities/user'; -import { Mutings, NoteUnreads } from '@/models/index'; +import { Mutings, NoteThreadMutings, NoteUnreads } from '@/models/index'; import { genId } from '@/misc/gen-id'; -export default async function(userId: User['id'], note: Note, params: { +export async function insertNoteUnread(userId: User['id'], note: Note, params: { // NOTE: isSpecifiedãŒtrueãªã‚‰isMentionedã¯å¿…ãšfalse isSpecified: boolean; isMentioned: boolean; @@ -17,6 +17,13 @@ export default async function(userId: User['id'], note: Note, params: { if (mute.map(m => m.muteeId).includes(note.userId)) return; //#endregion + // スレッドミュート + const threadMute = await NoteThreadMutings.findOne({ + userId: userId, + threadId: note.threadId || note.id, + }); + if (threadMute) return; + const unread = { id: genId(), noteId: note.id, diff --git a/test/thread-mute.ts b/test/thread-mute.ts new file mode 100644 index 0000000000000000000000000000000000000000..95601cd903515a451ed7778bcb542f7adb3ee510 --- /dev/null +++ b/test/thread-mute.ts @@ -0,0 +1,103 @@ +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils'; + +describe('Note thread mute', () => { + let p: childProcess.ChildProcess; + + let alice: any; + let bob: any; + let carol: any; + + before(async () => { + p = await startServer(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + carol = await signup({ username: 'carol' }); + }); + + after(async () => { + await shutdownServer(p); + }); + + it('notes/mentions ã«ãƒŸãƒ¥ãƒ¼ãƒˆã—ã¦ã„るスレッドã®æŠ•ç¨¿ãŒå«ã¾ã‚Œãªã„', async(async () => { + const bobNote = await post(bob, { text: '@alice @carol root note' }); + const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); + + const res = await request('/notes/mentions', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === carolReply.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === carolReplyWithoutMention.id), false); + })); + + it('ミュートã—ã¦ã„るスレッドã‹ã‚‰ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ã•ã‚Œã¦ã‚‚ã€hasUnreadMentions ㌠true ã«ãªã‚‰ãªã„', async(async () => { + // 状態リセット + await request('/i/read-all-unread-notes', {}, alice); + + const bobNote = await post(bob, { text: '@alice @carol root note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + + const res = await request('/i', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(res.body.hasUnreadMentions, false); + })); + + it('ミュートã—ã¦ã„るスレッドã‹ã‚‰ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ã•ã‚Œã¦ã‚‚ã€ã‚¹ãƒˆãƒªãƒ¼ãƒ ã« unreadMention イベントãŒæµã‚Œã¦ã“ãªã„', () => new Promise(async done => { + // 状態リセット + await request('/i/read-all-unread-notes', {}, alice); + + const bobNote = await post(bob, { text: '@alice @carol root note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + let fired = false; + + const ws = await connectStream(alice, 'main', async ({ type, body }) => { + if (type === 'unreadMention') { + if (body === bobNote.id) return; + fired = true; + } + }); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 5000); + })); + + it('i/notifications ã«ãƒŸãƒ¥ãƒ¼ãƒˆã—ã¦ã„るスレッドã®é€šçŸ¥ãŒå«ã¾ã‚Œãªã„', async(async () => { + const bobNote = await post(bob, { text: '@alice @carol root note' }); + const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); + + const res = await request('/i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReply.id), false); + assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReplyWithoutMention.id), false); + + // NOTE: bobã®æŠ•ç¨¿ã¯ã‚¹ãƒ¬ãƒƒãƒ‰ãƒŸãƒ¥ãƒ¼ãƒˆå‰ã«è¡Œã‚ã‚ŒãŸãŸã‚通知ã«å«ã¾ã‚Œã¦ã„ã¦ã‚‚よㄠ+ })); +}); diff --git a/test/utils.ts b/test/utils.ts index 1a0c54463dd88cf2033e6dd75d5b06a1f19c0cf9..54bcf65ab15e3da9c66da3047e61a67d7966087c 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as WebSocket from 'ws'; +import * as misskey from 'misskey-js'; import fetch from 'node-fetch'; const FormData = require('form-data'); import * as childProcess from 'child_process'; @@ -52,7 +53,7 @@ export const signup = async (params?: any): Promise<any> => { return res.body; }; -export const post = async (user: any, params?: any): Promise<any> => { +export const post = async (user: any, params?: misskey.Endpoints['notes/create']['req']): Promise<misskey.entities.Note> => { const q = Object.assign({ text: 'test' }, params);