diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e8c2890d..66fd443668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,16 +10,28 @@ --> -## 12.x.x (unreleased) - -### Improvements +## 12.103.1 (2022/02/02) ### Bugfixes -- 投稿のNSFW画像を表示したあとにリアクションが更新されると画像が非表示になる問題を修正 -- 「クリップ」ページが開かない問題を修正 -- トレンドウィジェットが動作しないのを修正 -- リアクション設定で絵文字ピッカーが開かないのを修正 -- DMページでメンションが含まれる問題を修正 +- クライアント: ツールチップの表示位置が正しくない問題を修正 + +## 12.103.0 (2022/02/02) + +### Improvements +- クライアント: 連合インスタンスページからインスタンス情報再取得を行えるように + +### Bugfixes +- クライアント: 投稿のNSFW画像を表示したあとにリアクションが更新されると画像が非表示になる問題を修正 +- クライアント: 「クリップ」ページが開かない問題を修正 +- クライアント: トレンドウィジェットが動作しないのを修正 +- クライアント: フェデレーションウィジェットが動作しないのを修正 +- クライアント: リアクション設定で絵文字ピッカーが開かないのを修正 +- クライアント: DMページでメンションが含まれる問題を修正 +- クライアント: 投稿フォームのハッシュタグ保持フィールドが動作しない問題を修正 +- クライアント: サイドビューが動かないのを修正 +- クライアント: ensure that specified users does not get duplicates +- Add `img-src` and `media-src` directives to `Content-Security-Policy` for + files and media proxy ## 12.102.1 (2022/01/27) ### Bugfixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27f5598a66..662fa709b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ We're glad you're interested in contributing Misskey! In this document you will **ℹ️ Important:** This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.** Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\ -The accuracy of translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. +The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language. It will also allow the reader to use the translation tool of their preference if necessary. ## Issues diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js index aca44ef15d..7d27b649f4 100644 --- a/cypress/integration/basic.js +++ b/cypress/integration/basic.js @@ -176,3 +176,7 @@ describe('After user singed in', () => { cy.contains('Hello, Misskey!'); }); }); + +// TODO: 投稿フォームの公開範囲指定のテスト +// TODO: 投稿フォームのファイル添付のテスト +// TODO: 投稿フォームのハッシュタグ保持フィールドのテスト diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index ed97d539c0..02f18cd1e9 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -1 +1,510 @@ --- +_lang_: "বাংলা" +headlineMisskey: "নোট ব্যাবহার করে সংযুক্ত নেটওয়ার্ক" +introMisskey: "স্বাগতম! মিসকি একটি ওপেন সোর্স, ডিসেন্ট্রালাইজড মাইক্রোব্লগিং পরিষেবা। \n\"নোট\" তৈরির মাধ্যমে যা ঘটছে তা সবার সাথে শেয়ার করুন 📡\n\"রিঅ্যাকশন\" গুলির মাধ্যমে যেকোনো নোট সম্পর্কে আপনার অনুভূতি ব্যাক্ত করতে পারেন 👍\nএকটি নতুন দুনিয়া ঘুরে দেখুন 🚀\n" +monthAndDay: "{day}/{month}" +search: "খুঁজুন" +notifications: "বিজ্ঞপ্তি" +username: "ব্যবহারকারীর নাম" +password: "পাসওয়ার্ড" +forgotPassword: "পাসওয়ার্ড ভুলে গেছেন" +fetchingAsApObject: "ফেডিভার্স থেকে খবর আনা হচ্ছে..." +ok: "ঠিক" +gotIt: "বুঝেছি" +cancel: "বাতিল" +enterUsername: "ইউজারনেম লিখুন" +renotedBy: "{user} রিনোট করেছেন" +noNotes: "কোন নোট নেই" +noNotifications: "কোনো বিজ্ঞপ্তি নেই" +instance: "ইন্সট্যান্স" +settings: "সেটিংস" +basicSettings: "সাধারণ সেটিংস" +otherSettings: "অন্যান্য সেটিংস" +openInWindow: "নতুন উইন্ডোতে খুলা" +profile: "প্রোফাইল" +timeline: "টাইমলাইন" +noAccountDescription: "এই ব্যাবহারকারীর কোন বায়ো নেই" +login: "প্রবেশ করুন" +loggingIn: "প্রবেশ করা হচ্ছে..." +logout: "লগআউট" +signup: "নিবন্ধন করুন" +uploading: "আপলোড হচ্ছ …" +save: "সংরক্ষণ" +users: "ব্যবহারকারীগণ" +addUser: "ব্যবহারকারী যোগ করুন" +favorite: "পছন্দ" +favorites: "পছন্দগুলি" +unfavorite: "পছন্দ না" +favorited: "পছন্দ করা হয়েছে" +alreadyFavorited: "ইতিমধ্যে পছন্দ করা হয়েছে" +cantFavorite: "পছন্দ করা যায়নি" +pin: "পিন করা" +unpin: "পিন সরান" +copyContent: "বিষয়বস্তু কপি করুন" +copyLink: "লিঙ্ক কপি করুন" +delete: "মুছুন" +deleteAndEdit: "মুছুন এবং সম্পাদনা করুন" +deleteAndEditConfirm: "আপনি কি এই নোটটি মুছে এটি সম্পাদনা করার বিষয়ে নিশ্চিত? আপনি এটির সমস্ত রিঅ্যাকশন, রিনোট এবং জবাব হারাবেন।" +addToList: "লিস্ট এ যোগ করুন" +sendMessage: "একটি বার্তা পাঠান" +copyUsername: "ব্যবহারকারীর নাম কপি করুন" +searchUser: "ব্যবহারকারী খুঁজুন..." +reply: "জবাব" +loadMore: "আরও দেখুন" +showMore: "আরও দেখুন" +youGotNewFollower: "আপনাকে অনুসরণ করছে" +receiveFollowRequest: "অনুসরণ করার জন্য অনুরোধ পাওয়া গেছে" +followRequestAccepted: "অনুসরণ করার অনুরোধ গৃহীত হয়েছে" +mention: "উল্লেখ" +mentions: "উল্লেখসমূহ" +directNotes: "ডাইরেক্ট নোটগুলি" +importAndExport: "আমদানি এবং রপ্তানি" +import: "আমদানি করুণ" +export: "রপ্তানি" +files: "ফাইলগুলি" +download: "ডাউনলোড" +driveFileDeleteConfirm: "আপনি কি নিশ্চিত যে আপনি \"{name}\" ডিলিট করতে চান? যে সকল নোটের সাথে এই ফাইলটি সংযুক্ত সেগুলোও ডিলিট করা হবে।" +unfollowConfirm: "{name} কে আনফলোও করার ব্যাপারে নিশ্চিত?" +exportRequested: "আপনার তথ্যসমূহ রপ্তানির জন্য অনুরোধ করেছেন। এতে কিছু সময় লাগতে পারে। রপ্তানি সম্পন্ন হলে তা আপনার ড্রাইভে সংরক্ষিত হবে।" +importRequested: "আপনার তথ্যসমূহ আমদানির জন্য অনুরোধ করেছেন। এতে কিছু সময় লাগতে পারে। " +lists: "লিস্ট" +noLists: "কোন লিস্ট নেই" +note: "নোট" +notes: "নোটগুলি" +following: "অনুসরণ করা হচ্ছে" +followers: "অনুসরণকারী" +followsYou: "আপনাকে অনুসরণ করে" +createList: "লিস্ট তৈরি করুন" +manageLists: "লিস্ট ব্যাবস্থাপনা" +error: "সমস্যা" +somethingHappened: "একটি ত্রুটি হয়েছে" +retry: "আবার চেষ্টা করুন" +pageLoadError: "পেজ লোড করা যায়নি" +pageLoadErrorDescription: "এটি সাধারনত নেটওয়ার্কের সমস্যার বা ব্রাউজার ক্যাশের কারণে ঘটে থাকে। ব্রাউজার এর ক্যাশ পরিষ্কার করুন এবং একটু পর আবার চেষ্টা করুন। " +serverIsDead: "এই সার্ভার বর্তমানে সাড়া দিচ্ছে না। একটু পরে আবার চেষ্টা করুন।" +youShouldUpgradeClient: "এই পেজ দেখার জন্য আপনার ব্রাউজার রিফ্রেশ করে ক্লায়েন্ট আপডেট করুন। " +enterListName: "লিস্টের নাম লিখুন" +privacy: "গোপনীয়তা" +makeFollowManuallyApprove: "অনুসরণ করার অনুরোধগুলি গৃহীত হওয়ার জন্য আপনার অনুমতি লাগবে" +defaultNoteVisibility: "ডিফল্ট দৃশ্যমান্যতা" +follow: "অনুসরণ" +followRequest: "অনুসরণ করার অনুরোধ" +followRequests: "অনুসরণ করার অনুরোধসমূহ" +unfollow: "অনুসরণ বাতিল" +followRequestPending: "অনুসরণ করার অনুরোধ বিচারাধীন" +enterEmoji: "ইমোজি প্রবেশ করান" +renote: "রিনোট" +unrenote: "রিনোট সরান " +renoted: "রিনোট করা হয়েছে" +cantRenote: "এই নোটটি রিনোট করা যাবে না।" +cantReRenote: "রিনোটকে রিনোট করা যাবে না।" +quote: "উদ্ধৃতি" +pinnedNote: "পিন করা নোট" +pinned: "পিন করা" +you: "আপনি" +clickToShow: "দেখার জন্য ক্লিক করুন" +sensitive: "সংবেদনশীল বিষয়বস্তু" +add: "যুক্ত করুন" +reaction: "প্রতিক্রিয়া" +reactionSetting: "রিঅ্যাকশন পিকারে যেসকল প্রতিক্রিয়া দেখানো হবে" +reactionSettingDescription2: "পুনরায় সাজাতে টেনে আনুন, মুছতে ক্লিক করুন, যোগ করতে + টিপুন।" +rememberNoteVisibility: "নোটের দৃশ্যমান্যতার সেটিংস মনে রাখুন" +attachCancel: "অ্যাটাচমেন্ট সরান " +markAsSensitive: "সংবেদনশীল হিসাবে চিহ্নিত করুন" +unmarkAsSensitive: "সংবেদনশীল চিহ্ন সরান" +enterFileName: "ফাইলের নাম লিখুন" +mute: "মিউট" +unmute: "আনমিউট" +block: "ব্লক" +unblock: "ব্লক সরান" +suspend: "স্থগিত করা" +unsuspend: "অস্থগিত করা" +blockConfirm: "ব্লক করতে চান?" +unblockConfirm: "ব্লক সরাতে চান?" +suspendConfirm: "স্থগিত করতে চান?" +unsuspendConfirm: "অস্থগিত করতে চান?" +selectList: "লিস্ট নির্বাচন করুন" +selectAntenna: "অ্যান্টেনা নির্বাচন করুন" +selectWidget: "উইজেট নির্বাচন করুন" +editWidgets: "উইজেট সম্পাদনা করুন" +editWidgetsExit: "সম্পাদনা শেষ করুন" +customEmojis: "স্বনির্ধারিত ইমোজিগুলি" +emoji: "ইমোজি" +emojis: "ইমোজিগুলি" +emojiName: "ইমোজির নাম" +emojiUrl: "ইমোজির URL" +addEmoji: "ইমোজি যুক্ত করুন" +settingGuide: "সুপারিশকৃত সেটিংস" +cacheRemoteFiles: "রিমোট ফাইলসমুহ ক্যাশ করুন" +cacheRemoteFilesDescription: "যখন এই অপশনটি বন্ধ থাকে তখন রিমোট ফাইল সমূহ সরাসরি রিমোট ইন্সট্যান্স থেকে লোড করা হয়। এই অপশনটি বন্ধ করলে স্টোরেজ এর ব্যাবহার কমবে তবে থাম্বনেইল তৈরি না করার কারণে নেটওয়ার্ক ব্যান্ডউইথ বেশী লাগবে। " +flagAsBot: "বট হিসাবে চিহ্নিত করুন" +flagAsBotDescription: "এই অ্যাকাউন্টটি যদি একটি প্রোগ্রাম দ্বারা পরিচালিত হয়, তাহলে এই অপশনটি চালু করুন। ইন্টারঅ্যাকশান চেইনিং রোধ করতে, মিস্কির সিস্টেম পরিচালনাকে বট-বান্ধব করতে এবং অন্যান্য ডেভেলপারদের সাহায্য করতে আপনার বট এ এই অপশনটি চালু করুন৷" +flagAsCat: "বিড়াল হিসাবে চিহ্নিত করুন" +flagAsCatDescription: "অ্যাকাউন্টটিকে বিড়াল হিসাবে চিহ্নিত করার জন্য অপশনটি চালু করুন।" +autoAcceptFollowed: "আপনি যেসব অ্যাকাউন্ট অনুসরণ করেন, স্বয়ংক্রিয়ভাবে তাদের অনুসরণের অনুরধ স্বীকার করুন" +addAccount: "অ্যাকাউন্ট যোগ করুন" +loginFailed: "প্রবেশ করা যায়নি" +showOnRemote: "রিমোট সার্ভারে দেখুন" +general: "সাধারণ" +wallpaper: "ওয়ালপেপার" +setWallpaper: "ওয়ালপেপার সেট করুন" +removeWallpaper: "ওয়ালপেপার সরান" +searchWith: "খুঁজুন: {q}" +youHaveNoLists: "আপনার কোন লিস্ট নেই" +followConfirm: "{name} কে ফলোও করার ব্যাপারে নিশ্চিত?" +proxyAccount: "প্রক্সি অ্যাকাউন্ট" +proxyAccountDescription: "একটি প্রক্সি অ্যাকাউন্ট এমন একটি অ্যাকাউন্ট যা নির্দিষ্ট শর্তে ব্যবহারকারীদের জন্য রিমোট অনুসরণকারী হিসাবে কাজ করে। উদাহরণস্বরূপ, যখন একজন ব্যবহারকারী একটি রিমোট ব্যবহারকারীকে তালিকাভুক্ত করে, তখন ক্রিয়াকলাপের দৃষ্টান্তে বিতরণ করা হবে না যদি না কেউ তালিকাভুক্ত ব্যবহারকারীকে অনুসরণ করে, তাই প্রক্সি অ্যাকাউন্ট দ্বারা তাকে অনুসরণ করা হবে।" +host: "হোস্ট" +selectUser: "ব্যবহারকারী নির্বাচন করুন" +recipient: "প্রতি" +annotation: "মন্তব্য" +federation: "ফেডিভার্স" +instances: "ইন্সট্যান্স" +registeredAt: "যোগ দিয়েছেন" +latestRequestSentAt: "শেষ রিকুয়েস্ট পাঠানো হয়েছে" +latestRequestReceivedAt: "শেষ রিকুয়েস্ট গৃহীত হয়েছে" +latestStatus: "সর্বশেষ অবস্থা" +storageUsage: "স্টোরেজের ব্যাবহার" +charts: "চার্ট" +perHour: "ঘন্টা প্রতি" +perDay: "দৈনিক" +stopActivityDelivery: "অ্যাক্টিভিটি পাঠানো বন্ধ করুন" +blockThisInstance: "ইন্সট্যান্স ব্লক করুন" +operations: "ক্রিয়াকলাপ" +software: "সফটওয়্যার" +version: "সংস্করণ" +metadata: "মেটাডাটা" +withNFiles: "{n} টি ফাইল" +monitor: "মনিটর" +jobQueue: "জব কিউ" +cpuAndMemory: "সিপিউ এবং মেমরি" +network: "নেটওয়ার্ক" +disk: "ডিস্ক" +instanceInfo: "ইন্সট্যান্সের তথ্য" +statistics: "পরিসংখ্যান" +clearQueue: "কিউ পরিষ্কার করুন" +clearQueueConfirmTitle: "আপনি কি কিউ পরিষ্কার করার ব্যাপারে নিশ্চিত?" +clearQueueConfirmText: "বিতরণ না করা নোট আর বিতরণ করা হবে না। সাধারণত আপনার এটি করার দরকার নেই।" +clearCachedFiles: "ক্যাশ পরিষ্কার করুন" +clearCachedFilesConfirm: "আপনি কি ক্যাশ পরিষ্কার করার ব্যাপারে নিশ্চিত?" +blockedInstances: "ব্লককৃত ইন্সট্যান্সসমুহ" +blockedInstancesDescription: "আপনি যে ইন্সট্যান্সগুলি ব্লক করতে চান তার হোস্টনেমগুলি প্রত্যেকটি আলাদা লাইনে লিখুন। ব্লককৃত ইন্সট্যান্সগুলি এই ইন্সট্যান্সের সাথে যোগাযোগ করতে পারবেনা৷" +muteAndBlock: "মিউট এবং ব্লকগুলি" +mutedUsers: "নিঃশব্দকৃত ব্যবহারকারী" +blockedUsers: "যাদের ব্লক করা হয়েছে" +noUsers: "কোন ব্যাবহারকারী নেই" +editProfile: "প্রোফাইল সম্পাদনা করুন" +noteDeleteConfirm: "আপনি কি নোট ডিলিট করার ব্যাপারে নিশ্চিত?" +pinLimitExceeded: "আপনি আর কোন নোট পিন করতে পারবেন না" +intro: "Misskey এর ইন্সটলেশন সম্পন্ন হয়েছে!দয়া করে অ্যাডমিন ইউজার তৈরি করুন।" +done: "সম্পন্ন" +processing: "প্রক্রিয়াধীন..." +preview: "পূর্বরূপ দেখুন" +default: "পূর্বনির্ধারিত" +noCustomEmojis: "কোন ইমোজি নাই" +noJobs: "কোন জব নাই" +federating: "ফেডারেট করা হচ্ছে" +blocked: "ব্লক করা হয়েছে" +suspended: "স্থগিত করা হয়েছে" +all: "সবগুলো" +subscribing: "সদস্যতা নেয়া হচ্ছে" +publishing: "প্রকাশ করা হচ্ছে" +notResponding: "সাড়া নেই" +instanceFollowing: "ইন্সট্যান্স অনুসরণ করা হচ্ছে" +instanceFollowers: "ইন্সট্যান্স অনুসরণকারী" +instanceUsers: "ইন্সট্যান্স ব্যাবহারকারী" +changePassword: "পাসওয়ার্ড পরিবর্তন করুন" +security: "নিরাপত্তা" +retypedNotMatch: "ইনপুট মেলে না।" +currentPassword: "বর্তমান পাসওয়ার্ড" +newPassword: "নতুন পাসওয়ার্ড" +newPasswordRetype: "নতুন পাসওয়ার্ড (পুনরায় লিখুন)" +attachFile: "ফাইল সংযুক্ত করুন" +more: "আরও!" +featured: "হাইলাইট" +usernameOrUserId: "ব্যাবহারকারীর নাম বা ব্যাবহারকারী ID" +noSuchUser: "কোন ব্যবহারকারী খুঁজে পাওয়া যায়নি" +lookup: "খুঁজে দেখো" +announcements: "ঘোষণা" +imageUrl: "চিত্রের URL" +remove: "মুছুন" +removed: "সরানো হয়েছে" +removeAreYouSure: "আপনি কি \"{x}\" সরানোর ব্যাপারে নিশ্চিত?" +deleteAreYouSure: "আপনি কি \"{x}\" সরানোর ব্যাপারে নিশ্চিত?" +resetAreYouSure: "রিসেট করার ব্যাপারে নিশ্চিত?" +saved: "সংরক্ষিত হয়েছে" +messaging: "চ্যাট" +upload: "আপলোড" +keepOriginalUploading: "আসল ছবি রাখুন" +keepOriginalUploadingDescription: "ছবিটি আপলোড করার সময় আসল সংস্করণটি রাখুন। অপশনটি বন্ধ থাকলে, আপলোডের সময় ওয়েব প্রকাশনার জন্য ছবি ব্রাউজারে তৈরি করা হবে।" +fromDrive: "ড্রাইভ হতে" +fromUrl: "URL হতে" +uploadFromUrl: "URL হতে আপলোড" +uploadFromUrlDescription: "যে ফাইলটি আপলোড করতে চান, সেটির URL" +uploadFromUrlRequested: "আপলোড অনুরোধ করা হয়েছে" +uploadFromUrlMayTakeTime: "URL হতে আপলোড হতে কিছু সময় লাগতে পারে।" +explore: "ঘুরে দেখুন" +messageRead: "পড়া" +noMoreHistory: "আর কোন ইতিহাস নেই" +startMessaging: "চ্যাট শুরু করুন" +nUsersRead: "{n} জন পড়েছেন" +agreeTo: "{0} এর প্রতি আমি সম্মত" +tos: "পরিষেবার শর্তাদি" +start: "শুরু করুন" +home: "মূল পাতা" +remoteUserCaution: "এই ব্যাবহারকারী রিমোট ইন্সট্যান্সের, নিম্নক্ত তথ্য অসম্পূর্ণ হতে পারে।" +activity: "কার্যকলাপ" +images: "ছবি" +birthday: "জন্মদিন" +yearsOld: "{age} বছর" +registeredDate: "যোগদানের তারিখ" +location: "অবস্থান" +theme: "থিম" +themeForLightMode: "লাইট মোডের থিম" +themeForDarkMode: "ডার্ক মোডের থিম" +light: "আলোকিত" +dark: "অন্ধকার" +lightThemes: "আলোকিত থিম" +darkThemes: "অন্ধকার থিম" +syncDeviceDarkMode: "ডিভাইসের সেটিং অনুযায়ী ডার্ক মোড সেট করুন" +drive: "ড্রাইভ" +fileName: "ফাইলের নাম" +selectFile: "ফাইল নির্বাচন করুন" +selectFiles: "ফাইল নির্বাচন করুন" +selectFolder: "ফোল্ডার নির্বাচন করুন" +selectFolders: "ফোল্ডার নির্বাচন করুন" +renameFile: "ফাইল পুনঃনামকরন" +folderName: "ফোল্ডারের নাম" +createFolder: "ফোল্ডার তৈরি করুন" +renameFolder: "ফোল্ডার পুনঃনামকরন" +deleteFolder: "ফোল্ডার মুছুন" +addFile: "ফাইল যোগ করুন" +emptyDrive: "আপনার ড্রাইভ খালি" +emptyFolder: "এই ফোল্ডার খালি" +unableToDelete: "মুছে ফেলা যায়নি" +inputNewFileName: "ফাইলের নতুন নাম লিখুন" +inputNewDescription: "নতুন ক্যাপশন লিখুন" +inputNewFolderName: "ফোল্ডারের নতুন নাম লিখুন" +circularReferenceFolder: "গন্তব্য ফোল্ডারটি আপনি যে ফোল্ডারটি সরাতে চান তার একটি সাবফোল্ডার।" +hasChildFilesOrFolders: "এই ফোল্ডারটি খালি না হওয়ায় ডিলিট করা যায়নি।" +copyUrl: "URL কপি করুন" +rename: "পুনঃনামকরণ" +avatar: "প্রোফাইল ছবি" +banner: "ব্যানার" +nsfw: "সংবেদনশীল বিষয়বস্তু" +whenServerDisconnected: "সার্ভারের সাথে সংযোগ বিচ্ছিন্ন হয়ে গেলে" +disconnectedFromServer: "সার্ভার থেকে সংযোগ বিচ্ছিন্ন হয়েছে" +reload: "আবার লোড করুন" +doNothing: "কিছু করবেন না" +reloadConfirm: "আপনি কি রিলোড করতে চান?" +watch: "দেখুন" +unwatch: "দেখা বন্ধ করুন " +accept: "অনুমোদন" +reject: "প্রত্যাখ্যান" +normal: "স্বাভাবিক" +instanceName: "ইন্সট্যান্সের নাম" +instanceDescription: "ইন্সট্যান্সের বর্ণনা" +maintainerName: "মেইনটেইনার" +maintainerEmail: "মেইনটেইনারের ইমেইল" +tosUrl: "ব্যবহারের শর্তাবলীর URL" +thisYear: "বছর" +thisMonth: "মাস" +today: "আজ" +dayX: "{day}" +monthX: "{month}" +yearX: "{year}" +pages: "পৃষ্ঠা" +integration: "ইন্টিগ্রেশন" +connectService: "সংযুক্ত করুন" +disconnectService: "সংযোগ বিচ্ছিন্ন করুন" +enableLocalTimeline: "স্থানীয় টাইমলাইন চালু করুন" +enableGlobalTimeline: "গ্লোবাল টাইমলাইন চালু করুন" +disablingTimelinesInfo: "আপনি এই টাইমলাইনগুলি বন্ধ করলেও প্রশাসক এবং মডারেটররা এই টাইমলাইনগুলি ব্যাবহার করতে পারবে" +registration: "নিবন্ধন" +enableRegistration: "নতুন ব্যাবহারকারী নিবন্ধন চালু করুন" +invite: "আমন্ত্রণ" +proxyRemoteFiles: "রিমোট ফাইলসমুহ প্রক্সি করুন" +proxyRemoteFilesDescription: "যখন এই সেটিংটি চালু থাকে, তখন অসংরক্ষিত বা অতিরিক্ত ক্ষমতার কারণে দূরবর্তী ফাইলগুলিকে স্থানীয়ভাবে প্রক্সি করা হবে এবং থাম্বনেলগুলিও তৈরি করা হবে৷ সার্ভার স্টোরেজ ব্যাবহার করে না," +driveCapacityPerLocalAccount: "প্রত্যেক স্থানীয় ব্যাবহারকারীর জন্য ড্রাইভের জায়গা" +driveCapacityPerRemoteAccount: "প্রত্যেক রিমোট ব্যাবহারকারীর জন্য ড্রাইভের জায়গা" +inMb: "মেগাবাইটে লিখুন" +iconUrl: "আইকনের URL (ফ্যাভিকন, ইত্যাদি)" +bannerUrl: "ব্যানার ছবির URL" +backgroundImageUrl: "পটভূমির চিত্রের URL" +basicInfo: "আপনার ব্যক্তিগত তথ্য" +pinnedUsers: "পিন করা ব্যাবহারকারীগণ" +pinnedUsersDescription: "আপনি যেসব ব্যবহারকারীদের \"ঘুরে দেখুন\" পৃষ্ঠায় পিন করতে চান তাদের বর্ণনা করুন, প্রত্যেকের বর্ণনা আলাদা লাইনে লিখুন" +pinnedPages: "পিন করা পৃষ্ঠাসুমহ" +pinnedPagesDescription: "আপনি যেসকল পৃষ্ঠাসমূহকে \"ঘুরে দেখুন\" পৃষ্ঠায় পিন করতে চান তাদের বর্ণনা করুন, প্রত্যেকের বর্ণনা আলাদা লাইনে লিখুন" +pinnedClipId: "পিনকৃত ক্লিপের ID" +pinnedNotes: "পিন করা নোট" +hcaptcha: "hCaptcha" +enableHcaptcha: "hCaptcha চালু করুন" +hcaptchaSiteKey: "সাইট কী" +hcaptchaSecretKey: "সিক্রেট কী" +recaptcha: "reCAPTCHA" +enableRecaptcha: "reCAPTCHA চালু করুন" +recaptchaSiteKey: "সাইট কী" +antennas: "অ্যান্টেনা" +manageAntennas: "অ্যান্টেনা ব্যবস্থাপনা" +name: "নাম" +antennaSource: "অ্যান্টেনার উৎস" +antennaKeywords: "যেসব কীওয়ার্ড দেখা হবে" +antennaExcludeKeywords: "যেসব কীওয়ার্ড দেখা হবে না" +antennaKeywordsDescription: "স্পেস দিয়ে আলাদা করলে AND শর্ত তৈরি হবে এবং আলাদা লাইনে লিখলে OR শর্ত তৈরি হবে।" +notifyAntenna: "নতুন নোট সম্পর্কে অবহিত করুন" +withFileAntenna: "শুধুমাত্র ফাইলযুক্ত নোট" +enableServiceworker: "ServiceWorker চালু করুন" +antennaUsersDescription: "প্রত্যেক লাইনে একজন ব্যবহারকারীর নাম লিখুন" +caseSensitive: "ছোট হাতের এবং বড় হাতের অক্ষর নির্দিষ্ট করুন" +withReplies: "জবাবসমুহ যুক্ত করুন" +connectedTo: "আপনি নিম্নলিখিত অ্যাকাউন্টের সাথে সংযুক্ত" +notesAndReplies: "নোটসমূহ এবং জবাবগুলি" +withFiles: "ফাইলগুলি যুক্ত করুন" +silence: "নীরব" +silenceConfirm: "আপনি কি এই ব্যাবহারকারীকের নীরব করতে চান?" +unsilence: "সরব" +unsilenceConfirm: "আপনি কি এই ব্যাবহারকারীকের সরব করতে চান?" +popularUsers: "জনপ্রিয় ব্যবহারকারীগন" +recentlyUpdatedUsers: "সম্প্রতি পোস্ট করা ব্যবহারকারীগন" +recentlyRegisteredUsers: "নতুন যোগ দেওয়া ব্যবহারকারীগন" +recentlyDiscoveredUsers: "নতুন খুঁজে পাওয়া ব্যবহারকারীগন" +exploreUsersCount: "{count} জন ব্যাবহারকারী" +exploreFediverse: "Fediverse ঘুরে দেখুন" +popularTags: "জনপ্রিয় ট্যাগগুলি" +userList: "লিস্ট" +about: "আপনার সম্পর্কে" +aboutMisskey: "Misskey সম্পর্কে" +administrator: "প্রশাসক" +token: "টোকেন" +twoStepAuthentication: "২-ধাপ প্রমাণীকরণ" +moderator: "মডারেটর" +nUsersMentioned: "{n} জনকে উল্লেখ করা হয়েছে" +securityKey: "সিকিউরিটি কী" +securityKeyName: "কী'র নাম" +registerSecurityKey: "সিকিউরিটি কী নিবন্ধন করুন" +lastUsed: "শেষ ব্যাবহার করা হয়েছে" +unregister: "নিবন্ধনমুক্ত হন" +passwordLessLogin: "পাসওয়ার্ড-বিহীন লগইন সেট আপ করুন" +resetPassword: "পাসওয়ার্ড রিসেট করুন" +newPasswordIs: "নতুন পাসওয়ার্ড হচ্ছে \"{password}\"" +reduceUiAnimation: "UI অ্যানিমেশন কমান" +share: "শেয়ার" +notFound: "পাওয়া যায়নি" +notFoundDescription: "এই URL-এর সাথে সম্পর্কিত কোনো পৃষ্ঠা নেই।" +uploadFolder: "আপলোডের জন্য ডিফল্ট ফোল্ডার" +cacheClear: "ক্যাশ পরিষ্কার করুন" +markAsReadAllNotifications: "সমস্ত বিজ্ঞপ্তিগুলি পঠিত হিসাবে চিহ্নিত করুন" +markAsReadAllUnreadNotes: "সমস্ত নোটগুলি পঠিত হিসাবে চিহ্নিত করুন" +invites: "আমন্ত্রণ" +invitations: "আমন্ত্রণ" +useOsNativeEmojis: "অপারেটিং সিস্টেমের নেটিভ ইমোজি ব্যবহার করুন" +disableDrawer: "ড্রয়ার মেনু প্রদর্শন করবেন না" +youHaveNoGroups: "আপনার কোন গ্রুপ নেই " +joinOrCreateGroup: "একটি বিদ্যমান গ্রুপের আমন্ত্রণ পান বা একটি নতুন গ্রুপ তৈরি করুন৷" +noHistory: "কোনো ইতিহাস নেই" +signinHistory: "প্রবেশ করার ইতিহাস" +disableAnimatedMfm: "অ্যানিমেটেড MFM অক্ষম করুন" +doing: "প্রক্রিয়া করছে..." +category: "বিভাগ" +tags: "ট‍্যাগসমূহ" +docSource: "ডকুমেন্টের উৎস" +createAccount: "অ্যাকাউন্ট তৈরি করুন" +existingAccount: "বিদ্যমান অ্যাকাউন্ট" +regenerate: "আবারও তৈরি করুন" +fontSize: "ফন্টের আকার" +noFollowRequests: "আপনার কোন ফলোও রিকুয়েস্ট নেই" +openImageInNewTab: "ছবি নতুন ট্যাবে খুলুন" +dashboard: "ড্যাশবোর্ড" +local: "স্থানীয়" +remote: "রিমোট" +total: "মোট" +weekOverWeekChanges: "গত সপ্তাহে" +dayOverDayChanges: "গতকাল" +appearance: "অবয়ব" +clientSettings: "ক্লায়েন্ট সেটিংস" +accountSettings: "অ্যাকাউন্ট সেটিংস" +promotion: "প্রমোশন" +promote: "প্রচার করুন" +numberOfDays: "দিনের সংখ্যা" +hideThisNote: "নোটটি লুকান" +smtpHost: "হোস্ট" +smtpUser: "ব্যবহারকারীর নাম" +smtpPass: "পাসওয়ার্ড" +clearCache: "ক্যাশ পরিষ্কার করুন" +info: "আপনার সম্পর্কে" +user: "ব্যবহারকারীগণ" +controlPanel: "নিয়ন্ত্রন কেন্দ্র" +_email: + _follow: + title: "আপনাকে অনুসরণ করছে" +_mfm: + mention: "উল্লেখ" + quote: "উদ্ধৃতি" + emoji: "স্বনির্ধারিত ইমোজিগুলি" + search: "খুঁজুন" +_theme: + keys: + mention: "উল্লেখ" + renote: "রিনোট" +_sfx: + note: "নোটগুলি" + notification: "বিজ্ঞপ্তি" + chat: "চ্যাট" +_widgets: + notifications: "বিজ্ঞপ্তি" + timeline: "টাইমলাইন" + activity: "কার্যকলাপ" + federation: "ফেডিভার্স" + jobQueue: "জব কিউ" +_cw: + show: "আরও দেখুন" +_visibility: + home: "মূল পাতা" + followers: "অনুসরণকারী" +_profile: + name: "নাম" + username: "ব্যবহারকারীর নাম" +_exportOrImport: + followingList: "অনুসরণ করা হচ্ছে" + muteList: "মিউট" + blockingList: "ব্লক" + userLists: "লিস্ট" +_timelines: + home: "মূল পাতা" +_pages: + blocks: + image: "ছবি" + script: + categories: + list: "লিস্ট" + blocks: + _join: + arg1: "লিস্ট" + _randomPick: + arg1: "লিস্ট" + _dailyRandomPick: + arg1: "লিস্ট" + _seedRandomPick: + arg2: "লিস্ট" + _pick: + arg1: "লিস্ট" + _listLen: + arg1: "লিস্ট" + types: + array: "লিস্ট" +_notification: + youWereFollowed: "আপনাকে অনুসরণ করছে" + _types: + follow: "অনুসরণ করা হচ্ছে" + mention: "উল্লেখ" + renote: "রিনোট" + quote: "উদ্ধৃতি" + reaction: "প্রতিক্রিয়া" +_deck: + _columns: + notifications: "বিজ্ঞপ্তি" + tl: "টাইমলাইন" + antenna: "অ্যান্টেনা" + list: "লিস্ট" + mentions: "উল্লেখসমূহ" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 2f327a905c..05360e1703 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -235,6 +235,8 @@ resetAreYouSure: "Wirklich zurücksetzen?" saved: "Gespeichert" messaging: "Chat" upload: "Hochladen" +keepOriginalUploading: "Originalbild speichern" +keepOriginalUploadingDescription: "Speichert das Originalbild so, wie es ist. Ist dies deaktiviert, wird eine Version zum Anzeigen im Internet generiert." fromDrive: "Aus Drive" fromUrl: "Von einer URL" uploadFromUrl: "Von einer URL hochladen" diff --git a/locales/en-US.yml b/locales/en-US.yml index 6bbe848210..9a2b0bf5c5 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -235,6 +235,8 @@ resetAreYouSure: "Really reset?" saved: "Saved" messaging: "Chat" upload: "Upload" +keepOriginalUploading: "Keep original image" +keepOriginalUploadingDescription: "Saves the originally uploaded image as-is. If turned off, a version to display on the web will be generated on upload." fromDrive: "From Drive" fromUrl: "From URL" uploadFromUrl: "Upload from a URL" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 58dd000ccc..62f85bef8d 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -235,6 +235,7 @@ resetAreYouSure: "Voulez-vous réinitialiser ?" saved: "Enregistré" messaging: "Discuter" upload: "Téléverser" +keepOriginalUploading: "Garder l’image d’origine" fromDrive: "Depuis le Drive" fromUrl: "Depuis une URL" uploadFromUrl: "Téléverser via une URL" @@ -743,6 +744,7 @@ notRecommended: "Déconseillé" botProtection: "Protection contre les bots" instanceBlocking: "Instances bloquées" selectAccount: "Sélectionner un compte" +switchAccount: "Changer de compte" enabled: "Activé" disabled: "Désactivé" quickAction: "Actions rapides" @@ -803,6 +805,7 @@ 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" +unmuteThread: "Ne plus masquer le fil" 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" @@ -1241,6 +1244,7 @@ _exportOrImport: muteList: "Comptes masqués" blockingList: "Comptes bloqués" userLists: "Listes" + excludeMutingUsers: "Exclure les utilisateur·rice·s mis en sourdine" excludeInactiveUsers: "Exclure les utilisateur·rice·s inactifs" _charts: federationInstancesIncDec: "Variation du nombre d'instances fédérées" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3d723226e9..20745f39ee 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -235,6 +235,8 @@ resetAreYouSure: "リセットしますか?" saved: "保存しました" messaging: "チャット" upload: "アップロード" +keepOriginalUploading: "オリジナル画像を保持" +keepOriginalUploadingDescription: "画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。" fromDrive: "ドライブから" fromUrl: "URLから" uploadFromUrl: "URLアップロード" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 7451603a66..38a328862f 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -106,6 +106,7 @@ clickToShow: "클릭하여 보기" sensitive: "열람주의" add: "추가" reaction: "리액션" +reactionSetting: "선택기에 표시할 리액션" reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다." rememberNoteVisibility: "공개 범위를 기억하기" attachCancel: "첨부 취소" @@ -234,6 +235,8 @@ resetAreYouSure: "초기화 하시겠습니까?" saved: "저장하였습니다" messaging: "대화" upload: "업로드" +keepOriginalUploading: "원본 이미지를 유지" +keepOriginalUploadingDescription: "이미지를 업로드할 때에 원본을 그대로 유지합니다. 비활성화하면 업로드할 때 브라우저에서 웹 공개용 이미지를 생성합니다." fromDrive: "드라이브에서" fromUrl: "URL로부터" uploadFromUrl: "URL 업로드" @@ -446,6 +449,7 @@ uiLanguage: "UI 표시 언어" groupInvited: "그룹에 초대되었습니다" aboutX: "{x}에 대하여" useOsNativeEmojis: "OS 기본 이모지를 사용" +disableDrawer: "드로어 메뉴를 사용하지 않기" youHaveNoGroups: "그룹이 없습니다" joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을 만들어 보세요." noHistory: "기록이 없습니다" @@ -617,8 +621,11 @@ reportAbuse: "신고" reportAbuseOf: "{name}을 신고하기" fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요." abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다." +reporter: "신고자" reporteeOrigin: "피신고자" reporterOrigin: "신고자" +forwardReport: "리모트 인스턴스에도 신고 내용 보내기" +forwardReportIsAnonymous: "리모트 인스턴스에서는 나의 정보를 볼 수 없으며, 익명의 시스템 계정으로 표시됩니다." send: "전송" abuseMarkAsResolved: "해결됨으로 표시" openInNewTab: "새 탭에서 열기" @@ -680,6 +687,7 @@ center: "가운데" wide: "넓게" narrow: "좁게" reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?" +needReloadToApply: "변경 사항은 새로고침하면 적용됩니다." showTitlebar: "타이틀 바를 표시하기" clearCache: "캐시 비우기" onlineUsersCount: "{n}명이 접속 중" @@ -740,6 +748,7 @@ notRecommended: "추천하지 않음" botProtection: "Bot 방어" instanceBlocking: "인스턴스 차단" selectAccount: "계정 선택" +switchAccount: "계정 바꾸기" enabled: "활성화" disabled: "비활성화" quickAction: "빠른 동작" @@ -808,6 +817,11 @@ deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. incorrectPassword: "비밀번호가 올바르지 않습니다." voteConfirm: "\"{choice}\"에 투표하시겠습니까?" hide: "숨기기" +leaveGroup: "그룹 나가기" +leaveGroupConfirm: "\"{name}\"에서 나갈까요?" +useDrawerReactionPickerForMobile: "모바일에서 드로어 메뉴로 표시" +welcomeBackWithName: "환영합니다, {name}님" +clickToFinishEmailVerification: "[{ok}]를 눌러 이메일 인증을 완료하세요." _emailUnavailable: used: "이 메일 주소는 사용중입니다" format: "형식이 올바르지 않습니다" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 5815c92f43..c54e64214a 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -235,6 +235,8 @@ resetAreYouSure: "恢复默认设置?" saved: "已保存" messaging: "聊天" upload: "本地上传" +keepOriginalUploading: "保留原图" +keepOriginalUploadingDescription: "上传图片时保留原始图片。关闭时,浏览器会在上传时生成一张用于web发布的图片。" fromDrive: "从网盘中" fromUrl: "从 URL" uploadFromUrl: "从网址上传" @@ -619,8 +621,11 @@ reportAbuse: "举报" reportAbuseOf: "举报{name}" fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写URL地址。" abuseReported: "内容已发送。感谢您的报告。" +reporter: "报告者" reporteeOrigin: "举报来源" reporterOrigin: "举报者来源" +forwardReport: "将报告转发给远程实例" +forwardReportIsAnonymous: "在远程实例上显示的报告者是匿名的系统账号,而不是您的账号。" send: "发送" abuseMarkAsResolved: "处理完毕" openInNewTab: "在新标签页中打开" diff --git a/package.json b/package.json index d945672987..5c6f303f12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.102.1", + "version": "12.103.1", "codename": "indigo", "repository": { "type": "git", @@ -46,7 +46,7 @@ "@types/fluent-ffmpeg": "2.1.20", "@typescript-eslint/parser": "5.10.0", "cross-env": "7.0.3", - "cypress": "9.3.1", + "cypress": "9.4.1", "start-server-and-test": "1.14.0", "typescript": "4.5.5" } diff --git a/packages/backend/package.json b/packages/backend/package.json index 3d3a901f34..3541e803f3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -122,7 +122,7 @@ "langmap": "0.0.16", "mfm-js": "0.21.0", "mime-types": "2.1.34", - "misskey-js": "0.0.13", + "misskey-js": "0.0.14", "mocha": "8.4.0", "ms": "3.0.0-canary.1", "multer": "1.4.4", diff --git a/packages/backend/src/misc/is-duplicate-key-value-error.ts b/packages/backend/src/misc/is-duplicate-key-value-error.ts index 23d8ceb1b7..04ff191e41 100644 --- a/packages/backend/src/misc/is-duplicate-key-value-error.ts +++ b/packages/backend/src/misc/is-duplicate-key-value-error.ts @@ -1,3 +1,3 @@ -export function isDuplicateKeyValueError(e: Error): boolean { - return e.message.startsWith('duplicate key value'); +export function isDuplicateKeyValueError(e: unknown | Error): boolean { + return (e as any).message && (e as Error).message.startsWith('duplicate key value'); } diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts index faa35d12d4..362bbb0f57 100644 --- a/packages/backend/src/server/api/api-handler.ts +++ b/packages/backend/src/server/api/api-handler.ts @@ -32,7 +32,7 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => { // Authentication authenticate(body['i']).then(([user, app]) => { // API invoking - call(endpoint.name, user, app, body, (ctx as any).file).then((res: any) => { + call(endpoint.name, user, app, body, ctx).then((res: any) => { reply(res); }).catch((e: ApiError) => { reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 399ee65bde..ea9ab3ccba 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,3 +1,4 @@ +import * as Koa from 'koa'; import { performance } from 'perf_hooks'; import { limiter } from './limiter'; import { User } from '@/models/entities/user'; @@ -12,7 +13,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', }; -export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, file?: any) => { +export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; const ep = endpoints.find(e => e.name === endpoint); @@ -76,9 +77,20 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac }); } + // Cast non JSON input + if (ep.meta.requireFile && ep.meta.params) { + const body = (ctx!.request as any).body; + for (const k of Object.keys(ep.meta.params)) { + const param = ep.meta.params[k]; + if (['Boolean', 'Number'].includes(param.validator.name) && typeof body[k] === 'string') { + body[k] = JSON.parse(body[k]); + } + } + } + // API invoking const before = performance.now(); - return await ep.exec(data, user, token, file).catch((e: Error) => { + return await ep.exec(data, user, token, ctx?.file).catch((e: Error) => { if (e instanceof ApiError) { throw e; } else { 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 dd65ab0611..877e76677e 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -39,15 +39,13 @@ export const meta = { }, isSensitive: { - validator: $.optional.either($.bool, $.str), + validator: $.optional.bool, default: false, - transform: (v: any): boolean => v === true || v === 'true', }, force: { - validator: $.optional.either($.bool, $.str), + validator: $.optional.bool, default: false, - transform: (v: any): boolean => v === true || v === 'true', }, }, diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index e70c26f5e5..e2f1c6fc9c 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -105,7 +105,10 @@ export interface NoteStreamTypes { }; reacted: { reaction: string; - emoji?: Emoji; + emoji?: { + name: string; + url: string; + } | null; userId: User['id']; }; unreacted: { diff --git a/packages/backend/src/server/file/index.ts b/packages/backend/src/server/file/index.ts index a455acd1cf..6fe6110dc9 100644 --- a/packages/backend/src/server/file/index.ts +++ b/packages/backend/src/server/file/index.ts @@ -18,7 +18,7 @@ const _dirname = dirname(_filename); const app = new Koa(); app.use(cors()); app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', `default-src 'none'; style-src 'unsafe-inline'`); + ctx.set('Content-Security-Policy', `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`); await next(); }); diff --git a/packages/backend/src/server/proxy/index.ts b/packages/backend/src/server/proxy/index.ts index b8993f19f8..7a3094311c 100644 --- a/packages/backend/src/server/proxy/index.ts +++ b/packages/backend/src/server/proxy/index.ts @@ -11,7 +11,7 @@ import { proxyMedia } from './proxy-media'; const app = new Koa(); app.use(cors()); app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', `default-src 'none'; style-src 'unsafe-inline'`); + ctx.set('Content-Security-Policy', `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`); await next(); }); diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 42c068c403..8e880bf04c 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -21,6 +21,7 @@ html meta(name='referrer' content='origin') meta(name='theme-color' content='#86b300') meta(name='theme-color-orig' content='#86b300') + meta(property='twitter:card' content='summary') meta(property='og:site_name' content= instanceName || 'Misskey') meta(name='viewport' content='width=device-width, initial-scale=1') link(rel='icon' href= icon || '/favicon.ico') @@ -42,7 +43,9 @@ html block meta block og - meta(property='og:image' content=img) + meta(property='og:title' content= title || 'Misskey') + meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') + meta(property='og:image' content= img) style include ../style.css diff --git a/packages/backend/src/server/web/views/channel.pug b/packages/backend/src/server/web/views/channel.pug index 273632f0e0..486f0ecc47 100644 --- a/packages/backend/src/server/web/views/channel.pug +++ b/packages/backend/src/server/web/views/channel.pug @@ -16,6 +16,3 @@ block og meta(property='og:description' content= channel.description) meta(property='og:url' content= url) meta(property='og:image' content= channel.bannerUrl) - -block meta - meta(name='twitter:card' content='summary') diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug index 8de53f19d6..7a84d50f6c 100644 --- a/packages/backend/src/server/web/views/clip.pug +++ b/packages/backend/src/server/web/views/clip.pug @@ -26,8 +26,6 @@ block meta meta(name='misskey:user-id' content=user.id) meta(name='misskey:clip-id' content=clip.id) - meta(name='twitter:card' content='summary') - // todo if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug index 95bbb2437c..ca0663a481 100644 --- a/packages/backend/src/server/web/views/gallery-post.pug +++ b/packages/backend/src/server/web/views/gallery-post.pug @@ -25,8 +25,6 @@ block meta meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-id' content=user.id) - meta(name='twitter:card' content='summary') - // todo if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index fce91bdabe..34b03f9833 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -26,9 +26,7 @@ block meta meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-id' content=user.id) meta(name='misskey:note-id' content=note.id) - - meta(name='twitter:card' content='summary') - + // todo if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug index cb9e1039e1..b6c9548025 100644 --- a/packages/backend/src/server/web/views/page.pug +++ b/packages/backend/src/server/web/views/page.pug @@ -26,8 +26,6 @@ block meta meta(name='misskey:user-id' content=user.id) meta(name='misskey:page-id' content=page.id) - meta(name='twitter:card' content='summary') - // todo if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug index 1a8a6b4413..2adec0f889 100644 --- a/packages/backend/src/server/web/views/user.pug +++ b/packages/backend/src/server/web/views/user.pug @@ -25,8 +25,6 @@ block meta meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-id' content=user.id) - meta(name='twitter:card' content='summary') - if profile.twitter meta(name='twitter:creator' content=`@${profile.twitter.screenName}`) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index fb22bd6593..cfb1f8c1e5 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -59,7 +59,7 @@ class NotificationManager { if (exist) { // 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする - if (reason != 'mention') { + if (reason !== 'mention') { exist.reason = reason; } } else { @@ -201,7 +201,7 @@ export default async (user: { id: User['id']; username: User['username']; host: mentionedUsers.push(await Users.findOneOrFail(data.reply.userId)); } - if (data.visibility == 'specified') { + if (data.visibility === 'specified') { if (data.visibleUsers == null) throw new Error('invalid param'); for (const u of data.visibleUsers) { @@ -301,7 +301,7 @@ export default async (user: { id: User['id']; username: User['username']; host: if (Users.isRemoteUser(user)) activeUsersChart.update(user); // 未読通知を作成 - if (data.visibility == 'specified') { + if (data.visibility === 'specified') { if (data.visibleUsers == null) throw new Error('invalid param'); for (const u of data.visibleUsers) { @@ -439,7 +439,7 @@ export default async (user: { id: User['id']; username: User['username']; host: async function renderNoteOrRenoteActivity(data: Option, note: Note) { if (data.localOnly) return null; - const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length == 0) + const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0) ? renderAnnounce(data.renote.uri ? data.renote.uri : `${config.url}/notes/${data.renote.id}`, note) : renderCreate(await renderNote(note, false), note); @@ -478,7 +478,7 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O userId: user.id, localOnly: data.localOnly!, visibility: data.visibility as any, - visibleUserIds: data.visibility == 'specified' + visibleUserIds: data.visibility === 'specified' ? data.visibleUsers ? data.visibleUsers.map(u => u.id) : [] @@ -502,7 +502,7 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O insert.mentions = mentionedUsers.map(u => u.id); const profiles = await UserProfiles.find({ userId: In(insert.mentions) }); insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => { - const profile = profiles.find(p => p.userId == u.id); + const profile = profiles.find(p => p.userId === u.id); const url = profile != null ? profile.url : null; return { uri: u.uri, diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index 64383ee928..77723fe8b7 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -39,7 +39,7 @@ export default async function(user: User, note: Note, quiet = false) { let renote: Note | undefined; // if deletd note is renote - if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) { + if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { renote = await Notes.findOne({ id: note.renoteId, }); diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 47f46419dd..c01d43c221 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -76,7 +76,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, // カスタム絵文字リアクションだったら絵文字情報も送る const decodedReaction = decodeReaction(reaction); - let emoji = await Emojis.findOne({ + const emoji = await Emojis.findOne({ where: { name: decodedReaction.name, host: decodedReaction.host, diff --git a/packages/backend/src/services/note/read.ts b/packages/backend/src/services/note/read.ts index aaf1c5ed71..032f1e84e6 100644 --- a/packages/backend/src/services/note/read.ts +++ b/packages/backend/src/services/note/read.ts @@ -52,7 +52,7 @@ export default async function( if (note.user != null) { // たぶんnullになることは無いはずだけど一応 for (const antenna of myAntennas) { - if (await checkHitAntenna(antenna, note, note.user as any, undefined, Array.from(following))) { + if (await checkHitAntenna(antenna, note, note.user, undefined, Array.from(following))) { readAntennaNotes.push(note); } } diff --git a/packages/backend/src/services/send-email.ts b/packages/backend/src/services/send-email.ts index f5f36148f6..1fd406cb68 100644 --- a/packages/backend/src/services/send-email.ts +++ b/packages/backend/src/services/send-email.ts @@ -114,9 +114,9 @@ export async function sendEmail(to: string, subject: string, html: string, text: `, }); - logger.info('Message sent: %s', info.messageId); - } catch (e) { - logger.error(e); - throw e; + logger.info(`Message sent: ${info.messageId}`); + } catch (err) { + logger.error(err as Error); + throw err; } } diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 99e2e2306e..5bf6e05a71 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -4967,10 +4967,10 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -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== +misskey-js@0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.14.tgz#1a616bdfbe81c6ee6900219eaf425bb5c714dd4d" + integrity sha512-bvLx6U3OwQwqHfp/WKwIVwdvNYAAPk0+YblXyxmSG3dwlzCgBRRLcB8o6bNruUDyJgh3t73pLDcOz3myxcUmww== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index d414f86ed3..acbb7c0c6b 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -18,6 +18,7 @@ module.exports = { // data の禁止理由: 抽象的すぎるため // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため "id-denylist": ["error", "window", "data", "e"], + 'eqeqeq': ['error', 'always', { 'null': 'ignore' }], "vue/attributes-order": ["error", { "alphabetical": false }], diff --git a/packages/client/package.json b/packages/client/package.json index 71dd89bea4..b840bafe8c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -69,7 +69,7 @@ "langmap": "0.0.16", "matter-js": "0.18.0", "mfm-js": "0.21.0", - "misskey-js": "0.0.13", + "misskey-js": "0.0.14", "mocha": "8.4.0", "ms": "2.1.3", "nested-property": "4.0.0", diff --git a/packages/client/src/components/chart-tooltip.vue b/packages/client/src/components/chart-tooltip.vue new file mode 100644 index 0000000000..b080eaf2b4 --- /dev/null +++ b/packages/client/src/components/chart-tooltip.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index d17c0c9f3e..e9938e6903 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -8,7 +8,7 @@ diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index fffde14874..dd715227a4 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -48,7 +48,7 @@ export default { popup(import('@/components/ui/tooltip.vue'), { showing, text: self.text, - source: el + targetElement: el, }, {}, 'closed'); self._close = () => { @@ -56,8 +56,8 @@ export default { }; }; - el.addEventListener('selectstart', e => { - e.preventDefault(); + el.addEventListener('selectstart', ev => { + ev.preventDefault(); }); el.addEventListener(start, () => { diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 9e713afe76..64eb794aaa 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -14,7 +14,7 @@ if (localStorage.getItem('accounts') != null) { //#endregion import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue'; -import * as compareVersions from 'compare-versions'; +import compareVersions from 'compare-versions'; import widgets from '@/widgets'; import directives from '@/directives'; diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index f3be5c68fb..95b4e87a1f 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -7,8 +7,10 @@ import * as Misskey from 'misskey-js'; import { apiUrl, url } from '@/config'; import MkPostFormDialog from '@/components/post-form-dialog.vue'; import MkWaitingDialog from '@/components/waiting-dialog.vue'; +import { MenuItem } from '@/types/menu'; import { resolve } from '@/router'; import { $i } from '@/account'; +import { defaultStore } from '@/store'; export const pendingApiRequestsCount = ref(0); @@ -470,7 +472,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: }); } -export function popupMenu(items: any[] | Ref, src?: HTMLElement, options?: { +export function popupMenu(items: MenuItem[] | Ref, src?: HTMLElement, options?: { align?: string; width?: number; viaKeyboard?: boolean; @@ -494,7 +496,7 @@ export function popupMenu(items: any[] | Ref, src?: HTMLElement, options? }); } -export function contextMenu(items: any[], ev: MouseEvent) { +export function contextMenu(items: MenuItem[] | Ref, ev: MouseEvent) { ev.preventDefault(); return new Promise((resolve, reject) => { let dispose; @@ -541,7 +543,7 @@ export const uploads = ref<{ img: string; }[]>([]); -export function upload(file: File, folder?: any, name?: string): Promise { +export function upload(file: File, folder?: any, name?: string, keepOriginal: boolean = defaultStore.state.keepOriginalUploading): Promise { if (folder && typeof folder == 'object') folder = folder.id; return new Promise((resolve, reject) => { @@ -559,6 +561,8 @@ export function upload(file: File, folder?: any, name?: string): Promise ({ sort: sort, - host: host != '' ? host : null, + host: host !== '' ? host : null, ...( state === 'federating' ? { federating: true } : state === 'subscribing' ? { subscribing: true } : @@ -157,11 +157,10 @@ defineExpose({ > .instance { padding: 16px; - border: solid 1px var(--divider); - border-radius: 6px; + background: var(--panel); + border-radius: 8px; &:hover { - border: solid 1px var(--accent); text-decoration: none; } diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue index fa36db0659..f19cb9d1a2 100644 --- a/packages/client/src/pages/instance-info.vue +++ b/packages/client/src/pages/instance-info.vue @@ -29,6 +29,7 @@ {{ $ts.stopActivityDelivery }} {{ $ts.blockThisInstance }} + Refresh metadata @@ -111,6 +112,7 @@ import MkChart from '@/components/chart.vue'; import MkObjectView from '@/components/object-view.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/link.vue'; +import MkButton from '@/components/ui/button.vue'; import FormSection from '@/components/form/section.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkSelect from '@/components/form/select.vue'; @@ -155,6 +157,15 @@ async function toggleSuspend(v) { }); } +function refreshMetadata() { + os.api('admin/federation/refresh-remote-instance-metadata', { + host: instance.host, + }); + os.alert({ + text: 'Refresh requested', + }); +} + fetch(); defineExpose({ diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index f1016ebd84..134fa63308 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -28,6 +28,7 @@ + {{ $ts.keepOriginalUploading }} @@ -36,18 +37,21 @@ import { defineComponent } from 'vue'; import * as tinycolor from 'tinycolor2'; import FormLink from '@/components/form/link.vue'; +import FormSwitch from '@/components/form/switch.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'; +import { defaultStore } from '@/store'; // TODO: render chart export default defineComponent({ components: { FormLink, + FormSwitch, FormSection, MkKeyValue, FormSplit, @@ -79,7 +83,8 @@ export default defineComponent({ l: 0.5 }) }; - } + }, + keepOriginalUploading: defaultStore.makeGetterSetter('keepOriginalUploading'), }, async created() { diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 56e0b564f3..23df4edf54 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -1,3 +1,4 @@ +import { ref } from 'vue'; import * as os from '@/os'; import { stream } from '@/stream'; import { i18n } from '@/i18n'; @@ -6,12 +7,14 @@ import { DriveFile } from 'misskey-js/built/entities'; function select(src: any, label: string | null, multiple: boolean): Promise { return new Promise((res, rej) => { + const keepOriginal = ref(defaultStore.state.keepOriginalUploading); + const chooseFileFromPc = () => { const input = document.createElement('input'); input.type = 'file'; input.multiple = multiple; input.onchange = () => { - const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder)); + const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); Promise.all(promises).then(driveFiles => { res(multiple ? driveFiles : driveFiles[0]); @@ -74,6 +77,10 @@ function select(src: any, label: string | null, multiple: boolean): Promise void; + +export type MenuDivider = null; +export type MenuNull = undefined; +export type MenuLabel = { type: 'label', text: string }; +export type MenuLink = { type: 'link', to: string, text: string, icon?: string, indicate?: boolean, avatar?: Misskey.entities.User }; +export type MenuA = { type: 'a', href: string, target?: string, download?: string, text: string, icon?: string, indicate?: boolean }; +export type MenuUser = { type: 'user', user: Misskey.entities.User, active?: boolean, indicate?: boolean, action: MenuAction }; +export type MenuSwitch = { type: 'switch', ref: Ref, text: string, disabled?: boolean }; +export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean, avatar?: Misskey.entities.User; action: MenuAction }; + +export type MenuPending = { type: 'pending' }; + +type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton; +type OuterPromiseMenuItem = Promise; +export type MenuItem = OuterMenuItem | OuterPromiseMenuItem; +export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton; diff --git a/packages/client/src/ui/classic.side.vue b/packages/client/src/ui/classic.side.vue index f816834141..6c2329194e 100644 --- a/packages/client/src/ui/classic.side.vue +++ b/packages/client/src/ui/classic.side.vue @@ -4,7 +4,7 @@
- {{ pageInfo.title }} +
@@ -13,99 +13,89 @@ - diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 8fe9dcffaf..b0dfc5aadc 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -20,7 +20,7 @@ - +
@@ -31,9 +31,9 @@
- + - +
@@ -64,155 +64,133 @@
-