Compare commits
	
		
			94 Commits
		
	
	
		
			13.0.0-bet
			...
			13.0.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b52fd72727 | ||
|   | d79905e141 | ||
|   | cd6b1290cb | ||
|   | c382497167 | ||
|   | a8fb578854 | ||
|   | ff00c90a88 | ||
|   | d0755b5ce8 | ||
|   | 17fa5667b8 | ||
|   | 01d5e385ec | ||
|   | af80fee899 | ||
|   | 6b37c09274 | ||
|   | 1453a0f5cf | ||
|   | 1688083e9a | ||
|   | 616594d3cd | ||
|   | 6783178dc3 | ||
|   | 3f033d6ab7 | ||
|   | d10e000883 | ||
|   | ce528ff22e | ||
|   | 5e4e02235a | ||
|   | e4179336e4 | ||
|   | 7823ba494f | ||
|   | 7bdff90415 | ||
|   | f3c0af7e23 | ||
|   | 72dfbfcf35 | ||
|   | 9cbe878d0b | ||
|   | 618405c4d3 | ||
|   | 0b08fcac4a | ||
|   | eac6ebb239 | ||
|   | 194fb14e07 | ||
|   | c2d05b507a | ||
|   | 4df43a9107 | ||
|   | 0da7fcdbed | ||
|   | 1e50b2688a | ||
|   | c1cd018626 | ||
|   | b588e8b60b | ||
|   | 06f55ffb37 | ||
|   | 02df6a28cd | ||
|   | d64abedf9f | ||
|   | 4d39d1caf6 | ||
|   | d06f61f23f | ||
|   | c179d6f735 | ||
|   | 3bc0cdbfb7 | ||
|   | b04155e7ba | ||
|   | 014c97fa85 | ||
|   | 96ccf550b1 | ||
|   | 8f28ff63f1 | ||
|   | b7dec6e87d | ||
|   | 1bb2c22493 | ||
|   | 39c3995c74 | ||
|   | 8cc80faf20 | ||
|   | 4d66077f85 | ||
|   | 3ece2dc990 | ||
|   | 6071e962f4 | ||
|   | ed43369797 | ||
|   | c65957853b | ||
|   | 6a18360269 | ||
|   | c438bd2e27 | ||
|   | 462acc9eee | ||
|   | e4144a17a4 | ||
|   | 3cfd017538 | ||
|   | 403849805a | ||
|   | 402b234d15 | ||
|   | eba6b326fa | ||
|   | 4c9b93a12f | ||
|   | dfee79f841 | ||
|   | 962373cf06 | ||
|   | 13aa4b64b4 | ||
|   | 5ce56886a1 | ||
|   | 2817ca03f5 | ||
|   | e633c3b84b | ||
|   | 8524e9d735 | ||
|   | 91ced90fb2 | ||
|   | 2acb3917ba | ||
|   | dd78ac089c | ||
|   | 10e526ba56 | ||
|   | 7ed905f76b | ||
|   | 5d13e2744f | ||
|   | 1d7e0293a8 | ||
|   | 8977d87021 | ||
|   | 809400ff23 | ||
|   | 4c8dbcc20d | ||
|   | 416dcf884d | ||
|   | 09d3ce444a | ||
|   | 27c2ca5048 | ||
|   | fceeb1b108 | ||
|   | b442c38f41 | ||
|   | 7c2d2676f7 | ||
|   | 1f6a41cea7 | ||
|   | 0d7ee20a77 | ||
|   | dcca2350dd | ||
|   | 1cfdd4c41a | ||
|   | 25f4ee7030 | ||
|   | 5320f23017 | ||
|   | 4ffbbbe6d8 | 
							
								
								
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -12,7 +12,7 @@ You should also include the user name that made the change. | |||||||
| ## 13.0.0 (unreleased) | ## 13.0.0 (unreleased) | ||||||
|  |  | ||||||
| ### TL;DR | ### TL;DR | ||||||
| - New features (Play, new widgets, new charts, etc) | - New features (Play, new widgets, new charts, 🍪👈, etc) | ||||||
| - Rewriten backend | - Rewriten backend | ||||||
| - Better performance (backend and frontend) | - Better performance (backend and frontend) | ||||||
| - Various usability improvements | - Various usability improvements | ||||||
| @@ -40,6 +40,8 @@ You should also include the user name that made the change. | |||||||
| - Firefox109以下はサポートされなくなりました | - Firefox109以下はサポートされなくなりました | ||||||
|  |  | ||||||
| #### For app developers | #### For app developers | ||||||
|  | - API: metaのレスポンスに`emojis`プロパティが含まれなくなりました | ||||||
|  | 	- カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします | ||||||
| - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました | - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました | ||||||
| 	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。 | 	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。 | ||||||
| 	- e.g. `https://p1.a9z.dev/emoji/misskey.webp` | 	- e.g. `https://p1.a9z.dev/emoji/misskey.webp` | ||||||
| @@ -64,7 +66,6 @@ You should also include the user name that made the change. | |||||||
| - Server: delete outdated notes of antenna regularly to improve db performance @syuilo | - Server: delete outdated notes of antenna regularly to improve db performance @syuilo | ||||||
| - Server: improve activitypub deliver performance @syuilo | - Server: improve activitypub deliver performance @syuilo | ||||||
| - Client: use tabler-icons instead of fontawesome to better design @syuilo | - Client: use tabler-icons instead of fontawesome to better design @syuilo | ||||||
| - Client: Add AiScript App widget |  | ||||||
| - Client: Add new gabber kick sounds (thanks for noizenecio) | - Client: Add new gabber kick sounds (thanks for noizenecio) | ||||||
| - Client: Add link to user RSS feed in profile menu @ssmucny | - Client: Add link to user RSS feed in profile menu @ssmucny | ||||||
| - Client: Compress non-animated PNG files @saschanaz | - Client: Compress non-animated PNG files @saschanaz | ||||||
| @@ -72,14 +73,18 @@ You should also include the user name that made the change. | |||||||
| - Client: enhance dashboard of control panel @syuilo | - Client: enhance dashboard of control panel @syuilo | ||||||
| - Client: Vite is upgraded to v4 @syuilo, @tamaina | - Client: Vite is upgraded to v4 @syuilo, @tamaina | ||||||
| - Client: HMR is available while yarn dev @tamaina | - Client: HMR is available while yarn dev @tamaina | ||||||
| - Client: Make widgets of universal/classic sync between devices @tamaina |  | ||||||
| - Client: Implement the button to subscribe push notification @tamaina | - Client: Implement the button to subscribe push notification @tamaina | ||||||
| - Client: Implement the toggle to or not to close push notifications when notifications or messages are read @tamaina | - Client: Implement the toggle to or not to close push notifications when notifications or messages are read @tamaina | ||||||
| - Client: Improve RSS widget @tamaina |  | ||||||
| - Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz | - Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz | ||||||
| - Client: OpenSearch support @SoniEx2 @chaoticryptidz | - Client: OpenSearch support @SoniEx2 @chaoticryptidz | ||||||
| - Client: Support remote objects in search @SoniEx2 | - Client: Support remote objects in search @SoniEx2 | ||||||
|  | - Client: user activity page @syuilo | ||||||
|  | - Client: Make widgets of universal/classic sync between devices @tamaina | ||||||
| - Client: add user list widget @syuilo | - Client: add user list widget @syuilo | ||||||
|  | - Client: Add AiScript App widget | ||||||
|  | - Client: add profile widget @syuilo | ||||||
|  | - Client: add instance info widget @syuilo | ||||||
|  | - Client: Improve RSS widget @tamaina | ||||||
| - Client: add heatmap of daily active users to about page @syuilo | - Client: add heatmap of daily active users to about page @syuilo | ||||||
| - Client: introduce fluent emoji @syuilo | - Client: introduce fluent emoji @syuilo | ||||||
| - Client: add new theme @syuilo | - Client: add new theme @syuilo | ||||||
| @@ -87,6 +92,7 @@ You should also include the user name that made the change. | |||||||
| - Client: show bot warning on screen when logged in as bot account @syuilo | - Client: show bot warning on screen when logged in as bot account @syuilo | ||||||
| - Client: improve overall performance of client @syuilo | - Client: improve overall performance of client @syuilo | ||||||
| - Client: ui tweaks @syuilo | - Client: ui tweaks @syuilo | ||||||
|  | - Client: clicker game @syuilo | ||||||
|  |  | ||||||
| ### Bugfixes | ### Bugfixes | ||||||
| - Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468 | - Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468 | ||||||
| @@ -97,6 +103,11 @@ You should also include the user name that made the change. | |||||||
| - Server: アンテナの作成数上限を追加 @syuilo | - Server: アンテナの作成数上限を追加 @syuilo | ||||||
| - Server: pages/likeのエラーIDが重複しているのを修正 @syuilo | - Server: pages/likeのエラーIDが重複しているのを修正 @syuilo | ||||||
| - Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo | - Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo | ||||||
|  | - Server: Escape SQL LIKE @mei23 | ||||||
|  | - Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu | ||||||
|  | - Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo | ||||||
|  | - Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo | ||||||
|  | - Client: 日付形式の文字列などがカスタム絵文字として表示されるのを修正 @syuilo | ||||||
| - Client: case insensitive emoji search @saschanaz | - Client: case insensitive emoji search @saschanaz | ||||||
| - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina | - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina | ||||||
| - Client: use proxied image for instance icon @syuilo | - Client: use proxied image for instance icon @syuilo | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM node:18.12.1-bullseye AS builder | FROM node:18.13.0-bullseye AS builder | ||||||
|  |  | ||||||
| ARG NODE_ENV=production | ARG NODE_ENV=production | ||||||
|  |  | ||||||
| @@ -22,7 +22,7 @@ COPY . ./ | |||||||
| RUN git submodule update --init | RUN git submodule update --init | ||||||
| RUN yarn build | RUN yarn build | ||||||
|  |  | ||||||
| FROM node:18.12.1-bullseye-slim AS runner | FROM node:18.13.0-bullseye-slim AS runner | ||||||
|  |  | ||||||
| WORKDIR /misskey | WORKDIR /misskey | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1117,6 +1117,8 @@ _weekday: | |||||||
|   friday: "الجمعة" |   friday: "الجمعة" | ||||||
|   saturday: "السبت" |   saturday: "السبت" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "الملف التعريفي" | ||||||
|  |   instanceInfo: "معلومات مثيل الخادم" | ||||||
|   memo: "ملاحظة لاصقة" |   memo: "ملاحظة لاصقة" | ||||||
|   notifications: "الإشعارات" |   notifications: "الإشعارات" | ||||||
|   timeline: "الخيط الزمني" |   timeline: "الخيط الزمني" | ||||||
| @@ -1294,7 +1296,6 @@ _notification: | |||||||
|   youGotReply: "ردّ عليك {name}" |   youGotReply: "ردّ عليك {name}" | ||||||
|   youGotQuote: "اقتبس منك {name}" |   youGotQuote: "اقتبس منك {name}" | ||||||
|   youRenoted: "إعادت نشر من {name}" |   youRenoted: "إعادت نشر من {name}" | ||||||
|   youGotPoll: "شارك {name} في استطلاع الرأي" |  | ||||||
|   youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}" |   youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}" | ||||||
|   youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}" |   youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}" | ||||||
|   youWereFollowed: "يتابعك" |   youWereFollowed: "يتابعك" | ||||||
| @@ -1311,7 +1312,6 @@ _notification: | |||||||
|     renote: "أعد النشر" |     renote: "أعد النشر" | ||||||
|     quote: "الاقتباسات" |     quote: "الاقتباسات" | ||||||
|     reaction: "التفاعلات" |     reaction: "التفاعلات" | ||||||
|     pollVote: "مصوِت شارك في الاستطلاع" |  | ||||||
|     receiveFollowRequest: "طلبات المتابعة المتلقاة" |     receiveFollowRequest: "طلبات المتابعة المتلقاة" | ||||||
|     followRequestAccepted: "طلبات المتابعة المقبولة" |     followRequestAccepted: "طلبات المتابعة المقبولة" | ||||||
|     groupInvited: "دعوات الفريق" |     groupInvited: "دعوات الفريق" | ||||||
|   | |||||||
| @@ -1200,6 +1200,8 @@ _weekday: | |||||||
|   friday: "শুক্রবার" |   friday: "শুক্রবার" | ||||||
|   saturday: "শনিবার" |   saturday: "শনিবার" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "প্রোফাইল" | ||||||
|  |   instanceInfo: "ইন্সট্যান্সের তথ্য" | ||||||
|   memo: "স্টিকি নোট" |   memo: "স্টিকি নোট" | ||||||
|   notifications: "বিজ্ঞপ্তি" |   notifications: "বিজ্ঞপ্তি" | ||||||
|   timeline: "টাইমলাইন" |   timeline: "টাইমলাইন" | ||||||
| @@ -1386,7 +1388,6 @@ _notification: | |||||||
|   youGotReply: "{name} আপনাকে জবাব দিয়েছে" |   youGotReply: "{name} আপনাকে জবাব দিয়েছে" | ||||||
|   youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে" |   youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে" | ||||||
|   youRenoted: "{name} এর Renote" |   youRenoted: "{name} এর Renote" | ||||||
|   youGotPoll: "{name} আপনার পোলে ভোট দিয়েছে" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে" |   youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে" | ||||||
|   youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে" |   youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে" | ||||||
|   youWereFollowed: "আপনাকে অনুসরণ করছে" |   youWereFollowed: "আপনাকে অনুসরণ করছে" | ||||||
| @@ -1403,7 +1404,6 @@ _notification: | |||||||
|     renote: "রিনোট" |     renote: "রিনোট" | ||||||
|     quote: "উদ্ধৃতি" |     quote: "উদ্ধৃতি" | ||||||
|     reaction: "প্রতিক্রিয়া" |     reaction: "প্রতিক্রিয়া" | ||||||
|     pollVote: "পোলে ভোট আছে" |  | ||||||
|     pollEnded: "পোল শেষ" |     pollEnded: "পোল শেষ" | ||||||
|     receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ" |     receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ" | ||||||
|     followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ" |     followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ" | ||||||
|   | |||||||
| @@ -399,6 +399,8 @@ _antennaSources: | |||||||
|   userList: "Publicacions d'una llista d'usuaris" |   userList: "Publicacions d'una llista d'usuaris" | ||||||
|   userGroup: "Publicacions d'usuaris d'un grup" |   userGroup: "Publicacions d'usuaris d'un grup" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Perfil" | ||||||
|  |   instanceInfo: "Informació del fitxer d'instal·lació" | ||||||
|   notifications: "Notificacions" |   notifications: "Notificacions" | ||||||
|   timeline: "Línia de temps" |   timeline: "Línia de temps" | ||||||
|   activity: "Activitat" |   activity: "Activitat" | ||||||
|   | |||||||
| @@ -694,6 +694,8 @@ _weekday: | |||||||
|   friday: "Pátek" |   friday: "Pátek" | ||||||
|   saturday: "Sobota" |   saturday: "Sobota" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Váš profil" | ||||||
|  |   instanceInfo: "Informace o instanci" | ||||||
|   notifications: "Oznámení" |   notifications: "Oznámení" | ||||||
|   timeline: "Časová osa" |   timeline: "Časová osa" | ||||||
|   calendar: "Kalendář" |   calendar: "Kalendář" | ||||||
|   | |||||||
| @@ -1302,6 +1302,8 @@ _weekday: | |||||||
|   friday: "Freitag" |   friday: "Freitag" | ||||||
|   saturday: "Samstag" |   saturday: "Samstag" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Instanzinformationen" | ||||||
|   memo: "Merkzettel" |   memo: "Merkzettel" | ||||||
|   notifications: "Benachrichtigungen" |   notifications: "Benachrichtigungen" | ||||||
|   timeline: "Chronik" |   timeline: "Chronik" | ||||||
| @@ -1328,6 +1330,7 @@ _widgets: | |||||||
|   userList: "Benutzerliste" |   userList: "Benutzerliste" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "Liste auswählen" |     chooseList: "Liste auswählen" | ||||||
|  |   clicker: "Klickzähler" | ||||||
| _cw: | _cw: | ||||||
|   hide: "Inhalt verbergen" |   hide: "Inhalt verbergen" | ||||||
|   show: "Inhalt anzeigen" |   show: "Inhalt anzeigen" | ||||||
| @@ -1503,7 +1506,6 @@ _notification: | |||||||
|   youGotReply: "{name} hat dir geantwortet" |   youGotReply: "{name} hat dir geantwortet" | ||||||
|   youGotQuote: "{name} hat dich zitiert" |   youGotQuote: "{name} hat dich zitiert" | ||||||
|   youRenoted: "Renote deiner Notiz von {name}" |   youRenoted: "Renote deiner Notiz von {name}" | ||||||
|   youGotPoll: "{name} hat in deiner Umfrage abgestimmt" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet" |   youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet" | ||||||
|   youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet" |   youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet" | ||||||
|   youWereFollowed: "ist dir gefolgt" |   youWereFollowed: "ist dir gefolgt" | ||||||
| @@ -1521,7 +1523,6 @@ _notification: | |||||||
|     renote: "Renotes" |     renote: "Renotes" | ||||||
|     quote: "Zitationen" |     quote: "Zitationen" | ||||||
|     reaction: "Reaktionen" |     reaction: "Reaktionen" | ||||||
|     pollVote: "Antworten auf Umfragen" |  | ||||||
|     pollEnded: "Ende von Umfragen" |     pollEnded: "Ende von Umfragen" | ||||||
|     receiveFollowRequest: "Erhaltene Follow-Anfragen" |     receiveFollowRequest: "Erhaltene Follow-Anfragen" | ||||||
|     followRequestAccepted: "Akzeptierte Follow-Anfragen" |     followRequestAccepted: "Akzeptierte Follow-Anfragen" | ||||||
|   | |||||||
| @@ -343,6 +343,8 @@ _antennaSources: | |||||||
|   userList: "Σημειώματα από καθορισμένη λίστα μελών" |   userList: "Σημειώματα από καθορισμένη λίστα μελών" | ||||||
|   userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας" |   userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Προφίλ" | ||||||
|  |   instanceInfo: "Πληροφορίες του instance" | ||||||
|   notifications: "Ειδοποιήσεις" |   notifications: "Ειδοποιήσεις" | ||||||
|   timeline: "Χρονολόγιο" |   timeline: "Χρονολόγιο" | ||||||
|   calendar: "Ημερολόγιο" |   calendar: "Ημερολόγιο" | ||||||
|   | |||||||
| @@ -1302,6 +1302,8 @@ _weekday: | |||||||
|   friday: "Friday" |   friday: "Friday" | ||||||
|   saturday: "Saturday" |   saturday: "Saturday" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profile" | ||||||
|  |   instanceInfo: "Instance Information" | ||||||
|   memo: "Sticky notes" |   memo: "Sticky notes" | ||||||
|   notifications: "Notifications" |   notifications: "Notifications" | ||||||
|   timeline: "Timeline" |   timeline: "Timeline" | ||||||
| @@ -1328,6 +1330,7 @@ _widgets: | |||||||
|   userList: "User list" |   userList: "User list" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "Select a list" |     chooseList: "Select a list" | ||||||
|  |   clicker: "Clicker" | ||||||
| _cw: | _cw: | ||||||
|   hide: "Hide" |   hide: "Hide" | ||||||
|   show: "Show content" |   show: "Show content" | ||||||
| @@ -1503,7 +1506,6 @@ _notification: | |||||||
|   youGotReply: "{name} replied to you" |   youGotReply: "{name} replied to you" | ||||||
|   youGotQuote: "{name} quoted you" |   youGotQuote: "{name} quoted you" | ||||||
|   youRenoted: "Renote from {name}" |   youRenoted: "Renote from {name}" | ||||||
|   youGotPoll: "{name} voted on your poll" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} sent you a chat message" |   youGotMessagingMessageFromUser: "{name} sent you a chat message" | ||||||
|   youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group" |   youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group" | ||||||
|   youWereFollowed: "followed you" |   youWereFollowed: "followed you" | ||||||
| @@ -1521,7 +1523,6 @@ _notification: | |||||||
|     renote: "Renotes" |     renote: "Renotes" | ||||||
|     quote: "Quotes" |     quote: "Quotes" | ||||||
|     reaction: "Reactions" |     reaction: "Reactions" | ||||||
|     pollVote: "Votes on polls" |  | ||||||
|     pollEnded: "Polls ending" |     pollEnded: "Polls ending" | ||||||
|     receiveFollowRequest: "Received follow requests" |     receiveFollowRequest: "Received follow requests" | ||||||
|     followRequestAccepted: "Accepted follow requests" |     followRequestAccepted: "Accepted follow requests" | ||||||
|   | |||||||
| @@ -1296,6 +1296,8 @@ _weekday: | |||||||
|   friday: "Viernes" |   friday: "Viernes" | ||||||
|   saturday: "Sábado" |   saturday: "Sábado" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Perfil" | ||||||
|  |   instanceInfo: "información de la instancia" | ||||||
|   memo: "Nota adhesiva" |   memo: "Nota adhesiva" | ||||||
|   notifications: "Notificaciones" |   notifications: "Notificaciones" | ||||||
|   timeline: "Linea de tiempo" |   timeline: "Linea de tiempo" | ||||||
| @@ -1487,7 +1489,6 @@ _notification: | |||||||
|   youGotReply: "Respuesta de {name}" |   youGotReply: "Respuesta de {name}" | ||||||
|   youGotQuote: "Citado por {name}" |   youGotQuote: "Citado por {name}" | ||||||
|   youRenoted: "Renotado por {name}" |   youRenoted: "Renotado por {name}" | ||||||
|   youGotPoll: "Encuestado por {name}" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} comenzó un chat contigo" |   youGotMessagingMessageFromUser: "{name} comenzó un chat contigo" | ||||||
|   youGotMessagingMessageFromGroup: "Tienes un chat de {name}" |   youGotMessagingMessageFromGroup: "Tienes un chat de {name}" | ||||||
|   youWereFollowed: "te ha seguido" |   youWereFollowed: "te ha seguido" | ||||||
| @@ -1505,7 +1506,6 @@ _notification: | |||||||
|     renote: "Renotar" |     renote: "Renotar" | ||||||
|     quote: "Citar" |     quote: "Citar" | ||||||
|     reaction: "Reacción" |     reaction: "Reacción" | ||||||
|     pollVote: "Votado en la encuesta" |  | ||||||
|     pollEnded: "La encuesta terminó" |     pollEnded: "La encuesta terminó" | ||||||
|     receiveFollowRequest: "Recibió una solicitud de seguimiento" |     receiveFollowRequest: "Recibió una solicitud de seguimiento" | ||||||
|     followRequestAccepted: "El seguimiento fue aceptado" |     followRequestAccepted: "El seguimiento fue aceptado" | ||||||
|   | |||||||
| @@ -1289,6 +1289,8 @@ _weekday: | |||||||
|   friday: "Vendredi" |   friday: "Vendredi" | ||||||
|   saturday: "Samedi" |   saturday: "Samedi" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Informations sur l’instance" | ||||||
|   memo: "Note collante" |   memo: "Note collante" | ||||||
|   notifications: "Notifications" |   notifications: "Notifications" | ||||||
|   timeline: "Fil" |   timeline: "Fil" | ||||||
| @@ -1478,7 +1480,6 @@ _notification: | |||||||
|   youGotReply: "Réponse de {name}" |   youGotReply: "Réponse de {name}" | ||||||
|   youGotQuote: "Cité·e par {name}" |   youGotQuote: "Cité·e par {name}" | ||||||
|   youRenoted: "{name} vous a Renoté" |   youRenoted: "{name} vous a Renoté" | ||||||
|   youGotPoll: "{name} a participé à votre sondage" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} vous envoyé un message" |   youGotMessagingMessageFromUser: "{name} vous envoyé un message" | ||||||
|   youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}" |   youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}" | ||||||
|   youWereFollowed: "Vous suit" |   youWereFollowed: "Vous suit" | ||||||
| @@ -1496,7 +1497,6 @@ _notification: | |||||||
|     renote: "Renotes" |     renote: "Renotes" | ||||||
|     quote: "Citations" |     quote: "Citations" | ||||||
|     reaction: "Réactions" |     reaction: "Réactions" | ||||||
|     pollVote: "Votes dans des sondages" |  | ||||||
|     pollEnded: "Sondages se cloturant" |     pollEnded: "Sondages se cloturant" | ||||||
|     receiveFollowRequest: "Demande d'abonnement reçue" |     receiveFollowRequest: "Demande d'abonnement reçue" | ||||||
|     followRequestAccepted: "Demande d'abonnement acceptée" |     followRequestAccepted: "Demande d'abonnement acceptée" | ||||||
|   | |||||||
| @@ -1206,6 +1206,8 @@ _weekday: | |||||||
|   friday: "Jumat" |   friday: "Jumat" | ||||||
|   saturday: "Sabtu" |   saturday: "Sabtu" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Informasi Instansi" | ||||||
|   memo: "Catatan memo" |   memo: "Catatan memo" | ||||||
|   notifications: "Pemberitahuan" |   notifications: "Pemberitahuan" | ||||||
|   timeline: "Linimasa" |   timeline: "Linimasa" | ||||||
| @@ -1402,7 +1404,6 @@ _notification: | |||||||
|   youGotReply: "{name} membalas kamu" |   youGotReply: "{name} membalas kamu" | ||||||
|   youGotQuote: "{name} mengutip kamu" |   youGotQuote: "{name} mengutip kamu" | ||||||
|   youRenoted: "{name} me-renote kamu" |   youRenoted: "{name} me-renote kamu" | ||||||
|   youGotPoll: "{name} memilih di angket kamu" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan" |   youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan" | ||||||
|   youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}" |   youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}" | ||||||
|   youWereFollowed: "Mengikuti kamu" |   youWereFollowed: "Mengikuti kamu" | ||||||
| @@ -1419,7 +1420,6 @@ _notification: | |||||||
|     renote: "Renote" |     renote: "Renote" | ||||||
|     quote: "Kutip" |     quote: "Kutip" | ||||||
|     reaction: "Reaksi" |     reaction: "Reaksi" | ||||||
|     pollVote: "Memilih di angket" |  | ||||||
|     pollEnded: "Jajak pendapat berakhir" |     pollEnded: "Jajak pendapat berakhir" | ||||||
|     receiveFollowRequest: "Permintaan mengikuti diterima" |     receiveFollowRequest: "Permintaan mengikuti diterima" | ||||||
|     followRequestAccepted: "Permintaan mengikuti disetujui" |     followRequestAccepted: "Permintaan mengikuti disetujui" | ||||||
|   | |||||||
| @@ -1296,6 +1296,8 @@ _weekday: | |||||||
|   friday: "Venerdì" |   friday: "Venerdì" | ||||||
|   saturday: "Sabato" |   saturday: "Sabato" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profilo" | ||||||
|  |   instanceInfo: "Informazioni sull'istanza" | ||||||
|   memo: "Promemoria" |   memo: "Promemoria" | ||||||
|   notifications: "Notifiche" |   notifications: "Notifiche" | ||||||
|   timeline: "Timeline" |   timeline: "Timeline" | ||||||
| @@ -1487,7 +1489,6 @@ _notification: | |||||||
|   youGotReply: "{name} ti ha risposto" |   youGotReply: "{name} ti ha risposto" | ||||||
|   youGotQuote: "{name} ha citato il tuo Nota e ha detto" |   youGotQuote: "{name} ha citato il tuo Nota e ha detto" | ||||||
|   youRenoted: "{name} ha rinotato" |   youRenoted: "{name} ha rinotato" | ||||||
|   youGotPoll: "{name} ha votato" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio" |   youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio" | ||||||
|   youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat" |   youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat" | ||||||
|   youWereFollowed: "Ha iniziato a seguirti" |   youWereFollowed: "Ha iniziato a seguirti" | ||||||
| @@ -1505,7 +1506,6 @@ _notification: | |||||||
|     renote: "Rinota" |     renote: "Rinota" | ||||||
|     quote: "Cita" |     quote: "Cita" | ||||||
|     reaction: "Reazioni" |     reaction: "Reazioni" | ||||||
|     pollVote: "Voti ricevuti" |  | ||||||
|     pollEnded: "Sondaggio chiuso." |     pollEnded: "Sondaggio chiuso." | ||||||
|     receiveFollowRequest: "Richiesta di follow ricevuta" |     receiveFollowRequest: "Richiesta di follow ricevuta" | ||||||
|     followRequestAccepted: "Richiesta di follow accettata" |     followRequestAccepted: "Richiesta di follow accettata" | ||||||
|   | |||||||
| @@ -1335,6 +1335,8 @@ _weekday: | |||||||
|   saturday: "土曜日" |   saturday: "土曜日" | ||||||
|  |  | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "プロフィール" | ||||||
|  |   instanceInfo: "インスタンス情報" | ||||||
|   memo: "付箋" |   memo: "付箋" | ||||||
|   notifications: "通知" |   notifications: "通知" | ||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
| @@ -1361,6 +1363,7 @@ _widgets: | |||||||
|   userList: "ユーザーリスト" |   userList: "ユーザーリスト" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "リストを選択" |     chooseList: "リストを選択" | ||||||
|  |   clicker: "クリッカー" | ||||||
|  |  | ||||||
| _cw: | _cw: | ||||||
|   hide: "隠す" |   hide: "隠す" | ||||||
|   | |||||||
| @@ -1295,6 +1295,8 @@ _weekday: | |||||||
|   friday: "金曜日" |   friday: "金曜日" | ||||||
|   saturday: "土曜日" |   saturday: "土曜日" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "プロフィール" | ||||||
|  |   instanceInfo: "インスタンス情報" | ||||||
|   memo: "付箋" |   memo: "付箋" | ||||||
|   notifications: "通知" |   notifications: "通知" | ||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
| @@ -1485,7 +1487,6 @@ _notification: | |||||||
|   youGotReply: "{name}からのリプライ" |   youGotReply: "{name}からのリプライ" | ||||||
|   youGotQuote: "{name}による引用" |   youGotQuote: "{name}による引用" | ||||||
|   youRenoted: "{name}がRenoteしたみたいやで" |   youRenoted: "{name}がRenoteしたみたいやで" | ||||||
|   youGotPoll: "{name}が投票したみたいやで" |  | ||||||
|   youGotMessagingMessageFromUser: "{name}からのチャットがあるで" |   youGotMessagingMessageFromUser: "{name}からのチャットがあるで" | ||||||
|   youGotMessagingMessageFromGroup: "{name}のチャットがあるで" |   youGotMessagingMessageFromGroup: "{name}のチャットがあるで" | ||||||
|   youWereFollowed: "フォローされたで" |   youWereFollowed: "フォローされたで" | ||||||
| @@ -1503,7 +1504,6 @@ _notification: | |||||||
|     renote: "Renote" |     renote: "Renote" | ||||||
|     quote: "引用" |     quote: "引用" | ||||||
|     reaction: "リアクション" |     reaction: "リアクション" | ||||||
|     pollVote: "アンケートに投票されたで" |  | ||||||
|     pollEnded: "アンケートが終了したで" |     pollEnded: "アンケートが終了したで" | ||||||
|     receiveFollowRequest: "フォロー許可してほしいみたいやで" |     receiveFollowRequest: "フォロー許可してほしいみたいやで" | ||||||
|     followRequestAccepted: "フォローが受理されたで" |     followRequestAccepted: "フォローが受理されたで" | ||||||
|   | |||||||
| @@ -73,6 +73,7 @@ _sfx: | |||||||
| _permissions: | _permissions: | ||||||
|   "write:account": "Ẓreg talɣut n umiḍan-ik·im" |   "write:account": "Ẓreg talɣut n umiḍan-ik·im" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Amaɣnu" | ||||||
|   notifications: "Ilɣuyen" |   notifications: "Ilɣuyen" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "Fren tabdart" |     chooseList: "Fren tabdart" | ||||||
|   | |||||||
| @@ -69,6 +69,7 @@ _mfm: | |||||||
| _sfx: | _sfx: | ||||||
|   notification: "ಅಧಿಸೂಚನೆಗಳು" |   notification: "ಅಧಿಸೂಚನೆಗಳು" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "ಪ್ರೊಫೈಲು" | ||||||
|   notifications: "ಅಧಿಸೂಚನೆಗಳು" |   notifications: "ಅಧಿಸೂಚನೆಗಳು" | ||||||
|   timeline: "ಸಮಯಸಾಲು" |   timeline: "ಸಮಯಸಾಲು" | ||||||
| _cw: | _cw: | ||||||
|   | |||||||
| @@ -1302,6 +1302,8 @@ _weekday: | |||||||
|   friday: "금요일" |   friday: "금요일" | ||||||
|   saturday: "토요일" |   saturday: "토요일" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "프로필" | ||||||
|  |   instanceInfo: "인스턴스 정보" | ||||||
|   memo: "스티커 메모" |   memo: "스티커 메모" | ||||||
|   notifications: "알림" |   notifications: "알림" | ||||||
|   timeline: "타임라인" |   timeline: "타임라인" | ||||||
| @@ -1328,6 +1330,7 @@ _widgets: | |||||||
|   userList: "사용자 목록" |   userList: "사용자 목록" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "리스트 선택" |     chooseList: "리스트 선택" | ||||||
|  |   clicker: "클리커" | ||||||
| _cw: | _cw: | ||||||
|   hide: "숨기기" |   hide: "숨기기" | ||||||
|   show: "더 보기" |   show: "더 보기" | ||||||
| @@ -1503,7 +1506,6 @@ _notification: | |||||||
|   youGotReply: "{name}님이 답글함" |   youGotReply: "{name}님이 답글함" | ||||||
|   youGotQuote: "{name}님이 인용함" |   youGotQuote: "{name}님이 인용함" | ||||||
|   youRenoted: "{name}님이 Renote" |   youRenoted: "{name}님이 Renote" | ||||||
|   youGotPoll: "{name}님이 투표함" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요" |   youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요" | ||||||
|   youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요" |   youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요" | ||||||
|   youWereFollowed: "새로운 팔로워가 있습니다" |   youWereFollowed: "새로운 팔로워가 있습니다" | ||||||
| @@ -1521,7 +1523,6 @@ _notification: | |||||||
|     renote: "리노트" |     renote: "리노트" | ||||||
|     quote: "인용" |     quote: "인용" | ||||||
|     reaction: "리액션" |     reaction: "리액션" | ||||||
|     pollVote: "투표 참여" |  | ||||||
|     pollEnded: "투표가 종료됨" |     pollEnded: "투표가 종료됨" | ||||||
|     receiveFollowRequest: "팔로우 요청을 받았을 때" |     receiveFollowRequest: "팔로우 요청을 받았을 때" | ||||||
|     followRequestAccepted: "팔로우 요청이 승인되었을 때" |     followRequestAccepted: "팔로우 요청이 승인되었을 때" | ||||||
|   | |||||||
| @@ -440,6 +440,8 @@ _sfx: | |||||||
|   notification: "Meldingen" |   notification: "Meldingen" | ||||||
|   chat: "Chat" |   chat: "Chat" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profiel" | ||||||
|  |   instanceInfo: "Serverinformatie" | ||||||
|   notifications: "Meldingen" |   notifications: "Meldingen" | ||||||
|   timeline: "Tijdlijn" |   timeline: "Tijdlijn" | ||||||
|   activity: "Activiteit" |   activity: "Activiteit" | ||||||
|   | |||||||
| @@ -1213,6 +1213,8 @@ _weekday: | |||||||
|   friday: "Piątek" |   friday: "Piątek" | ||||||
|   saturday: "Sobota" |   saturday: "Sobota" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Informacje o instancji" | ||||||
|   memo: "Przypięte notatki" |   memo: "Przypięte notatki" | ||||||
|   notifications: "Powiadomienia" |   notifications: "Powiadomienia" | ||||||
|   timeline: "Oś czasu" |   timeline: "Oś czasu" | ||||||
| @@ -1380,7 +1382,6 @@ _notification: | |||||||
|   youGotReply: "{name} odpowiedział(a) Tobie" |   youGotReply: "{name} odpowiedział(a) Tobie" | ||||||
|   youGotQuote: "{name} zacytował(a) Ciebie" |   youGotQuote: "{name} zacytował(a) Ciebie" | ||||||
|   youRenoted: "{name} udostępnił(a) Twój wpis" |   youRenoted: "{name} udostępnił(a) Twój wpis" | ||||||
|   youGotPoll: "{name} zagłosował(a) w Twojej ankiecie" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość" |   youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość" | ||||||
|   youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}" |   youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}" | ||||||
|   youWereFollowed: "Zaobserwował(a) Cię" |   youWereFollowed: "Zaobserwował(a) Cię" | ||||||
| @@ -1398,7 +1399,6 @@ _notification: | |||||||
|     renote: "Udostępnij" |     renote: "Udostępnij" | ||||||
|     quote: "Cytuj" |     quote: "Cytuj" | ||||||
|     reaction: "Reakcja" |     reaction: "Reakcja" | ||||||
|     pollVote: "Głosy w ankietach" |  | ||||||
|     receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji" |     receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji" | ||||||
|     followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji" |     followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji" | ||||||
|     groupInvited: "Zaproszono do grup" |     groupInvited: "Zaproszono do grup" | ||||||
|   | |||||||
| @@ -488,6 +488,8 @@ _sfx: | |||||||
|   notification: "Notificações" |   notification: "Notificações" | ||||||
|   chat: "Chat" |   chat: "Chat" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Perfil" | ||||||
|  |   instanceInfo: "Informações da instância" | ||||||
|   notifications: "Notificações" |   notifications: "Notificações" | ||||||
|   timeline: "Timeline" |   timeline: "Timeline" | ||||||
|   activity: "atividade" |   activity: "atividade" | ||||||
| @@ -524,7 +526,6 @@ _notification: | |||||||
|   youGotMention: "{name} te mencionou" |   youGotMention: "{name} te mencionou" | ||||||
|   youGotReply: "{name} te respondeu" |   youGotReply: "{name} te respondeu" | ||||||
|   youGotQuote: "{name} te citou" |   youGotQuote: "{name} te citou" | ||||||
|   youGotPoll: "{name} votou em sua enquete" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo" |   youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo" | ||||||
|   youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}" |   youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}" | ||||||
|   youWereFollowed: "Você tem um novo seguidor" |   youWereFollowed: "Você tem um novo seguidor" | ||||||
| @@ -541,7 +542,6 @@ _notification: | |||||||
|     renote: "Repostar" |     renote: "Repostar" | ||||||
|     quote: "Citar" |     quote: "Citar" | ||||||
|     reaction: "Reações" |     reaction: "Reações" | ||||||
|     pollVote: "Votações em enquetes" |  | ||||||
|     pollEnded: "Enquetes terminando" |     pollEnded: "Enquetes terminando" | ||||||
|     receiveFollowRequest: "Recebeu pedidos de seguimento" |     receiveFollowRequest: "Recebeu pedidos de seguimento" | ||||||
|     followRequestAccepted: "Aceitou pedidos de seguimento" |     followRequestAccepted: "Aceitou pedidos de seguimento" | ||||||
|   | |||||||
| @@ -667,6 +667,8 @@ _sfx: | |||||||
|   notification: "Notificări" |   notification: "Notificări" | ||||||
|   chat: "Chat" |   chat: "Chat" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Informații despre instanță" | ||||||
|   notifications: "Notificări" |   notifications: "Notificări" | ||||||
|   timeline: "Cronologie" |   timeline: "Cronologie" | ||||||
|   activity: "Activitate" |   activity: "Activitate" | ||||||
|   | |||||||
| @@ -1213,6 +1213,8 @@ _weekday: | |||||||
|   friday: "Пятница" |   friday: "Пятница" | ||||||
|   saturday: "Суббота" |   saturday: "Суббота" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Профиль" | ||||||
|  |   instanceInfo: "Информация об инстансе" | ||||||
|   memo: "Напоминания" |   memo: "Напоминания" | ||||||
|   notifications: "Уведомления" |   notifications: "Уведомления" | ||||||
|   timeline: "Лента" |   timeline: "Лента" | ||||||
| @@ -1399,7 +1401,6 @@ _notification: | |||||||
|   youGotReply: "{name} отвечает вам." |   youGotReply: "{name} отвечает вам." | ||||||
|   youGotQuote: "{name} цитирует вас." |   youGotQuote: "{name} цитирует вас." | ||||||
|   youRenoted: "{name} передаёт вашу заметку." |   youRenoted: "{name} передаёт вашу заметку." | ||||||
|   youGotPoll: "{name} участвует в вашем опросе." |  | ||||||
|   youGotMessagingMessageFromUser: "{name} пишет вам." |   youGotMessagingMessageFromUser: "{name} пишет вам." | ||||||
|   youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»." |   youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»." | ||||||
|   youWereFollowed: "У вас новый подписчик." |   youWereFollowed: "У вас новый подписчик." | ||||||
| @@ -1414,7 +1415,6 @@ _notification: | |||||||
|     renote: "Репосты" |     renote: "Репосты" | ||||||
|     quote: "Цитаты" |     quote: "Цитаты" | ||||||
|     reaction: "Реакции" |     reaction: "Реакции" | ||||||
|     pollVote: "Голосования" |  | ||||||
|     receiveFollowRequest: "Получен запрос на подписку" |     receiveFollowRequest: "Получен запрос на подписку" | ||||||
|     followRequestAccepted: "Запрос на подписку одобрен" |     followRequestAccepted: "Запрос на подписку одобрен" | ||||||
|     groupInvited: "Приглашение в группы" |     groupInvited: "Приглашение в группы" | ||||||
|   | |||||||
| @@ -1295,6 +1295,8 @@ _weekday: | |||||||
|   friday: "Piatok" |   friday: "Piatok" | ||||||
|   saturday: "Sobota" |   saturday: "Sobota" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Informácie o serveri" | ||||||
|   memo: "Prilepené poznámky" |   memo: "Prilepené poznámky" | ||||||
|   notifications: "Oznámenia" |   notifications: "Oznámenia" | ||||||
|   timeline: "Časová os" |   timeline: "Časová os" | ||||||
| @@ -1484,7 +1486,6 @@ _notification: | |||||||
|   youGotReply: "{name} vám odpovedal/a" |   youGotReply: "{name} vám odpovedal/a" | ||||||
|   youGotQuote: "{name} vás citoval/a" |   youGotQuote: "{name} vás citoval/a" | ||||||
|   youRenoted: "{name} preposlal/a vašu poznámku" |   youRenoted: "{name} preposlal/a vašu poznámku" | ||||||
|   youGotPoll: "{name} hlasoval/a" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} vám poslal/a správu" |   youGotMessagingMessageFromUser: "{name} vám poslal/a správu" | ||||||
|   youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}" |   youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}" | ||||||
|   youWereFollowed: "Máte nového sledujúceho" |   youWereFollowed: "Máte nového sledujúceho" | ||||||
| @@ -1502,7 +1503,6 @@ _notification: | |||||||
|     renote: "Preposlať" |     renote: "Preposlať" | ||||||
|     quote: "Citovať" |     quote: "Citovať" | ||||||
|     reaction: "Reakcie" |     reaction: "Reakcie" | ||||||
|     pollVote: "Hlasy v hlasovaniach" |  | ||||||
|     pollEnded: "Hlasovanie skončilo" |     pollEnded: "Hlasovanie skončilo" | ||||||
|     receiveFollowRequest: "Doručené žiadosti o sledovanie" |     receiveFollowRequest: "Doručené žiadosti o sledovanie" | ||||||
|     followRequestAccepted: "Schválené žiadosti o sledovanie" |     followRequestAccepted: "Schválené žiadosti o sledovanie" | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| --- | --- | ||||||
| _lang_: "Svenska" | _lang_: "Svenska" | ||||||
| headlineMisskey: "Ett nätverk kopplat av noter" | headlineMisskey: "Ett nätverk kopplat av noter" | ||||||
| introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀" | introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter. 👍\nLåt oss utforska en ny värld! 🚀" | ||||||
| poweredByMisskeyDescription: "{name} är en tjänst driven av den öppna källkodsplatformen <b>Misskey</b> (benämns \"Misskey instans\")." | poweredByMisskeyDescription: "{name} är en tjänst driven av den öppna källkodsplatformen <b>Misskey</b> (benämns \"Misskey instans\")." | ||||||
| monthAndDay: "{day}/{month}" | monthAndDay: "{day}/{month}" | ||||||
| search: "Sök" | search: "Sök" | ||||||
| @@ -17,7 +17,7 @@ noThankYou: "Nej tack" | |||||||
| enterUsername: "Ange användarnamn" | enterUsername: "Ange användarnamn" | ||||||
| renotedBy: "Omnoterad av {user}" | renotedBy: "Omnoterad av {user}" | ||||||
| noNotes: "Inga noteringar" | noNotes: "Inga noteringar" | ||||||
| noNotifications: "Inga aviseringar" | noNotifications: "Inga notifikationer" | ||||||
| instance: "Instanser" | instance: "Instanser" | ||||||
| settings: "Inställningar" | settings: "Inställningar" | ||||||
| basicSettings: "Basinställningar" | basicSettings: "Basinställningar" | ||||||
| @@ -30,13 +30,13 @@ login: "Logga in" | |||||||
| loggingIn: "Loggar in" | loggingIn: "Loggar in" | ||||||
| logout: "Logga ut" | logout: "Logga ut" | ||||||
| signup: "Registrera" | signup: "Registrera" | ||||||
| uploading: "Uppladdning sker..." | uploading: "Laddar upp..." | ||||||
| save: "Spara" | save: "Spara" | ||||||
| users: "Användare" | users: "Användare" | ||||||
| addUser: "Lägg till användare" | addUser: "Lägg till användare" | ||||||
| favorite: "Lägg till i favoriter" | favorite: "Lägg till i favoriter" | ||||||
| favorites: "Favoriter" | favorites: "Favoriter" | ||||||
| unfavorite: "Avfavorisera" | unfavorite: "Ta bort från favoriter" | ||||||
| favorited: "Tillagd i favoriter." | favorited: "Tillagd i favoriter." | ||||||
| alreadyFavorited: "Redan tillagd i favoriter." | alreadyFavorited: "Redan tillagd i favoriter." | ||||||
| cantFavorite: "Gick inte att lägga till i favoriter." | cantFavorite: "Gick inte att lägga till i favoriter." | ||||||
| @@ -146,7 +146,7 @@ flagAsBotDescription: "Aktivera det här alternativet om kontot är kontrollerat | |||||||
| flagAsCat: "Markera konto som katt" | flagAsCat: "Markera konto som katt" | ||||||
| flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt." | flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt." | ||||||
| flagShowTimelineReplies: "Visa svar i tidslinje" | flagShowTimelineReplies: "Visa svar i tidslinje" | ||||||
| flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om påslagen." | flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om aktiverad." | ||||||
| autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt" | autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt" | ||||||
| addAccount: "Lägg till konto" | addAccount: "Lägg till konto" | ||||||
| loginFailed: "Inloggningen misslyckades" | loginFailed: "Inloggningen misslyckades" | ||||||
| @@ -253,16 +253,120 @@ explore: "Utforska" | |||||||
| messageRead: "Läs" | messageRead: "Läs" | ||||||
| noMoreHistory: "Det finns ingen mer historik" | noMoreHistory: "Det finns ingen mer historik" | ||||||
| startMessaging: "Starta en chatt" | startMessaging: "Starta en chatt" | ||||||
|  | nUsersRead: "läst av {n}" | ||||||
|  | agreeTo: "Jag accepterar {0}" | ||||||
|  | tos: "Användarvillkor" | ||||||
|  | home: "Hem" | ||||||
|  | remoteUserCaution: "Då denna användaren kommer från en fjärrinstans, kan informationen visad vara ofullständig." | ||||||
|  | activity: "Aktivitet" | ||||||
|  | images: "Bilder" | ||||||
|  | birthday: "Födelsedag" | ||||||
|  | yearsOld: "{age} år gammal" | ||||||
|  | registeredDate: "Gick med" | ||||||
|  | location: "Plats" | ||||||
|  | theme: "Teman" | ||||||
|  | themeForLightMode: "Tema att använda i Ljust Läge" | ||||||
|  | themeForDarkMode: "Tema att använda i Mörkt Läge" | ||||||
|  | light: "Ljust" | ||||||
|  | dark: "Mörk" | ||||||
|  | lightThemes: "Ljusa teman" | ||||||
|  | darkThemes: "Mörka teman" | ||||||
|  | syncDeviceDarkMode: "Synka Mörkt Läge med din enhets inställningar" | ||||||
|  | drive: "Drive" | ||||||
|  | fileName: "Filnamn" | ||||||
|  | selectFile: "Välj en fil" | ||||||
|  | selectFiles: "Välj filer" | ||||||
|  | selectFolder: "Välj en mapp" | ||||||
|  | selectFolders: "Välj mappar" | ||||||
|  | renameFile: "Byt namn på filen" | ||||||
|  | folderName: "Mappnamn" | ||||||
|  | createFolder: "Skapa en mapp" | ||||||
|  | renameFolder: "Byt namn på mappen" | ||||||
|  | deleteFolder: "Ta bort mappen" | ||||||
|  | addFile: "Lägg till fil" | ||||||
|  | emptyDrive: "Din Drive är tom" | ||||||
|  | emptyFolder: "Denna mappen är tom" | ||||||
|  | unableToDelete: "Kunde inte ta bort" | ||||||
|  | inputNewFileName: "Ange nytt filnamn" | ||||||
|  | inputNewDescription: "Ange ny bildtext" | ||||||
|  | inputNewFolderName: "Ange nytt mappnamn" | ||||||
|  | circularReferenceFolder: "Destinationsmappen är en undermapp av mappen du vill flytta." | ||||||
|  | hasChildFilesOrFolders: "Då denna mappen inte är tom, kan den inte tas bort." | ||||||
|  | copyUrl: "Kopiera URL" | ||||||
|  | rename: "Byt namn" | ||||||
|  | avatar: "Profilbild" | ||||||
|  | banner: "Banner" | ||||||
| nsfw: "Känsligt innehåll" | nsfw: "Känsligt innehåll" | ||||||
|  | reload: "Ladda om" | ||||||
|  | doNothing: "Ignorera" | ||||||
|  | reloadConfirm: "Vill du ladda om tidslinjen?" | ||||||
|  | accept: "Tillåt" | ||||||
|  | reject: "Neka" | ||||||
|  | normal: "Normal" | ||||||
|  | instanceName: "Instansnamn" | ||||||
|  | instanceDescription: "Instansbeskrivning" | ||||||
|  | maintainerEmail: "Administratörens epost" | ||||||
|  | tosUrl: "URL till användarvillkår" | ||||||
|  | thisYear: "Detta året" | ||||||
|  | thisMonth: "Denna månaden" | ||||||
|  | today: "Idag" | ||||||
|  | dayX: "{day}" | ||||||
|  | monthX: "{month}" | ||||||
|  | yearX: "{year}" | ||||||
|  | pages: "Sidor" | ||||||
|  | integration: "Integrationer" | ||||||
|  | connectService: "Anslut" | ||||||
|  | disconnectService: "Koppla från" | ||||||
|  | enableLocalTimeline: "Aktivera lokal tidslinje" | ||||||
|  | enableGlobalTimeline: "Aktivera global tidslinje" | ||||||
|  | enableRegistration: "Aktivera registrering av nya användare" | ||||||
|  | inMb: "I megabyte" | ||||||
|  | iconUrl: "URL till profilbilden" | ||||||
|  | bannerUrl: "URL till banner-bilden" | ||||||
| pinnedNotes: "Fästad not" | pinnedNotes: "Fästad not" | ||||||
|  | enableHcaptcha: "Aktivera hCaptcha" | ||||||
|  | enableRecaptcha: "Aktivera reCAPTCHA" | ||||||
|  | enableTurnstile: "Aktivera Turnstile" | ||||||
|  | antennas: "Antenner" | ||||||
|  | manageAntennas: "Hantera Antenner" | ||||||
|  | antennaSource: "Antennkälla" | ||||||
|  | antennaKeywords: "Nyckelord att lyssna efter" | ||||||
|  | antennaExcludeKeywords: "Nyckelord att exkludera" | ||||||
|  | antennaKeywordsDescription: "Separera med mellanslag för en AND kondition, eller med nya linjer för en OR kondition" | ||||||
|  | notifyAntenna: "Notifiera om nya noter" | ||||||
|  | withFileAntenna: "Endast noter med filer" | ||||||
|  | enableServiceworker: "Aktivera pushnotiser i denna webbläsaren" | ||||||
|  | antennaUsersDescription: "Ange ett användarnamn per linje" | ||||||
|  | recentlyUpdatedUsers: "Nyligen aktiva användare" | ||||||
|  | recentlyRegisteredUsers: "Nyligen registrerade användare" | ||||||
| userList: "Listor" | userList: "Listor" | ||||||
|  | aboutMisskey: "Om Misskey" | ||||||
|  | administrator: "Administratör" | ||||||
|  | newPasswordIs: "Det nya lösenordet är \"{password}\"" | ||||||
|  | share: "Dela" | ||||||
|  | enable: "Aktivera" | ||||||
|  | serviceworkerInfo: "Måste vara aktiverad för pushnotiser." | ||||||
|  | enableInfiniteScroll: "Ladda mer automatiskt" | ||||||
|  | enablePlayer: "Öppna videospelare" | ||||||
|  | enableAll: "Aktivera alla" | ||||||
|  | enableEmail: "Aktivera epost-utskick" | ||||||
| smtpHost: "Värd" | smtpHost: "Värd" | ||||||
| smtpUser: "Användarnamn" | smtpUser: "Användarnamn" | ||||||
| smtpPass: "Lösenord" | smtpPass: "Lösenord" | ||||||
| clearCache: "Rensa cache" | clearCache: "Rensa cache" | ||||||
|  | enabled: "Aktiverad" | ||||||
| user: "Användare" | user: "Användare" | ||||||
|  | global: "Global" | ||||||
|  | squareAvatars: "Visa fyrkantiga profilbilder" | ||||||
| searchByGoogle: "Sök" | searchByGoogle: "Sök" | ||||||
| file: "Filer" | file: "Filer" | ||||||
|  | enableAutoSensitive: "Automatisk NSFW markering" | ||||||
|  | enableAutoSensitiveDescription: "Tillåter automatiskt detektering och marketing av NSFW media genom Maskininlärning när möjligt. Även om denna inställningen är avaktiverad, kan det vara aktiverat på hela instansen." | ||||||
|  | pushNotification: "Pushnotiser" | ||||||
|  | subscribePushNotification: "Aktivera pushnotiser" | ||||||
|  | unsubscribePushNotification: "Avaktivera pushnotiser" | ||||||
|  | pushNotificationAlreadySubscribed: "Pushnotiser är redan aktiverade" | ||||||
|  | pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för pushnotiser" | ||||||
| _email: | _email: | ||||||
|   _follow: |   _follow: | ||||||
|     title: "följde dig" |     title: "följde dig" | ||||||
| @@ -271,6 +375,9 @@ _mfm: | |||||||
|   quote: "Citat" |   quote: "Citat" | ||||||
|   emoji: "Anpassa emoji" |   emoji: "Anpassa emoji" | ||||||
|   search: "Sök" |   search: "Sök" | ||||||
|  | _channel: | ||||||
|  |   setBanner: "Välj banner" | ||||||
|  |   removeBanner: "Ta bort banner" | ||||||
| _theme: | _theme: | ||||||
|   keys: |   keys: | ||||||
|     mention: "Nämn" |     mention: "Nämn" | ||||||
| @@ -279,9 +386,19 @@ _sfx: | |||||||
|   note: "Noter" |   note: "Noter" | ||||||
|   notification: "Notifikationer" |   notification: "Notifikationer" | ||||||
|   chat: "Chatt" |   chat: "Chatt" | ||||||
|  |   antenna: "Antenner" | ||||||
|  | _antennaSources: | ||||||
|  |   all: "Alla noter" | ||||||
|  |   homeTimeline: "Noter från följda användare" | ||||||
|  |   users: "Noter från specifika användare" | ||||||
|  |   userList: "Noter från en specificerad lista av användare" | ||||||
|  |   userGroup: "Noter från användare i en specificerad grupp" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|  |   instanceInfo: "Instansinformation" | ||||||
|   notifications: "Notifikationer" |   notifications: "Notifikationer" | ||||||
|   timeline: "Tidslinje" |   timeline: "Tidslinje" | ||||||
|  |   activity: "Aktivitet" | ||||||
|   federation: "Federation" |   federation: "Federation" | ||||||
|   jobQueue: "Jobbkö" |   jobQueue: "Jobbkö" | ||||||
|   _userList: |   _userList: | ||||||
| @@ -289,18 +406,29 @@ _widgets: | |||||||
| _cw: | _cw: | ||||||
|   show: "Ladda mer" |   show: "Ladda mer" | ||||||
| _visibility: | _visibility: | ||||||
|  |   home: "Hem" | ||||||
|   followers: "Följare" |   followers: "Följare" | ||||||
| _profile: | _profile: | ||||||
|   username: "Användarnamn" |   username: "Användarnamn" | ||||||
|  |   changeAvatar: "Ändra profilbild" | ||||||
|  |   changeBanner: "Ändra banner" | ||||||
| _exportOrImport: | _exportOrImport: | ||||||
|  |   allNotes: "Alla noter" | ||||||
|   followingList: "Följer" |   followingList: "Följer" | ||||||
|   muteList: "Tysta" |   muteList: "Tysta" | ||||||
|   blockingList: "Blockera" |   blockingList: "Blockera" | ||||||
|   userLists: "Listor" |   userLists: "Listor" | ||||||
| _charts: | _charts: | ||||||
|   federation: "Federation" |   federation: "Federation" | ||||||
|  | _timelines: | ||||||
|  |   home: "Hem" | ||||||
|  |   global: "Global" | ||||||
|  | _pages: | ||||||
|  |   blocks: | ||||||
|  |     image: "Bilder" | ||||||
| _notification: | _notification: | ||||||
|   youWereFollowed: "följde dig" |   youWereFollowed: "följde dig" | ||||||
|  |   unreadAntennaNote: "Antenn {name}" | ||||||
|   _types: |   _types: | ||||||
|     follow: "Följer" |     follow: "Följer" | ||||||
|     mention: "Nämn" |     mention: "Nämn" | ||||||
| @@ -314,5 +442,6 @@ _deck: | |||||||
|   _columns: |   _columns: | ||||||
|     notifications: "Notifikationer" |     notifications: "Notifikationer" | ||||||
|     tl: "Tidslinje" |     tl: "Tidslinje" | ||||||
|  |     antenna: "Antenner" | ||||||
|     list: "Listor" |     list: "Listor" | ||||||
|     mentions: "Omnämningar" |     mentions: "Omnämningar" | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ search: "ค้นหา" | |||||||
| notifications: "การเเจ้งเตือน" | notifications: "การเเจ้งเตือน" | ||||||
| username: "ชื่อผู้ใช้" | username: "ชื่อผู้ใช้" | ||||||
| password: "รหัสผ่าน" | password: "รหัสผ่าน" | ||||||
| forgotPassword: "ลืมรหัสผ่าน?" | forgotPassword: "ลืมรหัสผ่านใช่ไหม" | ||||||
| fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..." | fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..." | ||||||
| ok: "โอเค" | ok: "โอเค" | ||||||
| gotIt: "เข้าใจแล้ว !" | gotIt: "เข้าใจแล้ว !" | ||||||
| @@ -920,6 +920,10 @@ like: "ชื่นชอบ" | |||||||
| unlike: "ไม่ชอบ" | unlike: "ไม่ชอบ" | ||||||
| numberOfLikes: "จำนวนไลค์" | numberOfLikes: "จำนวนไลค์" | ||||||
| show: "แสดงผล" | show: "แสดงผล" | ||||||
|  | neverShow: "ไม่ต้องแสดงข้อความนี้อีก" | ||||||
|  | remindMeLater: "ไว้ครั้งหน้าแล้วกัน" | ||||||
|  | didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?" | ||||||
|  | pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!" | ||||||
| _sensitiveMediaDetection: | _sensitiveMediaDetection: | ||||||
|   description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย" |   description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย" | ||||||
|   sensitivity: "การตรวจจับความไว" |   sensitivity: "การตรวจจับความไว" | ||||||
| @@ -1298,6 +1302,8 @@ _weekday: | |||||||
|   friday: "วันศุกร์" |   friday: "วันศุกร์" | ||||||
|   saturday: "วันเสาร์" |   saturday: "วันเสาร์" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "โปรไฟล์" | ||||||
|  |   instanceInfo: "ข้อมูล อินสแตนซ์" | ||||||
|   memo: "โน้ตแปะ" |   memo: "โน้ตแปะ" | ||||||
|   notifications: "การเเจ้งเตือน" |   notifications: "การเเจ้งเตือน" | ||||||
|   timeline: "ไทม์ไลน์" |   timeline: "ไทม์ไลน์" | ||||||
| @@ -1324,6 +1330,7 @@ _widgets: | |||||||
|   userList: "รายชื่อผู้ใช้" |   userList: "รายชื่อผู้ใช้" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "เลือกรายการ" |     chooseList: "เลือกรายการ" | ||||||
|  |   clicker: "คลิกเกอร์" | ||||||
| _cw: | _cw: | ||||||
|   hide: "ซ่อน" |   hide: "ซ่อน" | ||||||
|   show: "โหลดเพิ่มเติม" |   show: "โหลดเพิ่มเติม" | ||||||
| @@ -1499,7 +1506,6 @@ _notification: | |||||||
|   youGotReply: "{name} ตอบกลับถึงคุณ" |   youGotReply: "{name} ตอบกลับถึงคุณ" | ||||||
|   youGotQuote: "{name} อ้างถึงคุณ" |   youGotQuote: "{name} อ้างถึงคุณ" | ||||||
|   youRenoted: "รีโน้ตจาก {name}" |   youRenoted: "รีโน้ตจาก {name}" | ||||||
|   youGotPoll: "{name} โหวตบนแบบสำรวจความคิดเห็นของคุณ" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ" |   youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ" | ||||||
|   youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม" |   youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม" | ||||||
|   youWereFollowed: "ได้ติดตามคุณ" |   youWereFollowed: "ได้ติดตามคุณ" | ||||||
| @@ -1517,7 +1523,6 @@ _notification: | |||||||
|     renote: "รีโน้ต" |     renote: "รีโน้ต" | ||||||
|     quote: "อ้างคำพูด" |     quote: "อ้างคำพูด" | ||||||
|     reaction: "รีแอคชั่น" |     reaction: "รีแอคชั่น" | ||||||
|     pollVote: "จำนวนโหวตที่ได้รับ" |  | ||||||
|     pollEnded: "โพลนี้สิ้นสุดลงแล้ว" |     pollEnded: "โพลนี้สิ้นสุดลงแล้ว" | ||||||
|     receiveFollowRequest: "ได้รับคำขอติดตาม\n" |     receiveFollowRequest: "ได้รับคำขอติดตาม\n" | ||||||
|     followRequestAccepted: "ยอมรับคำขอติดตาม" |     followRequestAccepted: "ยอมรับคำขอติดตาม" | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ _mfm: | |||||||
| _sfx: | _sfx: | ||||||
|   notification: "Bildirim" |   notification: "Bildirim" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Profil" | ||||||
|   notifications: "Bildirim" |   notifications: "Bildirim" | ||||||
|   timeline: "Zaman çizelgesi" |   timeline: "Zaman çizelgesi" | ||||||
| _profile: | _profile: | ||||||
|   | |||||||
| @@ -1229,6 +1229,8 @@ _weekday: | |||||||
|   friday: "П'ятниця" |   friday: "П'ятниця" | ||||||
|   saturday: "Субота" |   saturday: "Субота" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Профіль" | ||||||
|  |   instanceInfo: "Про цей інстанс" | ||||||
|   memo: "Нагадування" |   memo: "Нагадування" | ||||||
|   notifications: "Сповіщення" |   notifications: "Сповіщення" | ||||||
|   timeline: "Стрічка" |   timeline: "Стрічка" | ||||||
| @@ -1415,7 +1417,6 @@ _notification: | |||||||
|   youGotReply: "{name} відповідає" |   youGotReply: "{name} відповідає" | ||||||
|   youGotQuote: "{name} цитує вас" |   youGotQuote: "{name} цитує вас" | ||||||
|   youRenoted: "{name} поширює" |   youRenoted: "{name} поширює" | ||||||
|   youGotPoll: "{name} бере участь в опитуванні" |  | ||||||
|   youGotMessagingMessageFromUser: "Повідомлення від {name}" |   youGotMessagingMessageFromUser: "Повідомлення від {name}" | ||||||
|   youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}" |   youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}" | ||||||
|   youWereFollowed: "Новий підписник" |   youWereFollowed: "Новий підписник" | ||||||
| @@ -1430,7 +1431,6 @@ _notification: | |||||||
|     renote: "Поширення" |     renote: "Поширення" | ||||||
|     quote: "Цитування" |     quote: "Цитування" | ||||||
|     reaction: "Реакції" |     reaction: "Реакції" | ||||||
|     pollVote: "Опитування" |  | ||||||
|     receiveFollowRequest: "Запити на підписку" |     receiveFollowRequest: "Запити на підписку" | ||||||
|     followRequestAccepted: "Прийняті підписки" |     followRequestAccepted: "Прийняті підписки" | ||||||
|     groupInvited: "Запрошення до груп" |     groupInvited: "Запрошення до груп" | ||||||
|   | |||||||
| @@ -1271,6 +1271,8 @@ _weekday: | |||||||
|   friday: "Thứ Sáu" |   friday: "Thứ Sáu" | ||||||
|   saturday: "Thứ Bảy" |   saturday: "Thứ Bảy" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "Trang cá nhân" | ||||||
|  |   instanceInfo: "Thông tin máy chủ" | ||||||
|   memo: "Tút đã ghim" |   memo: "Tút đã ghim" | ||||||
|   notifications: "Thông báo" |   notifications: "Thông báo" | ||||||
|   timeline: "Bảng tin" |   timeline: "Bảng tin" | ||||||
| @@ -1460,7 +1462,6 @@ _notification: | |||||||
|   youGotReply: "{name} trả lời bạn" |   youGotReply: "{name} trả lời bạn" | ||||||
|   youGotQuote: "{name} trích dẫn tút của bạn" |   youGotQuote: "{name} trích dẫn tút của bạn" | ||||||
|   youRenoted: "{name} đăng lại tút của bạn" |   youRenoted: "{name} đăng lại tút của bạn" | ||||||
|   youGotPoll: "{name} bình chọn tút của bạn" |  | ||||||
|   youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn" |   youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn" | ||||||
|   youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}" |   youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}" | ||||||
|   youWereFollowed: "đã theo dõi bạn" |   youWereFollowed: "đã theo dõi bạn" | ||||||
| @@ -1477,7 +1478,6 @@ _notification: | |||||||
|     renote: "Đăng lại" |     renote: "Đăng lại" | ||||||
|     quote: "Trích dẫn" |     quote: "Trích dẫn" | ||||||
|     reaction: "Biểu cảm" |     reaction: "Biểu cảm" | ||||||
|     pollVote: "Lượt bình chọn" |  | ||||||
|     pollEnded: "Bình chọn kết thúc" |     pollEnded: "Bình chọn kết thúc" | ||||||
|     receiveFollowRequest: "Yêu cầu theo dõi" |     receiveFollowRequest: "Yêu cầu theo dõi" | ||||||
|     followRequestAccepted: "Yêu cầu theo dõi được chấp nhận" |     followRequestAccepted: "Yêu cầu theo dõi được chấp nhận" | ||||||
|   | |||||||
| @@ -920,6 +920,10 @@ like: "点赞!" | |||||||
| unlike: "取消赞" | unlike: "取消赞" | ||||||
| numberOfLikes: "点赞数" | numberOfLikes: "点赞数" | ||||||
| show: "显示" | show: "显示" | ||||||
|  | neverShow: "不再显示" | ||||||
|  | remindMeLater: "稍后提醒我" | ||||||
|  | didYouLikeMisskey: "您喜欢Misskey吗?" | ||||||
|  | pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发,请在有余力的情况下进行捐助!" | ||||||
| _sensitiveMediaDetection: | _sensitiveMediaDetection: | ||||||
|   description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" |   description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" | ||||||
|   sensitivity: "检测敏感度" |   sensitivity: "检测敏感度" | ||||||
| @@ -1298,6 +1302,8 @@ _weekday: | |||||||
|   friday: "星期五" |   friday: "星期五" | ||||||
|   saturday: "星期六" |   saturday: "星期六" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "个人资料" | ||||||
|  |   instanceInfo: "实例信息" | ||||||
|   memo: "便签" |   memo: "便签" | ||||||
|   notifications: "通知" |   notifications: "通知" | ||||||
|   timeline: "时间线" |   timeline: "时间线" | ||||||
| @@ -1324,6 +1330,7 @@ _widgets: | |||||||
|   userList: "用户列表" |   userList: "用户列表" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "选择列表" |     chooseList: "选择列表" | ||||||
|  |   clicker: "点击器" | ||||||
| _cw: | _cw: | ||||||
|   hide: "隐藏" |   hide: "隐藏" | ||||||
|   show: "查看更多" |   show: "查看更多" | ||||||
| @@ -1499,7 +1506,6 @@ _notification: | |||||||
|   youGotReply: "来自{name}的回复" |   youGotReply: "来自{name}的回复" | ||||||
|   youGotQuote: "来自{name}的引用" |   youGotQuote: "来自{name}的引用" | ||||||
|   youRenoted: "来自{name}的转发" |   youRenoted: "来自{name}的转发" | ||||||
|   youGotPoll: "来自{name}的投票" |  | ||||||
|   youGotMessagingMessageFromUser: "来自{name}的聊天" |   youGotMessagingMessageFromUser: "来自{name}的聊天" | ||||||
|   youGotMessagingMessageFromGroup: "来自{name}的群聊" |   youGotMessagingMessageFromGroup: "来自{name}的群聊" | ||||||
|   youWereFollowed: "关注了你。" |   youWereFollowed: "关注了你。" | ||||||
| @@ -1517,7 +1523,6 @@ _notification: | |||||||
|     renote: "转发" |     renote: "转发" | ||||||
|     quote: "引用" |     quote: "引用" | ||||||
|     reaction: "回应" |     reaction: "回应" | ||||||
|     pollVote: "问卷调查被投票" |  | ||||||
|     pollEnded: "问卷调查结束" |     pollEnded: "问卷调查结束" | ||||||
|     receiveFollowRequest: "收到关注请求" |     receiveFollowRequest: "收到关注请求" | ||||||
|     followRequestAccepted: "关注请求已通过" |     followRequestAccepted: "关注请求已通过" | ||||||
|   | |||||||
| @@ -252,7 +252,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。" | |||||||
| explore: "探索" | explore: "探索" | ||||||
| messageRead: "已讀" | messageRead: "已讀" | ||||||
| noMoreHistory: "沒有更多歷史紀錄" | noMoreHistory: "沒有更多歷史紀錄" | ||||||
| startMessaging: "開始傳送訊息" | startMessaging: "開始聊天" | ||||||
| nUsersRead: "{n}人已讀" | nUsersRead: "{n}人已讀" | ||||||
| agreeTo: "我同意{0}" | agreeTo: "我同意{0}" | ||||||
| tos: "使用條款" | tos: "使用條款" | ||||||
| @@ -797,7 +797,7 @@ squareAvatars: "頭像以方形顯示" | |||||||
| sent: "發送" | sent: "發送" | ||||||
| received: "收取" | received: "收取" | ||||||
| searchResult: "搜尋結果" | searchResult: "搜尋結果" | ||||||
| hashtags: "#tag" | hashtags: "標籤" | ||||||
| troubleshooting: "故障排除" | troubleshooting: "故障排除" | ||||||
| useBlurEffect: "在 UI 上使用模糊效果" | useBlurEffect: "在 UI 上使用模糊效果" | ||||||
| learnMore: "更多資訊" | learnMore: "更多資訊" | ||||||
| @@ -923,6 +923,7 @@ show: "檢視" | |||||||
| neverShow: "不再顯示" | neverShow: "不再顯示" | ||||||
| remindMeLater: "以後再說" | remindMeLater: "以後再說" | ||||||
| didYouLikeMisskey: "您是否喜愛Misskey呢?" | didYouLikeMisskey: "您是否喜愛Misskey呢?" | ||||||
|  | pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!" | ||||||
| _sensitiveMediaDetection: | _sensitiveMediaDetection: | ||||||
|   description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" |   description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" | ||||||
|   sensitivity: "檢測敏感度" |   sensitivity: "檢測敏感度" | ||||||
| @@ -1158,7 +1159,7 @@ _theme: | |||||||
|     navActive: "側邊欄文本 (活動)" |     navActive: "側邊欄文本 (活動)" | ||||||
|     navIndicator: "側邊欄指示符" |     navIndicator: "側邊欄指示符" | ||||||
|     link: "鏈接" |     link: "鏈接" | ||||||
|     hashtag: "#tag" |     hashtag: "標籤" | ||||||
|     mention: "提到" |     mention: "提到" | ||||||
|     mentionMe: "提到了我" |     mentionMe: "提到了我" | ||||||
|     renote: "轉發貼文" |     renote: "轉發貼文" | ||||||
| @@ -1191,7 +1192,7 @@ _sfx: | |||||||
|   note: "貼文" |   note: "貼文" | ||||||
|   noteMy: "我的貼文" |   noteMy: "我的貼文" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   chat: "傳送訊息" |   chat: "聊天" | ||||||
|   chatBg: "聊天背景" |   chatBg: "聊天背景" | ||||||
|   antenna: "天線接收" |   antenna: "天線接收" | ||||||
|   channel: "頻道通知" |   channel: "頻道通知" | ||||||
| @@ -1301,6 +1302,8 @@ _weekday: | |||||||
|   friday: "週五" |   friday: "週五" | ||||||
|   saturday: "週六" |   saturday: "週六" | ||||||
| _widgets: | _widgets: | ||||||
|  |   profile: "個人檔案" | ||||||
|  |   instanceInfo: "實例資訊" | ||||||
|   memo: "備忘錄" |   memo: "備忘錄" | ||||||
|   notifications: "通知" |   notifications: "通知" | ||||||
|   timeline: "時間軸" |   timeline: "時間軸" | ||||||
| @@ -1327,6 +1330,7 @@ _widgets: | |||||||
|   userList: "使用者列表" |   userList: "使用者列表" | ||||||
|   _userList: |   _userList: | ||||||
|     chooseList: "選擇清單" |     chooseList: "選擇清單" | ||||||
|  |   clicker: "點擊器" | ||||||
| _cw: | _cw: | ||||||
|   hide: "隱藏" |   hide: "隱藏" | ||||||
|   show: "瀏覽更多" |   show: "瀏覽更多" | ||||||
| @@ -1502,7 +1506,6 @@ _notification: | |||||||
|   youGotReply: "{name}回覆了您" |   youGotReply: "{name}回覆了您" | ||||||
|   youGotQuote: "{name}引用了您" |   youGotQuote: "{name}引用了您" | ||||||
|   youRenoted: "{name} 轉發了你的貼文" |   youRenoted: "{name} 轉發了你的貼文" | ||||||
|   youGotPoll: "{name}已投票" |  | ||||||
|   youGotMessagingMessageFromUser: "{name}發送給您的訊息" |   youGotMessagingMessageFromUser: "{name}發送給您的訊息" | ||||||
|   youGotMessagingMessageFromGroup: "{name}發送給您的訊息" |   youGotMessagingMessageFromGroup: "{name}發送給您的訊息" | ||||||
|   youWereFollowed: "您有新的追隨者" |   youWereFollowed: "您有新的追隨者" | ||||||
| @@ -1520,7 +1523,6 @@ _notification: | |||||||
|     renote: "轉發貼文" |     renote: "轉發貼文" | ||||||
|     quote: "引用" |     quote: "引用" | ||||||
|     reaction: "反應" |     reaction: "反應" | ||||||
|     pollVote: "統計已投票數" |  | ||||||
|     pollEnded: "問卷調查結束" |     pollEnded: "問卷調查結束" | ||||||
|     receiveFollowRequest: "已收到追隨請求" |     receiveFollowRequest: "已收到追隨請求" | ||||||
|     followRequestAccepted: "追隨請求已接受" |     followRequestAccepted: "追隨請求已接受" | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"version": "13.0.0-beta.29", | 	"version": "13.0.0-beta.38", | ||||||
| 	"codename": "indigo", | 	"codename": "indigo", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
|   | |||||||
| @@ -1,5 +0,0 @@ | |||||||
| Font Awesome Icons |  | ||||||
| ------------------------- |  | ||||||
|  |  | ||||||
| Ⓒ Font Awesome |  | ||||||
| CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/) |  | ||||||
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 577 B | 
| Before Width: | Height: | Size: 1.4 KiB | 
| Before Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 844 B | 
| Before Width: | Height: | Size: 507 B | 
| Before Width: | Height: | Size: 689 B | 
| Before Width: | Height: | Size: 772 B | 
| Before Width: | Height: | Size: 930 B | 
| Before Width: | Height: | Size: 798 B | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 991 B | 
							
								
								
									
										24
									
								
								packages/backend/assets/tabler-badges/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | |||||||
|  | Tabler Icons   | ||||||
|  | https://github.com/tabler/tabler-icons/blob/master/LICENSE | ||||||
|  | ==== | ||||||
|  | MIT License | ||||||
|  |  | ||||||
|  | Copyright (c) 2020-2022 Paweł Kuna | ||||||
|  |  | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  |  | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|  | copies or substantial portions of the Software. | ||||||
|  |  | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/antenna.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 516 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/arrow-back-up.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 952 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/at.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/chart-arrows.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 829 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/circle-check.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/messages.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 KiB | 
| Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/plus.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 414 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/quote.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1011 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/repeat.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/user-plus.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/backend/assets/tabler-badges/users.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
| @@ -72,7 +72,7 @@ | |||||||
| 		"json5-loader": "4.0.1", | 		"json5-loader": "4.0.1", | ||||||
| 		"jsonld": "8.1.0", | 		"jsonld": "8.1.0", | ||||||
| 		"jsrsasign": "10.6.1", | 		"jsrsasign": "10.6.1", | ||||||
| 		"mfm-js": "0.23.0", | 		"mfm-js": "0.23.1", | ||||||
| 		"mime-types": "2.1.35", | 		"mime-types": "2.1.35", | ||||||
| 		"misskey-js": "0.0.14", | 		"misskey-js": "0.0.14", | ||||||
| 		"ms": "3.0.0-canary.1", | 		"ms": "3.0.0-canary.1", | ||||||
|   | |||||||
| @@ -15,8 +15,8 @@ import type { Packed } from '@/misc/schema.js'; | |||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; | import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; | ||||||
| import { UtilityService } from '@/core/UtilityService.js'; | import { UtilityService } from '@/core/UtilityService.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; |  | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
|  | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
|  |  | ||||||
| @Injectable() | @Injectable() | ||||||
| export class AntennaService implements OnApplicationShutdown { | export class AntennaService implements OnApplicationShutdown { | ||||||
| @@ -135,7 +135,7 @@ export class AntennaService implements OnApplicationShutdown { | |||||||
| 					this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna); | 					this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna); | ||||||
| 					this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', { | 					this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', { | ||||||
| 						antenna: { id: antenna.id, name: antenna.name }, | 						antenna: { id: antenna.id, name: antenna.name }, | ||||||
| 						note: await this.noteEntityService.pack(note) | 						note: await this.noteEntityService.pack(note), | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
| 			}, 2000); | 			}, 2000); | ||||||
| @@ -144,27 +144,19 @@ export class AntennaService implements OnApplicationShutdown { | |||||||
|  |  | ||||||
| 	// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている | 	// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい |  | ||||||
| 	 */ |  | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> { | 	public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise<boolean> { | ||||||
| 		if (note.visibility === 'specified') return false; | 		if (note.visibility === 'specified') return false; | ||||||
|  | 		if (note.visibility === 'followers') return false; | ||||||
|  |  | ||||||
| 		// アンテナ作成者がノート作成者にブロックされていたらスキップ | 		// アンテナ作成者がノート作成者にブロックされていたらスキップ | ||||||
| 		const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); | 		const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); | ||||||
| 		if (blockings.some(blocking => blocking === antenna.userId)) return false; | 		if (blockings.some(blocking => blocking === antenna.userId)) return false; | ||||||
| 	 | 	 | ||||||
| 		if (note.visibility === 'followers') { |  | ||||||
| 			if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; |  | ||||||
| 			if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; |  | ||||||
| 		} |  | ||||||
| 	 |  | ||||||
| 		if (!antenna.withReplies && note.replyId != null) return false; | 		if (!antenna.withReplies && note.replyId != null) return false; | ||||||
| 	 | 	 | ||||||
| 		if (antenna.src === 'home') { | 		if (antenna.src === 'home') { | ||||||
| 			if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false; | 			// TODO | ||||||
| 			if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false; |  | ||||||
| 		} else if (antenna.src === 'list') { | 		} else if (antenna.src === 'list') { | ||||||
| 			const listUsers = (await this.userListJoiningsRepository.findBy({ | 			const listUsers = (await this.userListJoiningsRepository.findBy({ | ||||||
| 				userListId: antenna.userListId!, | 				userListId: antenna.userListId!, | ||||||
|   | |||||||
| @@ -398,13 +398,13 @@ export class FileInfoService { | |||||||
| 				.raw() | 				.raw() | ||||||
| 				.ensureAlpha() | 				.ensureAlpha() | ||||||
| 				.resize(64, 64, { fit: 'inside' }) | 				.resize(64, 64, { fit: 'inside' }) | ||||||
| 				.toBuffer((err, buffer, { width, height }) => { | 				.toBuffer((err, buffer, info) => { | ||||||
| 					if (err) return reject(err); | 					if (err) return reject(err); | ||||||
|  |  | ||||||
| 					let hash; | 					let hash; | ||||||
|  |  | ||||||
| 					try { | 					try { | ||||||
| 						hash = encode(new Uint8ClampedArray(buffer), width, height, 5, 5); | 						hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5); | ||||||
| 					} catch (e) { | 					} catch (e) { | ||||||
| 						return reject(e); | 						return reject(e); | ||||||
| 					} | 					} | ||||||
|   | |||||||
| @@ -22,23 +22,25 @@ export class EmojiEntityService { | |||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public async pack( | 	public async pack( | ||||||
| 		src: Emoji['id'] | Emoji, | 		src: Emoji['id'] | Emoji, | ||||||
|  | 		opts: { omitHost?: boolean; omitId?: boolean; } = {}, | ||||||
| 	): Promise<Packed<'Emoji'>> { | 	): Promise<Packed<'Emoji'>> { | ||||||
| 		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); | 		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); | ||||||
|  |  | ||||||
| 		return { | 		return { | ||||||
| 			id: emoji.id, | 			id: opts.omitId ? undefined : emoji.id, | ||||||
| 			aliases: emoji.aliases, | 			aliases: emoji.aliases, | ||||||
| 			name: emoji.name, | 			name: emoji.name, | ||||||
| 			category: emoji.category, | 			category: emoji.category, | ||||||
| 			host: emoji.host, | 			host: opts.omitHost ? undefined : emoji.host, | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public packMany( | 	public packMany( | ||||||
| 		emojis: any[], | 		emojis: any[], | ||||||
|  | 		opts: { omitHost?: boolean; omitId?: boolean; } = {}, | ||||||
| 	) { | 	) { | ||||||
| 		return Promise.all(emojis.map(x => this.pack(x))); | 		return Promise.all(emojis.map(x => this.pack(x, opts))); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								packages/backend/src/misc/sql-like-escape.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | export function sqlLikeEscape(s: string) { | ||||||
|  | 	return s.replace(/([%_])/g, '\\$1'); | ||||||
|  | } | ||||||
| @@ -3,7 +3,7 @@ export const packedEmojiSchema = { | |||||||
| 	properties: { | 	properties: { | ||||||
| 		id: { | 		id: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: false, nullable: false, | 			optional: true, nullable: false, | ||||||
| 			format: 'id', | 			format: 'id', | ||||||
| 			example: 'xxxxxxxxxx', | 			example: 'xxxxxxxxxx', | ||||||
| 		}, | 		}, | ||||||
| @@ -26,12 +26,8 @@ export const packedEmojiSchema = { | |||||||
| 		}, | 		}, | ||||||
| 		host: { | 		host: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: false, nullable: true, | 			optional: true, nullable: true, | ||||||
| 			description: 'The local host is represented with `null`.', | 			description: 'The local host is represented with `null`.', | ||||||
| 		}, | 		}, | ||||||
| 		url: { |  | ||||||
| 			type: 'string', |  | ||||||
| 			optional: true, nullable: false, |  | ||||||
| 		}, |  | ||||||
| 	}, | 	}, | ||||||
| } as const; | } as const; | ||||||
|   | |||||||
| @@ -220,6 +220,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/ | |||||||
| import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; | import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; | ||||||
| import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; | import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; | ||||||
| import * as ep___meta from './endpoints/meta.js'; | import * as ep___meta from './endpoints/meta.js'; | ||||||
|  | import * as ep___emojis from './endpoints/emojis.js'; | ||||||
| import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; | import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; | ||||||
| import * as ep___mute_create from './endpoints/mute/create.js'; | import * as ep___mute_create from './endpoints/mute/create.js'; | ||||||
| import * as ep___mute_delete from './endpoints/mute/delete.js'; | import * as ep___mute_delete from './endpoints/mute/delete.js'; | ||||||
| @@ -550,6 +551,7 @@ const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/c | |||||||
| const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default }; | const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default }; | ||||||
| const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default }; | const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default }; | ||||||
| const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; | const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; | ||||||
|  | const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default }; | ||||||
| const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default }; | const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default }; | ||||||
| const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default }; | const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default }; | ||||||
| const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default }; | const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default }; | ||||||
| @@ -884,6 +886,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention | |||||||
| 		$messaging_messages_delete, | 		$messaging_messages_delete, | ||||||
| 		$messaging_messages_read, | 		$messaging_messages_read, | ||||||
| 		$meta, | 		$meta, | ||||||
|  | 		$emojis, | ||||||
| 		$miauth_genToken, | 		$miauth_genToken, | ||||||
| 		$mute_create, | 		$mute_create, | ||||||
| 		$mute_delete, | 		$mute_delete, | ||||||
| @@ -1212,6 +1215,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention | |||||||
| 		$messaging_messages_delete, | 		$messaging_messages_delete, | ||||||
| 		$messaging_messages_read, | 		$messaging_messages_read, | ||||||
| 		$meta, | 		$meta, | ||||||
|  | 		$emojis, | ||||||
| 		$miauth_genToken, | 		$miauth_genToken, | ||||||
| 		$mute_create, | 		$mute_create, | ||||||
| 		$mute_delete, | 		$mute_delete, | ||||||
|   | |||||||
| @@ -219,6 +219,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/ | |||||||
| import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; | import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; | ||||||
| import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; | import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; | ||||||
| import * as ep___meta from './endpoints/meta.js'; | import * as ep___meta from './endpoints/meta.js'; | ||||||
|  | import * as ep___emojis from './endpoints/emojis.js'; | ||||||
| import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; | import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; | ||||||
| import * as ep___mute_create from './endpoints/mute/create.js'; | import * as ep___mute_create from './endpoints/mute/create.js'; | ||||||
| import * as ep___mute_delete from './endpoints/mute/delete.js'; | import * as ep___mute_delete from './endpoints/mute/delete.js'; | ||||||
| @@ -547,6 +548,7 @@ const eps = [ | |||||||
| 	['messaging/messages/delete', ep___messaging_messages_delete], | 	['messaging/messages/delete', ep___messaging_messages_delete], | ||||||
| 	['messaging/messages/read', ep___messaging_messages_read], | 	['messaging/messages/read', ep___messaging_messages_read], | ||||||
| 	['meta', ep___meta], | 	['meta', ep___meta], | ||||||
|  | 	['emojis', ep___emojis], | ||||||
| 	['miauth/gen-token', ep___miauth_genToken], | 	['miauth/gen-token', ep___miauth_genToken], | ||||||
| 	['mute/create', ep___mute_create], | 	['mute/create', ep___mute_create], | ||||||
| 	['mute/delete', ep___mute_delete], | 	['mute/delete', ep___mute_delete], | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import { QueryService } from '@/core/QueryService.js'; | |||||||
| import { UtilityService } from '@/core/UtilityService.js'; | import { UtilityService } from '@/core/UtilityService.js'; | ||||||
| import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; | import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
| @@ -92,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (ps.query) { | 			if (ps.query) { | ||||||
| 				q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); | 				q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const emojis = await q | 			const emojis = await q | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import type { Emoji } from '@/models/entities/Emoji.js'; | |||||||
| import { QueryService } from '@/core/QueryService.js'; | import { QueryService } from '@/core/QueryService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; | import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; | ||||||
|  | //import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
| @@ -82,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			let emojis: Emoji[]; | 			let emojis: Emoji[]; | ||||||
|  |  | ||||||
| 			if (ps.query) { | 			if (ps.query) { | ||||||
| 				//q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` }); | 				//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); | ||||||
| 				//const emojis = await q.take(ps.limit).getMany(); | 				//const emojis = await q.take(ps.limit).getMany(); | ||||||
|  |  | ||||||
| 				emojis = await q.getMany(); | 				emojis = await q.getMany(); | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import type { UsersRepository } from '@/models/index.js'; | |||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['admin'], | 	tags: ['admin'], | ||||||
| @@ -68,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (ps.username) { | 			if (ps.username) { | ||||||
| 				query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); | 				query.andWhere('user.usernameLower like :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (ps.hostname) { | 			if (ps.hostname) { | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								packages/backend/src/server/api/endpoints/emojis.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,90 @@ | |||||||
|  | import { IsNull, MoreThan } from 'typeorm'; | ||||||
|  | import { Inject, Injectable } from '@nestjs/common'; | ||||||
|  | import type { EmojisRepository } from '@/models/index.js'; | ||||||
|  | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
|  | import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; | ||||||
|  | import type { Config } from '@/config.js'; | ||||||
|  | import { DI } from '@/di-symbols.js'; | ||||||
|  |  | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['meta'], | ||||||
|  |  | ||||||
|  | 	requireCredential: false, | ||||||
|  |  | ||||||
|  | 	res: { | ||||||
|  | 		type: 'object', | ||||||
|  | 		optional: false, nullable: false, | ||||||
|  | 		properties: { | ||||||
|  | 			emojis: { | ||||||
|  | 				type: 'array', | ||||||
|  | 				optional: false, nullable: false, | ||||||
|  | 				items: { | ||||||
|  | 					type: 'object', | ||||||
|  | 					optional: false, nullable: false, | ||||||
|  | 					properties: { | ||||||
|  | 						name: { | ||||||
|  | 							type: 'string', | ||||||
|  | 							optional: false, nullable: false, | ||||||
|  | 						}, | ||||||
|  | 						aliases: { | ||||||
|  | 							type: 'array', | ||||||
|  | 							optional: false, nullable: false, | ||||||
|  | 							items: { | ||||||
|  | 								type: 'string', | ||||||
|  | 								optional: false, nullable: false, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						category: { | ||||||
|  | 							type: 'string', | ||||||
|  | 							optional: false, nullable: true, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } as const; | ||||||
|  |  | ||||||
|  | export const paramDef = { | ||||||
|  | 	type: 'object', | ||||||
|  | 	properties: { | ||||||
|  | 	}, | ||||||
|  | 	required: [], | ||||||
|  | } as const; | ||||||
|  |  | ||||||
|  | // eslint-disable-next-line import/no-default-export | ||||||
|  | @Injectable() | ||||||
|  | export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
|  | 	constructor( | ||||||
|  | 		@Inject(DI.config) | ||||||
|  | 		private config: Config, | ||||||
|  | 	 | ||||||
|  | 		@Inject(DI.emojisRepository) | ||||||
|  | 		private emojisRepository: EmojisRepository, | ||||||
|  |  | ||||||
|  | 		private emojiEntityService: EmojiEntityService, | ||||||
|  | 	) { | ||||||
|  | 		super(meta, paramDef, async (ps, me) => { | ||||||
|  | 			const emojis = await this.emojisRepository.find({ | ||||||
|  | 				where: { | ||||||
|  | 					host: IsNull(), | ||||||
|  | 				}, | ||||||
|  | 				order: { | ||||||
|  | 					category: 'ASC', | ||||||
|  | 					name: 'ASC', | ||||||
|  | 				}, | ||||||
|  | 				cache: { | ||||||
|  | 					id: 'meta_emojis', | ||||||
|  | 					milliseconds: 3600000,	// 1 hour | ||||||
|  | 				}, | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			return { | ||||||
|  | 				emojis: await this.emojiEntityService.packMany(emojis, { | ||||||
|  | 					omitId: true, | ||||||
|  | 					omitHost: true, | ||||||
|  | 				}), | ||||||
|  | 			}; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -4,6 +4,7 @@ import type { InstancesRepository } from '@/models/index.js'; | |||||||
| import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; | import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js'; | ||||||
| import { MetaService } from '@/core/MetaService.js'; | import { MetaService } from '@/core/MetaService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['federation'], | 	tags: ['federation'], | ||||||
| @@ -120,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (ps.host) { | 			if (ps.host) { | ||||||
| 				query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); | 				query.andWhere('instance.host like :host', { host: '%' + sqlLikeEscape(ps.host.toLowerCase()) + '%' }); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const instances = await query.take(ps.limit).skip(ps.offset).getMany(); | 			const instances = await query.take(ps.limit).skip(ps.offset).getMany(); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; | |||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import type { HashtagsRepository } from '@/models/index.js'; | import type { HashtagsRepository } from '@/models/index.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['hashtags'], | 	tags: ['hashtags'], | ||||||
| @@ -37,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
| 			const hashtags = await this.hashtagsRepository.createQueryBuilder('tag') | 			const hashtags = await this.hashtagsRepository.createQueryBuilder('tag') | ||||||
| 				.where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) | 				.where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' }) | ||||||
| 				.orderBy('tag.count', 'DESC') | 				.orderBy('tag.count', 'DESC') | ||||||
| 				.groupBy('tag.id') | 				.groupBy('tag.id') | ||||||
| 				.take(ps.limit) | 				.take(ps.limit) | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/ | |||||||
| import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; | import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; | ||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
| import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; |  | ||||||
| import { MetaService } from '@/core/MetaService.js'; | import { MetaService } from '@/core/MetaService.js'; | ||||||
| import type { Config } from '@/config.js'; | import type { Config } from '@/config.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| @@ -152,43 +151,6 @@ export const meta = { | |||||||
| 				type: 'number', | 				type: 'number', | ||||||
| 				optional: false, nullable: false, | 				optional: false, nullable: false, | ||||||
| 			}, | 			}, | ||||||
| 			emojis: { |  | ||||||
| 				type: 'array', |  | ||||||
| 				optional: false, nullable: false, |  | ||||||
| 				items: { |  | ||||||
| 					type: 'object', |  | ||||||
| 					optional: false, nullable: false, |  | ||||||
| 					properties: { |  | ||||||
| 						id: { |  | ||||||
| 							type: 'string', |  | ||||||
| 							optional: false, nullable: false, |  | ||||||
| 							format: 'id', |  | ||||||
| 						}, |  | ||||||
| 						aliases: { |  | ||||||
| 							type: 'array', |  | ||||||
| 							optional: false, nullable: false, |  | ||||||
| 							items: { |  | ||||||
| 								type: 'string', |  | ||||||
| 								optional: false, nullable: false, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 						category: { |  | ||||||
| 							type: 'string', |  | ||||||
| 							optional: false, nullable: true, |  | ||||||
| 						}, |  | ||||||
| 						host: { |  | ||||||
| 							type: 'string', |  | ||||||
| 							optional: false, nullable: true, |  | ||||||
| 							description: 'The local host is represented with `null`.', |  | ||||||
| 						}, |  | ||||||
| 						url: { |  | ||||||
| 							type: 'string', |  | ||||||
| 							optional: false, nullable: false, |  | ||||||
| 							format: 'url', |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			ads: { | 			ads: { | ||||||
| 				type: 'array', | 				type: 'array', | ||||||
| 				optional: false, nullable: false, | 				optional: false, nullable: false, | ||||||
| @@ -326,30 +288,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 		@Inject(DI.adsRepository) | 		@Inject(DI.adsRepository) | ||||||
| 		private adsRepository: AdsRepository, | 		private adsRepository: AdsRepository, | ||||||
|  |  | ||||||
| 		@Inject(DI.emojisRepository) |  | ||||||
| 		private emojisRepository: EmojisRepository, |  | ||||||
|  |  | ||||||
| 		private userEntityService: UserEntityService, | 		private userEntityService: UserEntityService, | ||||||
| 		private emojiEntityService: EmojiEntityService, |  | ||||||
| 		private metaService: MetaService, | 		private metaService: MetaService, | ||||||
| 	) { | 	) { | ||||||
| 		super(meta, paramDef, async (ps, me) => { | 		super(meta, paramDef, async (ps, me) => { | ||||||
| 			const instance = await this.metaService.fetch(true); | 			const instance = await this.metaService.fetch(true); | ||||||
|  |  | ||||||
| 			const emojis = await this.emojisRepository.find({ |  | ||||||
| 				where: { |  | ||||||
| 					host: IsNull(), |  | ||||||
| 				}, |  | ||||||
| 				order: { |  | ||||||
| 					category: 'ASC', |  | ||||||
| 					name: 'ASC', |  | ||||||
| 				}, |  | ||||||
| 				cache: { |  | ||||||
| 					id: 'meta_emojis', |  | ||||||
| 					milliseconds: 3600000,	// 1 hour |  | ||||||
| 				}, |  | ||||||
| 			}); |  | ||||||
|  |  | ||||||
| 			const ads = await this.adsRepository.find({ | 			const ads = await this.adsRepository.find({ | ||||||
| 				where: { | 				where: { | ||||||
| 					expiresAt: MoreThan(new Date()), | 					expiresAt: MoreThan(new Date()), | ||||||
| @@ -390,7 +334,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 				backgroundImageUrl: instance.backgroundImageUrl, | 				backgroundImageUrl: instance.backgroundImageUrl, | ||||||
| 				logoImageUrl: instance.logoImageUrl, | 				logoImageUrl: instance.logoImageUrl, | ||||||
| 				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため | 				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため | ||||||
| 				emojis: await this.emojiEntityService.packMany(emojis), |  | ||||||
| 				defaultLightTheme: instance.defaultLightTheme, | 				defaultLightTheme: instance.defaultLightTheme, | ||||||
| 				defaultDarkTheme: instance.defaultDarkTheme, | 				defaultDarkTheme: instance.defaultDarkTheme, | ||||||
| 				ads: ads.map(ad => ({ | 				ads: ads.map(ad => ({ | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import { QueryService } from '@/core/QueryService.js'; | |||||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||||
| import type { Config } from '@/config.js'; | import type { Config } from '@/config.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['notes'], | 	tags: ['notes'], | ||||||
| @@ -70,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			query | 			query | ||||||
| 				.andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) | 				.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) | ||||||
| 				.innerJoinAndSelect('note.user', 'user') | 				.innerJoinAndSelect('note.user', 'user') | ||||||
| 				.leftJoinAndSelect('user.avatar', 'avatar') | 				.leftJoinAndSelect('user.avatar', 'avatar') | ||||||
| 				.leftJoinAndSelect('user.banner', 'banner') | 				.leftJoinAndSelect('user.banner', 'banner') | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import type { User } from '@/models/entities/User.js'; | |||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['users'], | 	tags: ['users'], | ||||||
| @@ -59,10 +60,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			if (ps.host) { | 			if (ps.host) { | ||||||
| 				const q = this.usersRepository.createQueryBuilder('user') | 				const q = this.usersRepository.createQueryBuilder('user') | ||||||
| 					.where('user.isSuspended = FALSE') | 					.where('user.isSuspended = FALSE') | ||||||
| 					.andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' }); | 					.andWhere('user.host LIKE :host', { host: sqlLikeEscape(ps.host.toLowerCase()) + '%' }); | ||||||
|  |  | ||||||
| 				if (ps.username) { | 				if (ps.username) { | ||||||
| 					q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }); | 					q.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				q.andWhere('user.updatedAt IS NOT NULL'); | 				q.andWhere('user.updatedAt IS NOT NULL'); | ||||||
| @@ -83,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 						.where(`user.id IN (${ followingQuery.getQuery() })`) | 						.where(`user.id IN (${ followingQuery.getQuery() })`) | ||||||
| 						.andWhere('user.id != :meId', { meId: me.id }) | 						.andWhere('user.id != :meId', { meId: me.id }) | ||||||
| 						.andWhere('user.isSuspended = FALSE') | 						.andWhere('user.isSuspended = FALSE') | ||||||
| 						.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) | 						.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }) | ||||||
| 						.andWhere(new Brackets(qb => { qb | 						.andWhere(new Brackets(qb => { qb | ||||||
| 							.where('user.updatedAt IS NULL') | 							.where('user.updatedAt IS NULL') | ||||||
| 							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); | 							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); | ||||||
| @@ -101,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 							.where(`user.id NOT IN (${ followingQuery.getQuery() })`) | 							.where(`user.id NOT IN (${ followingQuery.getQuery() })`) | ||||||
| 							.andWhere('user.id != :meId', { meId: me.id }) | 							.andWhere('user.id != :meId', { meId: me.id }) | ||||||
| 							.andWhere('user.isSuspended = FALSE') | 							.andWhere('user.isSuspended = FALSE') | ||||||
| 							.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) | 							.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }) | ||||||
| 							.andWhere('user.updatedAt IS NOT NULL'); | 							.andWhere('user.updatedAt IS NOT NULL'); | ||||||
|  |  | ||||||
| 						otherQuery.setParameters(followingQuery.getParameters()); | 						otherQuery.setParameters(followingQuery.getParameters()); | ||||||
| @@ -116,7 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 				} else { | 				} else { | ||||||
| 					users = await this.usersRepository.createQueryBuilder('user') | 					users = await this.usersRepository.createQueryBuilder('user') | ||||||
| 						.where('user.isSuspended = FALSE') | 						.where('user.isSuspended = FALSE') | ||||||
| 						.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) | 						.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' }) | ||||||
| 						.andWhere('user.updatedAt IS NOT NULL') | 						.andWhere('user.updatedAt IS NOT NULL') | ||||||
| 						.orderBy('user.updatedAt', 'DESC') | 						.orderBy('user.updatedAt', 'DESC') | ||||||
| 						.take(ps.limit - users.length) | 						.take(ps.limit - users.length) | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import type { User } from '@/models/entities/User.js'; | |||||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||||
| import { UserEntityService } from '@/core/entities/UserEntityService.js'; | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
|  | import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; | ||||||
|  |  | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['users'], | 	tags: ['users'], | ||||||
| @@ -57,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
|  |  | ||||||
| 			if (isUsername) { | 			if (isUsername) { | ||||||
| 				const usernameQuery = this.usersRepository.createQueryBuilder('user') | 				const usernameQuery = this.usersRepository.createQueryBuilder('user') | ||||||
| 					.where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) | 					.where('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' }) | ||||||
| 					.andWhere(new Brackets(qb => { qb | 					.andWhere(new Brackets(qb => { qb | ||||||
| 						.where('user.updatedAt IS NULL') | 						.where('user.updatedAt IS NULL') | ||||||
| 						.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); | 						.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); | ||||||
| @@ -78,11 +79,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 			} else { | 			} else { | ||||||
| 				const nameQuery = this.usersRepository.createQueryBuilder('user') | 				const nameQuery = this.usersRepository.createQueryBuilder('user') | ||||||
| 					.where(new Brackets(qb => {  | 					.where(new Brackets(qb => {  | ||||||
| 						qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); | 						qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); | ||||||
|  |  | ||||||
| 						// Also search username if it qualifies as username | 						// Also search username if it qualifies as username | ||||||
| 						if (this.userEntityService.validateLocalUsername(ps.query)) { | 						if (this.userEntityService.validateLocalUsername(ps.query)) { | ||||||
| 							qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); | 							qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' }); | ||||||
| 						} | 						} | ||||||
| 					})) | 					})) | ||||||
| 					.andWhere(new Brackets(qb => { qb | 					.andWhere(new Brackets(qb => { qb | ||||||
| @@ -106,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||||||
| 				if (users.length < ps.limit) { | 				if (users.length < ps.limit) { | ||||||
| 					const profQuery = this.userProfilesRepository.createQueryBuilder('prof') | 					const profQuery = this.userProfilesRepository.createQueryBuilder('prof') | ||||||
| 						.select('prof.userId') | 						.select('prof.userId') | ||||||
| 						.where('prof.description ILIKE :query', { query: '%' + ps.query + '%' }); | 						.where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }); | ||||||
|  |  | ||||||
| 					if (ps.origin === 'local') { | 					if (ps.origin === 'local') { | ||||||
| 						profQuery.andWhere('prof.userHost IS NULL'); | 						profQuery.andWhere('prof.userHost IS NULL'); | ||||||
|   | |||||||
| @@ -312,7 +312,7 @@ export class ClientServerService { | |||||||
| 		fastify.get('/opensearch.xml', async (request, reply) => { | 		fastify.get('/opensearch.xml', async (request, reply) => { | ||||||
| 			const meta = await this.metaService.fetch(); | 			const meta = await this.metaService.fetch(); | ||||||
|  |  | ||||||
| 			const name = meta.name || 'Misskey'; | 			const name = meta.name ?? 'Misskey'; | ||||||
| 			let content = ''; | 			let content = ''; | ||||||
| 			content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">'; | 			content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">'; | ||||||
| 			content += `<ShortName>${name}</ShortName>`; | 			content += `<ShortName>${name}</ShortName>`; | ||||||
| @@ -533,13 +533,12 @@ export class ClientServerService { | |||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		// Clip | 		// Clip | ||||||
| 		// TODO: 非publicなclipのハンドリング |  | ||||||
| 		fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => { | 		fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => { | ||||||
| 			const clip = await this.clipsRepository.findOneBy({ | 			const clip = await this.clipsRepository.findOneBy({ | ||||||
| 				id: request.params.clip, | 				id: request.params.clip, | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			if (clip) { | 			if (clip && clip.isPublic) { | ||||||
| 				const _clip = await this.clipEntityService.pack(clip); | 				const _clip = await this.clipEntityService.pack(clip); | ||||||
| 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId }); | 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId }); | ||||||
| 				const meta = await this.metaService.fetch(); | 				const meta = await this.metaService.fetch(); | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								packages/frontend/assets/cookie.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 38 KiB | 
| @@ -38,7 +38,7 @@ | |||||||
| 		"json5": "2.2.3", | 		"json5": "2.2.3", | ||||||
| 		"katex": "0.16.4", | 		"katex": "0.16.4", | ||||||
| 		"matter-js": "0.18.0", | 		"matter-js": "0.18.0", | ||||||
| 		"mfm-js": "0.23.0", | 		"mfm-js": "0.23.1", | ||||||
| 		"misskey-js": "0.0.14", | 		"misskey-js": "0.0.14", | ||||||
| 		"photoswipe": "5.3.4", | 		"photoswipe": "5.3.4", | ||||||
| 		"prismjs": "1.29.0", | 		"prismjs": "1.29.0", | ||||||
|   | |||||||
| @@ -33,12 +33,12 @@ | |||||||
| 		<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option> | 		<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option> | ||||||
| 	</MkSelect> | 	</MkSelect> | ||||||
| 	<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="openPostForm">{{ c.text }}</MkButton> | 	<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="openPostForm">{{ c.text }}</MkButton> | ||||||
| 	<FormFolder v-else-if="c.type === 'folder'" :default-open="c.opened"> | 	<MkFolder v-else-if="c.type === 'folder'" :default-open="c.opened"> | ||||||
| 		<template #label>{{ c.title }}</template> | 		<template #label>{{ c.title }}</template> | ||||||
| 		<template v-for="child in c.children" :key="child"> | 		<template v-for="child in c.children" :key="child"> | ||||||
| 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/> | 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/> | ||||||
| 		</template> | 		</template> | ||||||
| 	</FormFolder> | 	</MkFolder> | ||||||
| 	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace', [$style.containerCenter]: c.align === 'center' }]" :style="{ backgroundColor: c.bgColor ?? null, color: c.fgColor ?? null, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }"> | 	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace', [$style.containerCenter]: c.align === 'center' }]" :style="{ backgroundColor: c.bgColor ?? null, color: c.fgColor ?? null, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }"> | ||||||
| 		<template v-for="child in c.children" :key="child"> | 		<template v-for="child in c.children" :key="child"> | ||||||
| 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/> | 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/> | ||||||
| @@ -56,7 +56,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; | |||||||
| import MkTextarea from '@/components/MkTextarea.vue'; | import MkTextarea from '@/components/MkTextarea.vue'; | ||||||
| import MkSelect from '@/components/MkSelect.vue'; | import MkSelect from '@/components/MkSelect.vue'; | ||||||
| import { AsUiComponent } from '@/scripts/aiscript/ui'; | import { AsUiComponent } from '@/scripts/aiscript/ui'; | ||||||
| import FormFolder from '@/components/form/folder.vue'; | import MkFolder from '@/components/MkFolder.vue'; | ||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| 	component: AsUiComponent; | 	component: AsUiComponent; | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ import { emojilist } from '@/scripts/emojilist'; | |||||||
| import { instance } from '@/instance'; | import { instance } from '@/instance'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
| import { miLocalStorage } from '@/local-storage'; | import { miLocalStorage } from '@/local-storage'; | ||||||
|  | import { customEmojis } from '@/custom-emojis'; | ||||||
|  |  | ||||||
| type EmojiDef = { | type EmojiDef = { | ||||||
| 	emoji: string; | 	emoji: string; | ||||||
| @@ -86,7 +87,6 @@ for (const x of lib) { | |||||||
| emjdb.sort((a, b) => a.name.length - b.name.length); | emjdb.sort((a, b) => a.name.length - b.name.length); | ||||||
|  |  | ||||||
| //#region Construct Emoji DB | //#region Construct Emoji DB | ||||||
| const customEmojis = instance.emojis; |  | ||||||
| const emojiDefinitions: EmojiDef[] = []; | const emojiDefinitions: EmojiDef[] = []; | ||||||
|  |  | ||||||
| for (const x of customEmojis) { | for (const x of customEmojis) { | ||||||
| @@ -117,7 +117,6 @@ export default { | |||||||
| 	emojiDb, | 	emojiDb, | ||||||
| 	emojiDefinitions, | 	emojiDefinitions, | ||||||
| 	emojilist, | 	emojilist, | ||||||
| 	customEmojis, |  | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										92
									
								
								packages/frontend/src/components/MkClickerGame.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,92 @@ | |||||||
|  | <template> | ||||||
|  | <div> | ||||||
|  | 	<div v-if="game.ready" :class="$style.game"> | ||||||
|  | 		<div :class="$style.cps" class="">{{ number(cps) }}cps</div> | ||||||
|  | 		<div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div> | ||||||
|  | 		<button v-click-anime class="_button" :class="$style.button" @click="onClick"> | ||||||
|  | 			<img src="/client-assets/cookie.png" :class="$style.img"> | ||||||
|  | 		</button> | ||||||
|  | 	</div> | ||||||
|  | 	<div v-else> | ||||||
|  | 		<MkLoading/> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { computed, defineAsyncComponent, onMounted, onUnmounted } from 'vue'; | ||||||
|  | import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import { useInterval } from '@/scripts/use-interval'; | ||||||
|  | import * as game from '@/scripts/clicker-game'; | ||||||
|  | import number from '@/filters/number'; | ||||||
|  |  | ||||||
|  | defineProps<{ | ||||||
|  | }>(); | ||||||
|  |  | ||||||
|  | const saveData = game.saveData; | ||||||
|  | const cookies = computed(() => saveData.value?.cookies); | ||||||
|  | let cps = $ref(0); | ||||||
|  | let prevCookies = $ref(0); | ||||||
|  |  | ||||||
|  | function onClick(ev: MouseEvent) { | ||||||
|  | 	saveData.value!.cookies++; | ||||||
|  | 	saveData.value!.totalCookies++; | ||||||
|  | 	saveData.value!.totalHandmadeCookies++; | ||||||
|  | 	saveData.value!.clicked++; | ||||||
|  |  | ||||||
|  | 	const x = ev.clientX; | ||||||
|  | 	const y = ev.clientY; | ||||||
|  | 	os.popup(MkPlusOneEffect, { x, y }, {}, 'end'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | useInterval(() => { | ||||||
|  | 	const diff = saveData.value!.cookies - prevCookies; | ||||||
|  | 	cps = diff; | ||||||
|  | 	prevCookies = saveData.value!.cookies; | ||||||
|  | }, 1000, { | ||||||
|  | 	immediate: false, | ||||||
|  | 	afterMounted: true, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | useInterval(game.save, 1000 * 5, { | ||||||
|  | 	immediate: false, | ||||||
|  | 	afterMounted: true, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | onMounted(async () => { | ||||||
|  | 	await game.load(); | ||||||
|  | 	prevCookies = saveData.value!.cookies; | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | onUnmounted(() => { | ||||||
|  | 	game.save(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" module> | ||||||
|  | .game { | ||||||
|  | 	padding: 16px; | ||||||
|  | 	text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .cps { | ||||||
|  | 	position: absolute; | ||||||
|  | 	top: 12px; | ||||||
|  | 	left: 12px; | ||||||
|  | 	opacity: 0.5; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .count { | ||||||
|  | 	font-size: 1.3em; | ||||||
|  | 	margin-bottom: 6px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .button { | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .img { | ||||||
|  | 	max-width: 90px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -80,7 +80,6 @@ export default defineComponent({ | |||||||
| 			} else { | 			} else { | ||||||
| 				if (props.ad && item._shouldInsertAd_) { | 				if (props.ad && item._shouldInsertAd_) { | ||||||
| 					return [h(MkAd, { | 					return [h(MkAd, { | ||||||
| 						class: 'a', // advertiseの意(ブロッカー対策) |  | ||||||
| 						key: item.id + ':ad', | 						key: item.id + ':ad', | ||||||
| 						prefer: ['horizontal', 'horizontal-big'], | 						prefer: ['horizontal', 'horizontal-big'], | ||||||
| 					}), el]; | 					}), el]; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <template> | <template> | ||||||
| <MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')"> | <MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')"> | ||||||
| 	<div class="mk-dialog"> | 	<div :class="$style.root"> | ||||||
| 		<div v-if="icon" class="icon"> | 		<div v-if="icon" :class="$style.icon"> | ||||||
| 			<i :class="icon"></i> | 			<i :class="icon"></i> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-else-if="!input && !select" class="icon" :class="type"> | 		<div v-else-if="!input && !select" :class="[$style.icon, type]"> | ||||||
| 			<i v-if="type === 'success'" class="ti ti-check"></i> | 			<i v-if="type === 'success'" class="ti ti-check"></i> | ||||||
| 			<i v-else-if="type === 'error'" class="ti ti-circle-x"></i> | 			<i v-else-if="type === 'error'" class="ti ti-circle-x"></i> | ||||||
| 			<i v-else-if="type === 'warning'" class="ti ti-alert-triangle"></i> | 			<i v-else-if="type === 'warning'" class="ti ti-alert-triangle"></i> | ||||||
| @@ -12,8 +12,8 @@ | |||||||
| 			<i v-else-if="type === 'question'" class="ti ti-question-circle"></i> | 			<i v-else-if="type === 'question'" class="ti ti-question-circle"></i> | ||||||
| 			<MkLoading v-else-if="type === 'waiting'" :em="true"/> | 			<MkLoading v-else-if="type === 'waiting'" :em="true"/> | ||||||
| 		</div> | 		</div> | ||||||
| 		<header v-if="title"><Mfm :text="title"/></header> | 		<header v-if="title" :class="$style.title"><Mfm :text="title"/></header> | ||||||
| 		<div v-if="text" class="body"><Mfm :text="text"/></div> | 		<div v-if="text" :class="$style.text"><Mfm :text="text"/></div> | ||||||
| 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> | 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> | ||||||
| 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> | 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| @@ -27,11 +27,11 @@ | |||||||
| 				</optgroup> | 				</optgroup> | ||||||
| 			</template> | 			</template> | ||||||
| 		</MkSelect> | 		</MkSelect> | ||||||
| 		<div v-if="(showOkButton || showCancelButton) && !actions" class="buttons"> | 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons"> | ||||||
| 			<MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt }}</MkButton> | 			<MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt }}</MkButton> | ||||||
| 			<MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.ts.cancel }}</MkButton> | 			<MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.ts.cancel }}</MkButton> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-if="actions" class="buttons"> | 		<div v-if="actions" :class="$style.buttons"> | ||||||
| 			<MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton> | 			<MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| @@ -143,8 +143,8 @@ onBeforeUnmount(() => { | |||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .mk-dialog { | .root { | ||||||
| 	position: relative; | 	position: relative; | ||||||
| 	padding: 32px; | 	padding: 32px; | ||||||
| 	min-width: 320px; | 	min-width: 320px; | ||||||
| @@ -153,56 +153,56 @@ onBeforeUnmount(() => { | |||||||
| 	text-align: center; | 	text-align: center; | ||||||
| 	background: var(--panel); | 	background: var(--panel); | ||||||
| 	border-radius: var(--radius); | 	border-radius: var(--radius); | ||||||
|  | } | ||||||
|  |  | ||||||
| 	> .icon { | .icon { | ||||||
| 		font-size: 24px; | 	font-size: 24px; | ||||||
|  |  | ||||||
| 		&.info { | 	&.info { | ||||||
| 			color: #55c4dd; | 		color: #55c4dd; | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.success { |  | ||||||
| 			color: var(--success); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.error { |  | ||||||
| 			color: var(--error); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.warning { |  | ||||||
| 			color: var(--warn); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> * { |  | ||||||
| 			display: block; |  | ||||||
| 			margin: 0 auto; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		& + header { |  | ||||||
| 			margin-top: 8px; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> header { | 	&.success { | ||||||
| 		margin: 0 0 8px 0; | 		color: var(--success); | ||||||
| 		font-weight: bold; |  | ||||||
| 		font-size: 1.1em; |  | ||||||
|  |  | ||||||
| 		& + .body { |  | ||||||
| 			margin-top: 8px; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .body { | 	&.error { | ||||||
| 		margin: 16px 0 0 0; | 		color: var(--error); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .buttons { | 	&.warning { | ||||||
| 		margin-top: 16px; | 		color: var(--warn); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 		> * { | 	> * { | ||||||
| 			margin: 0 8px; | 		display: block; | ||||||
| 		} | 		margin: 0 auto; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	& + .title { | ||||||
|  | 		margin-top: 8px; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .title { | ||||||
|  | 	margin: 0 0 8px 0; | ||||||
|  | 	font-weight: bold; | ||||||
|  | 	font-size: 1.1em; | ||||||
|  |  | ||||||
|  | 	& + .text { | ||||||
|  | 		margin-top: 8px; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .text { | ||||||
|  | 	margin: 16px 0 0 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .buttons { | ||||||
|  | 	margin-top: 16px; | ||||||
|  | 	display: flex; | ||||||
|  | 	gap: 8px; | ||||||
|  | 	flex-wrap: wrap; | ||||||
|  | 	justify-content: center; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| <template> | <template> | ||||||
| <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> | <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> | ||||||
| 	<input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()"> | 	<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()"> | ||||||
| 	<div ref="emojis" class="emojis"> | 	<div ref="emojisEl" class="emojis"> | ||||||
| 		<section class="result"> | 		<section class="result"> | ||||||
| 			<div v-if="searchResultCustom.length > 0" class="body"> | 			<div v-if="searchResultCustom.length > 0" class="body"> | ||||||
| 				<button | 				<button | ||||||
| 					v-for="emoji in searchResultCustom" | 					v-for="emoji in searchResultCustom" | ||||||
| 					:key="emoji.id" | 					:key="emoji.name" | ||||||
| 					class="_button item" | 					class="_button item" | ||||||
| 					:title="emoji.name" | 					:title="emoji.name" | ||||||
| 					tabindex="0" | 					tabindex="0" | ||||||
| @@ -85,9 +85,10 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; | |||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { isTouchUsing } from '@/scripts/touch'; | import { isTouchUsing } from '@/scripts/touch'; | ||||||
| import { deviceKind } from '@/scripts/device-kind'; | import { deviceKind } from '@/scripts/device-kind'; | ||||||
| import { emojiCategories, instance } from '@/instance'; | import { instance } from '@/instance'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
| import { defaultStore } from '@/store'; | import { defaultStore } from '@/store'; | ||||||
|  | import { getCustomEmojiCategories, customEmojis } from '@/custom-emojis'; | ||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| 	showPinned?: boolean; | 	showPinned?: boolean; | ||||||
| @@ -103,8 +104,9 @@ const emit = defineEmits<{ | |||||||
| 	(ev: 'chosen', v: string): void; | 	(ev: 'chosen', v: string): void; | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const search = shallowRef<HTMLInputElement>(); | const customEmojiCategories = getCustomEmojiCategories(); | ||||||
| const emojis = shallowRef<HTMLDivElement>(); | const searchEl = shallowRef<HTMLInputElement>(); | ||||||
|  | const emojisEl = shallowRef<HTMLDivElement>(); | ||||||
|  |  | ||||||
| const { | const { | ||||||
| 	reactions: pinned, | 	reactions: pinned, | ||||||
| @@ -118,15 +120,13 @@ const { | |||||||
| const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1); | const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1); | ||||||
| const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3); | const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3); | ||||||
| const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2); | const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2); | ||||||
| const customEmojiCategories = emojiCategories; |  | ||||||
| const customEmojis = instance.emojis; |  | ||||||
| const q = ref<string>(''); | const q = ref<string>(''); | ||||||
| const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]); | const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]); | ||||||
| const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); | const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); | ||||||
| const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); | const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); | ||||||
|  |  | ||||||
| watch(q, () => { | watch(q, () => { | ||||||
| 	if (emojis.value) emojis.value.scrollTop = 0; | 	if (emojisEl.value) emojisEl.value.scrollTop = 0; | ||||||
|  |  | ||||||
| 	if (q.value === '') { | 	if (q.value === '') { | ||||||
| 		searchResultCustom.value = []; | 		searchResultCustom.value = []; | ||||||
| @@ -268,14 +268,14 @@ watch(q, () => { | |||||||
|  |  | ||||||
| function focus() { | function focus() { | ||||||
| 	if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { | 	if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { | ||||||
| 		search.value?.focus({ | 		searchEl.value?.focus({ | ||||||
| 			preventScroll: true, | 			preventScroll: true, | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function reset() { | function reset() { | ||||||
| 	if (emojis.value) emojis.value.scrollTop = 0; | 	if (emojisEl.value) emojisEl.value.scrollTop = 0; | ||||||
| 	q.value = ''; | 	q.value = ''; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -308,7 +308,7 @@ function input(): void { | |||||||
| 	// Using custom input event instead of v-model to respond immediately on | 	// Using custom input event instead of v-model to respond immediately on | ||||||
| 	// Android, where composition happens on all languages | 	// Android, where composition happens on all languages | ||||||
| 	// (v-model does not update during composition) | 	// (v-model does not update during composition) | ||||||
| 	q.value = search.value?.value.trim() ?? ''; | 	q.value = searchEl.value?.value.trim() ?? ''; | ||||||
| } | } | ||||||
|  |  | ||||||
| function paste(event: ClipboardEvent): void { | function paste(event: ClipboardEvent): void { | ||||||
|   | |||||||
| @@ -59,6 +59,11 @@ function chosen(emoji: any) { | |||||||
| function opening() { | function opening() { | ||||||
| 	picker.value?.reset(); | 	picker.value?.reset(); | ||||||
| 	picker.value?.focus(); | 	picker.value?.focus(); | ||||||
|  |  | ||||||
|  | 	// 何故かちょっと待たないとフォーカスされない | ||||||
|  | 	setTimeout(() => { | ||||||
|  | 		picker.value?.focus(); | ||||||
|  | 	}, 10); | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										154
									
								
								packages/frontend/src/components/MkFoldableSection.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,154 @@ | |||||||
|  | <template> | ||||||
|  | <div class="ssazuxis"> | ||||||
|  | 	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody"> | ||||||
|  | 		<div class="title"><div><slot name="header"></slot></div></div> | ||||||
|  | 		<div class="divider"></div> | ||||||
|  | 		<button class="_button"> | ||||||
|  | 			<template v-if="showBody"><i class="ti ti-chevron-up"></i></template> | ||||||
|  | 			<template v-else><i class="ti ti-chevron-down"></i></template> | ||||||
|  | 		</button> | ||||||
|  | 	</header> | ||||||
|  | 	<Transition | ||||||
|  | 		:name="$store.state.animation ? 'folder-toggle' : ''" | ||||||
|  | 		@enter="enter" | ||||||
|  | 		@after-enter="afterEnter" | ||||||
|  | 		@leave="leave" | ||||||
|  | 		@after-leave="afterLeave" | ||||||
|  | 	> | ||||||
|  | 		<div v-show="showBody"> | ||||||
|  | 			<slot></slot> | ||||||
|  | 		</div> | ||||||
|  | 	</Transition> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent } from 'vue'; | ||||||
|  | import tinycolor from 'tinycolor2'; | ||||||
|  | import { miLocalStorage } from '@/local-storage'; | ||||||
|  |  | ||||||
|  | const miLocalStoragePrefix = 'ui:folder:' as const; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  | 	props: { | ||||||
|  | 		expanded: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: true, | ||||||
|  | 		}, | ||||||
|  | 		persistKey: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false, | ||||||
|  | 			default: null, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			bg: null, | ||||||
|  | 			showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded, | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 	watch: { | ||||||
|  | 		showBody() { | ||||||
|  | 			if (this.persistKey) { | ||||||
|  | 				miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f'); | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	mounted() { | ||||||
|  | 		function getParentBg(el: Element | null): string { | ||||||
|  | 			if (el == null || el.tagName === 'BODY') return 'var(--bg)'; | ||||||
|  | 			const bg = el.style.background || el.style.backgroundColor; | ||||||
|  | 			if (bg) { | ||||||
|  | 				return bg; | ||||||
|  | 			} else { | ||||||
|  | 				return getParentBg(el.parentElement); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		const rawBg = getParentBg(this.$el); | ||||||
|  | 		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); | ||||||
|  | 		bg.setAlpha(0.85); | ||||||
|  | 		this.bg = bg.toRgbString(); | ||||||
|  | 	}, | ||||||
|  | 	methods: { | ||||||
|  | 		toggleContent(show: boolean) { | ||||||
|  | 			this.showBody = show; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		enter(el) { | ||||||
|  | 			const elementHeight = el.getBoundingClientRect().height; | ||||||
|  | 			el.style.height = 0; | ||||||
|  | 			el.offsetHeight; // reflow | ||||||
|  | 			el.style.height = elementHeight + 'px'; | ||||||
|  | 		}, | ||||||
|  | 		afterEnter(el) { | ||||||
|  | 			el.style.height = null; | ||||||
|  | 		}, | ||||||
|  | 		leave(el) { | ||||||
|  | 			const elementHeight = el.getBoundingClientRect().height; | ||||||
|  | 			el.style.height = elementHeight + 'px'; | ||||||
|  | 			el.offsetHeight; // reflow | ||||||
|  | 			el.style.height = 0; | ||||||
|  | 		}, | ||||||
|  | 		afterLeave(el) { | ||||||
|  | 			el.style.height = null; | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .folder-toggle-enter-active, .folder-toggle-leave-active { | ||||||
|  | 	overflow-y: clip; | ||||||
|  | 	transition: opacity 0.5s, height 0.5s !important; | ||||||
|  | } | ||||||
|  | .folder-toggle-enter-from { | ||||||
|  | 	opacity: 0; | ||||||
|  | } | ||||||
|  | .folder-toggle-leave-to { | ||||||
|  | 	opacity: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ssazuxis { | ||||||
|  | 	position: relative; | ||||||
|  |  | ||||||
|  | 	> header { | ||||||
|  | 		display: flex; | ||||||
|  | 		position: relative; | ||||||
|  | 		z-index: 10; | ||||||
|  | 		position: sticky; | ||||||
|  | 		top: var(--stickyTop, 0px); | ||||||
|  | 		padding: var(--x-padding); | ||||||
|  | 		-webkit-backdrop-filter: var(--blur, blur(8px)); | ||||||
|  | 		backdrop-filter: var(--blur, blur(20px)); | ||||||
|  |  | ||||||
|  | 		> .title { | ||||||
|  | 			display: grid; | ||||||
|  | 			place-content: center; | ||||||
|  | 			margin: 0; | ||||||
|  | 			padding: 12px 16px 12px 0; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		> .divider { | ||||||
|  | 			flex: 1; | ||||||
|  | 			margin: auto; | ||||||
|  | 			height: 1px; | ||||||
|  | 			background: var(--divider); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		> button { | ||||||
|  | 			padding: 12px 0 12px 16px; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @container (max-width: 500px) { | ||||||
|  | 	.ssazuxis { | ||||||
|  | 		> header { | ||||||
|  | 			> .title { | ||||||
|  | 				padding: 8px 10px 8px 0; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -1,161 +1,177 @@ | |||||||
| <template> | <template> | ||||||
| <div class="ssazuxis"> | <div ref="rootEl" class="dwzlatin" :class="{ opened }"> | ||||||
| 	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody"> | 	<div class="header _button" @click="toggle"> | ||||||
| 		<div class="title"><slot name="header"></slot></div> | 		<span class="icon"><slot name="icon"></slot></span> | ||||||
| 		<div class="divider"></div> | 		<span class="text"><slot name="label"></slot></span> | ||||||
| 		<button class="_button"> | 		<span class="right"> | ||||||
| 			<template v-if="showBody"><i class="ti ti-chevron-up"></i></template> | 			<span class="text"><slot name="suffix"></slot></span> | ||||||
| 			<template v-else><i class="ti ti-chevron-down"></i></template> | 			<i v-if="opened" class="ti ti-chevron-up icon"></i> | ||||||
| 		</button> | 			<i v-else class="ti ti-chevron-down icon"></i> | ||||||
| 	</header> | 		</span> | ||||||
| 	<Transition | 	</div> | ||||||
| 		:name="$store.state.animation ? 'folder-toggle' : ''" | 	<div v-if="openedAtLeastOnce" class="body" :class="{ bgSame }" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null }"> | ||||||
| 		@enter="enter" | 		<Transition | ||||||
| 		@after-enter="afterEnter" | 			:name="$store.state.animation ? 'folder-toggle' : ''" | ||||||
| 		@leave="leave" | 			@enter="enter" | ||||||
| 		@after-leave="afterLeave" | 			@after-enter="afterEnter" | ||||||
| 	> | 			@leave="leave" | ||||||
| 		<div v-show="showBody"> | 			@after-leave="afterLeave" | ||||||
| 			<slot></slot> | 		> | ||||||
| 		</div> | 			<KeepAlive> | ||||||
| 	</Transition> | 				<div v-show="opened"> | ||||||
|  | 					<MkSpacer :margin-min="14" :margin-max="22"> | ||||||
|  | 						<slot></slot> | ||||||
|  | 					</MkSpacer> | ||||||
|  | 				</div> | ||||||
|  | 			</KeepAlive> | ||||||
|  | 		</Transition> | ||||||
|  | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts" setup> | ||||||
| import { defineComponent } from 'vue'; | import { nextTick, onMounted } from 'vue'; | ||||||
| import tinycolor from 'tinycolor2'; |  | ||||||
| import { miLocalStorage } from '@/local-storage'; |  | ||||||
|  |  | ||||||
| const miLocalStoragePrefix = 'ui:folder:' as const; | const props = withDefaults(defineProps<{ | ||||||
|  | 	defaultOpen: boolean; | ||||||
|  | 	maxHeight: number | null; | ||||||
|  | }>(), { | ||||||
|  | 	defaultOpen: false, | ||||||
|  | 	maxHeight: null, | ||||||
|  | }); | ||||||
|  |  | ||||||
| export default defineComponent({ | const getBgColor = (el: HTMLElement) => { | ||||||
| 	props: { | 	const style = window.getComputedStyle(el); | ||||||
| 		expanded: { | 	if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { | ||||||
| 			type: Boolean, | 		return style.backgroundColor; | ||||||
| 			required: false, | 	} else { | ||||||
| 			default: true, | 		return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; | ||||||
| 		}, | 	} | ||||||
| 		persistKey: { | }; | ||||||
| 			type: String, |  | ||||||
| 			required: false, |  | ||||||
| 			default: null, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			bg: null, |  | ||||||
| 			showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded, |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 	watch: { |  | ||||||
| 		showBody() { |  | ||||||
| 			if (this.persistKey) { |  | ||||||
| 				miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f'); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 	mounted() { |  | ||||||
| 		function getParentBg(el: Element | null): string { |  | ||||||
| 			if (el == null || el.tagName === 'BODY') return 'var(--bg)'; |  | ||||||
| 			const bg = el.style.background || el.style.backgroundColor; |  | ||||||
| 			if (bg) { |  | ||||||
| 				return bg; |  | ||||||
| 			} else { |  | ||||||
| 				return getParentBg(el.parentElement); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		const rawBg = getParentBg(this.$el); |  | ||||||
| 		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); |  | ||||||
| 		bg.setAlpha(0.85); |  | ||||||
| 		this.bg = bg.toRgbString(); |  | ||||||
| 	}, |  | ||||||
| 	methods: { |  | ||||||
| 		toggleContent(show: boolean) { |  | ||||||
| 			this.showBody = show; |  | ||||||
| 		}, |  | ||||||
|  |  | ||||||
| 		enter(el) { | let rootEl = $ref<HTMLElement>(); | ||||||
| 			const elementHeight = el.getBoundingClientRect().height; | let bgSame = $ref(false); | ||||||
| 			el.style.height = 0; | let opened = $ref(props.defaultOpen); | ||||||
| 			el.offsetHeight; // reflow | let openedAtLeastOnce = $ref(props.defaultOpen); | ||||||
| 			el.style.height = elementHeight + 'px'; |  | ||||||
| 		}, | function enter(el) { | ||||||
| 		afterEnter(el) { | 	const elementHeight = el.getBoundingClientRect().height; | ||||||
| 			el.style.height = null; | 	el.style.height = 0; | ||||||
| 		}, | 	el.offsetHeight; // reflow | ||||||
| 		leave(el) { | 	el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px'; | ||||||
| 			const elementHeight = el.getBoundingClientRect().height; | } | ||||||
| 			el.style.height = elementHeight + 'px'; |  | ||||||
| 			el.offsetHeight; // reflow | function afterEnter(el) { | ||||||
| 			el.style.height = 0; | 	el.style.height = null; | ||||||
| 		}, | } | ||||||
| 		afterLeave(el) { |  | ||||||
| 			el.style.height = null; | function leave(el) { | ||||||
| 		}, | 	const elementHeight = el.getBoundingClientRect().height; | ||||||
| 	}, | 	el.style.height = elementHeight + 'px'; | ||||||
|  | 	el.offsetHeight; // reflow | ||||||
|  | 	el.style.height = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function afterLeave(el) { | ||||||
|  | 	el.style.height = null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function toggle() { | ||||||
|  | 	if (!opened) { | ||||||
|  | 		openedAtLeastOnce = true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nextTick(() => { | ||||||
|  | 		opened = !opened; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | onMounted(() => { | ||||||
|  | 	const computedStyle = getComputedStyle(document.documentElement); | ||||||
|  | 	const parentBg = getBgColor(rootEl.parentElement); | ||||||
|  | 	const myBg = computedStyle.getPropertyValue('--panel'); | ||||||
|  | 	bgSame = parentBg === myBg; | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .folder-toggle-enter-active, .folder-toggle-leave-active { | .folder-toggle-enter-active, .folder-toggle-leave-active { | ||||||
| 	overflow-y: clip; | 	overflow-y: clip; | ||||||
| 	transition: opacity 0.5s, height 0.5s !important; | 	transition: opacity 0.3s, height 0.3s, transform 0.3s !important; | ||||||
| } | } | ||||||
| .folder-toggle-enter-from { | .folder-toggle-enter-from, .folder-toggle-leave-to { | ||||||
| 	opacity: 0; |  | ||||||
| } |  | ||||||
| .folder-toggle-leave-to { |  | ||||||
| 	opacity: 0; | 	opacity: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ssazuxis { | .dwzlatin { | ||||||
| 	position: relative; | 	display: block; | ||||||
|  |  | ||||||
| 	> header { | 	> .header { | ||||||
| 		display: flex; | 		display: flex; | ||||||
| 		position: relative; | 		align-items: center; | ||||||
| 		z-index: 10; | 		width: 100%; | ||||||
| 		position: sticky; | 		box-sizing: border-box; | ||||||
| 		top: var(--stickyTop, 0px); | 		padding: 10px 14px 10px 14px; | ||||||
| 		padding: var(--x-padding); | 		background: var(--buttonBg); | ||||||
| 		-webkit-backdrop-filter: var(--blur, blur(8px)); | 		border-radius: 6px; | ||||||
| 		backdrop-filter: var(--blur, blur(20px)); |  | ||||||
|  |  | ||||||
| 		> .title { | 		&:hover { | ||||||
| 			display: grid; | 			text-decoration: none; | ||||||
| 			place-content: center; | 			background: var(--buttonHoverBg); | ||||||
| 			margin: 0; | 		} | ||||||
| 			padding: 12px 16px 12px 0; |  | ||||||
|  |  | ||||||
| 			> i { | 		&.active { | ||||||
| 				margin-right: 6px; | 			color: var(--accent); | ||||||
| 			} | 			background: var(--buttonHoverBg); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		> .icon { | ||||||
|  | 			margin-right: 0.75em; | ||||||
|  | 			flex-shrink: 0; | ||||||
|  | 			text-align: center; | ||||||
|  | 			opacity: 0.8; | ||||||
|  |  | ||||||
| 			&:empty { | 			&:empty { | ||||||
| 				display: none; | 				display: none; | ||||||
|  |  | ||||||
|  | 				& + .text { | ||||||
|  | 					padding-left: 4px; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .divider { | 		> .text { | ||||||
| 			flex: 1; | 			white-space: nowrap; | ||||||
| 			margin: auto; | 			text-overflow: ellipsis; | ||||||
| 			height: 1px; | 			overflow: hidden; | ||||||
| 			background: var(--divider); | 			padding-right: 12px; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> button { | 		> .right { | ||||||
| 			padding: 12px 0 12px 16px; | 			margin-left: auto; | ||||||
|  | 			opacity: 0.7; | ||||||
|  | 			white-space: nowrap; | ||||||
|  |  | ||||||
|  | 			> .text:not(:empty) { | ||||||
|  | 				margin-right: 0.75em; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } |  | ||||||
|  |  | ||||||
| @container (max-width: 500px) { | 	> .body { | ||||||
| 	.ssazuxis { | 		background: var(--panel); | ||||||
| 		> header { | 		border-radius: 0 0 6px 6px; | ||||||
| 			> .title { | 		container-type: inline-size; | ||||||
| 				padding: 8px 10px 8px 0; | 		overflow: auto; | ||||||
| 			} |  | ||||||
|  | 		&.bgSame { | ||||||
|  | 			background: var(--bg); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.opened { | ||||||
|  | 		> .header { | ||||||
|  | 			border-radius: 6px 6px 0 0; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]"> | <div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]"> | ||||||
| 	<img class="icon" :src="getInstanceIcon(instance)" alt=""/> | 	<img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/> | ||||||
| 	<div class="body"> | 	<div class="body"> | ||||||
| 		<span class="host">{{ instance.name ?? instance.host }}</span> | 		<span class="host">{{ instance.name ?? instance.host }}</span> | ||||||
| 		<span class="sub _monospace"><b>{{ instance.host }}</b> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span> | 		<span class="sub _monospace"><b>{{ instance.host }}</b> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div :class="$style.root"> | <div :class="$style.root"> | ||||||
| 	<MkFolder class="item"> | 	<MkFoldableSection class="item"> | ||||||
| 		<template #header>Chart</template> | 		<template #header>Chart</template> | ||||||
| 		<div :class="$style.chart"> | 		<div :class="$style.chart"> | ||||||
| 			<div class="selects"> | 			<div class="selects"> | ||||||
| @@ -34,9 +34,9 @@ | |||||||
| 				<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="true"></MkChart> | 				<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="true"></MkChart> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkFolder> | 	</MkFoldableSection> | ||||||
|  |  | ||||||
| 	<MkFolder class="item"> | 	<MkFoldableSection class="item"> | ||||||
| 		<template #header>Active users heatmap</template> | 		<template #header>Active users heatmap</template> | ||||||
| 		<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;"> | 		<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;"> | ||||||
| 			<option value="active-users">Active users</option> | 			<option value="active-users">Active users</option> | ||||||
| @@ -48,16 +48,16 @@ | |||||||
| 		<div class="_panel" :class="$style.heatmap"> | 		<div class="_panel" :class="$style.heatmap"> | ||||||
| 			<MkHeatmap :src="heatmapSrc"/> | 			<MkHeatmap :src="heatmapSrc"/> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkFolder> | 	</MkFoldableSection> | ||||||
|  |  | ||||||
| 	<MkFolder class="item"> | 	<MkFoldableSection class="item"> | ||||||
| 		<template #header>Retention rate</template> | 		<template #header>Retention rate</template> | ||||||
| 		<div class="_panel" :class="$style.retention"> | 		<div class="_panel" :class="$style.retention"> | ||||||
| 			<MkRetentionHeatmap/> | 			<MkRetentionHeatmap/> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkFolder> | 	</MkFoldableSection> | ||||||
|  |  | ||||||
| 	<MkFolder class="item"> | 	<MkFoldableSection class="item"> | ||||||
| 		<template #header>Federation</template> | 		<template #header>Federation</template> | ||||||
| 		<div :class="$style.federation"> | 		<div :class="$style.federation"> | ||||||
| 			<div class="pies"> | 			<div class="pies"> | ||||||
| @@ -71,7 +71,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkFolder> | 	</MkFoldableSection> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -84,7 +84,7 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip'; | |||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
| import MkHeatmap from '@/components/MkHeatmap.vue'; | import MkHeatmap from '@/components/MkHeatmap.vue'; | ||||||
| import MkFolder from '@/components/MkFolder.vue'; | import MkFoldableSection from '@/components/MkFoldableSection.vue'; | ||||||
| import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; | import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; | ||||||
| import { initChart } from '@/scripts/init-chart'; | import { initChart } from '@/scripts/init-chart'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ function close() { | |||||||
|  |  | ||||||
| 	&.asDrawer { | 	&.asDrawer { | ||||||
| 		width: 100%; | 		width: 100%; | ||||||
| 		padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px) 16px; | 		padding: 16px 16px max(env(safe-area-inset-bottom, 0px), 16px) 16px; | ||||||
| 		border-radius: 24px; | 		border-radius: 24px; | ||||||
| 		border-bottom-right-radius: 0; | 		border-bottom-right-radius: 0; | ||||||
| 		border-bottom-left-radius: 0; | 		border-bottom-left-radius: 0; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <template> | <template> | ||||||
| <component | <component | ||||||
| 	:is="self ? 'MkA' : 'a'" ref="el" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | 	:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||||
| 	:title="url" | 	:title="url" | ||||||
| > | > | ||||||
| 	<slot></slot> | 	<slot></slot> | ||||||
| 	<i v-if="target === '_blank'" class="ti ti-external-link icon"></i> | 	<i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i> | ||||||
| </component> | </component> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -35,13 +35,9 @@ useTooltip($$(el), (showing) => { | |||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .xlcxczvw { | .icon { | ||||||
| 	word-break: break-all; | 	padding-left: 2px; | ||||||
|  | 	font-size: .9em; | ||||||
| 	> .icon { |  | ||||||
| 		padding-left: 2px; |  | ||||||
| 		font-size: .9em; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -2,54 +2,54 @@ | |||||||
| <div> | <div> | ||||||
| 	<div | 	<div | ||||||
| 		ref="itemsEl" v-hotkey="keymap" | 		ref="itemsEl" v-hotkey="keymap" | ||||||
| 		class="rrevdjwt _popup _shadow" | 		class="_popup _shadow" | ||||||
| 		:class="{ center: align === 'center', asDrawer }" | 		:class="[$style.root, { [$style.center]: align === 'center', [$style.asDrawer]: asDrawer }]" | ||||||
| 		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" | 		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" | ||||||
| 		@contextmenu.self="e => e.preventDefault()" | 		@contextmenu.self="e => e.preventDefault()" | ||||||
| 	> | 	> | ||||||
| 		<template v-for="(item, i) in items2"> | 		<template v-for="(item, i) in items2"> | ||||||
| 			<div v-if="item === null" class="divider"></div> | 			<div v-if="item === null" :class="$style.divider"></div> | ||||||
| 			<span v-else-if="item.type === 'label'" class="label item"> | 			<span v-else-if="item.type === 'label'" :class="[$style.label, $style.item]"> | ||||||
| 				<span>{{ item.text }}</span> | 				<span>{{ item.text }}</span> | ||||||
| 			</span> | 			</span> | ||||||
| 			<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item"> | 			<span v-else-if="item.type === 'pending'" :tabindex="i" :class="[$style.pending, $style.item]"> | ||||||
| 				<span><MkEllipsis/></span> | 				<span><MkEllipsis/></span> | ||||||
| 			</span> | 			</span> | ||||||
| 			<MkA v-else-if="item.type === 'link'" :to="item.to" :tabindex="i" class="_button item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | 			<MkA v-else-if="item.type === 'link'" :to="item.to" :tabindex="i" class="_button" :class="$style.item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | ||||||
| 				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i> | 				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> | ||||||
| 				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> | 				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> | ||||||
| 				<span>{{ item.text }}</span> | 				<span>{{ item.text }}</span> | ||||||
| 				<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> | 				<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> | ||||||
| 			</MkA> | 			</MkA> | ||||||
| 			<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | 			<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button" :class="$style.item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | ||||||
| 				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i> | 				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> | ||||||
| 				<span>{{ item.text }}</span> | 				<span>{{ item.text }}</span> | ||||||
| 				<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> | 				<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> | ||||||
| 			</a> | 			</a> | ||||||
| 			<button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | 			<button v-else-if="item.type === 'user'" :tabindex="i" class="_button" :class="[$style.item, { [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | ||||||
| 				<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> | 				<MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/> | ||||||
| 				<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> | 				<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> | ||||||
| 			</button> | 			</button> | ||||||
| 			<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | 			<span v-else-if="item.type === 'switch'" :tabindex="i" :class="$style.item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | ||||||
| 				<MkSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</MkSwitch> | 				<MkSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</MkSwitch> | ||||||
| 			</span> | 			</span> | ||||||
| 			<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)"> | 			<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="showChildren(item, $event)"> | ||||||
| 				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i> | 				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> | ||||||
| 				<span>{{ item.text }}</span> | 				<span>{{ item.text }}</span> | ||||||
| 				<span class="caret"><i class="ti ti-caret-right ti-fw"></i></span> | 				<span :class="$style.caret"><i class="ti ti-caret-right ti-fw"></i></span> | ||||||
| 			</button> | 			</button> | ||||||
| 			<button v-else :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | 			<button v-else :tabindex="i" class="_button" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> | ||||||
| 				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i> | 				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> | ||||||
| 				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> | 				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> | ||||||
| 				<span>{{ item.text }}</span> | 				<span>{{ item.text }}</span> | ||||||
| 				<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> | 				<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> | ||||||
| 			</button> | 			</button> | ||||||
| 		</template> | 		</template> | ||||||
| 		<span v-if="items2.length === 0" class="none item"> | 		<span v-if="items2.length === 0" :class="[$style.none, $style.item]"> | ||||||
| 			<span>{{ i18n.ts.none }}</span> | 			<span>{{ i18n.ts.none }}</span> | ||||||
| 		</span> | 		</span> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div v-if="childMenu" class="child"> | 	<div v-if="childMenu" :class="$style.child"> | ||||||
| 		<XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/> | 		<XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| @@ -186,8 +186,8 @@ onBeforeUnmount(() => { | |||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .rrevdjwt { | .root { | ||||||
| 	padding: 8px 0; | 	padding: 8px 0; | ||||||
| 	box-sizing: border-box; | 	box-sizing: border-box; | ||||||
| 	min-width: 200px; | 	min-width: 200px; | ||||||
| @@ -200,143 +200,8 @@ onBeforeUnmount(() => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .item { |  | ||||||
| 		display: block; |  | ||||||
| 		position: relative; |  | ||||||
| 		padding: 5px 16px; |  | ||||||
| 		width: 100%; |  | ||||||
| 		box-sizing: border-box; |  | ||||||
| 		white-space: nowrap; |  | ||||||
| 		font-size: 0.9em; |  | ||||||
| 		line-height: 20px; |  | ||||||
| 		text-align: left; |  | ||||||
| 		overflow: hidden; |  | ||||||
| 		text-overflow: ellipsis; |  | ||||||
|  |  | ||||||
| 		&:before { |  | ||||||
| 			content: ""; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			z-index: -1; |  | ||||||
| 			top: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			margin: auto; |  | ||||||
| 			width: calc(100% - 16px); |  | ||||||
| 			height: 100%; |  | ||||||
| 			border-radius: 6px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:not(:disabled):hover { |  | ||||||
| 			color: var(--accent); |  | ||||||
| 			text-decoration: none; |  | ||||||
|  |  | ||||||
| 			&:before { |  | ||||||
| 				background: var(--accentedBg); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.danger { |  | ||||||
| 			color: #ff2a2a; |  | ||||||
|  |  | ||||||
| 			&:hover { |  | ||||||
| 				color: #fff; |  | ||||||
|  |  | ||||||
| 				&:before { |  | ||||||
| 					background: #ff4242; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			&:active { |  | ||||||
| 				color: #fff; |  | ||||||
|  |  | ||||||
| 				&:before { |  | ||||||
| 					background: #d42e2e !important; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:active, |  | ||||||
| 		&.active { |  | ||||||
| 			color: var(--fgOnAccent) !important; |  | ||||||
| 			opacity: 1; |  | ||||||
|  |  | ||||||
| 			&:before { |  | ||||||
| 				background: var(--accent) !important; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:not(:active):focus-visible { |  | ||||||
| 			box-shadow: 0 0 0 2px var(--focus) inset; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.label { |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			font-size: 0.7em; |  | ||||||
| 			padding-bottom: 4px; |  | ||||||
|  |  | ||||||
| 			> span { |  | ||||||
| 				opacity: 0.7; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.pending { |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			opacity: 0.7; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.none { |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			opacity: 0.7; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&.parent { |  | ||||||
| 			display: flex; |  | ||||||
| 			align-items: center; |  | ||||||
| 			cursor: default; |  | ||||||
|  |  | ||||||
| 			> .caret { |  | ||||||
| 				margin-left: auto; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			&.childShowing { |  | ||||||
| 				color: var(--accent); |  | ||||||
| 				text-decoration: none; |  | ||||||
|  |  | ||||||
| 				&:before { |  | ||||||
| 					background: var(--accentedBg); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> i { |  | ||||||
| 			margin-right: 5px; |  | ||||||
| 			width: 20px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .avatar { |  | ||||||
| 			margin-right: 5px; |  | ||||||
| 			width: 20px; |  | ||||||
| 			height: 20px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .indicator { |  | ||||||
| 			position: absolute; |  | ||||||
| 			top: 5px; |  | ||||||
| 			left: 13px; |  | ||||||
| 			color: var(--indicator); |  | ||||||
| 			font-size: 12px; |  | ||||||
| 			animation: blink 1s infinite; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .divider { |  | ||||||
| 		margin: 8px 0; |  | ||||||
| 		border-top: solid 0.5px var(--divider); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	&.asDrawer { | 	&.asDrawer { | ||||||
| 		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0; | 		padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; | ||||||
| 		width: 100%; | 		width: 100%; | ||||||
| 		border-radius: 24px; | 		border-radius: 24px; | ||||||
| 		border-bottom-right-radius: 0; | 		border-bottom-right-radius: 0; | ||||||
| @@ -351,7 +216,7 @@ onBeforeUnmount(() => { | |||||||
| 				border-radius: 12px; | 				border-radius: 12px; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			> i { | 			> .icon { | ||||||
| 				margin-right: 14px; | 				margin-right: 14px; | ||||||
| 				width: 24px; | 				width: 24px; | ||||||
| 			} | 			} | ||||||
| @@ -362,4 +227,139 @@ onBeforeUnmount(() => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .item { | ||||||
|  | 	display: block; | ||||||
|  | 	position: relative; | ||||||
|  | 	padding: 5px 16px; | ||||||
|  | 	width: 100%; | ||||||
|  | 	box-sizing: border-box; | ||||||
|  | 	white-space: nowrap; | ||||||
|  | 	font-size: 0.9em; | ||||||
|  | 	line-height: 20px; | ||||||
|  | 	text-align: left; | ||||||
|  | 	overflow: hidden; | ||||||
|  | 	text-overflow: ellipsis; | ||||||
|  |  | ||||||
|  | 	&:before { | ||||||
|  | 		content: ""; | ||||||
|  | 		display: block; | ||||||
|  | 		position: absolute; | ||||||
|  | 		z-index: -1; | ||||||
|  | 		top: 0; | ||||||
|  | 		left: 0; | ||||||
|  | 		right: 0; | ||||||
|  | 		margin: auto; | ||||||
|  | 		width: calc(100% - 16px); | ||||||
|  | 		height: 100%; | ||||||
|  | 		border-radius: 6px; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&:not(:disabled):hover { | ||||||
|  | 		color: var(--accent); | ||||||
|  | 		text-decoration: none; | ||||||
|  |  | ||||||
|  | 		&:before { | ||||||
|  | 			background: var(--accentedBg); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.danger { | ||||||
|  | 		color: #ff2a2a; | ||||||
|  |  | ||||||
|  | 		&:hover { | ||||||
|  | 			color: #fff; | ||||||
|  |  | ||||||
|  | 			&:before { | ||||||
|  | 				background: #ff4242; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		&:active { | ||||||
|  | 			color: #fff; | ||||||
|  |  | ||||||
|  | 			&:before { | ||||||
|  | 				background: #d42e2e !important; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&:active, | ||||||
|  | 	&.active { | ||||||
|  | 		color: var(--fgOnAccent) !important; | ||||||
|  | 		opacity: 1; | ||||||
|  |  | ||||||
|  | 		&:before { | ||||||
|  | 			background: var(--accent) !important; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&:not(:active):focus-visible { | ||||||
|  | 		box-shadow: 0 0 0 2px var(--focus) inset; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.label { | ||||||
|  | 		pointer-events: none; | ||||||
|  | 		font-size: 0.7em; | ||||||
|  | 		padding-bottom: 4px; | ||||||
|  |  | ||||||
|  | 		> span { | ||||||
|  | 			opacity: 0.7; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.pending { | ||||||
|  | 		pointer-events: none; | ||||||
|  | 		opacity: 0.7; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.none { | ||||||
|  | 		pointer-events: none; | ||||||
|  | 		opacity: 0.7; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.parent { | ||||||
|  | 		display: flex; | ||||||
|  | 		align-items: center; | ||||||
|  | 		cursor: default; | ||||||
|  |  | ||||||
|  | 		&.childShowing { | ||||||
|  | 			color: var(--accent); | ||||||
|  | 			text-decoration: none; | ||||||
|  |  | ||||||
|  | 			&:before { | ||||||
|  | 				background: var(--accentedBg); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .icon { | ||||||
|  | 	margin-right: 5px; | ||||||
|  | 	width: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .caret { | ||||||
|  | 	margin-left: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .avatar { | ||||||
|  | 	margin-right: 5px; | ||||||
|  | 	width: 20px; | ||||||
|  | 	height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .indicator { | ||||||
|  | 	position: absolute; | ||||||
|  | 	top: 5px; | ||||||
|  | 	left: 13px; | ||||||
|  | 	color: var(--indicator); | ||||||
|  | 	font-size: 12px; | ||||||
|  | 	animation: blink 1s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .divider { | ||||||
|  | 	margin: 8px 0; | ||||||
|  | 	border-top: solid 0.5px var(--divider); | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -4,111 +4,110 @@ | |||||||
| 	v-show="!isDeleted" | 	v-show="!isDeleted" | ||||||
| 	ref="el" | 	ref="el" | ||||||
| 	v-hotkey="keymap" | 	v-hotkey="keymap" | ||||||
| 	class="tkcbzcuz" | 	:class="$style.root" | ||||||
| 	:tabindex="!isDeleted ? '-1' : null" | 	:tabindex="!isDeleted ? '-1' : null" | ||||||
| 	:class="{ renote: isRenote }" |  | ||||||
| > | > | ||||||
| 	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> | 	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/> | ||||||
| 	<div v-if="pinned" class="info"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> | 	<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> | ||||||
| 	<div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div> | 	<!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>--> | ||||||
| 	<div v-if="appearNote._featuredId_" class="info"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div> | 	<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>--> | ||||||
| 	<div v-if="isRenote" class="renote"> | 	<div v-if="isRenote" :class="$style.renote"> | ||||||
| 		<MkAvatar v-once class="avatar" :user="note.user"/> | 		<MkAvatar v-once :class="$style.renoteAvatar" :user="note.user"/> | ||||||
| 		<i class="ti ti-repeat"></i> | 		<i class="ti ti-repeat" style="margin-right: 4px;"></i> | ||||||
| 		<I18n :src="i18n.ts.renotedBy" tag="span"> | 		<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText"> | ||||||
| 			<template #user> | 			<template #user> | ||||||
| 				<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> | 				<MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.user)"> | ||||||
| 					<MkUserName :user="note.user"/> | 					<MkUserName :user="note.user"/> | ||||||
| 				</MkA> | 				</MkA> | ||||||
| 			</template> | 			</template> | ||||||
| 		</I18n> | 		</I18n> | ||||||
| 		<div class="info"> | 		<div :class="$style.renoteInfo"> | ||||||
| 			<button ref="renoteTime" class="_button time" @click="showRenoteMenu()"> | 			<button ref="renoteTime" :class="$style.renoteTime" class="_button" @click="showRenoteMenu()"> | ||||||
| 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i> | 				<i v-if="isMyRenote" class="ti ti-dots" :class="$style.renoteMenu"></i> | ||||||
| 				<MkTime :time="note.createdAt"/> | 				<MkTime :time="note.createdAt"/> | ||||||
| 			</button> | 			</button> | ||||||
| 			<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]"> | 			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> | ||||||
| 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i> | 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i> | ||||||
| 				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i> | 				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i> | ||||||
| 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | ||||||
| 			</span> | 			</span> | ||||||
| 			<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<article class="article" @contextmenu.stop="onContextmenu"> | 	<article :class="$style.article" @contextmenu.stop="onContextmenu"> | ||||||
| 		<MkAvatar v-once class="avatar" :user="appearNote.user"/> | 		<MkAvatar v-once :class="$style.avatar" :user="appearNote.user"/> | ||||||
| 		<div class="main"> | 		<div :class="$style.main"> | ||||||
| 			<MkNoteHeader class="header" :note="appearNote" :mini="true"/> | 			<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/> | ||||||
| 			<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/> | 			<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/> | ||||||
| 			<div class="body"> | 			<div style="container-type: inline-size;"> | ||||||
| 				<p v-if="appearNote.cw != null" class="cw"> | 				<p v-if="appearNote.cw != null" :class="$style.cw"> | ||||||
| 					<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/> | 					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/> | ||||||
| 					<MkCwButton v-model="showContent" :note="appearNote"/> | 					<MkCwButton v-model="showContent" :note="appearNote"/> | ||||||
| 				</p> | 				</p> | ||||||
| 				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }"> | 				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]"> | ||||||
| 					<div class="text"> | 					<div :class="$style.text"> | ||||||
| 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> | 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> | ||||||
| 						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> | 						<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> | ||||||
| 						<Mfm v-if="appearNote.text" v-once :text="appearNote.text" :author="appearNote.user" :i="$i"/> | 						<Mfm v-if="appearNote.text" v-once :text="appearNote.text" :author="appearNote.user" :i="$i"/> | ||||||
| 						<a v-if="appearNote.renote != null" class="rp">RN:</a> | 						<div v-if="translating || translation" :class="$style.translation"> | ||||||
| 						<div v-if="translating || translation" class="translation"> |  | ||||||
| 							<MkLoading v-if="translating" mini/> | 							<MkLoading v-if="translating" mini/> | ||||||
| 							<div v-else class="translated"> | 							<div v-else :class="$style.translated"> | ||||||
| 								<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b> | 								<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b> | ||||||
| 								<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/> | 								<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/> | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div v-if="appearNote.files.length > 0" class="files"> | 					<div v-if="appearNote.files.length > 0" :class="$style.files"> | ||||||
| 						<MkMediaList :media-list="appearNote.files"/> | 						<MkMediaList :media-list="appearNote.files"/> | ||||||
| 					</div> | 					</div> | ||||||
| 					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/> | 					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/> | ||||||
| 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> | 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> | ||||||
| 					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div> | 					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> | ||||||
| 					<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false"> | 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> | ||||||
| 						<span>{{ i18n.ts.showMore }}</span> | 						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> | ||||||
| 					</button> | 					</button> | ||||||
| 					<button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true"> | 					<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true"> | ||||||
| 						<span>{{ i18n.ts.showLess }}</span> | 						<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span> | ||||||
| 					</button> | 					</button> | ||||||
| 				</div> | 				</div> | ||||||
| 				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> | 				<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA> | ||||||
| 			</div> | 			</div> | ||||||
| 			<footer class="footer"> | 			<footer :class="$style.footer"> | ||||||
| 				<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/> | 				<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/> | ||||||
| 				<button class="button _button" @click="reply()"> | 				<button :class="$style.footerButton" class="_button" @click="reply()"> | ||||||
| 					<i class="ti ti-arrow-back-up"></i> | 					<i class="ti ti-arrow-back-up"></i> | ||||||
| 					<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p> | 					<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button | 				<button | ||||||
| 					v-if="canRenote" | 					v-if="canRenote" | ||||||
| 					ref="renoteButton" | 					ref="renoteButton" | ||||||
| 					class="button _button" | 					:class="$style.footerButton" | ||||||
|  | 					class="_button" | ||||||
| 					@mousedown="renote()" | 					@mousedown="renote()" | ||||||
| 				> | 				> | ||||||
| 					<i class="ti ti-repeat"></i> | 					<i class="ti ti-repeat"></i> | ||||||
| 					<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p> | 					<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button v-else class="button _button" disabled> | 				<button v-else :class="$style.footerButton" class="_button" disabled> | ||||||
| 					<i class="ti ti-ban"></i> | 					<i class="ti ti-ban"></i> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()"> | 				<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()"> | ||||||
| 					<i class="ti ti-plus"></i> | 					<i class="ti ti-plus"></i> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)"> | 				<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)"> | ||||||
| 					<i class="ti ti-minus"></i> | 					<i class="ti ti-minus"></i> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button ref="menuButton" class="button _button" @mousedown="menu()"> | 				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()"> | ||||||
| 					<i class="ti ti-dots"></i> | 					<i class="ti ti-dots"></i> | ||||||
| 				</button> | 				</button> | ||||||
| 			</footer> | 			</footer> | ||||||
| 		</div> | 		</div> | ||||||
| 	</article> | 	</article> | ||||||
| </div> | </div> | ||||||
| <div v-else class="muted" @click="muted = false"> | <div v-else :class="$style.muted" @click="muted = false"> | ||||||
| 	<I18n :src="i18n.ts.userSaysSomething" tag="small"> | 	<I18n :src="i18n.ts.userSaysSomething" tag="small"> | ||||||
| 		<template #name> | 		<template #name> | ||||||
| 			<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> | 			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)"> | ||||||
| 				<MkUserName :user="appearNote.user"/> | 				<MkUserName :user="appearNote.user"/> | ||||||
| 			</MkA> | 			</MkA> | ||||||
| 		</template> | 		</template> | ||||||
| @@ -349,8 +348,8 @@ function readPromo() { | |||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .tkcbzcuz { | .root { | ||||||
| 	position: relative; | 	position: relative; | ||||||
| 	transition: box-shadow 0.1s ease; | 	transition: box-shadow 0.1s ease; | ||||||
| 	font-size: 1.05em; | 	font-size: 1.05em; | ||||||
| @@ -387,322 +386,259 @@ function readPromo() { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	&:hover > .article > .main > .footer > .button { | 	&:hover > .article > .main > .footer > .footerButton { | ||||||
| 		opacity: 1; | 		opacity: 1; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .info { |  | ||||||
| 		display: flex; |  | ||||||
| 		align-items: center; |  | ||||||
| 		padding: 16px 32px 8px 32px; |  | ||||||
| 		line-height: 24px; |  | ||||||
| 		font-size: 90%; |  | ||||||
| 		white-space: pre; |  | ||||||
| 		color: #d28a3f; |  | ||||||
|  |  | ||||||
| 		> i { |  | ||||||
| 			margin-right: 4px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .hide { |  | ||||||
| 			margin-left: auto; |  | ||||||
| 			color: inherit; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .info + .article { |  | ||||||
| 		padding-top: 8px; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .reply-to { |  | ||||||
| 		opacity: 0.7; |  | ||||||
| 		padding-bottom: 0; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .renote { |  | ||||||
| 		display: flex; |  | ||||||
| 		align-items: center; |  | ||||||
| 		padding: 16px 32px 8px 32px; |  | ||||||
| 		line-height: 28px; |  | ||||||
| 		white-space: pre; |  | ||||||
| 		color: var(--renote); |  | ||||||
|  |  | ||||||
| 		> .avatar { |  | ||||||
| 			flex-shrink: 0; |  | ||||||
| 			display: inline-block; |  | ||||||
| 			width: 28px; |  | ||||||
| 			height: 28px; |  | ||||||
| 			margin: 0 8px 0 0; |  | ||||||
| 			border-radius: 6px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> i { |  | ||||||
| 			margin-right: 4px; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> span { |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			flex-shrink: 1; |  | ||||||
| 			text-overflow: ellipsis; |  | ||||||
| 			white-space: nowrap; |  | ||||||
|  |  | ||||||
| 			> .name { |  | ||||||
| 				font-weight: bold; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .info { |  | ||||||
| 			margin-left: auto; |  | ||||||
| 			font-size: 0.9em; |  | ||||||
|  |  | ||||||
| 			> .time { |  | ||||||
| 				flex-shrink: 0; |  | ||||||
| 				color: inherit; |  | ||||||
|  |  | ||||||
| 				> .dropdownIcon { |  | ||||||
| 					margin-right: 4px; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .renote + .article { |  | ||||||
| 		padding-top: 8px; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .article { |  | ||||||
| 		display: flex; |  | ||||||
| 		padding: 28px 32px 18px; |  | ||||||
|  |  | ||||||
| 		> .avatar { |  | ||||||
| 			flex-shrink: 0; |  | ||||||
| 			display: block; |  | ||||||
| 			margin: 0 14px 8px 0; |  | ||||||
| 			width: 58px; |  | ||||||
| 			height: 58px; |  | ||||||
| 			position: sticky; |  | ||||||
| 			top: calc(22px + var(--stickyTop, 0px)); |  | ||||||
| 			left: 0; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .main { |  | ||||||
| 			flex: 1; |  | ||||||
| 			min-width: 0; |  | ||||||
|  |  | ||||||
| 			> .body { |  | ||||||
| 				container-type: inline-size; |  | ||||||
|  |  | ||||||
| 				> .cw { |  | ||||||
| 					cursor: default; |  | ||||||
| 					display: block; |  | ||||||
| 					margin: 0; |  | ||||||
| 					padding: 0; |  | ||||||
| 					overflow-wrap: break-word; |  | ||||||
|  |  | ||||||
| 					> .text { |  | ||||||
| 						margin-right: 8px; |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				> .content { |  | ||||||
| 					&.isLong { |  | ||||||
| 						> .showLess { |  | ||||||
| 							width: 100%; |  | ||||||
| 							margin-top: 1em; |  | ||||||
| 							position: sticky; |  | ||||||
| 							bottom: 1em; |  | ||||||
|  |  | ||||||
| 							> span { |  | ||||||
| 								display: inline-block; |  | ||||||
| 								background: var(--popup); |  | ||||||
| 								padding: 6px 10px; |  | ||||||
| 								font-size: 0.8em; |  | ||||||
| 								border-radius: 999px; |  | ||||||
| 								box-shadow: 0 2px 6px rgb(0 0 0 / 20%); |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					&.collapsed { |  | ||||||
| 						position: relative; |  | ||||||
| 						max-height: 9em; |  | ||||||
| 						overflow: clip; |  | ||||||
|  |  | ||||||
| 						> .fade { |  | ||||||
| 							display: block; |  | ||||||
| 							position: absolute; |  | ||||||
| 							bottom: 0; |  | ||||||
| 							left: 0; |  | ||||||
| 							width: 100%; |  | ||||||
| 							height: 64px; |  | ||||||
| 							background: linear-gradient(0deg, var(--panel), var(--X15)); |  | ||||||
|  |  | ||||||
| 							> span { |  | ||||||
| 								display: inline-block; |  | ||||||
| 								background: var(--panel); |  | ||||||
| 								padding: 6px 10px; |  | ||||||
| 								font-size: 0.8em; |  | ||||||
| 								border-radius: 999px; |  | ||||||
| 								box-shadow: 0 2px 6px rgb(0 0 0 / 20%); |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							&:hover { |  | ||||||
| 								> span { |  | ||||||
| 									background: var(--panelHighlight); |  | ||||||
| 								} |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					> .text { |  | ||||||
| 						overflow-wrap: break-word; |  | ||||||
|  |  | ||||||
| 						> .reply { |  | ||||||
| 							color: var(--accent); |  | ||||||
| 							margin-right: 0.5em; |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						> .rp { |  | ||||||
| 							margin-left: 4px; |  | ||||||
| 							font-style: oblique; |  | ||||||
| 							color: var(--renote); |  | ||||||
| 						} |  | ||||||
|  |  | ||||||
| 						> .translation { |  | ||||||
| 							border: solid 0.5px var(--divider); |  | ||||||
| 							border-radius: var(--radius); |  | ||||||
| 							padding: 12px; |  | ||||||
| 							margin-top: 8px; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					> .url-preview { |  | ||||||
| 						margin-top: 8px; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					> .poll { |  | ||||||
| 						font-size: 80%; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					> .renote { |  | ||||||
| 						padding: 8px 0; |  | ||||||
|  |  | ||||||
| 						> .note { |  | ||||||
| 							padding: 16px; |  | ||||||
| 							border: dashed 1px var(--renote); |  | ||||||
| 							border-radius: 8px; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				> .channel { |  | ||||||
| 					opacity: 0.7; |  | ||||||
| 					font-size: 80%; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			> .footer { |  | ||||||
| 				> .button { |  | ||||||
| 					margin: 0; |  | ||||||
| 					padding: 8px; |  | ||||||
| 					opacity: 0.7; |  | ||||||
|  |  | ||||||
| 					&:not(:last-child) { |  | ||||||
| 						margin-right: 28px; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					&:hover { |  | ||||||
| 						color: var(--fgHighlighted); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					> .count { |  | ||||||
| 						display: inline; |  | ||||||
| 						margin: 0 0 0 8px; |  | ||||||
| 						opacity: 0.7; |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					&.reacted { |  | ||||||
| 						color: var(--accent); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .reply { |  | ||||||
| 		border-top: solid 0.5px var(--divider); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (max-width: 500px) { | .tip { | ||||||
| 	.tkcbzcuz { | 	display: flex; | ||||||
| 		font-size: 0.9em; | 	align-items: center; | ||||||
|  | 	padding: 16px 32px 8px 32px; | ||||||
|  | 	line-height: 24px; | ||||||
|  | 	font-size: 90%; | ||||||
|  | 	white-space: pre; | ||||||
|  | 	color: #d28a3f; | ||||||
|  | } | ||||||
|  |  | ||||||
| 		> .article { | .tip + .article { | ||||||
| 			> .avatar { | 	padding-top: 8px; | ||||||
| 				width: 50px; | } | ||||||
| 				height: 50px; |  | ||||||
| 			} | .replyTo { | ||||||
| 		} | 	opacity: 0.7; | ||||||
|  | 	padding-bottom: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renote { | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | 	padding: 16px 32px 8px 32px; | ||||||
|  | 	line-height: 28px; | ||||||
|  | 	white-space: pre; | ||||||
|  | 	color: var(--renote); | ||||||
|  |  | ||||||
|  | 	& + .article { | ||||||
|  | 		padding-top: 8px; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renoteAvatar { | ||||||
|  | 	flex-shrink: 0; | ||||||
|  | 	display: inline-block; | ||||||
|  | 	width: 28px; | ||||||
|  | 	height: 28px; | ||||||
|  | 	margin: 0 8px 0 0; | ||||||
|  | 	border-radius: 6px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renoteText { | ||||||
|  | 	overflow: hidden; | ||||||
|  | 	flex-shrink: 1; | ||||||
|  | 	text-overflow: ellipsis; | ||||||
|  | 	white-space: nowrap; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renoteUserName { | ||||||
|  | 	font-weight: bold; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renoteInfo { | ||||||
|  | 	margin-left: auto; | ||||||
|  | 	font-size: 0.9em; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renoteTime { | ||||||
|  | 	flex-shrink: 0; | ||||||
|  | 	color: inherit; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .renoteMenu { | ||||||
|  | 	margin-right: 4px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .article { | ||||||
|  | 	display: flex; | ||||||
|  | 	padding: 28px 32px 18px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .avatar { | ||||||
|  | 	flex-shrink: 0; | ||||||
|  | 	display: block !important; | ||||||
|  | 	margin: 0 14px 8px 0; | ||||||
|  | 	width: 58px; | ||||||
|  | 	height: 58px; | ||||||
|  | 	position: sticky !important; | ||||||
|  | 	top: calc(22px + var(--stickyTop, 0px)); | ||||||
|  | 	left: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .main { | ||||||
|  | 	flex: 1; | ||||||
|  | 	min-width: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .cw { | ||||||
|  | 	cursor: default; | ||||||
|  | 	display: block; | ||||||
|  | 	margin: 0; | ||||||
|  | 	padding: 0; | ||||||
|  | 	overflow-wrap: break-word; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .showLess { | ||||||
|  | 	width: 100%; | ||||||
|  | 	margin-top: 1em; | ||||||
|  | 	position: sticky; | ||||||
|  | 	bottom: 1em; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .howLessLabel { | ||||||
|  | 	display: inline-block; | ||||||
|  | 	background: var(--popup); | ||||||
|  | 	padding: 6px 10px; | ||||||
|  | 	font-size: 0.8em; | ||||||
|  | 	border-radius: 999px; | ||||||
|  | 	box-shadow: 0 2px 6px rgb(0 0 0 / 20%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .contentCollapsed { | ||||||
|  | 	position: relative; | ||||||
|  | 	max-height: 9em; | ||||||
|  | 	overflow: clip; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .collapsed { | ||||||
|  | 	display: block; | ||||||
|  | 	position: absolute; | ||||||
|  | 	bottom: 0; | ||||||
|  | 	left: 0; | ||||||
|  | 	width: 100%; | ||||||
|  | 	height: 64px; | ||||||
|  | 	background: linear-gradient(0deg, var(--panel), var(--X15)); | ||||||
|  |  | ||||||
|  | 	&:hover > .collapsedLabel { | ||||||
|  | 		background: var(--panelHighlight); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .collapsedLabel { | ||||||
|  | 	display: inline-block; | ||||||
|  | 	background: var(--panel); | ||||||
|  | 	padding: 6px 10px; | ||||||
|  | 	font-size: 0.8em; | ||||||
|  | 	border-radius: 999px; | ||||||
|  | 	box-shadow: 0 2px 6px rgb(0 0 0 / 20%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .text { | ||||||
|  | 	overflow-wrap: break-word; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .replyIcon { | ||||||
|  | 	color: var(--accent); | ||||||
|  | 	margin-right: 0.5em; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .translation { | ||||||
|  | 	border: solid 0.5px var(--divider); | ||||||
|  | 	border-radius: var(--radius); | ||||||
|  | 	padding: 12px; | ||||||
|  | 	margin-top: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .urlPreview { | ||||||
|  | 	margin-top: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .poll { | ||||||
|  | 	font-size: 80%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .quote { | ||||||
|  | 	padding: 8px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .quoteNote { | ||||||
|  | 	padding: 16px; | ||||||
|  | 	border: dashed 1px var(--renote); | ||||||
|  | 	border-radius: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .channel { | ||||||
|  | 	opacity: 0.7; | ||||||
|  | 	font-size: 80%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .footerButton { | ||||||
|  | 	margin: 0; | ||||||
|  | 	padding: 8px; | ||||||
|  | 	opacity: 0.7; | ||||||
|  |  | ||||||
|  | 	&:not(:last-child) { | ||||||
|  | 		margin-right: 28px; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&:hover { | ||||||
|  | 		color: var(--fgHighlighted); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .footerButtonCount { | ||||||
|  | 	display: inline; | ||||||
|  | 	margin: 0 0 0 8px; | ||||||
|  | 	opacity: 0.7; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @container (max-width: 500px) { | ||||||
|  | 	.root { | ||||||
|  | 		font-size: 0.9em; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	.avatar { | ||||||
|  | 		width: 50px; | ||||||
|  | 		height: 50px; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (max-width: 450px) { | @container (max-width: 450px) { | ||||||
| 	.tkcbzcuz { | 	.renote { | ||||||
| 		> .renote { | 		padding: 8px 16px 0 16px; | ||||||
| 			padding: 8px 16px 0 16px; | 	} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .info { | 	.tip { | ||||||
| 			padding: 8px 16px 0 16px; | 		padding: 8px 16px 0 16px; | ||||||
| 		} | 	} | ||||||
|  |  | ||||||
| 		> .article { | 	.article { | ||||||
| 			padding: 14px 16px 9px; | 		padding: 14px 16px 9px; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 			> .avatar { | 	.avatar { | ||||||
| 				margin: 0 10px 8px 0; | 		margin: 0 10px 8px 0; | ||||||
| 				width: 46px; | 		width: 46px; | ||||||
| 				height: 46px; | 		height: 46px; | ||||||
| 				top: calc(14px + var(--stickyTop, 0px)); | 		top: calc(14px + var(--stickyTop, 0px)); | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (max-width: 350px) { | @container (max-width: 350px) { | ||||||
| 	.tkcbzcuz { | 	.footerButton { | ||||||
| 		> .article { | 		&:not(:last-child) { | ||||||
| 			> .main { | 			margin-right: 18px; | ||||||
| 				> .footer { |  | ||||||
| 					> .button { |  | ||||||
| 						&:not(:last-child) { |  | ||||||
| 							margin-right: 18px; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (max-width: 300px) { | @container (max-width: 300px) { | ||||||
| 	.tkcbzcuz { | 	.avatar { | ||||||
| 		> .article { | 		width: 44px; | ||||||
| 			> .avatar { | 		height: 44px; | ||||||
| 				width: 44px; | 	} | ||||||
| 				height: 44px; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			> .main { | 	.footerButton { | ||||||
| 				> .footer { | 		&:not(:last-child) { | ||||||
| 					> .button { | 			margin-right: 12px; | ||||||
| 						&:not(:last-child) { |  | ||||||
| 							margin-right: 12px; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,12 +25,12 @@ | |||||||
| 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i> | 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i> | ||||||
| 				<MkTime :time="note.createdAt"/> | 				<MkTime :time="note.createdAt"/> | ||||||
| 			</button> | 			</button> | ||||||
| 			<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]"> | 			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> | ||||||
| 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i> | 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i> | ||||||
| 				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i> | 				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i> | ||||||
| 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | ||||||
| 			</span> | 			</span> | ||||||
| 			<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<article class="article" @contextmenu.stop="onContextmenu"> | 	<article class="article" @contextmenu.stop="onContextmenu"> | ||||||
| @@ -43,12 +43,12 @@ | |||||||
| 					</MkA> | 					</MkA> | ||||||
| 					<span v-if="appearNote.user.isBot" class="is-bot">bot</span> | 					<span v-if="appearNote.user.isBot" class="is-bot">bot</span> | ||||||
| 					<div class="info"> | 					<div class="info"> | ||||||
| 						<span v-if="appearNote.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[appearNote.visibility]"> | 						<span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]"> | ||||||
| 							<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i> | 							<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i> | ||||||
| 							<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock-open"></i> | 							<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i> | ||||||
| 							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | 							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | ||||||
| 						</span> | 						</span> | ||||||
| 						<span v-if="appearNote.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | 						<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="username"><MkAcct :user="appearNote.user"/></div> | 				<div class="username"><MkAcct :user="appearNote.user"/></div> | ||||||
|   | |||||||
| @@ -1,20 +1,20 @@ | |||||||
| <template> | <template> | ||||||
| <header class="kkwtjztg"> | <header :class="$style.root"> | ||||||
| 	<MkA v-once v-user-preview="note.user.id" class="name" :to="userPage(note.user)"> | 	<MkA v-once v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> | ||||||
| 		<MkUserName :user="note.user"/> | 		<MkUserName :user="note.user"/> | ||||||
| 	</MkA> | 	</MkA> | ||||||
| 	<div v-if="note.user.isBot" class="is-bot">bot</div> | 	<div v-if="note.user.isBot" :class="$style.isBot">bot</div> | ||||||
| 	<div class="username"><MkAcct :user="note.user"/></div> | 	<div :class="$style.username"><MkAcct :user="note.user"/></div> | ||||||
| 	<div class="info"> | 	<div :class="$style.info"> | ||||||
| 		<MkA class="created-at" :to="notePage(note)"> | 		<MkA :to="notePage(note)"> | ||||||
| 			<MkTime :time="note.createdAt"/> | 			<MkTime :time="note.createdAt"/> | ||||||
| 		</MkA> | 		</MkA> | ||||||
| 		<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]"> | 		<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> | ||||||
| 			<i v-if="note.visibility === 'home'" class="ti ti-home"></i> | 			<i v-if="note.visibility === 'home'" class="ti ti-home"></i> | ||||||
| 			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i> | 			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i> | ||||||
| 			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | 			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> | ||||||
| 		</span> | 		</span> | ||||||
| 		<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | 		<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span> | ||||||
| 	</div> | 	</div> | ||||||
| </header> | </header> | ||||||
| </template> | </template> | ||||||
| @@ -32,49 +32,49 @@ defineProps<{ | |||||||
| }>(); | }>(); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .kkwtjztg { | .root { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	align-items: baseline; | 	align-items: baseline; | ||||||
| 	white-space: nowrap; | 	white-space: nowrap; | ||||||
|  | } | ||||||
|  |  | ||||||
| 	> .name { | .name { | ||||||
| 		flex-shrink: 1; | 	flex-shrink: 1; | ||||||
| 		display: block; | 	display: block; | ||||||
| 		margin: 0 .5em 0 0; | 	margin: 0 .5em 0 0; | ||||||
| 		padding: 0; | 	padding: 0; | ||||||
| 		overflow: hidden; | 	overflow: hidden; | ||||||
| 		font-size: 1em; | 	font-size: 1em; | ||||||
| 		font-weight: bold; | 	font-weight: bold; | ||||||
| 		text-decoration: none; | 	text-decoration: none; | ||||||
| 		text-overflow: ellipsis; | 	text-overflow: ellipsis; | ||||||
|  |  | ||||||
| 		&:hover { | 	&:hover { | ||||||
| 			text-decoration: underline; | 		text-decoration: underline; | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .is-bot { |  | ||||||
| 		flex-shrink: 0; |  | ||||||
| 		align-self: center; |  | ||||||
| 		margin: 0 .5em 0 0; |  | ||||||
| 		padding: 1px 6px; |  | ||||||
| 		font-size: 80%; |  | ||||||
| 		border: solid 0.5px var(--divider); |  | ||||||
| 		border-radius: 3px; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .username { |  | ||||||
| 		flex-shrink: 9999999; |  | ||||||
| 		margin: 0 .5em 0 0; |  | ||||||
| 		overflow: hidden; |  | ||||||
| 		text-overflow: ellipsis; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .info { |  | ||||||
| 		flex-shrink: 0; |  | ||||||
| 		margin-left: auto; |  | ||||||
| 		font-size: 0.9em; |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .isBot { | ||||||
|  | 	flex-shrink: 0; | ||||||
|  | 	align-self: center; | ||||||
|  | 	margin: 0 .5em 0 0; | ||||||
|  | 	padding: 1px 6px; | ||||||
|  | 	font-size: 80%; | ||||||
|  | 	border: solid 0.5px var(--divider); | ||||||
|  | 	border-radius: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .username { | ||||||
|  | 	flex-shrink: 9999999; | ||||||
|  | 	margin: 0 .5em 0 0; | ||||||
|  | 	overflow: hidden; | ||||||
|  | 	text-overflow: ellipsis; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .info { | ||||||
|  | 	flex-shrink: 0; | ||||||
|  | 	margin-left: auto; | ||||||
|  | 	font-size: 0.9em; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,15 +1,15 @@ | |||||||
| <template> | <template> | ||||||
| <div class="yohlumlk"> | <div :class="$style.root"> | ||||||
| 	<MkAvatar class="avatar" :user="note.user"/> | 	<MkAvatar :class="$style.avatar" :user="note.user"/> | ||||||
| 	<div class="main"> | 	<div :class="$style.main"> | ||||||
| 		<MkNoteHeader class="header" :note="note" :mini="true"/> | 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> | ||||||
| 		<div class="body"> | 		<div> | ||||||
| 			<p v-if="note.cw != null" class="cw"> | 			<p v-if="note.cw != null" :class="$style.cw"> | ||||||
| 				<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/> | 				<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/> | ||||||
| 				<MkCwButton v-model="showContent" :note="note"/> | 				<MkCwButton v-model="showContent" :note="note"/> | ||||||
| 			</p> | 			</p> | ||||||
| 			<div v-show="note.cw == null || showContent" class="content"> | 			<div v-show="note.cw == null || showContent"> | ||||||
| 				<MkSubNoteContent class="text" :note="note"/> | 				<MkSubNoteContent :class="$style.text" :note="note"/> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| @@ -31,73 +31,60 @@ const props = defineProps<{ | |||||||
| const showContent = $ref(false); | const showContent = $ref(false); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .yohlumlk { | .root { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	margin: 0; | 	margin: 0; | ||||||
| 	padding: 0; | 	padding: 0; | ||||||
| 	overflow: clip; | 	overflow: clip; | ||||||
| 	font-size: 0.95em; | 	font-size: 0.95em; | ||||||
|  | } | ||||||
|  |  | ||||||
| 	> .avatar { | .avatar { | ||||||
| 		flex-shrink: 0; | 	flex-shrink: 0; | ||||||
| 		display: block; | 	display: block; | ||||||
| 		margin: 0 10px 0 0; | 	margin: 0 10px 0 0; | ||||||
| 		width: 40px; | 	width: 40px; | ||||||
| 		height: 40px; | 	height: 40px; | ||||||
| 		border-radius: 8px; | 	border-radius: 8px; | ||||||
| 	} | } | ||||||
|  |  | ||||||
| 	> .main { | .main { | ||||||
| 		flex: 1; | 	flex: 1; | ||||||
| 		min-width: 0; | 	min-width: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| 		> .header { | .header { | ||||||
| 			margin-bottom: 2px; | 	margin-bottom: 2px; | ||||||
| 		} | } | ||||||
|  |  | ||||||
| 		> .body { | .cw { | ||||||
|  | 	cursor: default; | ||||||
|  | 	display: block; | ||||||
|  | 	margin: 0; | ||||||
|  | 	padding: 0; | ||||||
|  | 	overflow-wrap: break-word; | ||||||
|  | } | ||||||
|  |  | ||||||
| 			> .cw { | .text { | ||||||
| 				cursor: default; | 	cursor: default; | ||||||
| 				display: block; | 	margin: 0; | ||||||
| 				margin: 0; | 	padding: 0; | ||||||
| 				padding: 0; |  | ||||||
| 				overflow-wrap: break-word; |  | ||||||
|  |  | ||||||
| 				> .text { |  | ||||||
| 					margin-right: 8px; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			> .content { |  | ||||||
| 				> .text { |  | ||||||
| 					cursor: default; |  | ||||||
| 					margin: 0; |  | ||||||
| 					padding: 0; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (min-width: 350px) { | @container (min-width: 350px) { | ||||||
| 	.yohlumlk { | 	.avatar { | ||||||
| 		> .avatar { | 		margin: 0 10px 0 0; | ||||||
| 			margin: 0 10px 0 0; | 		width: 44px; | ||||||
| 			width: 44px; | 		height: 44px; | ||||||
| 			height: 44px; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (min-width: 500px) { | @container (min-width: 500px) { | ||||||
| 	.yohlumlk { | 	.avatar { | ||||||
| 		> .avatar { | 		margin: 0 12px 0 0; | ||||||
| 			margin: 0 12px 0 0; | 		width: 48px; | ||||||
| 			width: 48px; | 		height: 48px; | ||||||
| 			height: 48px; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,25 +1,25 @@ | |||||||
| <template> | <template> | ||||||
| <div class="wrpstxzv" :class="{ children: depth > 1 }"> | <div :class="[$style.root, { [$style.children]: depth > 1 }]"> | ||||||
| 	<div class="main"> | 	<div :class="$style.main"> | ||||||
| 		<MkAvatar class="avatar" :user="note.user"/> | 		<MkAvatar :class="$style.avatar" :user="note.user"/> | ||||||
| 		<div class="body"> | 		<div :class="$style.body"> | ||||||
| 			<MkNoteHeader class="header" :note="note" :mini="true"/> | 			<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> | ||||||
| 			<div class="body"> | 			<div> | ||||||
| 				<p v-if="note.cw != null" class="cw"> | 				<p v-if="note.cw != null" :class="$style.cw"> | ||||||
| 					<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/> | 					<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/> | ||||||
| 					<MkCwButton v-model="showContent" :note="note"/> | 					<MkCwButton v-model="showContent" :note="note"/> | ||||||
| 				</p> | 				</p> | ||||||
| 				<div v-show="note.cw == null || showContent" class="content"> | 				<div v-show="note.cw == null || showContent"> | ||||||
| 					<MkSubNoteContent class="text" :note="note"/> | 					<MkSubNoteContent :class="$style.text" :note="note"/> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<template v-if="depth < 5"> | 	<template v-if="depth < 5"> | ||||||
| 		<MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> | 		<MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1"/> | ||||||
| 	</template> | 	</template> | ||||||
| 	<div v-else class="more"> | 	<div v-else :class="$style.more"> | ||||||
| 		<MkA class="text _link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA> | 		<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| @@ -57,8 +57,8 @@ if (props.detail) { | |||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .wrpstxzv { | .root { | ||||||
| 	padding: 16px 32px; | 	padding: 16px 32px; | ||||||
| 	font-size: 0.9em; | 	font-size: 0.9em; | ||||||
|  |  | ||||||
| @@ -66,62 +66,54 @@ if (props.detail) { | |||||||
| 		padding: 10px 0 0 16px; | 		padding: 10px 0 0 16px; | ||||||
| 		font-size: 1em; | 		font-size: 1em; | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| 	> .main { | .main { | ||||||
| 		display: flex; | 	display: flex; | ||||||
|  | } | ||||||
|  |  | ||||||
| 		> .avatar { | .avatar { | ||||||
| 			flex-shrink: 0; | 	flex-shrink: 0; | ||||||
| 			display: block; | 	display: block; | ||||||
| 			margin: 0 8px 0 0; | 	margin: 0 8px 0 0; | ||||||
| 			width: 38px; | 	width: 38px; | ||||||
| 			height: 38px; | 	height: 38px; | ||||||
| 			border-radius: 8px; | 	border-radius: 8px; | ||||||
| 		} | } | ||||||
|  |  | ||||||
| 		> .body { | .body { | ||||||
| 			flex: 1; | 	flex: 1; | ||||||
| 			min-width: 0; | 	min-width: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| 			> .header { | .header { | ||||||
| 				margin-bottom: 2px; | 	margin-bottom: 2px; | ||||||
| 			} | } | ||||||
|  |  | ||||||
| 			> .body { | .cw { | ||||||
| 				> .cw { | 	cursor: default; | ||||||
| 					cursor: default; | 	display: block; | ||||||
| 					display: block; | 	margin: 0; | ||||||
| 					margin: 0; | 	padding: 0; | ||||||
| 					padding: 0; | 	overflow-wrap: break-word; | ||||||
| 					overflow-wrap: break-word; | } | ||||||
|  |  | ||||||
| 					> .text { | .text { | ||||||
| 						margin-right: 8px; | 	margin: 0; | ||||||
| 					} | 	padding: 0; | ||||||
| 				} | } | ||||||
|  |  | ||||||
| 				> .content { | .reply, .more { | ||||||
| 					> .text { | 	border-left: solid 0.5px var(--divider); | ||||||
| 						margin: 0; | 	margin-top: 10px; | ||||||
| 						padding: 0; | } | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .reply, > .more { | .more { | ||||||
| 		border-left: solid 0.5px var(--divider); | 	padding: 10px 0 0 16px; | ||||||
| 		margin-top: 10px; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .more { |  | ||||||
| 		padding: 10px 0 0 16px; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (max-width: 450px) { | @container (max-width: 450px) { | ||||||
| 	.wrpstxzv { | 	.root { | ||||||
| 		padding: 14px 16px; | 		padding: 14px 16px; | ||||||
|  |  | ||||||
| 		&.children { | 		&.children { | ||||||
|   | |||||||
| @@ -8,10 +8,10 @@ | |||||||
| 	</template> | 	</template> | ||||||
|  |  | ||||||
| 	<template #default="{ items: notes }"> | 	<template #default="{ items: notes }"> | ||||||
| 		<div class="giivymft" :class="{ noGap }"> | 		<div :class="[$style.root, { [$style.noGap]: noGap }]"> | ||||||
| 			<XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes"> | 			<MkDateSeparatedList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" :class="$style.notes"> | ||||||
| 				<XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/> | 				<XNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/> | ||||||
| 			</XList> | 			</MkDateSeparatedList> | ||||||
| 		</div> | 		</div> | ||||||
| 	</template> | 	</template> | ||||||
| </MkPagination> | </MkPagination> | ||||||
| @@ -20,7 +20,7 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { shallowRef } from 'vue'; | import { shallowRef } from 'vue'; | ||||||
| import XNote from '@/components/MkNote.vue'; | import XNote from '@/components/MkNote.vue'; | ||||||
| import XList from '@/components/MkDateSeparatedList.vue'; | import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; | ||||||
| import MkPagination, { Paging } from '@/components/MkPagination.vue'; | import MkPagination, { Paging } from '@/components/MkPagination.vue'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
|  |  | ||||||
| @@ -36,8 +36,8 @@ defineExpose({ | |||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" module> | ||||||
| .giivymft { | .root { | ||||||
| 	&.noGap { | 	&.noGap { | ||||||
| 		> .notes { | 		> .notes { | ||||||
| 			background: var(--panel); | 			background: var(--panel); | ||||||
| @@ -48,7 +48,7 @@ defineExpose({ | |||||||
| 		> .notes { | 		> .notes { | ||||||
| 			background: var(--bg); | 			background: var(--bg); | ||||||
|  |  | ||||||
| 			.qtqtichx { | 			.note { | ||||||
| 				background: var(--panel); | 				background: var(--panel); | ||||||
| 				border-radius: var(--radius); | 				border-radius: var(--radius); | ||||||
| 			} | 			} | ||||||
|   | |||||||