Compare commits
	
		
			72 Commits
		
	
	
		
			13.0.0-bet
			...
			13.0.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					90e2186872 | ||
| 
						 | 
					3043b2f619 | ||
| 
						 | 
					d2fc5a248b | ||
| 
						 | 
					e6d666e1ee | ||
| 
						 | 
					c5cfbd99d0 | ||
| 
						 | 
					33b22a323c | ||
| 
						 | 
					f032fb628a | ||
| 
						 | 
					7761eb8897 | ||
| 
						 | 
					58fa8c4a01 | ||
| 
						 | 
					789d61d175 | ||
| 
						 | 
					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 | 
							
								
								
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -40,6 +40,8 @@ You should also include the user name that made the change.
 | 
			
		||||
- Firefox109以下はサポートされなくなりました
 | 
			
		||||
 | 
			
		||||
#### For app developers
 | 
			
		||||
- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました
 | 
			
		||||
	- カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします
 | 
			
		||||
- API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
 | 
			
		||||
	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
 | 
			
		||||
	- e.g. `https://p1.a9z.dev/emoji/misskey.webp`
 | 
			
		||||
@@ -59,12 +61,14 @@ You should also include the user name that made the change.
 | 
			
		||||
- Server: signToActivityPubGet is set to true by default @syuilo
 | 
			
		||||
- Server: improve syslog performance @syuilo
 | 
			
		||||
- Server: improve note scoring for featured notes @CyberRex0
 | 
			
		||||
- Server: アンケート選択肢の文字数制限を緩和 @syuilo
 | 
			
		||||
- Server: improve stats api performance @syuilo
 | 
			
		||||
- Server: improve nodeinfo performance @syuilo
 | 
			
		||||
- Server: delete outdated notifications regularly to improve db performance @syuilo
 | 
			
		||||
- Server: delete outdated hard-mutes regularly to improve db performance @syuilo
 | 
			
		||||
- Server: delete outdated notes of antenna regularly to improve db performance @syuilo
 | 
			
		||||
- Server: improve activitypub deliver performance @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 link to user RSS feed in profile menu @ssmucny
 | 
			
		||||
- Client: Compress non-animated PNG files @saschanaz
 | 
			
		||||
@@ -72,15 +76,18 @@ You should also include the user name that made the change.
 | 
			
		||||
- Client: enhance dashboard of control panel @syuilo
 | 
			
		||||
- Client: Vite is upgraded to v4 @syuilo, @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 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: OpenSearch support @SoniEx2 @chaoticryptidz
 | 
			
		||||
- 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 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: introduce fluent emoji @syuilo
 | 
			
		||||
- Client: add new theme @syuilo
 | 
			
		||||
@@ -100,8 +107,12 @@ You should also include the user name that made the change.
 | 
			
		||||
- Server: pages/likeのエラーIDが重複しているのを修正 @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: 画面の幅が狭いとウィジェットドロワーを閉じる手段がなくなるのを修正 @syuilo
 | 
			
		||||
- Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
 | 
			
		||||
- Client: use proxied image for instance icon @syuilo
 | 
			
		||||
- Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM node:18.12.1-bullseye AS builder
 | 
			
		||||
FROM node:18.13.0-bullseye AS builder
 | 
			
		||||
 | 
			
		||||
ARG NODE_ENV=production
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +22,7 @@ COPY . ./
 | 
			
		||||
RUN git submodule update --init
 | 
			
		||||
RUN yarn build
 | 
			
		||||
 | 
			
		||||
FROM node:18.12.1-bullseye-slim AS runner
 | 
			
		||||
FROM node:18.13.0-bullseye-slim AS runner
 | 
			
		||||
 | 
			
		||||
WORKDIR /misskey
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1117,6 +1117,8 @@ _weekday:
 | 
			
		||||
  friday: "الجمعة"
 | 
			
		||||
  saturday: "السبت"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "الملف التعريفي"
 | 
			
		||||
  instanceInfo: "معلومات مثيل الخادم"
 | 
			
		||||
  memo: "ملاحظة لاصقة"
 | 
			
		||||
  notifications: "الإشعارات"
 | 
			
		||||
  timeline: "الخيط الزمني"
 | 
			
		||||
@@ -1294,7 +1296,6 @@ _notification:
 | 
			
		||||
  youGotReply: "ردّ عليك {name}"
 | 
			
		||||
  youGotQuote: "اقتبس منك {name}"
 | 
			
		||||
  youRenoted: "إعادت نشر من {name}"
 | 
			
		||||
  youGotPoll: "شارك {name} في استطلاع الرأي"
 | 
			
		||||
  youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
 | 
			
		||||
  youWereFollowed: "يتابعك"
 | 
			
		||||
@@ -1311,7 +1312,6 @@ _notification:
 | 
			
		||||
    renote: "أعد النشر"
 | 
			
		||||
    quote: "الاقتباسات"
 | 
			
		||||
    reaction: "التفاعلات"
 | 
			
		||||
    pollVote: "مصوِت شارك في الاستطلاع"
 | 
			
		||||
    receiveFollowRequest: "طلبات المتابعة المتلقاة"
 | 
			
		||||
    followRequestAccepted: "طلبات المتابعة المقبولة"
 | 
			
		||||
    groupInvited: "دعوات الفريق"
 | 
			
		||||
 
 | 
			
		||||
@@ -1200,6 +1200,8 @@ _weekday:
 | 
			
		||||
  friday: "শুক্রবার"
 | 
			
		||||
  saturday: "শনিবার"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "প্রোফাইল"
 | 
			
		||||
  instanceInfo: "ইন্সট্যান্সের তথ্য"
 | 
			
		||||
  memo: "স্টিকি নোট"
 | 
			
		||||
  notifications: "বিজ্ঞপ্তি"
 | 
			
		||||
  timeline: "টাইমলাইন"
 | 
			
		||||
@@ -1386,7 +1388,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} আপনাকে জবাব দিয়েছে"
 | 
			
		||||
  youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে"
 | 
			
		||||
  youRenoted: "{name} এর Renote"
 | 
			
		||||
  youGotPoll: "{name} আপনার পোলে ভোট দিয়েছে"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে"
 | 
			
		||||
  youWereFollowed: "আপনাকে অনুসরণ করছে"
 | 
			
		||||
@@ -1403,7 +1404,6 @@ _notification:
 | 
			
		||||
    renote: "রিনোট"
 | 
			
		||||
    quote: "উদ্ধৃতি"
 | 
			
		||||
    reaction: "প্রতিক্রিয়া"
 | 
			
		||||
    pollVote: "পোলে ভোট আছে"
 | 
			
		||||
    pollEnded: "পোল শেষ"
 | 
			
		||||
    receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
 | 
			
		||||
    followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
 | 
			
		||||
 
 | 
			
		||||
@@ -399,6 +399,8 @@ _antennaSources:
 | 
			
		||||
  userList: "Publicacions d'una llista d'usuaris"
 | 
			
		||||
  userGroup: "Publicacions d'usuaris d'un grup"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Perfil"
 | 
			
		||||
  instanceInfo: "Informació del fitxer d'instal·lació"
 | 
			
		||||
  notifications: "Notificacions"
 | 
			
		||||
  timeline: "Línia de temps"
 | 
			
		||||
  activity: "Activitat"
 | 
			
		||||
 
 | 
			
		||||
@@ -694,6 +694,8 @@ _weekday:
 | 
			
		||||
  friday: "Pátek"
 | 
			
		||||
  saturday: "Sobota"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Váš profil"
 | 
			
		||||
  instanceInfo: "Informace o instanci"
 | 
			
		||||
  notifications: "Oznámení"
 | 
			
		||||
  timeline: "Časová osa"
 | 
			
		||||
  calendar: "Kalendář"
 | 
			
		||||
 
 | 
			
		||||
@@ -1302,6 +1302,8 @@ _weekday:
 | 
			
		||||
  friday: "Freitag"
 | 
			
		||||
  saturday: "Samstag"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Instanzinformationen"
 | 
			
		||||
  memo: "Merkzettel"
 | 
			
		||||
  notifications: "Benachrichtigungen"
 | 
			
		||||
  timeline: "Chronik"
 | 
			
		||||
@@ -1328,6 +1330,7 @@ _widgets:
 | 
			
		||||
  userList: "Benutzerliste"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "Liste auswählen"
 | 
			
		||||
  clicker: "Klickzähler"
 | 
			
		||||
_cw:
 | 
			
		||||
  hide: "Inhalt verbergen"
 | 
			
		||||
  show: "Inhalt anzeigen"
 | 
			
		||||
@@ -1503,7 +1506,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} hat dir geantwortet"
 | 
			
		||||
  youGotQuote: "{name} hat dich zitiert"
 | 
			
		||||
  youRenoted: "Renote deiner Notiz von {name}"
 | 
			
		||||
  youGotPoll: "{name} hat in deiner Umfrage abgestimmt"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
 | 
			
		||||
  youWereFollowed: "ist dir gefolgt"
 | 
			
		||||
@@ -1521,7 +1523,6 @@ _notification:
 | 
			
		||||
    renote: "Renotes"
 | 
			
		||||
    quote: "Zitationen"
 | 
			
		||||
    reaction: "Reaktionen"
 | 
			
		||||
    pollVote: "Antworten auf Umfragen"
 | 
			
		||||
    pollEnded: "Ende von Umfragen"
 | 
			
		||||
    receiveFollowRequest: "Erhaltene Follow-Anfragen"
 | 
			
		||||
    followRequestAccepted: "Akzeptierte Follow-Anfragen"
 | 
			
		||||
 
 | 
			
		||||
@@ -343,6 +343,8 @@ _antennaSources:
 | 
			
		||||
  userList: "Σημειώματα από καθορισμένη λίστα μελών"
 | 
			
		||||
  userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Προφίλ"
 | 
			
		||||
  instanceInfo: "Πληροφορίες του instance"
 | 
			
		||||
  notifications: "Ειδοποιήσεις"
 | 
			
		||||
  timeline: "Χρονολόγιο"
 | 
			
		||||
  calendar: "Ημερολόγιο"
 | 
			
		||||
 
 | 
			
		||||
@@ -1302,6 +1302,8 @@ _weekday:
 | 
			
		||||
  friday: "Friday"
 | 
			
		||||
  saturday: "Saturday"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profile"
 | 
			
		||||
  instanceInfo: "Instance Information"
 | 
			
		||||
  memo: "Sticky notes"
 | 
			
		||||
  notifications: "Notifications"
 | 
			
		||||
  timeline: "Timeline"
 | 
			
		||||
@@ -1328,6 +1330,7 @@ _widgets:
 | 
			
		||||
  userList: "User list"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "Select a list"
 | 
			
		||||
  clicker: "Clicker"
 | 
			
		||||
_cw:
 | 
			
		||||
  hide: "Hide"
 | 
			
		||||
  show: "Show content"
 | 
			
		||||
@@ -1503,7 +1506,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} replied to you"
 | 
			
		||||
  youGotQuote: "{name} quoted you"
 | 
			
		||||
  youRenoted: "Renote from {name}"
 | 
			
		||||
  youGotPoll: "{name} voted on your poll"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} sent you a chat message"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group"
 | 
			
		||||
  youWereFollowed: "followed you"
 | 
			
		||||
@@ -1521,7 +1523,6 @@ _notification:
 | 
			
		||||
    renote: "Renotes"
 | 
			
		||||
    quote: "Quotes"
 | 
			
		||||
    reaction: "Reactions"
 | 
			
		||||
    pollVote: "Votes on polls"
 | 
			
		||||
    pollEnded: "Polls ending"
 | 
			
		||||
    receiveFollowRequest: "Received follow requests"
 | 
			
		||||
    followRequestAccepted: "Accepted follow requests"
 | 
			
		||||
 
 | 
			
		||||
@@ -1296,6 +1296,8 @@ _weekday:
 | 
			
		||||
  friday: "Viernes"
 | 
			
		||||
  saturday: "Sábado"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Perfil"
 | 
			
		||||
  instanceInfo: "información de la instancia"
 | 
			
		||||
  memo: "Nota adhesiva"
 | 
			
		||||
  notifications: "Notificaciones"
 | 
			
		||||
  timeline: "Linea de tiempo"
 | 
			
		||||
@@ -1487,7 +1489,6 @@ _notification:
 | 
			
		||||
  youGotReply: "Respuesta de {name}"
 | 
			
		||||
  youGotQuote: "Citado por {name}"
 | 
			
		||||
  youRenoted: "Renotado por {name}"
 | 
			
		||||
  youGotPoll: "Encuestado por {name}"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
 | 
			
		||||
  youWereFollowed: "te ha seguido"
 | 
			
		||||
@@ -1505,7 +1506,6 @@ _notification:
 | 
			
		||||
    renote: "Renotar"
 | 
			
		||||
    quote: "Citar"
 | 
			
		||||
    reaction: "Reacción"
 | 
			
		||||
    pollVote: "Votado en la encuesta"
 | 
			
		||||
    pollEnded: "La encuesta terminó"
 | 
			
		||||
    receiveFollowRequest: "Recibió una solicitud de seguimiento"
 | 
			
		||||
    followRequestAccepted: "El seguimiento fue aceptado"
 | 
			
		||||
 
 | 
			
		||||
@@ -1289,6 +1289,8 @@ _weekday:
 | 
			
		||||
  friday: "Vendredi"
 | 
			
		||||
  saturday: "Samedi"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Informations sur l’instance"
 | 
			
		||||
  memo: "Note collante"
 | 
			
		||||
  notifications: "Notifications"
 | 
			
		||||
  timeline: "Fil"
 | 
			
		||||
@@ -1478,7 +1480,6 @@ _notification:
 | 
			
		||||
  youGotReply: "Réponse de {name}"
 | 
			
		||||
  youGotQuote: "Cité·e par {name}"
 | 
			
		||||
  youRenoted: "{name} vous a Renoté"
 | 
			
		||||
  youGotPoll: "{name} a participé à votre sondage"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} vous envoyé un message"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
 | 
			
		||||
  youWereFollowed: "Vous suit"
 | 
			
		||||
@@ -1496,7 +1497,6 @@ _notification:
 | 
			
		||||
    renote: "Renotes"
 | 
			
		||||
    quote: "Citations"
 | 
			
		||||
    reaction: "Réactions"
 | 
			
		||||
    pollVote: "Votes dans des sondages"
 | 
			
		||||
    pollEnded: "Sondages se cloturant"
 | 
			
		||||
    receiveFollowRequest: "Demande d'abonnement reçue"
 | 
			
		||||
    followRequestAccepted: "Demande d'abonnement acceptée"
 | 
			
		||||
 
 | 
			
		||||
@@ -1206,6 +1206,8 @@ _weekday:
 | 
			
		||||
  friday: "Jumat"
 | 
			
		||||
  saturday: "Sabtu"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Informasi Instansi"
 | 
			
		||||
  memo: "Catatan memo"
 | 
			
		||||
  notifications: "Pemberitahuan"
 | 
			
		||||
  timeline: "Linimasa"
 | 
			
		||||
@@ -1402,7 +1404,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} membalas kamu"
 | 
			
		||||
  youGotQuote: "{name} mengutip kamu"
 | 
			
		||||
  youRenoted: "{name} me-renote kamu"
 | 
			
		||||
  youGotPoll: "{name} memilih di angket kamu"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}"
 | 
			
		||||
  youWereFollowed: "Mengikuti kamu"
 | 
			
		||||
@@ -1419,7 +1420,6 @@ _notification:
 | 
			
		||||
    renote: "Renote"
 | 
			
		||||
    quote: "Kutip"
 | 
			
		||||
    reaction: "Reaksi"
 | 
			
		||||
    pollVote: "Memilih di angket"
 | 
			
		||||
    pollEnded: "Jajak pendapat berakhir"
 | 
			
		||||
    receiveFollowRequest: "Permintaan mengikuti diterima"
 | 
			
		||||
    followRequestAccepted: "Permintaan mengikuti disetujui"
 | 
			
		||||
 
 | 
			
		||||
@@ -1296,6 +1296,8 @@ _weekday:
 | 
			
		||||
  friday: "Venerdì"
 | 
			
		||||
  saturday: "Sabato"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profilo"
 | 
			
		||||
  instanceInfo: "Informazioni sull'istanza"
 | 
			
		||||
  memo: "Promemoria"
 | 
			
		||||
  notifications: "Notifiche"
 | 
			
		||||
  timeline: "Timeline"
 | 
			
		||||
@@ -1487,7 +1489,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} ti ha risposto"
 | 
			
		||||
  youGotQuote: "{name} ha citato il tuo Nota e ha detto"
 | 
			
		||||
  youRenoted: "{name} ha rinotato"
 | 
			
		||||
  youGotPoll: "{name} ha votato"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
 | 
			
		||||
  youWereFollowed: "Ha iniziato a seguirti"
 | 
			
		||||
@@ -1505,7 +1506,6 @@ _notification:
 | 
			
		||||
    renote: "Rinota"
 | 
			
		||||
    quote: "Cita"
 | 
			
		||||
    reaction: "Reazioni"
 | 
			
		||||
    pollVote: "Voti ricevuti"
 | 
			
		||||
    pollEnded: "Sondaggio chiuso."
 | 
			
		||||
    receiveFollowRequest: "Richiesta di follow ricevuta"
 | 
			
		||||
    followRequestAccepted: "Richiesta di follow accettata"
 | 
			
		||||
 
 | 
			
		||||
@@ -1335,6 +1335,8 @@ _weekday:
 | 
			
		||||
  saturday: "土曜日"
 | 
			
		||||
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "プロフィール"
 | 
			
		||||
  instanceInfo: "インスタンス情報"
 | 
			
		||||
  memo: "付箋"
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
 
 | 
			
		||||
@@ -1295,6 +1295,8 @@ _weekday:
 | 
			
		||||
  friday: "金曜日"
 | 
			
		||||
  saturday: "土曜日"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "プロフィール"
 | 
			
		||||
  instanceInfo: "インスタンス情報"
 | 
			
		||||
  memo: "付箋"
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
@@ -1485,7 +1487,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name}からのリプライ"
 | 
			
		||||
  youGotQuote: "{name}による引用"
 | 
			
		||||
  youRenoted: "{name}がRenoteしたみたいやで"
 | 
			
		||||
  youGotPoll: "{name}が投票したみたいやで"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name}からのチャットがあるで"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "{name}のチャットがあるで"
 | 
			
		||||
  youWereFollowed: "フォローされたで"
 | 
			
		||||
@@ -1503,7 +1504,6 @@ _notification:
 | 
			
		||||
    renote: "Renote"
 | 
			
		||||
    quote: "引用"
 | 
			
		||||
    reaction: "リアクション"
 | 
			
		||||
    pollVote: "アンケートに投票されたで"
 | 
			
		||||
    pollEnded: "アンケートが終了したで"
 | 
			
		||||
    receiveFollowRequest: "フォロー許可してほしいみたいやで"
 | 
			
		||||
    followRequestAccepted: "フォローが受理されたで"
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,7 @@ _sfx:
 | 
			
		||||
_permissions:
 | 
			
		||||
  "write:account": "Ẓreg talɣut n umiḍan-ik·im"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Amaɣnu"
 | 
			
		||||
  notifications: "Ilɣuyen"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "Fren tabdart"
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,7 @@ _mfm:
 | 
			
		||||
_sfx:
 | 
			
		||||
  notification: "ಅಧಿಸೂಚನೆಗಳು"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "ಪ್ರೊಫೈಲು"
 | 
			
		||||
  notifications: "ಅಧಿಸೂಚನೆಗಳು"
 | 
			
		||||
  timeline: "ಸಮಯಸಾಲು"
 | 
			
		||||
_cw:
 | 
			
		||||
 
 | 
			
		||||
@@ -1302,6 +1302,8 @@ _weekday:
 | 
			
		||||
  friday: "금요일"
 | 
			
		||||
  saturday: "토요일"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "프로필"
 | 
			
		||||
  instanceInfo: "인스턴스 정보"
 | 
			
		||||
  memo: "스티커 메모"
 | 
			
		||||
  notifications: "알림"
 | 
			
		||||
  timeline: "타임라인"
 | 
			
		||||
@@ -1328,6 +1330,7 @@ _widgets:
 | 
			
		||||
  userList: "사용자 목록"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "리스트 선택"
 | 
			
		||||
  clicker: "클리커"
 | 
			
		||||
_cw:
 | 
			
		||||
  hide: "숨기기"
 | 
			
		||||
  show: "더 보기"
 | 
			
		||||
@@ -1503,7 +1506,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name}님이 답글함"
 | 
			
		||||
  youGotQuote: "{name}님이 인용함"
 | 
			
		||||
  youRenoted: "{name}님이 Renote"
 | 
			
		||||
  youGotPoll: "{name}님이 투표함"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
 | 
			
		||||
  youWereFollowed: "새로운 팔로워가 있습니다"
 | 
			
		||||
@@ -1521,7 +1523,6 @@ _notification:
 | 
			
		||||
    renote: "리노트"
 | 
			
		||||
    quote: "인용"
 | 
			
		||||
    reaction: "리액션"
 | 
			
		||||
    pollVote: "투표 참여"
 | 
			
		||||
    pollEnded: "투표가 종료됨"
 | 
			
		||||
    receiveFollowRequest: "팔로우 요청을 받았을 때"
 | 
			
		||||
    followRequestAccepted: "팔로우 요청이 승인되었을 때"
 | 
			
		||||
 
 | 
			
		||||
@@ -440,6 +440,8 @@ _sfx:
 | 
			
		||||
  notification: "Meldingen"
 | 
			
		||||
  chat: "Chat"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profiel"
 | 
			
		||||
  instanceInfo: "Serverinformatie"
 | 
			
		||||
  notifications: "Meldingen"
 | 
			
		||||
  timeline: "Tijdlijn"
 | 
			
		||||
  activity: "Activiteit"
 | 
			
		||||
 
 | 
			
		||||
@@ -1213,6 +1213,8 @@ _weekday:
 | 
			
		||||
  friday: "Piątek"
 | 
			
		||||
  saturday: "Sobota"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Informacje o instancji"
 | 
			
		||||
  memo: "Przypięte notatki"
 | 
			
		||||
  notifications: "Powiadomienia"
 | 
			
		||||
  timeline: "Oś czasu"
 | 
			
		||||
@@ -1380,7 +1382,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} odpowiedział(a) Tobie"
 | 
			
		||||
  youGotQuote: "{name} zacytował(a) Ciebie"
 | 
			
		||||
  youRenoted: "{name} udostępnił(a) Twój wpis"
 | 
			
		||||
  youGotPoll: "{name} zagłosował(a) w Twojej ankiecie"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
 | 
			
		||||
  youWereFollowed: "Zaobserwował(a) Cię"
 | 
			
		||||
@@ -1398,7 +1399,6 @@ _notification:
 | 
			
		||||
    renote: "Udostępnij"
 | 
			
		||||
    quote: "Cytuj"
 | 
			
		||||
    reaction: "Reakcja"
 | 
			
		||||
    pollVote: "Głosy w ankietach"
 | 
			
		||||
    receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
 | 
			
		||||
    followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
 | 
			
		||||
    groupInvited: "Zaproszono do grup"
 | 
			
		||||
 
 | 
			
		||||
@@ -488,6 +488,8 @@ _sfx:
 | 
			
		||||
  notification: "Notificações"
 | 
			
		||||
  chat: "Chat"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Perfil"
 | 
			
		||||
  instanceInfo: "Informações da instância"
 | 
			
		||||
  notifications: "Notificações"
 | 
			
		||||
  timeline: "Timeline"
 | 
			
		||||
  activity: "atividade"
 | 
			
		||||
@@ -524,7 +526,6 @@ _notification:
 | 
			
		||||
  youGotMention: "{name} te mencionou"
 | 
			
		||||
  youGotReply: "{name} te respondeu"
 | 
			
		||||
  youGotQuote: "{name} te citou"
 | 
			
		||||
  youGotPoll: "{name} votou em sua enquete"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
 | 
			
		||||
  youWereFollowed: "Você tem um novo seguidor"
 | 
			
		||||
@@ -541,7 +542,6 @@ _notification:
 | 
			
		||||
    renote: "Repostar"
 | 
			
		||||
    quote: "Citar"
 | 
			
		||||
    reaction: "Reações"
 | 
			
		||||
    pollVote: "Votações em enquetes"
 | 
			
		||||
    pollEnded: "Enquetes terminando"
 | 
			
		||||
    receiveFollowRequest: "Recebeu pedidos de seguimento"
 | 
			
		||||
    followRequestAccepted: "Aceitou pedidos de seguimento"
 | 
			
		||||
 
 | 
			
		||||
@@ -667,6 +667,8 @@ _sfx:
 | 
			
		||||
  notification: "Notificări"
 | 
			
		||||
  chat: "Chat"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Informații despre instanță"
 | 
			
		||||
  notifications: "Notificări"
 | 
			
		||||
  timeline: "Cronologie"
 | 
			
		||||
  activity: "Activitate"
 | 
			
		||||
 
 | 
			
		||||
@@ -1213,6 +1213,8 @@ _weekday:
 | 
			
		||||
  friday: "Пятница"
 | 
			
		||||
  saturday: "Суббота"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Профиль"
 | 
			
		||||
  instanceInfo: "Информация об инстансе"
 | 
			
		||||
  memo: "Напоминания"
 | 
			
		||||
  notifications: "Уведомления"
 | 
			
		||||
  timeline: "Лента"
 | 
			
		||||
@@ -1399,7 +1401,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} отвечает вам."
 | 
			
		||||
  youGotQuote: "{name} цитирует вас."
 | 
			
		||||
  youRenoted: "{name} передаёт вашу заметку."
 | 
			
		||||
  youGotPoll: "{name} участвует в вашем опросе."
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} пишет вам."
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
 | 
			
		||||
  youWereFollowed: "У вас новый подписчик."
 | 
			
		||||
@@ -1414,7 +1415,6 @@ _notification:
 | 
			
		||||
    renote: "Репосты"
 | 
			
		||||
    quote: "Цитаты"
 | 
			
		||||
    reaction: "Реакции"
 | 
			
		||||
    pollVote: "Голосования"
 | 
			
		||||
    receiveFollowRequest: "Получен запрос на подписку"
 | 
			
		||||
    followRequestAccepted: "Запрос на подписку одобрен"
 | 
			
		||||
    groupInvited: "Приглашение в группы"
 | 
			
		||||
 
 | 
			
		||||
@@ -1295,6 +1295,8 @@ _weekday:
 | 
			
		||||
  friday: "Piatok"
 | 
			
		||||
  saturday: "Sobota"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Informácie o serveri"
 | 
			
		||||
  memo: "Prilepené poznámky"
 | 
			
		||||
  notifications: "Oznámenia"
 | 
			
		||||
  timeline: "Časová os"
 | 
			
		||||
@@ -1484,7 +1486,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} vám odpovedal/a"
 | 
			
		||||
  youGotQuote: "{name} vás citoval/a"
 | 
			
		||||
  youRenoted: "{name} preposlal/a vašu poznámku"
 | 
			
		||||
  youGotPoll: "{name} hlasoval/a"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} vám poslal/a správu"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}"
 | 
			
		||||
  youWereFollowed: "Máte nového sledujúceho"
 | 
			
		||||
@@ -1502,7 +1503,6 @@ _notification:
 | 
			
		||||
    renote: "Preposlať"
 | 
			
		||||
    quote: "Citovať"
 | 
			
		||||
    reaction: "Reakcie"
 | 
			
		||||
    pollVote: "Hlasy v hlasovaniach"
 | 
			
		||||
    pollEnded: "Hlasovanie skončilo"
 | 
			
		||||
    receiveFollowRequest: "Doručené žiadosti o sledovanie"
 | 
			
		||||
    followRequestAccepted: "Schválené žiadosti o sledovanie"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
---
 | 
			
		||||
_lang_: "Svenska"
 | 
			
		||||
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\")."
 | 
			
		||||
monthAndDay: "{day}/{month}"
 | 
			
		||||
search: "Sök"
 | 
			
		||||
@@ -17,7 +17,7 @@ noThankYou: "Nej tack"
 | 
			
		||||
enterUsername: "Ange användarnamn"
 | 
			
		||||
renotedBy: "Omnoterad av {user}"
 | 
			
		||||
noNotes: "Inga noteringar"
 | 
			
		||||
noNotifications: "Inga aviseringar"
 | 
			
		||||
noNotifications: "Inga notifikationer"
 | 
			
		||||
instance: "Instanser"
 | 
			
		||||
settings: "Inställningar"
 | 
			
		||||
basicSettings: "Basinställningar"
 | 
			
		||||
@@ -30,13 +30,13 @@ login: "Logga in"
 | 
			
		||||
loggingIn: "Loggar in"
 | 
			
		||||
logout: "Logga ut"
 | 
			
		||||
signup: "Registrera"
 | 
			
		||||
uploading: "Uppladdning sker..."
 | 
			
		||||
uploading: "Laddar upp..."
 | 
			
		||||
save: "Spara"
 | 
			
		||||
users: "Användare"
 | 
			
		||||
addUser: "Lägg till användare"
 | 
			
		||||
favorite: "Lägg till i favoriter"
 | 
			
		||||
favorites: "Favoriter"
 | 
			
		||||
unfavorite: "Avfavorisera"
 | 
			
		||||
unfavorite: "Ta bort från favoriter"
 | 
			
		||||
favorited: "Tillagd i favoriter."
 | 
			
		||||
alreadyFavorited: "Redan tillagd 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"
 | 
			
		||||
flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
 | 
			
		||||
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"
 | 
			
		||||
addAccount: "Lägg till konto"
 | 
			
		||||
loginFailed: "Inloggningen misslyckades"
 | 
			
		||||
@@ -253,16 +253,120 @@ explore: "Utforska"
 | 
			
		||||
messageRead: "Läs"
 | 
			
		||||
noMoreHistory: "Det finns ingen mer historik"
 | 
			
		||||
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"
 | 
			
		||||
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"
 | 
			
		||||
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"
 | 
			
		||||
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"
 | 
			
		||||
smtpUser: "Användarnamn"
 | 
			
		||||
smtpPass: "Lösenord"
 | 
			
		||||
clearCache: "Rensa cache"
 | 
			
		||||
enabled: "Aktiverad"
 | 
			
		||||
user: "Användare"
 | 
			
		||||
global: "Global"
 | 
			
		||||
squareAvatars: "Visa fyrkantiga profilbilder"
 | 
			
		||||
searchByGoogle: "Sök"
 | 
			
		||||
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:
 | 
			
		||||
  _follow:
 | 
			
		||||
    title: "följde dig"
 | 
			
		||||
@@ -271,6 +375,9 @@ _mfm:
 | 
			
		||||
  quote: "Citat"
 | 
			
		||||
  emoji: "Anpassa emoji"
 | 
			
		||||
  search: "Sök"
 | 
			
		||||
_channel:
 | 
			
		||||
  setBanner: "Välj banner"
 | 
			
		||||
  removeBanner: "Ta bort banner"
 | 
			
		||||
_theme:
 | 
			
		||||
  keys:
 | 
			
		||||
    mention: "Nämn"
 | 
			
		||||
@@ -279,9 +386,19 @@ _sfx:
 | 
			
		||||
  note: "Noter"
 | 
			
		||||
  notification: "Notifikationer"
 | 
			
		||||
  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:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  instanceInfo: "Instansinformation"
 | 
			
		||||
  notifications: "Notifikationer"
 | 
			
		||||
  timeline: "Tidslinje"
 | 
			
		||||
  activity: "Aktivitet"
 | 
			
		||||
  federation: "Federation"
 | 
			
		||||
  jobQueue: "Jobbkö"
 | 
			
		||||
  _userList:
 | 
			
		||||
@@ -289,18 +406,29 @@ _widgets:
 | 
			
		||||
_cw:
 | 
			
		||||
  show: "Ladda mer"
 | 
			
		||||
_visibility:
 | 
			
		||||
  home: "Hem"
 | 
			
		||||
  followers: "Följare"
 | 
			
		||||
_profile:
 | 
			
		||||
  username: "Användarnamn"
 | 
			
		||||
  changeAvatar: "Ändra profilbild"
 | 
			
		||||
  changeBanner: "Ändra banner"
 | 
			
		||||
_exportOrImport:
 | 
			
		||||
  allNotes: "Alla noter"
 | 
			
		||||
  followingList: "Följer"
 | 
			
		||||
  muteList: "Tysta"
 | 
			
		||||
  blockingList: "Blockera"
 | 
			
		||||
  userLists: "Listor"
 | 
			
		||||
_charts:
 | 
			
		||||
  federation: "Federation"
 | 
			
		||||
_timelines:
 | 
			
		||||
  home: "Hem"
 | 
			
		||||
  global: "Global"
 | 
			
		||||
_pages:
 | 
			
		||||
  blocks:
 | 
			
		||||
    image: "Bilder"
 | 
			
		||||
_notification:
 | 
			
		||||
  youWereFollowed: "följde dig"
 | 
			
		||||
  unreadAntennaNote: "Antenn {name}"
 | 
			
		||||
  _types:
 | 
			
		||||
    follow: "Följer"
 | 
			
		||||
    mention: "Nämn"
 | 
			
		||||
@@ -314,5 +442,6 @@ _deck:
 | 
			
		||||
  _columns:
 | 
			
		||||
    notifications: "Notifikationer"
 | 
			
		||||
    tl: "Tidslinje"
 | 
			
		||||
    antenna: "Antenner"
 | 
			
		||||
    list: "Listor"
 | 
			
		||||
    mentions: "Omnämningar"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ search: "ค้นหา"
 | 
			
		||||
notifications: "การเเจ้งเตือน"
 | 
			
		||||
username: "ชื่อผู้ใช้"
 | 
			
		||||
password: "รหัสผ่าน"
 | 
			
		||||
forgotPassword: "ลืมรหัสผ่าน?"
 | 
			
		||||
forgotPassword: "ลืมรหัสผ่านใช่ไหม"
 | 
			
		||||
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
 | 
			
		||||
ok: "โอเค"
 | 
			
		||||
gotIt: "เข้าใจแล้ว !"
 | 
			
		||||
@@ -920,6 +920,10 @@ like: "ชื่นชอบ"
 | 
			
		||||
unlike: "ไม่ชอบ"
 | 
			
		||||
numberOfLikes: "จำนวนไลค์"
 | 
			
		||||
show: "แสดงผล"
 | 
			
		||||
neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
 | 
			
		||||
remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
 | 
			
		||||
didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
 | 
			
		||||
pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
 | 
			
		||||
_sensitiveMediaDetection:
 | 
			
		||||
  description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
 | 
			
		||||
  sensitivity: "การตรวจจับความไว"
 | 
			
		||||
@@ -1298,6 +1302,8 @@ _weekday:
 | 
			
		||||
  friday: "วันศุกร์"
 | 
			
		||||
  saturday: "วันเสาร์"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "โปรไฟล์"
 | 
			
		||||
  instanceInfo: "ข้อมูล อินสแตนซ์"
 | 
			
		||||
  memo: "โน้ตแปะ"
 | 
			
		||||
  notifications: "การเเจ้งเตือน"
 | 
			
		||||
  timeline: "ไทม์ไลน์"
 | 
			
		||||
@@ -1324,6 +1330,7 @@ _widgets:
 | 
			
		||||
  userList: "รายชื่อผู้ใช้"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "เลือกรายการ"
 | 
			
		||||
  clicker: "คลิกเกอร์"
 | 
			
		||||
_cw:
 | 
			
		||||
  hide: "ซ่อน"
 | 
			
		||||
  show: "โหลดเพิ่มเติม"
 | 
			
		||||
@@ -1499,7 +1506,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} ตอบกลับถึงคุณ"
 | 
			
		||||
  youGotQuote: "{name} อ้างถึงคุณ"
 | 
			
		||||
  youRenoted: "รีโน้ตจาก {name}"
 | 
			
		||||
  youGotPoll: "{name} โหวตบนแบบสำรวจความคิดเห็นของคุณ"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม"
 | 
			
		||||
  youWereFollowed: "ได้ติดตามคุณ"
 | 
			
		||||
@@ -1517,7 +1523,6 @@ _notification:
 | 
			
		||||
    renote: "รีโน้ต"
 | 
			
		||||
    quote: "อ้างคำพูด"
 | 
			
		||||
    reaction: "รีแอคชั่น"
 | 
			
		||||
    pollVote: "จำนวนโหวตที่ได้รับ"
 | 
			
		||||
    pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
 | 
			
		||||
    receiveFollowRequest: "ได้รับคำขอติดตาม\n"
 | 
			
		||||
    followRequestAccepted: "ยอมรับคำขอติดตาม"
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ _mfm:
 | 
			
		||||
_sfx:
 | 
			
		||||
  notification: "Bildirim"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Profil"
 | 
			
		||||
  notifications: "Bildirim"
 | 
			
		||||
  timeline: "Zaman çizelgesi"
 | 
			
		||||
_profile:
 | 
			
		||||
 
 | 
			
		||||
@@ -1229,6 +1229,8 @@ _weekday:
 | 
			
		||||
  friday: "П'ятниця"
 | 
			
		||||
  saturday: "Субота"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Профіль"
 | 
			
		||||
  instanceInfo: "Про цей інстанс"
 | 
			
		||||
  memo: "Нагадування"
 | 
			
		||||
  notifications: "Сповіщення"
 | 
			
		||||
  timeline: "Стрічка"
 | 
			
		||||
@@ -1415,7 +1417,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} відповідає"
 | 
			
		||||
  youGotQuote: "{name} цитує вас"
 | 
			
		||||
  youRenoted: "{name} поширює"
 | 
			
		||||
  youGotPoll: "{name} бере участь в опитуванні"
 | 
			
		||||
  youGotMessagingMessageFromUser: "Повідомлення від {name}"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}"
 | 
			
		||||
  youWereFollowed: "Новий підписник"
 | 
			
		||||
@@ -1430,7 +1431,6 @@ _notification:
 | 
			
		||||
    renote: "Поширення"
 | 
			
		||||
    quote: "Цитування"
 | 
			
		||||
    reaction: "Реакції"
 | 
			
		||||
    pollVote: "Опитування"
 | 
			
		||||
    receiveFollowRequest: "Запити на підписку"
 | 
			
		||||
    followRequestAccepted: "Прийняті підписки"
 | 
			
		||||
    groupInvited: "Запрошення до груп"
 | 
			
		||||
 
 | 
			
		||||
@@ -1271,6 +1271,8 @@ _weekday:
 | 
			
		||||
  friday: "Thứ Sáu"
 | 
			
		||||
  saturday: "Thứ Bảy"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "Trang cá nhân"
 | 
			
		||||
  instanceInfo: "Thông tin máy chủ"
 | 
			
		||||
  memo: "Tút đã ghim"
 | 
			
		||||
  notifications: "Thông báo"
 | 
			
		||||
  timeline: "Bảng tin"
 | 
			
		||||
@@ -1460,7 +1462,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name} trả lời 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"
 | 
			
		||||
  youGotPoll: "{name} bình chọn tút của bạn"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
 | 
			
		||||
  youWereFollowed: "đã theo dõi bạn"
 | 
			
		||||
@@ -1477,7 +1478,6 @@ _notification:
 | 
			
		||||
    renote: "Đăng lại"
 | 
			
		||||
    quote: "Trích dẫn"
 | 
			
		||||
    reaction: "Biểu cảm"
 | 
			
		||||
    pollVote: "Lượt bình chọn"
 | 
			
		||||
    pollEnded: "Bình chọn kết thúc"
 | 
			
		||||
    receiveFollowRequest: "Yêu cầu theo dõi"
 | 
			
		||||
    followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
 | 
			
		||||
 
 | 
			
		||||
@@ -920,6 +920,10 @@ like: "点赞!"
 | 
			
		||||
unlike: "取消赞"
 | 
			
		||||
numberOfLikes: "点赞数"
 | 
			
		||||
show: "显示"
 | 
			
		||||
neverShow: "不再显示"
 | 
			
		||||
remindMeLater: "稍后提醒我"
 | 
			
		||||
didYouLikeMisskey: "您喜欢Misskey吗?"
 | 
			
		||||
pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发,请在有余力的情况下进行捐助!"
 | 
			
		||||
_sensitiveMediaDetection:
 | 
			
		||||
  description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
 | 
			
		||||
  sensitivity: "检测敏感度"
 | 
			
		||||
@@ -1298,6 +1302,8 @@ _weekday:
 | 
			
		||||
  friday: "星期五"
 | 
			
		||||
  saturday: "星期六"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "个人资料"
 | 
			
		||||
  instanceInfo: "实例信息"
 | 
			
		||||
  memo: "便签"
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
  timeline: "时间线"
 | 
			
		||||
@@ -1324,6 +1330,7 @@ _widgets:
 | 
			
		||||
  userList: "用户列表"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "选择列表"
 | 
			
		||||
  clicker: "点击器"
 | 
			
		||||
_cw:
 | 
			
		||||
  hide: "隐藏"
 | 
			
		||||
  show: "查看更多"
 | 
			
		||||
@@ -1499,7 +1506,6 @@ _notification:
 | 
			
		||||
  youGotReply: "来自{name}的回复"
 | 
			
		||||
  youGotQuote: "来自{name}的引用"
 | 
			
		||||
  youRenoted: "来自{name}的转发"
 | 
			
		||||
  youGotPoll: "来自{name}的投票"
 | 
			
		||||
  youGotMessagingMessageFromUser: "来自{name}的聊天"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "来自{name}的群聊"
 | 
			
		||||
  youWereFollowed: "关注了你。"
 | 
			
		||||
@@ -1517,7 +1523,6 @@ _notification:
 | 
			
		||||
    renote: "转发"
 | 
			
		||||
    quote: "引用"
 | 
			
		||||
    reaction: "回应"
 | 
			
		||||
    pollVote: "问卷调查被投票"
 | 
			
		||||
    pollEnded: "问卷调查结束"
 | 
			
		||||
    receiveFollowRequest: "收到关注请求"
 | 
			
		||||
    followRequestAccepted: "关注请求已通过"
 | 
			
		||||
 
 | 
			
		||||
@@ -252,7 +252,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
 | 
			
		||||
explore: "探索"
 | 
			
		||||
messageRead: "已讀"
 | 
			
		||||
noMoreHistory: "沒有更多歷史紀錄"
 | 
			
		||||
startMessaging: "開始傳送訊息"
 | 
			
		||||
startMessaging: "開始聊天"
 | 
			
		||||
nUsersRead: "{n}人已讀"
 | 
			
		||||
agreeTo: "我同意{0}"
 | 
			
		||||
tos: "使用條款"
 | 
			
		||||
@@ -797,7 +797,7 @@ squareAvatars: "頭像以方形顯示"
 | 
			
		||||
sent: "發送"
 | 
			
		||||
received: "收取"
 | 
			
		||||
searchResult: "搜尋結果"
 | 
			
		||||
hashtags: "#tag"
 | 
			
		||||
hashtags: "標籤"
 | 
			
		||||
troubleshooting: "故障排除"
 | 
			
		||||
useBlurEffect: "在 UI 上使用模糊效果"
 | 
			
		||||
learnMore: "更多資訊"
 | 
			
		||||
@@ -923,6 +923,7 @@ show: "檢視"
 | 
			
		||||
neverShow: "不再顯示"
 | 
			
		||||
remindMeLater: "以後再說"
 | 
			
		||||
didYouLikeMisskey: "您是否喜愛Misskey呢?"
 | 
			
		||||
pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!"
 | 
			
		||||
_sensitiveMediaDetection:
 | 
			
		||||
  description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
 | 
			
		||||
  sensitivity: "檢測敏感度"
 | 
			
		||||
@@ -1158,7 +1159,7 @@ _theme:
 | 
			
		||||
    navActive: "側邊欄文本 (活動)"
 | 
			
		||||
    navIndicator: "側邊欄指示符"
 | 
			
		||||
    link: "鏈接"
 | 
			
		||||
    hashtag: "#tag"
 | 
			
		||||
    hashtag: "標籤"
 | 
			
		||||
    mention: "提到"
 | 
			
		||||
    mentionMe: "提到了我"
 | 
			
		||||
    renote: "轉發貼文"
 | 
			
		||||
@@ -1191,7 +1192,7 @@ _sfx:
 | 
			
		||||
  note: "貼文"
 | 
			
		||||
  noteMy: "我的貼文"
 | 
			
		||||
  notification: "通知"
 | 
			
		||||
  chat: "傳送訊息"
 | 
			
		||||
  chat: "聊天"
 | 
			
		||||
  chatBg: "聊天背景"
 | 
			
		||||
  antenna: "天線接收"
 | 
			
		||||
  channel: "頻道通知"
 | 
			
		||||
@@ -1301,6 +1302,8 @@ _weekday:
 | 
			
		||||
  friday: "週五"
 | 
			
		||||
  saturday: "週六"
 | 
			
		||||
_widgets:
 | 
			
		||||
  profile: "個人檔案"
 | 
			
		||||
  instanceInfo: "實例資訊"
 | 
			
		||||
  memo: "備忘錄"
 | 
			
		||||
  notifications: "通知"
 | 
			
		||||
  timeline: "時間軸"
 | 
			
		||||
@@ -1327,6 +1330,7 @@ _widgets:
 | 
			
		||||
  userList: "使用者列表"
 | 
			
		||||
  _userList:
 | 
			
		||||
    chooseList: "選擇清單"
 | 
			
		||||
  clicker: "點擊器"
 | 
			
		||||
_cw:
 | 
			
		||||
  hide: "隱藏"
 | 
			
		||||
  show: "瀏覽更多"
 | 
			
		||||
@@ -1502,7 +1506,6 @@ _notification:
 | 
			
		||||
  youGotReply: "{name}回覆了您"
 | 
			
		||||
  youGotQuote: "{name}引用了您"
 | 
			
		||||
  youRenoted: "{name} 轉發了你的貼文"
 | 
			
		||||
  youGotPoll: "{name}已投票"
 | 
			
		||||
  youGotMessagingMessageFromUser: "{name}發送給您的訊息"
 | 
			
		||||
  youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
 | 
			
		||||
  youWereFollowed: "您有新的追隨者"
 | 
			
		||||
@@ -1520,7 +1523,6 @@ _notification:
 | 
			
		||||
    renote: "轉發貼文"
 | 
			
		||||
    quote: "引用"
 | 
			
		||||
    reaction: "反應"
 | 
			
		||||
    pollVote: "統計已投票數"
 | 
			
		||||
    pollEnded: "問卷調查結束"
 | 
			
		||||
    receiveFollowRequest: "已收到追隨請求"
 | 
			
		||||
    followRequestAccepted: "追隨請求已接受"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"version": "13.0.0-beta.32",
 | 
			
		||||
	"version": "13.0.0-beta.39",
 | 
			
		||||
	"codename": "indigo",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/backend/migration/1673336077243-PollChoiceLength.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/backend/migration/1673336077243-PollChoiceLength.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
export class PollChoiceLength1673336077243 {
 | 
			
		||||
    name = 'PollChoiceLength1673336077243'
 | 
			
		||||
 | 
			
		||||
    async up(queryRunner) {
 | 
			
		||||
				await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(256) array`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async down(queryRunner) {
 | 
			
		||||
			await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(128) array`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -72,7 +72,7 @@
 | 
			
		||||
		"json5-loader": "4.0.1",
 | 
			
		||||
		"jsonld": "8.1.0",
 | 
			
		||||
		"jsrsasign": "10.6.1",
 | 
			
		||||
		"mfm-js": "0.23.0",
 | 
			
		||||
		"mfm-js": "0.23.1",
 | 
			
		||||
		"mime-types": "2.1.35",
 | 
			
		||||
		"misskey-js": "0.0.14",
 | 
			
		||||
		"ms": "3.0.0-canary.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,8 @@ import type { Packed } from '@/misc/schema.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
 | 
			
		||||
import { UtilityService } from '@/core/UtilityService.js';
 | 
			
		||||
import type { OnApplicationShutdown } from '@nestjs/common';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import type { OnApplicationShutdown } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AntennaService implements OnApplicationShutdown {
 | 
			
		||||
@@ -135,7 +135,7 @@ export class AntennaService implements OnApplicationShutdown {
 | 
			
		||||
					this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
 | 
			
		||||
					this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
 | 
			
		||||
						antenna: { id: antenna.id, name: antenna.name },
 | 
			
		||||
						note: await this.noteEntityService.pack(note)
 | 
			
		||||
						note: await this.noteEntityService.pack(note),
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}, 2000);
 | 
			
		||||
@@ -144,27 +144,19 @@ export class AntennaService implements OnApplicationShutdown {
 | 
			
		||||
 | 
			
		||||
	// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
 | 
			
		||||
	 */
 | 
			
		||||
	@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 === 'followers') return false;
 | 
			
		||||
 | 
			
		||||
		// アンテナ作成者がノート作成者にブロックされていたらスキップ
 | 
			
		||||
		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 (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.src === 'home') {
 | 
			
		||||
			if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
 | 
			
		||||
			if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
 | 
			
		||||
			// TODO
 | 
			
		||||
		} else if (antenna.src === 'list') {
 | 
			
		||||
			const listUsers = (await this.userListJoiningsRepository.findBy({
 | 
			
		||||
				userListId: antenna.userListId!,
 | 
			
		||||
 
 | 
			
		||||
@@ -398,13 +398,13 @@ export class FileInfoService {
 | 
			
		||||
				.raw()
 | 
			
		||||
				.ensureAlpha()
 | 
			
		||||
				.resize(64, 64, { fit: 'inside' })
 | 
			
		||||
				.toBuffer((err, buffer, { width, height }) => {
 | 
			
		||||
				.toBuffer((err, buffer, info) => {
 | 
			
		||||
					if (err) return reject(err);
 | 
			
		||||
 | 
			
		||||
					let hash;
 | 
			
		||||
 | 
			
		||||
					try {
 | 
			
		||||
						hash = encode(new Uint8ClampedArray(buffer), width, height, 5, 5);
 | 
			
		||||
						hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
 | 
			
		||||
					} catch (e) {
 | 
			
		||||
						return reject(e);
 | 
			
		||||
					}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,23 +22,25 @@ export class EmojiEntityService {
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public async pack(
 | 
			
		||||
		src: Emoji['id'] | Emoji,
 | 
			
		||||
		opts: { omitHost?: boolean; omitId?: boolean; } = {},
 | 
			
		||||
	): Promise<Packed<'Emoji'>> {
 | 
			
		||||
		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			id: emoji.id,
 | 
			
		||||
			id: opts.omitId ? undefined : emoji.id,
 | 
			
		||||
			aliases: emoji.aliases,
 | 
			
		||||
			name: emoji.name,
 | 
			
		||||
			category: emoji.category,
 | 
			
		||||
			host: emoji.host,
 | 
			
		||||
			host: opts.omitHost ? undefined : emoji.host,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public packMany(
 | 
			
		||||
		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)));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ export class Poll {
 | 
			
		||||
	public multiple: boolean;
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 128, array: true, default: '{}',
 | 
			
		||||
		length: 256, array: true, default: '{}',
 | 
			
		||||
	})
 | 
			
		||||
	public choices: string[];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ export const packedEmojiSchema = {
 | 
			
		||||
	properties: {
 | 
			
		||||
		id: {
 | 
			
		||||
			type: 'string',
 | 
			
		||||
			optional: false, nullable: false,
 | 
			
		||||
			optional: true, nullable: false,
 | 
			
		||||
			format: 'id',
 | 
			
		||||
			example: 'xxxxxxxxxx',
 | 
			
		||||
		},
 | 
			
		||||
@@ -26,12 +26,8 @@ export const packedEmojiSchema = {
 | 
			
		||||
		},
 | 
			
		||||
		host: {
 | 
			
		||||
			type: 'string',
 | 
			
		||||
			optional: false, nullable: true,
 | 
			
		||||
			optional: true, nullable: true,
 | 
			
		||||
			description: 'The local host is represented with `null`.',
 | 
			
		||||
		},
 | 
			
		||||
		url: {
 | 
			
		||||
			type: 'string',
 | 
			
		||||
			optional: true, nullable: false,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
} as const;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 | 
			
		||||
import { Cache } from '@/misc/cache.js';
 | 
			
		||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import NotesChart from '@/core/chart/charts/notes.js';
 | 
			
		||||
import UsersChart from '@/core/chart/charts/users.js';
 | 
			
		||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
 | 
			
		||||
 | 
			
		||||
const nodeinfo2_1path = '/nodeinfo/2.1';
 | 
			
		||||
@@ -27,6 +29,8 @@ export class NodeinfoServerService {
 | 
			
		||||
 | 
			
		||||
		private userEntityService: UserEntityService,
 | 
			
		||||
		private metaService: MetaService,
 | 
			
		||||
		private notesChart: NotesChart,
 | 
			
		||||
		private usersChart: UsersChart,
 | 
			
		||||
	) {
 | 
			
		||||
		//this.createServer = this.createServer.bind(this);
 | 
			
		||||
	}
 | 
			
		||||
@@ -46,20 +50,27 @@ export class NodeinfoServerService {
 | 
			
		||||
	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
 | 
			
		||||
		const nodeinfo2 = async () => {
 | 
			
		||||
			const now = Date.now();
 | 
			
		||||
 | 
			
		||||
			const notesChart = await this.notesChart.getChart('hour', 1, null);
 | 
			
		||||
			const localPosts = notesChart.local.total[0];
 | 
			
		||||
 | 
			
		||||
			const usersChart = await this.usersChart.getChart('hour', 1, null);
 | 
			
		||||
			const total = usersChart.local.total[0];
 | 
			
		||||
 | 
			
		||||
			const [
 | 
			
		||||
				meta,
 | 
			
		||||
				total,
 | 
			
		||||
				activeHalfyear,
 | 
			
		||||
				activeMonth,
 | 
			
		||||
				localPosts,
 | 
			
		||||
				//activeHalfyear,
 | 
			
		||||
				//activeMonth,
 | 
			
		||||
			] = await Promise.all([
 | 
			
		||||
				this.metaService.fetch(true),
 | 
			
		||||
				this.usersRepository.count({ where: { host: IsNull() } }),
 | 
			
		||||
				this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
 | 
			
		||||
				this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
 | 
			
		||||
				this.notesRepository.count({ where: { userHost: IsNull() } }),
 | 
			
		||||
				// 重い
 | 
			
		||||
				//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
 | 
			
		||||
				//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			const activeHalfyear = null;
 | 
			
		||||
			const activeMonth = null;
 | 
			
		||||
 | 
			
		||||
			const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;
 | 
			
		||||
 | 
			
		||||
			return {
 | 
			
		||||
 
 | 
			
		||||
@@ -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_read from './endpoints/messaging/messages/read.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___mute_create from './endpoints/mute/create.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_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.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 $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 };
 | 
			
		||||
@@ -884,6 +886,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 | 
			
		||||
		$messaging_messages_delete,
 | 
			
		||||
		$messaging_messages_read,
 | 
			
		||||
		$meta,
 | 
			
		||||
		$emojis,
 | 
			
		||||
		$miauth_genToken,
 | 
			
		||||
		$mute_create,
 | 
			
		||||
		$mute_delete,
 | 
			
		||||
@@ -1212,6 +1215,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 | 
			
		||||
		$messaging_messages_delete,
 | 
			
		||||
		$messaging_messages_read,
 | 
			
		||||
		$meta,
 | 
			
		||||
		$emojis,
 | 
			
		||||
		$miauth_genToken,
 | 
			
		||||
		$mute_create,
 | 
			
		||||
		$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_read from './endpoints/messaging/messages/read.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___mute_create from './endpoints/mute/create.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/read', ep___messaging_messages_read],
 | 
			
		||||
	['meta', ep___meta],
 | 
			
		||||
	['emojis', ep___emojis],
 | 
			
		||||
	['miauth/gen-token', ep___miauth_genToken],
 | 
			
		||||
	['mute/create', ep___mute_create],
 | 
			
		||||
	['mute/delete', ep___mute_delete],
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								packages/backend/src/server/api/endpoints/emojis.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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,7 +4,6 @@ import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/
 | 
			
		||||
import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 | 
			
		||||
import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
			
		||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
 | 
			
		||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 | 
			
		||||
import { MetaService } from '@/core/MetaService.js';
 | 
			
		||||
import type { Config } from '@/config.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
@@ -152,43 +151,6 @@ export const meta = {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				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: {
 | 
			
		||||
				type: 'array',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
@@ -326,30 +288,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 | 
			
		||||
		@Inject(DI.adsRepository)
 | 
			
		||||
		private adsRepository: AdsRepository,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.emojisRepository)
 | 
			
		||||
		private emojisRepository: EmojisRepository,
 | 
			
		||||
 | 
			
		||||
		private userEntityService: UserEntityService,
 | 
			
		||||
		private emojiEntityService: EmojiEntityService,
 | 
			
		||||
		private metaService: MetaService,
 | 
			
		||||
	) {
 | 
			
		||||
		super(meta, paramDef, async (ps, me) => {
 | 
			
		||||
			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({
 | 
			
		||||
				where: {
 | 
			
		||||
					expiresAt: MoreThan(new Date()),
 | 
			
		||||
@@ -390,7 +334,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 | 
			
		||||
				backgroundImageUrl: instance.backgroundImageUrl,
 | 
			
		||||
				logoImageUrl: instance.logoImageUrl,
 | 
			
		||||
				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
 | 
			
		||||
				emojis: await this.emojiEntityService.packMany(emojis),
 | 
			
		||||
				defaultLightTheme: instance.defaultLightTheme,
 | 
			
		||||
				defaultDarkTheme: instance.defaultDarkTheme,
 | 
			
		||||
				ads: ads.map(ad => ({
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ import { IsNull } from 'typeorm';
 | 
			
		||||
import type { InstancesRepository, NoteReactionsRepository, NotesRepository, UsersRepository } from '@/models/index.js';
 | 
			
		||||
import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import NotesChart from '@/core/chart/charts/notes.js';
 | 
			
		||||
import UsersChart from '@/core/chart/charts/users.js';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	requireCredential: false,
 | 
			
		||||
@@ -66,21 +68,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.noteReactionsRepository)
 | 
			
		||||
		private noteReactionsRepository: NoteReactionsRepository,
 | 
			
		||||
 | 
			
		||||
		private notesChart: NotesChart,
 | 
			
		||||
		private usersChart: UsersChart,
 | 
			
		||||
	) {
 | 
			
		||||
		super(meta, paramDef, async () => {
 | 
			
		||||
			const notesChart = await this.notesChart.getChart('hour', 1, null);
 | 
			
		||||
			const notesCount = notesChart.local.total[0] + notesChart.remote.total[0];
 | 
			
		||||
			const originalNotesCount = notesChart.local.total[0];
 | 
			
		||||
 | 
			
		||||
			const usersChart = await this.usersChart.getChart('hour', 1, null);
 | 
			
		||||
			const usersCount = usersChart.local.total[0] + usersChart.remote.total[0];
 | 
			
		||||
			const originalUsersCount = usersChart.local.total[0];
 | 
			
		||||
 | 
			
		||||
			const [
 | 
			
		||||
				notesCount,
 | 
			
		||||
				originalNotesCount,
 | 
			
		||||
				usersCount,
 | 
			
		||||
				originalUsersCount,
 | 
			
		||||
				reactionsCount,
 | 
			
		||||
				//originalReactionsCount,
 | 
			
		||||
				instances,
 | 
			
		||||
			] = await Promise.all([
 | 
			
		||||
				this.notesRepository.count({ cache: 3600000 }), // 1 hour
 | 
			
		||||
				this.notesRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }),
 | 
			
		||||
				this.usersRepository.count({ cache: 3600000 }),
 | 
			
		||||
				this.usersRepository.count({ where: { host: IsNull() }, cache: 3600000 }),
 | 
			
		||||
				this.noteReactionsRepository.count({ cache: 3600000 }), // 1 hour
 | 
			
		||||
				//this.noteReactionsRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }),
 | 
			
		||||
				this.instancesRepository.count({ cache: 3600000 }),
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
		"json5": "2.2.3",
 | 
			
		||||
		"katex": "0.16.4",
 | 
			
		||||
		"matter-js": "0.18.0",
 | 
			
		||||
		"mfm-js": "0.23.0",
 | 
			
		||||
		"mfm-js": "0.23.1",
 | 
			
		||||
		"misskey-js": "0.0.14",
 | 
			
		||||
		"photoswipe": "5.3.4",
 | 
			
		||||
		"prismjs": "1.29.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ import { emojilist } from '@/scripts/emojilist';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { miLocalStorage } from '@/local-storage';
 | 
			
		||||
import { customEmojis } from '@/custom-emojis';
 | 
			
		||||
 | 
			
		||||
type EmojiDef = {
 | 
			
		||||
	emoji: string;
 | 
			
		||||
@@ -86,7 +87,6 @@ for (const x of lib) {
 | 
			
		||||
emjdb.sort((a, b) => a.name.length - b.name.length);
 | 
			
		||||
 | 
			
		||||
//#region Construct Emoji DB
 | 
			
		||||
const customEmojis = instance.emojis;
 | 
			
		||||
const emojiDefinitions: EmojiDef[] = [];
 | 
			
		||||
 | 
			
		||||
for (const x of customEmojis) {
 | 
			
		||||
@@ -117,7 +117,6 @@ export default {
 | 
			
		||||
	emojiDb,
 | 
			
		||||
	emojiDefinitions,
 | 
			
		||||
	emojilist,
 | 
			
		||||
	customEmojis,
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,6 @@ export default defineComponent({
 | 
			
		||||
			} else {
 | 
			
		||||
				if (props.ad && item._shouldInsertAd_) {
 | 
			
		||||
					return [h(MkAd, {
 | 
			
		||||
						class: 'a', // advertiseの意(ブロッカー対策)
 | 
			
		||||
						key: item.id + ':ad',
 | 
			
		||||
						prefer: ['horizontal', 'horizontal-big'],
 | 
			
		||||
					}), el];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')">
 | 
			
		||||
	<div class="mk-dialog">
 | 
			
		||||
		<div v-if="icon" class="icon">
 | 
			
		||||
	<div :class="$style.root">
 | 
			
		||||
		<div v-if="icon" :class="$style.icon">
 | 
			
		||||
			<i :class="icon"></i>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-else-if="!input && !select" class="icon" :class="type">
 | 
			
		||||
			<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 === 'warning'" class="ti ti-alert-triangle"></i>
 | 
			
		||||
			<i v-else-if="type === 'info'" class="ti ti-info-circle"></i>
 | 
			
		||||
			<i v-else-if="type === 'question'" class="ti ti-question-circle"></i>
 | 
			
		||||
			<MkLoading v-else-if="type === 'waiting'" :em="true"/>
 | 
			
		||||
		<div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]">
 | 
			
		||||
			<i v-if="type === 'success'" :class="$style.iconInner" class="ti ti-check"></i>
 | 
			
		||||
			<i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i>
 | 
			
		||||
			<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i>
 | 
			
		||||
			<i v-else-if="type === 'info'" :class="$style.iconInner" class="ti ti-info-circle"></i>
 | 
			
		||||
			<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-question-circle"></i>
 | 
			
		||||
			<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<header v-if="title"><Mfm :text="title"/></header>
 | 
			
		||||
		<div v-if="text" class="body"><Mfm :text="text"/></div>
 | 
			
		||||
		<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
 | 
			
		||||
		<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">
 | 
			
		||||
			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
@@ -27,11 +27,11 @@
 | 
			
		||||
				</optgroup>
 | 
			
		||||
			</template>
 | 
			
		||||
		</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="showCancelButton || input || select" inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
 | 
			
		||||
		</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>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -143,8 +143,8 @@ onBeforeUnmount(() => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.mk-dialog {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	padding: 32px;
 | 
			
		||||
	min-width: 320px;
 | 
			
		||||
@@ -153,56 +153,56 @@ onBeforeUnmount(() => {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	background: var(--panel);
 | 
			
		||||
	border-radius: var(--radius);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .icon {
 | 
			
		||||
.icon {
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
 | 
			
		||||
		&.info {
 | 
			
		||||
			color: #55c4dd;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&.success {
 | 
			
		||||
			color: var(--success);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&.error {
 | 
			
		||||
			color: var(--error);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		&.warning {
 | 
			
		||||
			color: var(--warn);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> * {
 | 
			
		||||
			display: block;
 | 
			
		||||
			margin: 0 auto;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		& + header {
 | 
			
		||||
	& + .title {
 | 
			
		||||
		margin-top: 8px;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> header {
 | 
			
		||||
.iconInner {
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.type_info {
 | 
			
		||||
	color: #55c4dd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.type_success {
 | 
			
		||||
	color: var(--success);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.type_error {
 | 
			
		||||
	color: var(--error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.type_warning {
 | 
			
		||||
	color: var(--warn);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
	margin: 0 0 8px 0;
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
	font-size: 1.1em;
 | 
			
		||||
 | 
			
		||||
		& + .body {
 | 
			
		||||
	& + .text {
 | 
			
		||||
		margin-top: 8px;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .body {
 | 
			
		||||
.text {
 | 
			
		||||
	margin: 16px 0 0 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .buttons {
 | 
			
		||||
.buttons {
 | 
			
		||||
	margin-top: 16px;
 | 
			
		||||
 | 
			
		||||
		> * {
 | 
			
		||||
			margin: 0 8px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	display: flex;
 | 
			
		||||
	gap: 8px;
 | 
			
		||||
	flex-wrap: wrap;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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()">
 | 
			
		||||
	<div ref="emojis" class="emojis">
 | 
			
		||||
	<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="emojisEl" class="emojis">
 | 
			
		||||
		<section class="result">
 | 
			
		||||
			<div v-if="searchResultCustom.length > 0" class="body">
 | 
			
		||||
				<button
 | 
			
		||||
					v-for="emoji in searchResultCustom"
 | 
			
		||||
					:key="emoji.id"
 | 
			
		||||
					:key="emoji.name"
 | 
			
		||||
					class="_button item"
 | 
			
		||||
					:title="emoji.name"
 | 
			
		||||
					tabindex="0"
 | 
			
		||||
@@ -85,9 +85,10 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { isTouchUsing } from '@/scripts/touch';
 | 
			
		||||
import { deviceKind } from '@/scripts/device-kind';
 | 
			
		||||
import { emojiCategories, instance } from '@/instance';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { defaultStore } from '@/store';
 | 
			
		||||
import { getCustomEmojiCategories, customEmojis } from '@/custom-emojis';
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	showPinned?: boolean;
 | 
			
		||||
@@ -103,8 +104,9 @@ const emit = defineEmits<{
 | 
			
		||||
	(ev: 'chosen', v: string): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const search = shallowRef<HTMLInputElement>();
 | 
			
		||||
const emojis = shallowRef<HTMLDivElement>();
 | 
			
		||||
const customEmojiCategories = getCustomEmojiCategories();
 | 
			
		||||
const searchEl = shallowRef<HTMLInputElement>();
 | 
			
		||||
const emojisEl = shallowRef<HTMLDivElement>();
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
	reactions: pinned,
 | 
			
		||||
@@ -118,15 +120,13 @@ const {
 | 
			
		||||
const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
 | 
			
		||||
const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
 | 
			
		||||
const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
 | 
			
		||||
const customEmojiCategories = emojiCategories;
 | 
			
		||||
const customEmojis = instance.emojis;
 | 
			
		||||
const q = ref<string>('');
 | 
			
		||||
const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
 | 
			
		||||
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
 | 
			
		||||
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
 | 
			
		||||
 | 
			
		||||
watch(q, () => {
 | 
			
		||||
	if (emojis.value) emojis.value.scrollTop = 0;
 | 
			
		||||
	if (emojisEl.value) emojisEl.value.scrollTop = 0;
 | 
			
		||||
 | 
			
		||||
	if (q.value === '') {
 | 
			
		||||
		searchResultCustom.value = [];
 | 
			
		||||
@@ -268,14 +268,14 @@ watch(q, () => {
 | 
			
		||||
 | 
			
		||||
function focus() {
 | 
			
		||||
	if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
 | 
			
		||||
		search.value?.focus({
 | 
			
		||||
		searchEl.value?.focus({
 | 
			
		||||
			preventScroll: true,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function reset() {
 | 
			
		||||
	if (emojis.value) emojis.value.scrollTop = 0;
 | 
			
		||||
	if (emojisEl.value) emojisEl.value.scrollTop = 0;
 | 
			
		||||
	q.value = '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -308,7 +308,7 @@ function input(): void {
 | 
			
		||||
	// Using custom input event instead of v-model to respond immediately on
 | 
			
		||||
	// Android, where composition happens on all languages
 | 
			
		||||
	// (v-model does not update during composition)
 | 
			
		||||
	q.value = search.value?.value.trim() ?? '';
 | 
			
		||||
	q.value = searchEl.value?.value.trim() ?? '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function paste(event: ClipboardEvent): void {
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,11 @@ function chosen(emoji: any) {
 | 
			
		||||
function opening() {
 | 
			
		||||
	picker.value?.reset();
 | 
			
		||||
	picker.value?.focus();
 | 
			
		||||
 | 
			
		||||
	// 何故かちょっと待たないとフォーカスされない
 | 
			
		||||
	setTimeout(() => {
 | 
			
		||||
		picker.value?.focus();
 | 
			
		||||
	}, 10);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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">
 | 
			
		||||
		<span class="host">{{ instance.name ?? instance.host }}</span>
 | 
			
		||||
		<span class="sub _monospace"><b>{{ instance.host }}</b> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ function close() {
 | 
			
		||||
 | 
			
		||||
	&.asDrawer {
 | 
			
		||||
		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-bottom-right-radius: 0;
 | 
			
		||||
		border-bottom-left-radius: 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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"
 | 
			
		||||
>
 | 
			
		||||
	<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>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -35,13 +35,9 @@ useTooltip($$(el), (showing) => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.xlcxczvw {
 | 
			
		||||
	word-break: break-all;
 | 
			
		||||
 | 
			
		||||
	> .icon {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.icon {
 | 
			
		||||
	padding-left: 2px;
 | 
			
		||||
	font-size: .9em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,54 +2,54 @@
 | 
			
		||||
<div>
 | 
			
		||||
	<div
 | 
			
		||||
		ref="itemsEl" v-hotkey="keymap"
 | 
			
		||||
		class="rrevdjwt _popup _shadow"
 | 
			
		||||
		:class="{ center: align === 'center', asDrawer }"
 | 
			
		||||
		class="_popup _shadow"
 | 
			
		||||
		:class="[$style.root, { [$style.center]: align === 'center', [$style.asDrawer]: asDrawer }]"
 | 
			
		||||
		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
 | 
			
		||||
		@contextmenu.self="e => e.preventDefault()"
 | 
			
		||||
	>
 | 
			
		||||
		<template v-for="(item, i) in items2">
 | 
			
		||||
			<div v-if="item === null" class="divider"></div>
 | 
			
		||||
			<span v-else-if="item.type === 'label'" class="label item">
 | 
			
		||||
			<div v-if="item === null" :class="$style.divider"></div>
 | 
			
		||||
			<span v-else-if="item.type === 'label'" :class="[$style.label, $style.item]">
 | 
			
		||||
				<span>{{ item.text }}</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>
 | 
			
		||||
			<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)">
 | 
			
		||||
				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
 | 
			
		||||
				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
 | 
			
		||||
			<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="[$style.icon, item.icon]"></i>
 | 
			
		||||
				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
 | 
			
		||||
				<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>
 | 
			
		||||
			<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)">
 | 
			
		||||
				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
 | 
			
		||||
			<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="[$style.icon, item.icon]"></i>
 | 
			
		||||
				<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>
 | 
			
		||||
			<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)">
 | 
			
		||||
				<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
 | 
			
		||||
				<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
 | 
			
		||||
			<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="$style.avatar"/><MkUserName :user="item.user"/>
 | 
			
		||||
				<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
 | 
			
		||||
			</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>
 | 
			
		||||
			</span>
 | 
			
		||||
			<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)">
 | 
			
		||||
				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
 | 
			
		||||
			<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="[$style.icon, item.icon]"></i>
 | 
			
		||||
				<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 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)">
 | 
			
		||||
				<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
 | 
			
		||||
				<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
 | 
			
		||||
			<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="[$style.icon, item.icon]"></i>
 | 
			
		||||
				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
 | 
			
		||||
				<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>
 | 
			
		||||
		</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>
 | 
			
		||||
	</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"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -186,8 +186,8 @@ onBeforeUnmount(() => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.rrevdjwt {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	padding: 8px 0;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	min-width: 200px;
 | 
			
		||||
@@ -200,7 +200,35 @@ onBeforeUnmount(() => {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.asDrawer {
 | 
			
		||||
		padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		border-radius: 24px;
 | 
			
		||||
		border-bottom-right-radius: 0;
 | 
			
		||||
		border-bottom-left-radius: 0;
 | 
			
		||||
 | 
			
		||||
		> .item {
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			padding: 12px 24px;
 | 
			
		||||
 | 
			
		||||
			&:before {
 | 
			
		||||
				width: calc(100% - 24px);
 | 
			
		||||
				border-radius: 12px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .icon {
 | 
			
		||||
				margin-right: 14px;
 | 
			
		||||
				width: 24px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .divider {
 | 
			
		||||
			margin: 12px 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.item {
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	padding: 5px 16px;
 | 
			
		||||
@@ -295,10 +323,6 @@ onBeforeUnmount(() => {
 | 
			
		||||
		align-items: center;
 | 
			
		||||
		cursor: default;
 | 
			
		||||
 | 
			
		||||
			> .caret {
 | 
			
		||||
				margin-left: auto;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		&.childShowing {
 | 
			
		||||
			color: var(--accent);
 | 
			
		||||
			text-decoration: none;
 | 
			
		||||
@@ -308,58 +332,34 @@ onBeforeUnmount(() => {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> i {
 | 
			
		||||
.icon {
 | 
			
		||||
	margin-right: 5px;
 | 
			
		||||
	width: 20px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .avatar {
 | 
			
		||||
.caret {
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.avatar {
 | 
			
		||||
	margin-right: 5px;
 | 
			
		||||
	width: 20px;
 | 
			
		||||
	height: 20px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .indicator {
 | 
			
		||||
.indicator {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 5px;
 | 
			
		||||
	left: 13px;
 | 
			
		||||
	color: var(--indicator);
 | 
			
		||||
	font-size: 12px;
 | 
			
		||||
	animation: blink 1s infinite;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .divider {
 | 
			
		||||
.divider {
 | 
			
		||||
	margin: 8px 0;
 | 
			
		||||
	border-top: solid 0.5px var(--divider);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.asDrawer {
 | 
			
		||||
		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		border-radius: 24px;
 | 
			
		||||
		border-bottom-right-radius: 0;
 | 
			
		||||
		border-bottom-left-radius: 0;
 | 
			
		||||
 | 
			
		||||
		> .item {
 | 
			
		||||
			font-size: 1em;
 | 
			
		||||
			padding: 12px 24px;
 | 
			
		||||
 | 
			
		||||
			&:before {
 | 
			
		||||
				width: calc(100% - 24px);
 | 
			
		||||
				border-radius: 12px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> i {
 | 
			
		||||
				margin-right: 14px;
 | 
			
		||||
				width: 24px;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .divider {
 | 
			
		||||
			margin: 12px 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,27 +4,26 @@
 | 
			
		||||
	v-show="!isDeleted"
 | 
			
		||||
	ref="el"
 | 
			
		||||
	v-hotkey="keymap"
 | 
			
		||||
	class="tkcbzcuz"
 | 
			
		||||
	:class="$style.root"
 | 
			
		||||
	:tabindex="!isDeleted ? '-1' : null"
 | 
			
		||||
	:class="{ renote: isRenote }"
 | 
			
		||||
>
 | 
			
		||||
	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
 | 
			
		||||
	<div v-if="pinned" class="info"><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._featuredId_" class="info"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>
 | 
			
		||||
	<div v-if="isRenote" class="renote">
 | 
			
		||||
		<MkAvatar v-once class="avatar" :user="note.user"/>
 | 
			
		||||
		<i class="ti ti-repeat"></i>
 | 
			
		||||
		<I18n :src="i18n.ts.renotedBy" tag="span">
 | 
			
		||||
	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
 | 
			
		||||
	<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</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="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
 | 
			
		||||
	<div v-if="isRenote" :class="$style.renote">
 | 
			
		||||
		<MkAvatar v-once :class="$style.renoteAvatar" :user="note.user"/>
 | 
			
		||||
		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
 | 
			
		||||
		<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
 | 
			
		||||
			<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"/>
 | 
			
		||||
				</MkA>
 | 
			
		||||
			</template>
 | 
			
		||||
		</I18n>
 | 
			
		||||
		<div class="info">
 | 
			
		||||
			<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
 | 
			
		||||
				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
 | 
			
		||||
		<div :class="$style.renoteInfo">
 | 
			
		||||
			<button ref="renoteTime" :class="$style.renoteTime" class="_button" @click="showRenoteMenu()">
 | 
			
		||||
				<i v-if="isMyRenote" class="ti ti-dots" :class="$style.renoteMenu"></i>
 | 
			
		||||
				<MkTime :time="note.createdAt"/>
 | 
			
		||||
			</button>
 | 
			
		||||
			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 | 
			
		||||
@@ -35,80 +34,80 @@
 | 
			
		||||
			<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>
 | 
			
		||||
	<article class="article" @contextmenu.stop="onContextmenu">
 | 
			
		||||
		<MkAvatar v-once class="avatar" :user="appearNote.user"/>
 | 
			
		||||
		<div class="main">
 | 
			
		||||
			<MkNoteHeader class="header" :note="appearNote" :mini="true"/>
 | 
			
		||||
			<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<p v-if="appearNote.cw != null" class="cw">
 | 
			
		||||
					<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
 | 
			
		||||
	<article :class="$style.article" @contextmenu.stop="onContextmenu">
 | 
			
		||||
		<MkAvatar v-once :class="$style.avatar" :user="appearNote.user"/>
 | 
			
		||||
		<div :class="$style.main">
 | 
			
		||||
			<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
 | 
			
		||||
			<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>
 | 
			
		||||
			<div style="container-type: inline-size;">
 | 
			
		||||
				<p v-if="appearNote.cw != null" :class="$style.cw">
 | 
			
		||||
					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
 | 
			
		||||
					<MkCwButton v-model="showContent" :note="appearNote"/>
 | 
			
		||||
				</p>
 | 
			
		||||
				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
 | 
			
		||||
					<div class="text">
 | 
			
		||||
				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
 | 
			
		||||
					<div :class="$style.text">
 | 
			
		||||
						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 | 
			
		||||
						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
 | 
			
		||||
						<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"/>
 | 
			
		||||
						<a v-if="appearNote.renote != null" class="rp">RN:</a>
 | 
			
		||||
						<div v-if="translating || translation" class="translation">
 | 
			
		||||
						<div v-if="translating || translation" :class="$style.translation">
 | 
			
		||||
							<MkLoading v-if="translating" mini/>
 | 
			
		||||
							<div v-else class="translated">
 | 
			
		||||
							<div v-else :class="$style.translated">
 | 
			
		||||
								<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
 | 
			
		||||
								<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
 | 
			
		||||
							</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"/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
 | 
			
		||||
					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
 | 
			
		||||
					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div>
 | 
			
		||||
					<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
 | 
			
		||||
						<span>{{ i18n.ts.showMore }}</span>
 | 
			
		||||
					<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="$style.urlPreview"/>
 | 
			
		||||
					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
 | 
			
		||||
					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
 | 
			
		||||
						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
 | 
			
		||||
					</button>
 | 
			
		||||
					<button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true">
 | 
			
		||||
						<span>{{ i18n.ts.showLess }}</span>
 | 
			
		||||
					<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
 | 
			
		||||
						<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
 | 
			
		||||
					</button>
 | 
			
		||||
				</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>
 | 
			
		||||
			<footer class="footer">
 | 
			
		||||
			<footer :class="$style.footer">
 | 
			
		||||
				<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>
 | 
			
		||||
					<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
 | 
			
		||||
					v-if="canRenote"
 | 
			
		||||
					ref="renoteButton"
 | 
			
		||||
					class="button _button"
 | 
			
		||||
					:class="$style.footerButton"
 | 
			
		||||
					class="_button"
 | 
			
		||||
					@mousedown="renote()"
 | 
			
		||||
				>
 | 
			
		||||
					<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 v-else class="button _button" disabled>
 | 
			
		||||
				<button v-else :class="$style.footerButton" class="_button" disabled>
 | 
			
		||||
					<i class="ti ti-ban"></i>
 | 
			
		||||
				</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>
 | 
			
		||||
				</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>
 | 
			
		||||
				</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>
 | 
			
		||||
				</button>
 | 
			
		||||
			</footer>
 | 
			
		||||
		</div>
 | 
			
		||||
	</article>
 | 
			
		||||
</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">
 | 
			
		||||
		<template #name>
 | 
			
		||||
			<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
 | 
			
		||||
			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
 | 
			
		||||
				<MkUserName :user="appearNote.user"/>
 | 
			
		||||
			</MkA>
 | 
			
		||||
		</template>
 | 
			
		||||
@@ -349,8 +348,8 @@ function readPromo() {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tkcbzcuz {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	transition: box-shadow 0.1s ease;
 | 
			
		||||
	font-size: 1.05em;
 | 
			
		||||
@@ -387,11 +386,12 @@ function readPromo() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:hover > .article > .main > .footer > .button {
 | 
			
		||||
	&:hover > .article > .main > .footer > .footerButton {
 | 
			
		||||
		opacity: 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .info {
 | 
			
		||||
.tip {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding: 16px 32px 8px 32px;
 | 
			
		||||
@@ -399,27 +399,18 @@ function readPromo() {
 | 
			
		||||
	font-size: 90%;
 | 
			
		||||
	white-space: pre;
 | 
			
		||||
	color: #d28a3f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> i {
 | 
			
		||||
			margin-right: 4px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .hide {
 | 
			
		||||
			margin-left: auto;
 | 
			
		||||
			color: inherit;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .info + .article {
 | 
			
		||||
.tip + .article {
 | 
			
		||||
	padding-top: 8px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .reply-to {
 | 
			
		||||
.replyTo {
 | 
			
		||||
	opacity: 0.7;
 | 
			
		||||
	padding-bottom: 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .renote {
 | 
			
		||||
.renote {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding: 16px 32px 8px 32px;
 | 
			
		||||
@@ -427,108 +418,97 @@ function readPromo() {
 | 
			
		||||
	white-space: pre;
 | 
			
		||||
	color: var(--renote);
 | 
			
		||||
 | 
			
		||||
		> .avatar {
 | 
			
		||||
	& + .article {
 | 
			
		||||
		padding-top: 8px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.renoteAvatar {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	width: 28px;
 | 
			
		||||
	height: 28px;
 | 
			
		||||
	margin: 0 8px 0 0;
 | 
			
		||||
	border-radius: 6px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> i {
 | 
			
		||||
			margin-right: 4px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> span {
 | 
			
		||||
.renoteText {
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	flex-shrink: 1;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .name {
 | 
			
		||||
.renoteUserName {
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .info {
 | 
			
		||||
.renoteInfo {
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .time {
 | 
			
		||||
.renoteTime {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	color: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .dropdownIcon {
 | 
			
		||||
.renoteMenu {
 | 
			
		||||
	margin-right: 4px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .renote + .article {
 | 
			
		||||
		padding-top: 8px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .article {
 | 
			
		||||
.article {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	padding: 28px 32px 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .avatar {
 | 
			
		||||
.avatar {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
			display: block;
 | 
			
		||||
	display: block !important;
 | 
			
		||||
	margin: 0 14px 8px 0;
 | 
			
		||||
	width: 58px;
 | 
			
		||||
	height: 58px;
 | 
			
		||||
			position: sticky;
 | 
			
		||||
	position: sticky !important;
 | 
			
		||||
	top: calc(22px + var(--stickyTop, 0px));
 | 
			
		||||
	left: 0;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .main {
 | 
			
		||||
.main {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .body {
 | 
			
		||||
				container-type: inline-size;
 | 
			
		||||
 | 
			
		||||
				> .cw {
 | 
			
		||||
.cw {
 | 
			
		||||
	cursor: default;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	overflow-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					> .text {
 | 
			
		||||
						margin-right: 8px;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				> .content {
 | 
			
		||||
					&.isLong {
 | 
			
		||||
						> .showLess {
 | 
			
		||||
.showLess {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	margin-top: 1em;
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							> span {
 | 
			
		||||
.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%);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					&.collapsed {
 | 
			
		||||
.contentCollapsed {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	max-height: 9em;
 | 
			
		||||
	overflow: clip;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
						> .fade {
 | 
			
		||||
.collapsed {
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
@@ -537,72 +517,60 @@ function readPromo() {
 | 
			
		||||
	height: 64px;
 | 
			
		||||
	background: linear-gradient(0deg, var(--panel), var(--X15));
 | 
			
		||||
 | 
			
		||||
							> span {
 | 
			
		||||
	&: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%);
 | 
			
		||||
							}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							&:hover {
 | 
			
		||||
								> span {
 | 
			
		||||
									background: var(--panelHighlight);
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					> .text {
 | 
			
		||||
.text {
 | 
			
		||||
	overflow-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
						> .reply {
 | 
			
		||||
.replyIcon {
 | 
			
		||||
	color: var(--accent);
 | 
			
		||||
	margin-right: 0.5em;
 | 
			
		||||
						}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
						> .rp {
 | 
			
		||||
							margin-left: 4px;
 | 
			
		||||
							font-style: oblique;
 | 
			
		||||
							color: var(--renote);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						> .translation {
 | 
			
		||||
.translation {
 | 
			
		||||
	border: solid 0.5px var(--divider);
 | 
			
		||||
	border-radius: var(--radius);
 | 
			
		||||
	padding: 12px;
 | 
			
		||||
	margin-top: 8px;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					> .url-preview {
 | 
			
		||||
.urlPreview {
 | 
			
		||||
	margin-top: 8px;
 | 
			
		||||
					}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					> .poll {
 | 
			
		||||
.poll {
 | 
			
		||||
	font-size: 80%;
 | 
			
		||||
					}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					> .renote {
 | 
			
		||||
.quote {
 | 
			
		||||
	padding: 8px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
						> .note {
 | 
			
		||||
.quoteNote {
 | 
			
		||||
	padding: 16px;
 | 
			
		||||
	border: dashed 1px var(--renote);
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .channel {
 | 
			
		||||
.channel {
 | 
			
		||||
	opacity: 0.7;
 | 
			
		||||
	font-size: 80%;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .footer {
 | 
			
		||||
				> .button {
 | 
			
		||||
.footerButton {
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
	opacity: 0.7;
 | 
			
		||||
@@ -614,97 +582,65 @@ function readPromo() {
 | 
			
		||||
	&:hover {
 | 
			
		||||
		color: var(--fgHighlighted);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					> .count {
 | 
			
		||||
.footerButtonCount {
 | 
			
		||||
	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) {
 | 
			
		||||
	.tkcbzcuz {
 | 
			
		||||
	.root {
 | 
			
		||||
		font-size: 0.9em;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		> .article {
 | 
			
		||||
			> .avatar {
 | 
			
		||||
	.avatar {
 | 
			
		||||
		width: 50px;
 | 
			
		||||
		height: 50px;
 | 
			
		||||
	}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (max-width: 450px) {
 | 
			
		||||
	.tkcbzcuz {
 | 
			
		||||
		> .renote {
 | 
			
		||||
	.renote {
 | 
			
		||||
		padding: 8px 16px 0 16px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		> .info {
 | 
			
		||||
	.tip {
 | 
			
		||||
		padding: 8px 16px 0 16px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		> .article {
 | 
			
		||||
	.article {
 | 
			
		||||
		padding: 14px 16px 9px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			> .avatar {
 | 
			
		||||
	.avatar {
 | 
			
		||||
		margin: 0 10px 8px 0;
 | 
			
		||||
		width: 46px;
 | 
			
		||||
		height: 46px;
 | 
			
		||||
		top: calc(14px + var(--stickyTop, 0px));
 | 
			
		||||
	}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (max-width: 350px) {
 | 
			
		||||
	.tkcbzcuz {
 | 
			
		||||
		> .article {
 | 
			
		||||
			> .main {
 | 
			
		||||
				> .footer {
 | 
			
		||||
					> .button {
 | 
			
		||||
	.footerButton {
 | 
			
		||||
		&:not(:last-child) {
 | 
			
		||||
			margin-right: 18px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (max-width: 300px) {
 | 
			
		||||
	.tkcbzcuz {
 | 
			
		||||
		> .article {
 | 
			
		||||
			> .avatar {
 | 
			
		||||
	.avatar {
 | 
			
		||||
		width: 44px;
 | 
			
		||||
		height: 44px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			> .main {
 | 
			
		||||
				> .footer {
 | 
			
		||||
					> .button {
 | 
			
		||||
	.footerButton {
 | 
			
		||||
		&:not(:last-child) {
 | 
			
		||||
			margin-right: 12px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.muted {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<header class="kkwtjztg">
 | 
			
		||||
	<MkA v-once v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
 | 
			
		||||
<header :class="$style.root">
 | 
			
		||||
	<MkA v-once v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
 | 
			
		||||
		<MkUserName :user="note.user"/>
 | 
			
		||||
	</MkA>
 | 
			
		||||
	<div v-if="note.user.isBot" class="is-bot">bot</div>
 | 
			
		||||
	<div class="username"><MkAcct :user="note.user"/></div>
 | 
			
		||||
	<div class="info">
 | 
			
		||||
		<MkA class="created-at" :to="notePage(note)">
 | 
			
		||||
	<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
 | 
			
		||||
	<div :class="$style.username"><MkAcct :user="note.user"/></div>
 | 
			
		||||
	<div :class="$style.info">
 | 
			
		||||
		<MkA :to="notePage(note)">
 | 
			
		||||
			<MkTime :time="note.createdAt"/>
 | 
			
		||||
		</MkA>
 | 
			
		||||
		<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 | 
			
		||||
@@ -32,13 +32,14 @@ defineProps<{
 | 
			
		||||
}>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.kkwtjztg {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: baseline;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .name {
 | 
			
		||||
.name {
 | 
			
		||||
	flex-shrink: 1;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0 .5em 0 0;
 | 
			
		||||
@@ -52,9 +53,9 @@ defineProps<{
 | 
			
		||||
	&:hover {
 | 
			
		||||
		text-decoration: underline;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .is-bot {
 | 
			
		||||
.isBot {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	align-self: center;
 | 
			
		||||
	margin: 0 .5em 0 0;
 | 
			
		||||
@@ -62,19 +63,18 @@ defineProps<{
 | 
			
		||||
	font-size: 80%;
 | 
			
		||||
	border: solid 0.5px var(--divider);
 | 
			
		||||
	border-radius: 3px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .username {
 | 
			
		||||
.username {
 | 
			
		||||
	flex-shrink: 9999999;
 | 
			
		||||
	margin: 0 .5em 0 0;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .info {
 | 
			
		||||
.info {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="yohlumlk">
 | 
			
		||||
	<MkAvatar class="avatar" :user="note.user"/>
 | 
			
		||||
	<div class="main">
 | 
			
		||||
		<MkNoteHeader class="header" :note="note" :mini="true"/>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<p v-if="note.cw != null" class="cw">
 | 
			
		||||
				<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
 | 
			
		||||
<div :class="$style.root">
 | 
			
		||||
	<MkAvatar :class="$style.avatar" :user="note.user"/>
 | 
			
		||||
	<div :class="$style.main">
 | 
			
		||||
		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
 | 
			
		||||
		<div>
 | 
			
		||||
			<p v-if="note.cw != null" :class="$style.cw">
 | 
			
		||||
				<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/>
 | 
			
		||||
				<MkCwButton v-model="showContent" :note="note"/>
 | 
			
		||||
			</p>
 | 
			
		||||
			<div v-show="note.cw == null || showContent" class="content">
 | 
			
		||||
				<MkSubNoteContent class="text" :note="note"/>
 | 
			
		||||
			<div v-show="note.cw == null || showContent">
 | 
			
		||||
				<MkSubNoteContent :class="$style.text" :note="note"/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -31,73 +31,60 @@ const props = defineProps<{
 | 
			
		||||
const showContent = $ref(false);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.yohlumlk {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	overflow: clip;
 | 
			
		||||
	font-size: 0.95em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .avatar {
 | 
			
		||||
.avatar {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0 10px 0 0;
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .main {
 | 
			
		||||
.main {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .header {
 | 
			
		||||
.header {
 | 
			
		||||
	margin-bottom: 2px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .body {
 | 
			
		||||
 | 
			
		||||
			> .cw {
 | 
			
		||||
.cw {
 | 
			
		||||
	cursor: default;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	overflow-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .text {
 | 
			
		||||
					margin-right: 8px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .content {
 | 
			
		||||
				> .text {
 | 
			
		||||
.text {
 | 
			
		||||
	cursor: default;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (min-width: 350px) {
 | 
			
		||||
	.yohlumlk {
 | 
			
		||||
		> .avatar {
 | 
			
		||||
	.avatar {
 | 
			
		||||
		margin: 0 10px 0 0;
 | 
			
		||||
		width: 44px;
 | 
			
		||||
		height: 44px;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (min-width: 500px) {
 | 
			
		||||
	.yohlumlk {
 | 
			
		||||
		> .avatar {
 | 
			
		||||
	.avatar {
 | 
			
		||||
		margin: 0 12px 0 0;
 | 
			
		||||
		width: 48px;
 | 
			
		||||
		height: 48px;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,25 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="wrpstxzv" :class="{ children: depth > 1 }">
 | 
			
		||||
	<div class="main">
 | 
			
		||||
		<MkAvatar class="avatar" :user="note.user"/>
 | 
			
		||||
		<div class="body">
 | 
			
		||||
			<MkNoteHeader class="header" :note="note" :mini="true"/>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<p v-if="note.cw != null" class="cw">
 | 
			
		||||
					<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
 | 
			
		||||
<div :class="[$style.root, { [$style.children]: depth > 1 }]">
 | 
			
		||||
	<div :class="$style.main">
 | 
			
		||||
		<MkAvatar :class="$style.avatar" :user="note.user"/>
 | 
			
		||||
		<div :class="$style.body">
 | 
			
		||||
			<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
 | 
			
		||||
			<div>
 | 
			
		||||
				<p v-if="note.cw != null" :class="$style.cw">
 | 
			
		||||
					<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/>
 | 
			
		||||
					<MkCwButton v-model="showContent" :note="note"/>
 | 
			
		||||
				</p>
 | 
			
		||||
				<div v-show="note.cw == null || showContent" class="content">
 | 
			
		||||
					<MkSubNoteContent class="text" :note="note"/>
 | 
			
		||||
				<div v-show="note.cw == null || showContent">
 | 
			
		||||
					<MkSubNoteContent :class="$style.text" :note="note"/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<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>
 | 
			
		||||
	<div v-else class="more">
 | 
			
		||||
		<MkA class="text _link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
 | 
			
		||||
	<div v-else :class="$style.more">
 | 
			
		||||
		<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -57,8 +57,8 @@ if (props.detail) {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.wrpstxzv {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	padding: 16px 32px;
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
 | 
			
		||||
@@ -66,62 +66,54 @@ if (props.detail) {
 | 
			
		||||
		padding: 10px 0 0 16px;
 | 
			
		||||
		font-size: 1em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .main {
 | 
			
		||||
.main {
 | 
			
		||||
	display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .avatar {
 | 
			
		||||
.avatar {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0 8px 0 0;
 | 
			
		||||
	width: 38px;
 | 
			
		||||
	height: 38px;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .body {
 | 
			
		||||
.body {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .header {
 | 
			
		||||
.header {
 | 
			
		||||
	margin-bottom: 2px;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .body {
 | 
			
		||||
				> .cw {
 | 
			
		||||
.cw {
 | 
			
		||||
	cursor: default;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	overflow-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
					> .text {
 | 
			
		||||
						margin-right: 8px;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				> .content {
 | 
			
		||||
					> .text {
 | 
			
		||||
.text {
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .reply, > .more {
 | 
			
		||||
.reply, .more {
 | 
			
		||||
	border-left: solid 0.5px var(--divider);
 | 
			
		||||
	margin-top: 10px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .more {
 | 
			
		||||
.more {
 | 
			
		||||
	padding: 10px 0 0 16px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (max-width: 450px) {
 | 
			
		||||
	.wrpstxzv {
 | 
			
		||||
	.root {
 | 
			
		||||
		padding: 14px 16px;
 | 
			
		||||
 | 
			
		||||
		&.children {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,10 @@
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template #default="{ items: notes }">
 | 
			
		||||
		<div class="giivymft" :class="{ noGap }">
 | 
			
		||||
			<XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes">
 | 
			
		||||
				<XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/>
 | 
			
		||||
			</XList>
 | 
			
		||||
		<div :class="[$style.root, { [$style.noGap]: noGap }]">
 | 
			
		||||
			<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="$style.note" :note="note"/>
 | 
			
		||||
			</MkDateSeparatedList>
 | 
			
		||||
		</div>
 | 
			
		||||
	</template>
 | 
			
		||||
</MkPagination>
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { shallowRef } from '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 { i18n } from '@/i18n';
 | 
			
		||||
 | 
			
		||||
@@ -36,8 +36,8 @@ defineExpose({
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.giivymft {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	&.noGap {
 | 
			
		||||
		> .notes {
 | 
			
		||||
			background: var(--panel);
 | 
			
		||||
@@ -48,7 +48,7 @@ defineExpose({
 | 
			
		||||
		> .notes {
 | 
			
		||||
			background: var(--bg);
 | 
			
		||||
 | 
			
		||||
			.qtqtichx {
 | 
			
		||||
			.note {
 | 
			
		||||
				background: var(--panel);
 | 
			
		||||
				border-radius: var(--radius);
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div ref="elRef" class="qglefbjs" :class="notification.type">
 | 
			
		||||
	<div v-once class="head">
 | 
			
		||||
		<MkAvatar v-if="notification.type === 'pollEnded'" class="icon" :user="notification.note.user"/>
 | 
			
		||||
		<MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/>
 | 
			
		||||
		<img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/>
 | 
			
		||||
		<div class="sub-icon" :class="notification.type">
 | 
			
		||||
<div ref="elRef" :class="$style.root">
 | 
			
		||||
	<div v-once :class="$style.head">
 | 
			
		||||
		<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user"/>
 | 
			
		||||
		<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user"/>
 | 
			
		||||
		<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
 | 
			
		||||
		<div :class="[$style.subIcon, $style['t_' + notification.type]]">
 | 
			
		||||
			<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
 | 
			
		||||
@@ -21,46 +21,47 @@
 | 
			
		||||
				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
 | 
			
		||||
				:custom-emojis="notification.note.emojis"
 | 
			
		||||
				:no-style="true"
 | 
			
		||||
				style="width: 100%; height: 100%;"
 | 
			
		||||
			/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="tail">
 | 
			
		||||
		<header>
 | 
			
		||||
	<div :class="$style.tail">
 | 
			
		||||
		<header :class="$style.header">
 | 
			
		||||
			<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
 | 
			
		||||
			<MkA v-else-if="notification.user" v-user-preview="notification.user.id" class="name" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
 | 
			
		||||
			<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
 | 
			
		||||
			<span v-else>{{ notification.header }}</span>
 | 
			
		||||
			<MkTime v-if="withTime" :time="notification.createdAt" class="time"/>
 | 
			
		||||
			<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
 | 
			
		||||
		</header>
 | 
			
		||||
		<div v-once class="content">
 | 
			
		||||
			<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<i class="ti ti-quote"></i>
 | 
			
		||||
		<div v-once :class="$style.content">
 | 
			
		||||
			<MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
 | 
			
		||||
				<i class="ti ti-quote"></i>
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
 | 
			
		||||
				<i class="ti ti-quote"></i>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'renote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
				<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
 | 
			
		||||
				<i class="ti ti-quote"></i>
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
			<MkA v-else-if="notification.type === 'reply'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
			<MkA v-else-if="notification.type === 'mention'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
			<MkA v-else-if="notification.type === 'quote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<i class="ti ti-quote"></i>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'pollEnded'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
 | 
			
		||||
				<i class="ti ti-quote"></i>
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<span v-else-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'app'" class="text">
 | 
			
		||||
			<span v-else-if="notification.type === 'follow'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'receiveFollowRequest'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'groupInvited'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'app'" :class="$style.text">
 | 
			
		||||
				<Mfm :text="notification.body" :nowrap="!full"/>
 | 
			
		||||
			</span>
 | 
			
		||||
		</div>
 | 
			
		||||
@@ -156,8 +157,8 @@ useTooltip(reactionRef, (showing) => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.qglefbjs {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	padding: 24px 32px;
 | 
			
		||||
@@ -165,23 +166,25 @@ useTooltip(reactionRef, (showing) => {
 | 
			
		||||
	overflow-wrap: break-word;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	contain: content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .head {
 | 
			
		||||
.head {
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	width: 42px;
 | 
			
		||||
	height: 42px;
 | 
			
		||||
	margin-right: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .icon {
 | 
			
		||||
.icon {
 | 
			
		||||
	display: block;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	border-radius: 6px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .sub-icon {
 | 
			
		||||
.subIcon {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	z-index: 1;
 | 
			
		||||
	bottom: -2px;
 | 
			
		||||
@@ -194,110 +197,104 @@ useTooltip(reactionRef, (showing) => {
 | 
			
		||||
	box-shadow: 0 0 0 3px var(--panel);
 | 
			
		||||
	font-size: 12px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
 | 
			
		||||
	&:empty {
 | 
			
		||||
		display: none;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> * {
 | 
			
		||||
				color: #fff;
 | 
			
		||||
				width: 100%;
 | 
			
		||||
				height: 100%;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&.follow, &.followRequestAccepted, &.receiveFollowRequest, &.groupInvited {
 | 
			
		||||
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest, .t_groupInvited {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: #36aed2;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			&.renote {
 | 
			
		||||
.t_renote {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: #36d298;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			&.quote {
 | 
			
		||||
.t_quote {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: #36d298;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			&.reply {
 | 
			
		||||
.t_reply {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: #007aff;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			&.mention {
 | 
			
		||||
.t_mention {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: #88a6b7;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			&.pollEnded {
 | 
			
		||||
.t_pollEnded {
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	background: #88a6b7;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .tail {
 | 
			
		||||
.tail {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> header {
 | 
			
		||||
.header {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: baseline;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .name {
 | 
			
		||||
.headerName {
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .time {
 | 
			
		||||
.headerTime {
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .content {
 | 
			
		||||
			> .text {
 | 
			
		||||
.content {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text {
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> i {
 | 
			
		||||
.quote {
 | 
			
		||||
	vertical-align: super;
 | 
			
		||||
	font-size: 50%;
 | 
			
		||||
	opacity: 0.5;
 | 
			
		||||
				}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> i:first-child {
 | 
			
		||||
.quote:first-child {
 | 
			
		||||
	margin-right: 4px;
 | 
			
		||||
				}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> i:last-child {
 | 
			
		||||
.quote:last-child {
 | 
			
		||||
	margin-left: 4px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (max-width: 600px) {
 | 
			
		||||
	.qglefbjs {
 | 
			
		||||
	.root {
 | 
			
		||||
		padding: 16px;
 | 
			
		||||
		font-size: 0.9em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@container (max-width: 500px) {
 | 
			
		||||
	.qglefbjs {
 | 
			
		||||
	.root {
 | 
			
		||||
		padding: 12px;
 | 
			
		||||
		font-size: 0.85em;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,10 @@
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<template #default="{ items: notifications }">
 | 
			
		||||
		<XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true">
 | 
			
		||||
		<MkDateSeparatedList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true">
 | 
			
		||||
			<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
 | 
			
		||||
			<XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/>
 | 
			
		||||
		</XList>
 | 
			
		||||
		</MkDateSeparatedList>
 | 
			
		||||
	</template>
 | 
			
		||||
</MkPagination>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -21,7 +21,7 @@ import { defineComponent, markRaw, onUnmounted, onMounted, computed, shallowRef
 | 
			
		||||
import { notificationTypes } from 'misskey-js';
 | 
			
		||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
 | 
			
		||||
import XNotification from '@/components/MkNotification.vue';
 | 
			
		||||
import XList from '@/components/MkDateSeparatedList.vue';
 | 
			
		||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 | 
			
		||||
import XNote from '@/components/MkNote.vue';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { stream } from '@/stream';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<Transition :name="$store.state.animation ? 'y' : ''">
 | 
			
		||||
	<TransitionGroup v-if="Object.keys(note.reactions).length > 0" :name="$store.state.animation ? 'x' : ''" tag="div" class="tdflqwzn" :class="{ isMe }">
 | 
			
		||||
<TransitionGroup :name="$store.state.animation ? 'x' : ''" tag="div" class="tdflqwzn" :class="{ isMe }">
 | 
			
		||||
	<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
 | 
			
		||||
	</TransitionGroup>
 | 
			
		||||
</Transition>
 | 
			
		||||
</TransitionGroup>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
@@ -22,16 +20,6 @@ const isMe = computed(() => $i && $i.id === props.note.userId);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.y-enter-active, .y-leave-active {
 | 
			
		||||
	overflow: clip;
 | 
			
		||||
	max-height: 36px;
 | 
			
		||||
	transition: opacity 0.2s cubic-bezier(0,.5,.5,1), max-height 0.2s cubic-bezier(0,.5,.5,1) !important;
 | 
			
		||||
}
 | 
			
		||||
.y-enter-from, .y-leave-to {
 | 
			
		||||
	max-height: 0px;
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.x-move, .x-enter-active, .x-leave-active {
 | 
			
		||||
	transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<Transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="emit('closed')">
 | 
			
		||||
	<div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
 | 
			
		||||
<Transition
 | 
			
		||||
	:enter-active-class="$store.state.animation ? $style.transition_tooltip_enterActive : ''"
 | 
			
		||||
	:leave-active-class="$store.state.animation ? $style.transition_tooltip_leaveActive : ''"
 | 
			
		||||
	:enter-from-class="$store.state.animation ? $style.transition_tooltip_enterFrom : ''"
 | 
			
		||||
	:leave-to-class="$store.state.animation ? $style.transition_tooltip_leaveTo : ''"
 | 
			
		||||
	appear @after-leave="emit('closed')"
 | 
			
		||||
>
 | 
			
		||||
	<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
 | 
			
		||||
		<slot>
 | 
			
		||||
			<Mfm v-if="asMfm" :text="text"/>
 | 
			
		||||
			<span v-else>{{ text }}</span>
 | 
			
		||||
@@ -74,20 +80,20 @@ onUnmounted(() => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tooltip-enter-active,
 | 
			
		||||
.tooltip-leave-active {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.transition_tooltip_enterActive,
 | 
			
		||||
.transition_tooltip_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transform: scale(1);
 | 
			
		||||
	transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.tooltip-enter-from,
 | 
			
		||||
.tooltip-leave-active {
 | 
			
		||||
.transition_tooltip_enterFrom,
 | 
			
		||||
.transition_tooltip_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: scale(0.75);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buebdbiu {
 | 
			
		||||
.root {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	font-size: 0.8em;
 | 
			
		||||
	padding: 8px 12px;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,21 +15,21 @@
 | 
			
		||||
			handle=".handle"
 | 
			
		||||
			:animation="150"
 | 
			
		||||
			:group="{ name: 'SortableMkWidgets' }"
 | 
			
		||||
			@update:model-value="v => emit('updateWidgets', v)"
 | 
			
		||||
			:class="$style['edit-editing']"
 | 
			
		||||
			@update:model-value="v => emit('updateWidgets', v)"
 | 
			
		||||
		>
 | 
			
		||||
			<template #item="{element}">
 | 
			
		||||
				<div :class="[$style.widget, $style['customize-container']]">
 | 
			
		||||
					<button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
 | 
			
		||||
					<button :class="$style['customize-container-remove']" class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
 | 
			
		||||
					<div class="handle">
 | 
			
		||||
						<component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/>
 | 
			
		||||
						<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
		</Sortable>
 | 
			
		||||
	</template>
 | 
			
		||||
	<component :is="`mkw-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
 | 
			
		||||
	<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<span class="mk-acct">
 | 
			
		||||
	<span class="name">@{{ user.username }}</span>
 | 
			
		||||
	<span v-if="user.host || detail || $store.state.showFullAcct" class="host">@{{ user.host || host }}</span>
 | 
			
		||||
<span>
 | 
			
		||||
	<span>@{{ user.username }}</span>
 | 
			
		||||
	<span v-if="user.host || detail || $store.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
 | 
			
		||||
</span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -18,10 +18,3 @@ defineProps<{
 | 
			
		||||
const host = toUnicode(hostRaw);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.mk-acct {
 | 
			
		||||
	> .host {
 | 
			
		||||
		opacity: 0.5;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick">
 | 
			
		||||
	<img class="inner" :src="url" decoding="async"/>
 | 
			
		||||
	<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
 | 
			
		||||
<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" class="_noSelect" :style="{ color }" :title="acct(user)" @click="onClick">
 | 
			
		||||
	<img :class="$style.inner" :src="url" decoding="async"/>
 | 
			
		||||
	<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
 | 
			
		||||
</span>
 | 
			
		||||
<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
 | 
			
		||||
	<img class="inner" :src="url" decoding="async"/>
 | 
			
		||||
	<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
 | 
			
		||||
<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
 | 
			
		||||
	<img :class="$style.inner" :src="url" decoding="async"/>
 | 
			
		||||
	<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
 | 
			
		||||
</MkA>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +52,7 @@ watch(() => props.user.avatarBlurhash, () => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
@keyframes earwiggleleft {
 | 
			
		||||
	from { transform: rotate(37.6deg) skew(30deg); }
 | 
			
		||||
	25% { transform: rotate(10deg) skew(30deg); }
 | 
			
		||||
@@ -69,15 +69,16 @@ watch(() => props.user.avatarBlurhash, () => {
 | 
			
		||||
	to { transform: rotate(-37.6deg) skew(-30deg); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.eiwwqkts {
 | 
			
		||||
.root {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	vertical-align: bottom;
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	border-radius: 100%;
 | 
			
		||||
	line-height: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .inner {
 | 
			
		||||
.inner {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
@@ -89,26 +90,26 @@ watch(() => props.user.avatarBlurhash, () => {
 | 
			
		||||
	object-fit: cover;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .indicator {
 | 
			
		||||
.indicator {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	z-index: 1;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	width: 20%;
 | 
			
		||||
	height: 20%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.square {
 | 
			
		||||
.square {
 | 
			
		||||
	border-radius: 20%;
 | 
			
		||||
 | 
			
		||||
	> .inner {
 | 
			
		||||
		border-radius: 20%;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.cat {
 | 
			
		||||
.cat {
 | 
			
		||||
	&:before, &:after {
 | 
			
		||||
		background: #df548f;
 | 
			
		||||
		border: solid 4px currentColor;
 | 
			
		||||
@@ -138,6 +139,5 @@ watch(() => props.user.avatarBlurhash, () => {
 | 
			
		||||
			animation: earwiggleright 1s infinite;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<img v-if="isCustom" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
 | 
			
		||||
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
 | 
			
		||||
<img v-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async"/>
 | 
			
		||||
<img v-else-if="char && !useOsNativeEmojis" :class="$style.root" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
 | 
			
		||||
<span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span>
 | 
			
		||||
<span v-else>{{ emoji }}</span>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -47,12 +47,13 @@ function computeTitle(event: PointerEvent): void {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.mk-emoji {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	height: 1.25em;
 | 
			
		||||
	vertical-align: -0.25em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.custom {
 | 
			
		||||
.custom {
 | 
			
		||||
	height: 2.5em;
 | 
			
		||||
	vertical-align: middle;
 | 
			
		||||
	transition: transform 0.2s ease;
 | 
			
		||||
@@ -60,19 +61,18 @@ function computeTitle(event: PointerEvent): void {
 | 
			
		||||
	&:hover {
 | 
			
		||||
		transform: scale(1.2);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		&.normal {
 | 
			
		||||
.normal {
 | 
			
		||||
	height: 1.25em;
 | 
			
		||||
	vertical-align: -0.25em;
 | 
			
		||||
 | 
			
		||||
	&:hover {
 | 
			
		||||
		transform: none;
 | 
			
		||||
	}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.noStyle {
 | 
			
		||||
.noStyle {
 | 
			
		||||
	height: auto !important;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
<template>
 | 
			
		||||
<component
 | 
			
		||||
	:is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel" :target="target"
 | 
			
		||||
	:is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel" :target="target"
 | 
			
		||||
	@contextmenu.stop="() => {}"
 | 
			
		||||
>
 | 
			
		||||
	<template v-if="!self">
 | 
			
		||||
		<span class="schema">{{ schema }}//</span>
 | 
			
		||||
		<span class="hostname">{{ hostname }}</span>
 | 
			
		||||
		<span v-if="port != ''" class="port">:{{ port }}</span>
 | 
			
		||||
		<span :class="$style.schema">{{ schema }}//</span>
 | 
			
		||||
		<span :class="$style.hostname">{{ hostname }}</span>
 | 
			
		||||
		<span v-if="port != ''" :class="$style.port">:{{ port }}</span>
 | 
			
		||||
	</template>
 | 
			
		||||
	<template v-if="pathname === '/' && self">
 | 
			
		||||
		<span class="self">{{ hostname }}</span>
 | 
			
		||||
		<span :class="$style.self">{{ hostname }}</span>
 | 
			
		||||
	</template>
 | 
			
		||||
	<span v-if="pathname != ''" class="pathname">{{ self ? pathname.substring(1) : pathname }}</span>
 | 
			
		||||
	<span class="query">{{ query }}</span>
 | 
			
		||||
	<span class="hash">{{ hash }}</span>
 | 
			
		||||
	<span v-if="pathname != ''" :class="$style.pathname">{{ self ? pathname.substring(1) : pathname }}</span>
 | 
			
		||||
	<span :class="$style.query">{{ query }}</span>
 | 
			
		||||
	<span :class="$style.hash">{{ hash }}</span>
 | 
			
		||||
	<i v-if="target === '_blank'" class="ti ti-external-link icon"></i>
 | 
			
		||||
</component>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -53,37 +53,37 @@ const attr = self ? 'to' : 'href';
 | 
			
		||||
const target = self ? null : '_blank';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.ieqqeuvs {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	word-break: break-all;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .icon {
 | 
			
		||||
.icon {
 | 
			
		||||
	padding-left: 2px;
 | 
			
		||||
	font-size: .9em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .self {
 | 
			
		||||
.self {
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .schema {
 | 
			
		||||
.schema {
 | 
			
		||||
	opacity: 0.5;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .hostname {
 | 
			
		||||
.hostname {
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .pathname {
 | 
			
		||||
.pathname {
 | 
			
		||||
	opacity: 0.8;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .query {
 | 
			
		||||
.query {
 | 
			
		||||
	opacity: 0.5;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .hash {
 | 
			
		||||
.hash {
 | 
			
		||||
	font-style: italic;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								packages/frontend/src/custom-emojis.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								packages/frontend/src/custom-emojis.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
import { api } from './os';
 | 
			
		||||
import { miLocalStorage } from './local-storage';
 | 
			
		||||
 | 
			
		||||
const storageCache = miLocalStorage.getItem('emojis');
 | 
			
		||||
export let customEmojis = storageCache ? JSON.parse(storageCache) : [];
 | 
			
		||||
 | 
			
		||||
fetchCustomEmojis();
 | 
			
		||||
 | 
			
		||||
export async function fetchCustomEmojis() {
 | 
			
		||||
	const now = Date.now();
 | 
			
		||||
	const lastFetchedAt = miLocalStorage.getItem('lastEmojisFetchedAt');
 | 
			
		||||
	if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return;
 | 
			
		||||
 | 
			
		||||
	const res = await api('emojis', {});
 | 
			
		||||
 | 
			
		||||
	customEmojis = res.emojis;
 | 
			
		||||
	miLocalStorage.setItem('emojis', JSON.stringify(customEmojis));
 | 
			
		||||
	miLocalStorage.setItem('lastEmojisFetchedAt', now.toString());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let cachedCategories;
 | 
			
		||||
export function getCustomEmojiCategories() {
 | 
			
		||||
	if (cachedCategories) return cachedCategories;
 | 
			
		||||
 | 
			
		||||
	const categories = new Set();
 | 
			
		||||
	for (const emoji of customEmojis) {
 | 
			
		||||
		categories.add(emoji.category);
 | 
			
		||||
	}
 | 
			
		||||
	const res = Array.from(categories);
 | 
			
		||||
	cachedCategories = res;
 | 
			
		||||
	return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let cachedTags;
 | 
			
		||||
export function getCustomEmojiTags() {
 | 
			
		||||
	if (cachedTags) return cachedTags;
 | 
			
		||||
 | 
			
		||||
	const tags = new Set();
 | 
			
		||||
	for (const emoji of customEmojis) {
 | 
			
		||||
		for (const tag of emoji.aliases) {
 | 
			
		||||
			tags.add(tag);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	const res = Array.from(tags);
 | 
			
		||||
	cachedTags = res;
 | 
			
		||||
	return res;
 | 
			
		||||
}
 | 
			
		||||
@@ -5,11 +5,11 @@ import { miLocalStorage } from './local-storage';
 | 
			
		||||
 | 
			
		||||
// TODO: 他のタブと永続化されたstateを同期
 | 
			
		||||
 | 
			
		||||
const instanceData = miLocalStorage.getItem('instance');
 | 
			
		||||
const cached = miLocalStorage.getItem('instance');
 | 
			
		||||
 | 
			
		||||
// TODO: instanceをリアクティブにするかは再考の余地あり
 | 
			
		||||
 | 
			
		||||
export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : {
 | 
			
		||||
export const instance: Misskey.entities.InstanceMetadata = reactive(cached ? JSON.parse(cached) : {
 | 
			
		||||
	// TODO: set default values
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -24,23 +24,3 @@ export async function fetchInstance() {
 | 
			
		||||
 | 
			
		||||
	miLocalStorage.setItem('instance', JSON.stringify(instance));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const emojiCategories = computed(() => {
 | 
			
		||||
	if (instance.emojis == null) return [];
 | 
			
		||||
	const categories = new Set();
 | 
			
		||||
	for (const emoji of instance.emojis) {
 | 
			
		||||
		categories.add(emoji.category);
 | 
			
		||||
	}
 | 
			
		||||
	return Array.from(categories);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const emojiTags = computed(() => {
 | 
			
		||||
	if (instance.emojis == null) return [];
 | 
			
		||||
	const tags = new Set();
 | 
			
		||||
	for (const emoji of instance.emojis) {
 | 
			
		||||
		for (const tag of emoji.aliases) {
 | 
			
		||||
			tags.add(tag);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return Array.from(tags);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ type Keys =
 | 
			
		||||
	'v' |
 | 
			
		||||
	'lastVersion' |
 | 
			
		||||
	'instance' |
 | 
			
		||||
	'emojis' | // TODO: indexed db
 | 
			
		||||
	'lastEmojisFetchedAt' |
 | 
			
		||||
	'account' |
 | 
			
		||||
	'accounts' |
 | 
			
		||||
	'latestDonationInfoShownAt' |
 | 
			
		||||
 
 | 
			
		||||
@@ -4,89 +4,11 @@ import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
 | 
			
		||||
import { EventEmitter } from 'eventemitter3';
 | 
			
		||||
import insertTextAtCursor from 'insert-text-at-cursor';
 | 
			
		||||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import { apiUrl, url } from '@/config';
 | 
			
		||||
import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
 | 
			
		||||
import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
 | 
			
		||||
import { MenuItem } from '@/types/menu';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
 | 
			
		||||
export const pendingApiRequestsCount = ref(0);
 | 
			
		||||
 | 
			
		||||
const apiClient = new Misskey.api.APIClient({
 | 
			
		||||
	origin: url,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const api = ((endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) => {
 | 
			
		||||
	pendingApiRequestsCount.value++;
 | 
			
		||||
 | 
			
		||||
	const onFinally = () => {
 | 
			
		||||
		pendingApiRequestsCount.value--;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const promise = new Promise((resolve, reject) => {
 | 
			
		||||
		// Append a credential
 | 
			
		||||
		if ($i) (data as any).i = $i.token;
 | 
			
		||||
		if (token !== undefined) (data as any).i = token;
 | 
			
		||||
 | 
			
		||||
		// Send request
 | 
			
		||||
		window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
 | 
			
		||||
			method: 'POST',
 | 
			
		||||
			body: JSON.stringify(data),
 | 
			
		||||
			credentials: 'omit',
 | 
			
		||||
			cache: 'no-cache',
 | 
			
		||||
			headers: {
 | 
			
		||||
				'Content-Type': 'application/json',
 | 
			
		||||
			},
 | 
			
		||||
		}).then(async (res) => {
 | 
			
		||||
			const body = res.status === 204 ? null : await res.json();
 | 
			
		||||
 | 
			
		||||
			if (res.status === 200) {
 | 
			
		||||
				resolve(body);
 | 
			
		||||
			} else if (res.status === 204) {
 | 
			
		||||
				resolve();
 | 
			
		||||
			} else {
 | 
			
		||||
				reject(body.error);
 | 
			
		||||
			}
 | 
			
		||||
		}).catch(reject);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	promise.then(onFinally, onFinally);
 | 
			
		||||
 | 
			
		||||
	return promise;
 | 
			
		||||
}) as typeof apiClient.request;
 | 
			
		||||
 | 
			
		||||
export const apiGet = ((endpoint: string, data: Record<string, any> = {}) => {
 | 
			
		||||
	pendingApiRequestsCount.value++;
 | 
			
		||||
 | 
			
		||||
	const onFinally = () => {
 | 
			
		||||
		pendingApiRequestsCount.value--;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const query = new URLSearchParams(data);
 | 
			
		||||
 | 
			
		||||
	const promise = new Promise((resolve, reject) => {
 | 
			
		||||
		// Send request
 | 
			
		||||
		window.fetch(`${apiUrl}/${endpoint}?${query}`, {
 | 
			
		||||
			method: 'GET',
 | 
			
		||||
			credentials: 'omit',
 | 
			
		||||
			cache: 'default',
 | 
			
		||||
		}).then(async (res) => {
 | 
			
		||||
			const body = res.status === 204 ? null : await res.json();
 | 
			
		||||
 | 
			
		||||
			if (res.status === 200) {
 | 
			
		||||
				resolve(body);
 | 
			
		||||
			} else if (res.status === 204) {
 | 
			
		||||
				resolve();
 | 
			
		||||
			} else {
 | 
			
		||||
				reject(body.error);
 | 
			
		||||
			}
 | 
			
		||||
		}).catch(reject);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	promise.then(onFinally, onFinally);
 | 
			
		||||
 | 
			
		||||
	return promise;
 | 
			
		||||
}) as typeof apiClient.request;
 | 
			
		||||
import { pendingApiRequestsCount, api, apiGet } from '@/scripts/api';
 | 
			
		||||
export { pendingApiRequestsCount, api, apiGet };
 | 
			
		||||
 | 
			
		||||
export const apiWithDialog = ((
 | 
			
		||||
	endpoint: string,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
 | 
			
		||||
		<!-- たくさんあると邪魔
 | 
			
		||||
		<div class="tags">
 | 
			
		||||
			<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
 | 
			
		||||
			<span class="tag _button" v-for="tag in customEmojiTags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
 | 
			
		||||
		</div>
 | 
			
		||||
		-->
 | 
			
		||||
	</div>
 | 
			
		||||
@@ -28,8 +28,8 @@
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent, computed } from 'vue';
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineComponent, computed, watch } from 'vue';
 | 
			
		||||
import XEmoji from './emojis.emoji.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import MkInput from '@/components/MkInput.vue';
 | 
			
		||||
@@ -37,62 +37,42 @@ import MkSelect from '@/components/MkSelect.vue';
 | 
			
		||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
 | 
			
		||||
import MkTab from '@/components/MkTab.vue';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { emojiCategories, emojiTags } from '@/instance';
 | 
			
		||||
import { customEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkButton,
 | 
			
		||||
		MkInput,
 | 
			
		||||
		MkSelect,
 | 
			
		||||
		MkFoldableSection,
 | 
			
		||||
		MkTab,
 | 
			
		||||
		XEmoji,
 | 
			
		||||
	},
 | 
			
		||||
const customEmojiCategories = getCustomEmojiCategories();
 | 
			
		||||
const customEmojiTags = getCustomEmojiTags();
 | 
			
		||||
let q = $ref('');
 | 
			
		||||
let searchEmojis = $ref(null);
 | 
			
		||||
let selectedTags = $ref(new Set());
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			q: '',
 | 
			
		||||
			customEmojiCategories: emojiCategories,
 | 
			
		||||
			customEmojis: this.$instance.emojis,
 | 
			
		||||
			tags: emojiTags,
 | 
			
		||||
			selectedTags: new Set(),
 | 
			
		||||
			searchEmojis: null,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		q() { this.search(); },
 | 
			
		||||
		selectedTags: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.search();
 | 
			
		||||
			},
 | 
			
		||||
			deep: true,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		search() {
 | 
			
		||||
			if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
 | 
			
		||||
				this.searchEmojis = null;
 | 
			
		||||
function search() {
 | 
			
		||||
	if ((q === '' || q == null) && selectedTags.size === 0) {
 | 
			
		||||
		searchEmojis = null;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			if (this.selectedTags.size === 0) {
 | 
			
		||||
				this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q));
 | 
			
		||||
	if (selectedTags.size === 0) {
 | 
			
		||||
		searchEmojis = customEmojis.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
 | 
			
		||||
	} else {
 | 
			
		||||
				this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t)));
 | 
			
		||||
		searchEmojis = customEmojis.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t)));
 | 
			
		||||
	}
 | 
			
		||||
		},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		toggleTag(tag) {
 | 
			
		||||
			if (this.selectedTags.has(tag)) {
 | 
			
		||||
				this.selectedTags.delete(tag);
 | 
			
		||||
function toggleTag(tag) {
 | 
			
		||||
	if (selectedTags.has(tag)) {
 | 
			
		||||
		selectedTags.delete(tag);
 | 
			
		||||
	} else {
 | 
			
		||||
				this.selectedTags.add(tag);
 | 
			
		||||
		selectedTags.add(tag);
 | 
			
		||||
	}
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch($$(q), () => {
 | 
			
		||||
	search();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch($$(selectedTags), () => {
 | 
			
		||||
	search();
 | 
			
		||||
}, { deep: true });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ import MkInput from '@/components/MkInput.vue';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { unique } from '@/scripts/array';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { emojiCategories } from '@/instance';
 | 
			
		||||
import { getCustomEmojiCategories } from '@/custom-emojis';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	emoji: any,
 | 
			
		||||
@@ -46,7 +46,7 @@ let dialog = $ref(null);
 | 
			
		||||
let name: string = $ref(props.emoji.name);
 | 
			
		||||
let category: string = $ref(props.emoji.category);
 | 
			
		||||
let aliases: string = $ref(props.emoji.aliases.join(' '));
 | 
			
		||||
let categories: string[] = $ref(emojiCategories);
 | 
			
		||||
const categories = getCustomEmojiCategories();
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
				<div class="icon"><i class="ti ti-icons"></i></div>
 | 
			
		||||
				<div class="body">
 | 
			
		||||
					<div class="value">
 | 
			
		||||
						<MkNumber :value="$instance.emojis.length" style="margin-right: 0.5em;"/>
 | 
			
		||||
						<MkNumber :value="customEmojis.length" style="margin-right: 0.5em;"/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="label">Custom emojis</div>
 | 
			
		||||
				</div>
 | 
			
		||||
@@ -63,6 +63,7 @@ import number from '@/filters/number';
 | 
			
		||||
import MkNumberDiff from '@/components/MkNumberDiff.vue';
 | 
			
		||||
import MkNumber from '@/components/MkNumber.vue';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { customEmojis } from '@/custom-emojis';
 | 
			
		||||
 | 
			
		||||
let stats: any = $ref(null);
 | 
			
		||||
let usersComparedToThePrevDay = $ref<number>();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
<MkStickyContainer>
 | 
			
		||||
	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
 | 
			
		||||
	<MkSpacer :content-max="800">
 | 
			
		||||
		<div class="_gaps">
 | 
			
		||||
			<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
 | 
			
		||||
				<div>{{ relay.inbox }}</div>
 | 
			
		||||
				<div class="status">
 | 
			
		||||
@@ -12,6 +13,7 @@
 | 
			
		||||
				</div>
 | 
			
		||||
				<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
</MkStickyContainer>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<button class="zuvgdzyu _button" @click="menu">
 | 
			
		||||
	<img :src="`/emoji/${emoji.name}.webp`" class="img" :alt="emoji.name"/>
 | 
			
		||||
	<img :src="`/emoji/${emoji.name}.webp`" class="img" loading="lazy"/>
 | 
			
		||||
	<div class="body">
 | 
			
		||||
		<div class="name _monospace">{{ emoji.name }}</div>
 | 
			
		||||
		<div class="info">{{ emoji.aliases.join(' ') }}</div>
 | 
			
		||||
@@ -49,6 +49,7 @@ function menu(ev) {
 | 
			
		||||
	> .img {
 | 
			
		||||
		width: 42px;
 | 
			
		||||
		height: 42px;
 | 
			
		||||
		object-fit: contain;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .body {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,9 @@
 | 
			
		||||
			</template>
 | 
			
		||||
 | 
			
		||||
			<template #default="{ items }">
 | 
			
		||||
				<XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
 | 
			
		||||
				<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
 | 
			
		||||
					<XNote :key="item.id" :note="item.note" :class="$style.note"/>
 | 
			
		||||
				</XList>
 | 
			
		||||
				</MkDateSeparatedList>
 | 
			
		||||
			</template>
 | 
			
		||||
		</MkPagination>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import MkPagination from '@/components/MkPagination.vue';
 | 
			
		||||
import XNote from '@/components/MkNote.vue';
 | 
			
		||||
import XList from '@/components/MkDateSeparatedList.vue';
 | 
			
		||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { definePageMetadata } from '@/scripts/page-metadata';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
				</template>
 | 
			
		||||
 | 
			
		||||
				<template #default="{ items: messages, fetching: pFetching }">
 | 
			
		||||
					<XList
 | 
			
		||||
					<MkDateSeparatedList
 | 
			
		||||
						v-if="messages.length > 0"
 | 
			
		||||
						v-slot="{ item: message }"
 | 
			
		||||
						:class="{ messages: true, 'deny-move-transition': pFetching }"
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
						reversed
 | 
			
		||||
					>
 | 
			
		||||
						<XMessage :key="message.id" :message="message" :is-group="group != null"/>
 | 
			
		||||
					</XList>
 | 
			
		||||
					</MkDateSeparatedList>
 | 
			
		||||
				</template>
 | 
			
		||||
			</MkPagination>
 | 
			
		||||
		</div>
 | 
			
		||||
@@ -55,7 +55,7 @@ import * as Misskey from 'misskey-js';
 | 
			
		||||
import * as Acct from 'misskey-js/built/acct';
 | 
			
		||||
import XMessage from './messaging-room.message.vue';
 | 
			
		||||
import XForm from './messaging-room.form.vue';
 | 
			
		||||
import XList from '@/components/MkDateSeparatedList.vue';
 | 
			
		||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 | 
			
		||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
 | 
			
		||||
import { isBottomVisible, onScrollBottom, scrollToBottom } from '@/scripts/scroll';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
@@ -351,7 +351,6 @@ definePageMetadata(computed(() => !fetching ? user ? {
 | 
			
		||||
		z-index: 2;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		padding-top: 8px;
 | 
			
		||||
		bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
 | 
			
		||||
 | 
			
		||||
		> .new-message {
 | 
			
		||||
			width: 100%;
 | 
			
		||||
 
 | 
			
		||||
@@ -317,12 +317,13 @@ import MkTextarea from '@/components/MkTextarea.vue';
 | 
			
		||||
import { definePageMetadata } from '@/scripts/page-metadata';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import { customEmojis } from '@/custom-emojis';
 | 
			
		||||
 | 
			
		||||
let preview_mention = $ref('@example');
 | 
			
		||||
let preview_hashtag = $ref('#test');
 | 
			
		||||
let preview_url = $ref('https://example.com');
 | 
			
		||||
let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`);
 | 
			
		||||
let preview_emoji = $ref(instance.emojis.length ? `:${instance.emojis[0].name}:` : ':emojiname:');
 | 
			
		||||
let preview_emoji = $ref(customEmojis.length ? `:${customEmojis[0].name}:` : ':emojiname:');
 | 
			
		||||
let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`);
 | 
			
		||||
let preview_small = $ref(`<small>${i18n.ts._mfm.dummy}</small>`);
 | 
			
		||||
let preview_center = $ref(`<center>${i18n.ts._mfm.dummy}</center>`);
 | 
			
		||||
 
 | 
			
		||||
@@ -183,6 +183,8 @@ const menuDef = computed(() => [{
 | 
			
		||||
		action: () => {
 | 
			
		||||
			miLocalStorage.removeItem('locale');
 | 
			
		||||
			miLocalStorage.removeItem('theme');
 | 
			
		||||
			miLocalStorage.removeItem('emojis');
 | 
			
		||||
			miLocalStorage.removeItem('lastEmojisFetchedAt');
 | 
			
		||||
			unisonReload();
 | 
			
		||||
		},
 | 
			
		||||
	}, {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,15 +2,14 @@
 | 
			
		||||
<MkStickyContainer>
 | 
			
		||||
	<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="headerTabs" :display-my-avatar="true"/></template>
 | 
			
		||||
	<MkSpacer :content-max="800">
 | 
			
		||||
		<div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf">
 | 
			
		||||
			<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _panel" style="margin-bottom: var(--margin);"/>
 | 
			
		||||
			<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
 | 
			
		||||
		<div ref="rootEl" v-hotkey.global="keymap">
 | 
			
		||||
			<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
 | 
			
		||||
			<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
 | 
			
		||||
 | 
			
		||||
			<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
 | 
			
		||||
			<div class="tl">
 | 
			
		||||
			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
 | 
			
		||||
			<div :class="$style.tl">
 | 
			
		||||
				<XTimeline
 | 
			
		||||
					ref="tl" :key="src"
 | 
			
		||||
					class="tl"
 | 
			
		||||
					:src="src"
 | 
			
		||||
					:sound="true"
 | 
			
		||||
					@queue="queueUpdated"
 | 
			
		||||
@@ -154,9 +153,8 @@ definePageMetadata(computed(() => ({
 | 
			
		||||
})));
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.cmuxhskf {
 | 
			
		||||
	> .new {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.new {
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	top: calc(var(--stickyTop, 0px) + 16px);
 | 
			
		||||
	z-index: 1000;
 | 
			
		||||
@@ -168,16 +166,15 @@ definePageMetadata(computed(() => ({
 | 
			
		||||
		padding: 8px 16px;
 | 
			
		||||
		border-radius: 32px;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .post-form {
 | 
			
		||||
.postForm {
 | 
			
		||||
	border-radius: var(--radius);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .tl {
 | 
			
		||||
.tl {
 | 
			
		||||
	background: var(--bg);
 | 
			
		||||
	border-radius: var(--radius);
 | 
			
		||||
	overflow: clip;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								packages/frontend/src/scripts/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								packages/frontend/src/scripts/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
import { Endpoints } from 'misskey-js/built/api.types';
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { apiUrl } from '@/config';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
export const pendingApiRequestsCount = ref(0);
 | 
			
		||||
 | 
			
		||||
// Implements Misskey.api.ApiClient.request
 | 
			
		||||
export function api<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, data: P = {} as any, token?: string | null | undefined): Promise<Endpoints[E]['res']> {
 | 
			
		||||
	pendingApiRequestsCount.value++;
 | 
			
		||||
 | 
			
		||||
	const onFinally = () => {
 | 
			
		||||
		pendingApiRequestsCount.value--;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const promise = new Promise<Endpoints[E]['res'] | void>((resolve, reject) => {
 | 
			
		||||
		// Append a credential
 | 
			
		||||
		if ($i) (data as any).i = $i.token;
 | 
			
		||||
		if (token !== undefined) (data as any).i = token;
 | 
			
		||||
 | 
			
		||||
		// Send request
 | 
			
		||||
		window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
 | 
			
		||||
			method: 'POST',
 | 
			
		||||
			body: JSON.stringify(data),
 | 
			
		||||
			credentials: 'omit',
 | 
			
		||||
			cache: 'no-cache',
 | 
			
		||||
			headers: {
 | 
			
		||||
				'Content-Type': 'application/json',
 | 
			
		||||
			},
 | 
			
		||||
		}).then(async (res) => {
 | 
			
		||||
			const body = res.status === 204 ? null : await res.json();
 | 
			
		||||
 | 
			
		||||
			if (res.status === 200) {
 | 
			
		||||
				resolve(body);
 | 
			
		||||
			} else if (res.status === 204) {
 | 
			
		||||
				resolve();
 | 
			
		||||
			} else {
 | 
			
		||||
				reject(body.error);
 | 
			
		||||
			}
 | 
			
		||||
		}).catch(reject);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	promise.then(onFinally, onFinally);
 | 
			
		||||
 | 
			
		||||
	return promise;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implements Misskey.api.ApiClient.request
 | 
			
		||||
export function apiGet<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, data: P = {} as any): Promise<Endpoints[E]['res']> {
 | 
			
		||||
	pendingApiRequestsCount.value++;
 | 
			
		||||
 | 
			
		||||
	const onFinally = () => {
 | 
			
		||||
		pendingApiRequestsCount.value--;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const query = new URLSearchParams(data as any);
 | 
			
		||||
 | 
			
		||||
	const promise = new Promise<Endpoints[E]['res'] | void>((resolve, reject) => {
 | 
			
		||||
		// Send request
 | 
			
		||||
		window.fetch(`${apiUrl}/${endpoint}?${query}`, {
 | 
			
		||||
			method: 'GET',
 | 
			
		||||
			credentials: 'omit',
 | 
			
		||||
			cache: 'default',
 | 
			
		||||
		}).then(async (res) => {
 | 
			
		||||
			const body = res.status === 204 ? null : await res.json();
 | 
			
		||||
 | 
			
		||||
			if (res.status === 200) {
 | 
			
		||||
				resolve(body);
 | 
			
		||||
			} else if (res.status === 204) {
 | 
			
		||||
				resolve();
 | 
			
		||||
			} else {
 | 
			
		||||
				reject(body.error);
 | 
			
		||||
			}
 | 
			
		||||
		}).catch(reject);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	promise.then(onFinally, onFinally);
 | 
			
		||||
 | 
			
		||||
	return promise;
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,7 @@ export class StickySidebar {
 | 
			
		||||
	private isTop = false;
 | 
			
		||||
	private isBottom = false;
 | 
			
		||||
	private offsetTop: number;
 | 
			
		||||
	private globalHeaderHeight: number = 59;
 | 
			
		||||
	private globalHeaderHeight = 59;
 | 
			
		||||
 | 
			
		||||
	constructor(container: StickySidebar['container'], marginTop = 0, globalHeaderHeight = 0) {
 | 
			
		||||
		this.container = container;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,11 @@
 | 
			
		||||
	--marginHalf: 10px;
 | 
			
		||||
 | 
			
		||||
	--margin: var(--marginFull);
 | 
			
		||||
	--minBottomSpacing: 0px;
 | 
			
		||||
	
 | 
			
		||||
	@media (max-width: 500px) {
 | 
			
		||||
		--margin: var(--marginHalf);
 | 
			
		||||
		--minBottomSpacing: calc(72px + max(12px, env(safe-area-inset-bottom, 0px)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//--ad: rgb(255 169 0 / 10%);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,15 @@
 | 
			
		||||
 | 
			
		||||
<XUpload v-if="uploads.length > 0"/>
 | 
			
		||||
 | 
			
		||||
<TransitionGroup :name="$store.state.animation ? 'notification' : ''" tag="div" class="notifications">
 | 
			
		||||
	<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" class="notification"/>
 | 
			
		||||
<TransitionGroup
 | 
			
		||||
	tag="div" :class="$style.notifications"
 | 
			
		||||
	:move-class="$store.state.animation ? $style.transition_notification_move : ''"
 | 
			
		||||
	:enter-active-class="$store.state.animation ? $style.transition_notification_enterActive : ''"
 | 
			
		||||
	:leave-active-class="$store.state.animation ? $style.transition_notification_leaveActive : ''"
 | 
			
		||||
	:enter-from-class="$store.state.animation ? $style.transition_notification_enterFrom : ''"
 | 
			
		||||
	:leave-to-class="$store.state.animation ? $style.transition_notification_leaveTo : ''"
 | 
			
		||||
>
 | 
			
		||||
	<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" :class="$style.notification"/>
 | 
			
		||||
</TransitionGroup>
 | 
			
		||||
 | 
			
		||||
<XStreamIndicator/>
 | 
			
		||||
@@ -73,11 +80,14 @@ if ($i) {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.notification-move, .notification-enter-active, .notification-leave-active {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.transition_notification_move,
 | 
			
		||||
.transition_notification_enterActive,
 | 
			
		||||
.transition_notification_leaveActive {
 | 
			
		||||
	transition: opacity 0.3s, transform 0.3s !important;
 | 
			
		||||
}
 | 
			
		||||
.notification-enter-from, .notification-leave-to {
 | 
			
		||||
.transition_notification_enterFrom,
 | 
			
		||||
.transition_notification_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: translateX(-250px);
 | 
			
		||||
}
 | 
			
		||||
@@ -91,32 +101,29 @@ if ($i) {
 | 
			
		||||
	padding: 0 32px;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	container-type: inline-size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .notification {
 | 
			
		||||
.notification {
 | 
			
		||||
	& + .notification {
 | 
			
		||||
		margin-top: 8px;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	@media (max-width: 700px) {
 | 
			
		||||
@media (max-width: 500px) {
 | 
			
		||||
	.notifications {
 | 
			
		||||
		top: initial;
 | 
			
		||||
		bottom: 112px;
 | 
			
		||||
		padding: 0 16px;
 | 
			
		||||
		bottom: calc(var(--minBottomSpacing) + var(--margin));
 | 
			
		||||
		padding: 0 var(--margin);
 | 
			
		||||
		display: flex;
 | 
			
		||||
		flex-direction: column-reverse;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		> .notification {
 | 
			
		||||
	.notification {
 | 
			
		||||
		& + .notification {
 | 
			
		||||
			margin-top: 0;
 | 
			
		||||
			margin-bottom: 8px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@media (max-width: 500px) {
 | 
			
		||||
		bottom: calc(env(safe-area-inset-bottom, 0px) + 92px);
 | 
			
		||||
		padding: 0 8px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,8 @@ onUnmounted(() => {
 | 
			
		||||
.nsbbhtug {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	z-index: 16385;
 | 
			
		||||
	bottom: 8px;
 | 
			
		||||
	right: 8px;
 | 
			
		||||
	bottom: calc(var(--minBottomSpacing) + var(--margin));
 | 
			
		||||
	right: var(--margin);
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	padding: 6px 12px;
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
			<XSidebar/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-else ref="widgetsLeft" class="widgets left">
 | 
			
		||||
			<XWidgets place="left" :classic="true" @mounted="attachSticky(widgetsLeft)"/>
 | 
			
		||||
			<XWidgets place="left" :margin-top="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<main class="main" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
		</main>
 | 
			
		||||
 | 
			
		||||
		<div v-if="isDesktop" ref="widgetsRight" class="widgets right">
 | 
			
		||||
			<XWidgets :place="showMenuOnTop ? 'right' : null" :classic="true" @mounted="attachSticky(widgetsRight)"/>
 | 
			
		||||
			<XWidgets :place="showMenuOnTop ? 'right' : null" :margin-top="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +80,7 @@ provide('shouldHeaderThin', showMenuOnTop);
 | 
			
		||||
provide('forceSpacerMin', true);
 | 
			
		||||
 | 
			
		||||
function attachSticky(el) {
 | 
			
		||||
	const sticky = new StickySidebar(el, defaultStore.state.menuDisplay === 'top' ? 0 : 16, defaultStore.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
 | 
			
		||||
	const sticky = new StickySidebar(el, 0, defaultStore.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
 | 
			
		||||
	window.addEventListener('scroll', () => {
 | 
			
		||||
		sticky.calc(window.scrollY);
 | 
			
		||||
	}, { passive: true });
 | 
			
		||||
@@ -248,7 +248,6 @@ onMounted(() => {
 | 
			
		||||
		> .widgets {
 | 
			
		||||
			//--panelBorder: none;
 | 
			
		||||
			width: 300px;
 | 
			
		||||
			margin-top: 16px;
 | 
			
		||||
 | 
			
		||||
			@media (max-width: $widgets-hide-threshold) {
 | 
			
		||||
				display: none;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div
 | 
			
		||||
	class="mk-deck" :class="[{ isMobile }]"
 | 
			
		||||
>
 | 
			
		||||
<div :class="[$style.root, { [$style.rootIsMobile]: isMobile }]">
 | 
			
		||||
	<XSidebar v-if="!isMobile"/>
 | 
			
		||||
 | 
			
		||||
	<div class="main">
 | 
			
		||||
		<XStatusBars class="statusbars"/>
 | 
			
		||||
		<div ref="columnsEl" class="columns" :class="deckStore.reactiveState.columnAlign.value" @contextmenu.self.prevent="onContextmenu">
 | 
			
		||||
	<div :class="$style.main">
 | 
			
		||||
		<XStatusBars/>
 | 
			
		||||
		<div ref="columnsEl" :class="[$style.columns, deckStore.reactiveState.columnAlign.value]" @contextmenu.self.prevent="onContextmenu">
 | 
			
		||||
			<template v-for="ids in layout">
 | 
			
		||||
				<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
 | 
			
		||||
				<section
 | 
			
		||||
					v-if="ids.length > 1"
 | 
			
		||||
					class="folder column"
 | 
			
		||||
					:class="$style.folder"
 | 
			
		||||
					:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
 | 
			
		||||
				>
 | 
			
		||||
					<DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/>
 | 
			
		||||
@@ -20,51 +18,64 @@
 | 
			
		||||
					v-else
 | 
			
		||||
					:ref="ids[0]"
 | 
			
		||||
					:key="ids[0]"
 | 
			
		||||
					class="column"
 | 
			
		||||
					:class="$style.column"
 | 
			
		||||
					:column="columns.find(c => c.id === ids[0])"
 | 
			
		||||
					:is-stacked="false"
 | 
			
		||||
					:style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }"
 | 
			
		||||
					@parent-focus="moveFocus(ids[0], $event)"
 | 
			
		||||
				/>
 | 
			
		||||
			</template>
 | 
			
		||||
			<div v-if="layout.length === 0" class="intro _panel">
 | 
			
		||||
			<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
 | 
			
		||||
				<div>{{ i18n.ts._deck.introduction }}</div>
 | 
			
		||||
				<MkButton primary class="add" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton>
 | 
			
		||||
				<MkButton primary style="margin: 1em auto;" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton>
 | 
			
		||||
				<div>{{ i18n.ts._deck.introduction2 }}</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="sideMenu">
 | 
			
		||||
				<div class="top">
 | 
			
		||||
					<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${deckStore.state.profile}`" class="_button button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
 | 
			
		||||
					<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" class="_button button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
 | 
			
		||||
			<div :class="$style.sideMenu">
 | 
			
		||||
				<div :class="$style.sideMenuTop">
 | 
			
		||||
					<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${deckStore.state.profile}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
 | 
			
		||||
					<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="middle">
 | 
			
		||||
					<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" class="_button button" @click="addColumn"><i class="ti ti-plus"></i></button>
 | 
			
		||||
				<div :class="$style.sideMenuMiddle">
 | 
			
		||||
					<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="bottom">
 | 
			
		||||
					<button v-tooltip.noDelay.left="i18n.ts.settings" class="_button button settings" @click="showSettings"><i class="ti ti-settings"></i></button>
 | 
			
		||||
				<div :class="$style.sideMenuBottom">
 | 
			
		||||
					<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings"></i></button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div v-if="isMobile" class="buttons">
 | 
			
		||||
		<button class="button nav _button" @click="drawerMenuShowing = true"><i class="ti ti-menu-2"></i><span v-if="menuIndicated" class="indicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button class="button home _button" @click="mainRouter.push('/')"><i class="ti ti-home"></i></button>
 | 
			
		||||
		<button class="button notifications _button" @click="mainRouter.push('/my/notifications')"><i class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button class="button post _button" @click="os.post()"><i class="ti ti-pencil"></i></button>
 | 
			
		||||
	<div v-if="isMobile" :class="$style.nav">
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i :class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<Transition :name="$store.state.animation ? 'menu-back' : ''">
 | 
			
		||||
	<Transition
 | 
			
		||||
		:enter-active-class="$store.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
 | 
			
		||||
		:leave-active-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
 | 
			
		||||
		:enter-from-class="$store.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
 | 
			
		||||
		:leave-to-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
 | 
			
		||||
	>
 | 
			
		||||
		<div
 | 
			
		||||
			v-if="drawerMenuShowing"
 | 
			
		||||
			class="menu-back _modalBg"
 | 
			
		||||
			:class="$style.menuBg"
 | 
			
		||||
			class="_modalBg"
 | 
			
		||||
			@click="drawerMenuShowing = false"
 | 
			
		||||
			@touchstart.passive="drawerMenuShowing = false"
 | 
			
		||||
		></div>
 | 
			
		||||
	</Transition>
 | 
			
		||||
 | 
			
		||||
	<Transition :name="$store.state.animation ? 'menu' : ''">
 | 
			
		||||
		<XDrawerMenu v-if="drawerMenuShowing" class="menu"/>
 | 
			
		||||
	<Transition
 | 
			
		||||
		:enter-active-class="$store.state.animation ? $style.transition_menuDrawer_enterActive : ''"
 | 
			
		||||
		:leave-active-class="$store.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
 | 
			
		||||
		:enter-from-class="$store.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
 | 
			
		||||
		:leave-to-class="$store.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
 | 
			
		||||
	>
 | 
			
		||||
		<div v-if="drawerMenuShowing" :class="$style.menu">
 | 
			
		||||
			<XDrawerMenu/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</Transition>
 | 
			
		||||
 | 
			
		||||
	<XCommon/>
 | 
			
		||||
@@ -223,30 +234,30 @@ async function deleteProfile() {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.menu-enter-active,
 | 
			
		||||
.menu-leave-active {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.transition_menuDrawerBg_enterActive,
 | 
			
		||||
.transition_menuDrawerBg_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.transition_menuDrawerBg_enterFrom,
 | 
			
		||||
.transition_menuDrawerBg_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.transition_menuDrawer_enterActive,
 | 
			
		||||
.transition_menuDrawer_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transform: translateX(0);
 | 
			
		||||
	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.menu-enter-from,
 | 
			
		||||
.menu-leave-active {
 | 
			
		||||
.transition_menuDrawer_enterFrom,
 | 
			
		||||
.transition_menuDrawer_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: translateX(-240px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menu-back-enter-active,
 | 
			
		||||
.menu-back-leave-active {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.menu-back-enter-from,
 | 
			
		||||
.menu-back-leave-active {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mk-deck {
 | 
			
		||||
.root {
 | 
			
		||||
	$nav-hide-threshold: 650px; // TODO: どこかに集約したい
 | 
			
		||||
 | 
			
		||||
	--margin: var(--marginHalf);
 | 
			
		||||
@@ -257,18 +268,20 @@ async function deleteProfile() {
 | 
			
		||||
	height: 100dvh;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.isMobile {
 | 
			
		||||
.rootIsMobile {
 | 
			
		||||
	padding-bottom: 100px;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .main {
 | 
			
		||||
.main {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .columns {
 | 
			
		||||
.columns {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	overflow-x: auto;
 | 
			
		||||
@@ -283,37 +296,35 @@ async function deleteProfile() {
 | 
			
		||||
			margin-right: auto;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .column {
 | 
			
		||||
.column {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	border-right: solid var(--deckDividerThickness) var(--deckDivider);
 | 
			
		||||
 | 
			
		||||
	&:first-of-type {
 | 
			
		||||
		border-left: solid var(--deckDividerThickness) var(--deckDivider);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				&.folder {
 | 
			
		||||
.folder {
 | 
			
		||||
	composes: column;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
 | 
			
		||||
	> *:not(:last-of-type) {
 | 
			
		||||
		border-bottom: solid var(--deckDividerThickness) var(--deckDivider);
 | 
			
		||||
	}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .intro {
 | 
			
		||||
.onboarding {
 | 
			
		||||
	padding: 32px;
 | 
			
		||||
	height: min-content;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .add {
 | 
			
		||||
					margin: 1em auto;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .sideMenu {
 | 
			
		||||
.sideMenu {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	margin-right: 0;
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
@@ -321,103 +332,32 @@ async function deleteProfile() {
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	width: 32px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .top, > .middle, > .bottom {
 | 
			
		||||
					> .button {
 | 
			
		||||
.sideMenuButton {
 | 
			
		||||
	display: block;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	aspect-ratio: 1;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .top {
 | 
			
		||||
.sideMenuTop {
 | 
			
		||||
	margin-bottom: auto;
 | 
			
		||||
				}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .middle {
 | 
			
		||||
.sideMenuMiddle {
 | 
			
		||||
	margin-top: auto;
 | 
			
		||||
	margin-bottom: auto;
 | 
			
		||||
				}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
				> .bottom {
 | 
			
		||||
.sideMenuBottom {
 | 
			
		||||
	margin-top: auto;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .buttons {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
		z-index: 1000;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
		padding: 16px;
 | 
			
		||||
		display: flex;
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		box-sizing: border-box;
 | 
			
		||||
 | 
			
		||||
		> .button {
 | 
			
		||||
			position: relative;
 | 
			
		||||
			flex: 1;
 | 
			
		||||
			padding: 0;
 | 
			
		||||
			margin: auto;
 | 
			
		||||
			height: 64px;
 | 
			
		||||
			border-radius: 8px;
 | 
			
		||||
			background: var(--panel);
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
 | 
			
		||||
			&:not(:last-child) {
 | 
			
		||||
				margin-right: 12px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@media (max-width: 400px) {
 | 
			
		||||
				height: 60px;
 | 
			
		||||
 | 
			
		||||
				&:not(:last-child) {
 | 
			
		||||
					margin-right: 8px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:hover {
 | 
			
		||||
				background: var(--X2);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> .indicator {
 | 
			
		||||
				position: absolute;
 | 
			
		||||
				top: 0;
 | 
			
		||||
				left: 0;
 | 
			
		||||
				color: var(--indicator);
 | 
			
		||||
				font-size: 16px;
 | 
			
		||||
				animation: blink 1s infinite;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:first-child {
 | 
			
		||||
				margin-left: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:last-child {
 | 
			
		||||
				margin-right: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			> * {
 | 
			
		||||
				font-size: 20px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:disabled {
 | 
			
		||||
				cursor: default;
 | 
			
		||||
 | 
			
		||||
				> * {
 | 
			
		||||
					opacity: 0.5;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .menu-back {
 | 
			
		||||
.menuBg {
 | 
			
		||||
	z-index: 1001;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .menu {
 | 
			
		||||
.menu {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
@@ -429,6 +369,69 @@ async function deleteProfile() {
 | 
			
		||||
	overflow: auto;
 | 
			
		||||
	overscroll-behavior: contain;
 | 
			
		||||
	background: var(--navBg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	z-index: 1000;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
 | 
			
		||||
	display: grid;
 | 
			
		||||
	grid-template-columns: 1fr 1fr 1fr 1fr;
 | 
			
		||||
	grid-gap: 8px;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	-webkit-backdrop-filter: var(--blur, blur(32px));
 | 
			
		||||
	backdrop-filter: var(--blur, blur(32px));
 | 
			
		||||
	background-color: var(--header);
 | 
			
		||||
	border-top: solid 0.5px var(--divider);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navButton {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	aspect-ratio: 1;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	max-width: 60px;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	border-radius: 100%;
 | 
			
		||||
	background: var(--panel);
 | 
			
		||||
	color: var(--fg);
 | 
			
		||||
 | 
			
		||||
	&:hover {
 | 
			
		||||
		background: var(--panelHighlight);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:active {
 | 
			
		||||
		background: var(--X2);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.postButton {
 | 
			
		||||
	composes: navButton;
 | 
			
		||||
	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
 | 
			
		||||
	color: var(--fgOnAccent);
 | 
			
		||||
 | 
			
		||||
	&:hover {
 | 
			
		||||
		background: linear-gradient(90deg, var(--X8), var(--X8));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:active {
 | 
			
		||||
		background: linear-gradient(90deg, var(--X8), var(--X8));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navButtonIcon {
 | 
			
		||||
	font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navButtonIndicator {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	color: var(--indicator);
 | 
			
		||||
	font-size: 16px;
 | 
			
		||||
	animation: blink 1s infinite;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
 | 
			
		||||
<section
 | 
			
		||||
	v-hotkey="keymap" class="dnpfarvg"
 | 
			
		||||
	:class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }"
 | 
			
		||||
	v-hotkey="keymap"
 | 
			
		||||
	:class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.isStacked]: isStacked, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]"
 | 
			
		||||
	@dragover.prevent.stop="onDragover"
 | 
			
		||||
	@dragleave="onDragleave"
 | 
			
		||||
	@drop.prevent.stop="onDrop"
 | 
			
		||||
>
 | 
			
		||||
	<header
 | 
			
		||||
		:class="{ indicated }"
 | 
			
		||||
		:class="[$style.header]"
 | 
			
		||||
		draggable="true"
 | 
			
		||||
		@click="goTop"
 | 
			
		||||
		@dragstart="onDragstart"
 | 
			
		||||
		@dragend="onDragend"
 | 
			
		||||
		@contextmenu.prevent.stop="onContextmenu"
 | 
			
		||||
	>
 | 
			
		||||
		<button v-if="isStacked && !isMainColumn" class="toggleActive _button" @click="toggleActive">
 | 
			
		||||
		<button v-if="isStacked && !isMainColumn" :class="$style.toggleActive" class="_button" @click="toggleActive">
 | 
			
		||||
			<template v-if="active"><i class="ti ti-chevron-up"></i></template>
 | 
			
		||||
			<template v-else><i class="ti ti-chevron-down"></i></template>
 | 
			
		||||
		</button>
 | 
			
		||||
		<div class="action">
 | 
			
		||||
			<slot name="action"></slot>
 | 
			
		||||
		</div>
 | 
			
		||||
		<span class="header"><slot name="header"></slot></span>
 | 
			
		||||
		<button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button>
 | 
			
		||||
		<span :class="$style.title"><slot name="header"></slot></span>
 | 
			
		||||
		<button v-tooltip="i18n.ts.settings" :class="$style.menu" class="_button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button>
 | 
			
		||||
	</header>
 | 
			
		||||
	<div v-show="active" ref="body">
 | 
			
		||||
	<div v-show="active" ref="body" :class="$style.body">
 | 
			
		||||
		<slot></slot>
 | 
			
		||||
	</div>
 | 
			
		||||
</section>
 | 
			
		||||
@@ -46,12 +43,10 @@ const props = withDefaults(defineProps<{
 | 
			
		||||
	column: Column;
 | 
			
		||||
	isStacked?: boolean;
 | 
			
		||||
	naked?: boolean;
 | 
			
		||||
	indicated?: boolean;
 | 
			
		||||
	menu?: MenuItem[];
 | 
			
		||||
}>(), {
 | 
			
		||||
	isStacked: false,
 | 
			
		||||
	naked: false,
 | 
			
		||||
	indicated: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
@@ -245,8 +240,8 @@ function onDrop(ev) {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.dnpfarvg {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	--root-margin: 10px;
 | 
			
		||||
	--deckColumnHeaderHeight: 40px;
 | 
			
		||||
 | 
			
		||||
@@ -292,10 +287,6 @@ function onDrop(ev) {
 | 
			
		||||
	&:not(.active) {
 | 
			
		||||
		flex-basis: var(--deckColumnHeaderHeight);
 | 
			
		||||
		min-height: var(--deckColumnHeaderHeight);
 | 
			
		||||
 | 
			
		||||
		> header.indicated {
 | 
			
		||||
			box-shadow: 4px 0px var(--accent) inset;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.naked {
 | 
			
		||||
@@ -303,21 +294,19 @@ function onDrop(ev) {
 | 
			
		||||
		-webkit-backdrop-filter: var(--blur, blur(10px));
 | 
			
		||||
		backdrop-filter: var(--blur, blur(10px));
 | 
			
		||||
 | 
			
		||||
		> header {
 | 
			
		||||
		> .header {
 | 
			
		||||
			background: transparent;
 | 
			
		||||
			box-shadow: none;
 | 
			
		||||
 | 
			
		||||
			> button {
 | 
			
		||||
			color: var(--fg);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&.paged {
 | 
			
		||||
		background: var(--bg) !important;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> header {
 | 
			
		||||
.header {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	z-index: 2;
 | 
			
		||||
@@ -333,26 +322,19 @@ function onDrop(ev) {
 | 
			
		||||
	&, * {
 | 
			
		||||
		user-select: none;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		&.indicated {
 | 
			
		||||
			box-shadow: 0 3px 0 0 var(--accent);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .header {
 | 
			
		||||
.title {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> span:only-of-type {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .toggleActive,
 | 
			
		||||
		> .action > ::v-deep(*),
 | 
			
		||||
		> .menu {
 | 
			
		||||
.toggleActive,
 | 
			
		||||
.menu {
 | 
			
		||||
	z-index: 1;
 | 
			
		||||
	width: var(--deckColumnHeaderHeight);
 | 
			
		||||
	line-height: var(--deckColumnHeaderHeight);
 | 
			
		||||
@@ -365,27 +347,18 @@ function onDrop(ev) {
 | 
			
		||||
	&:active {
 | 
			
		||||
		color: var(--faceTextButtonActive);
 | 
			
		||||
	}
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .toggleActive, > .action {
 | 
			
		||||
.toggleActive {
 | 
			
		||||
	margin-left: -16px;
 | 
			
		||||
		}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .action {
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .action:empty {
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .menu {
 | 
			
		||||
.menu {
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
	margin-right: -16px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> div {
 | 
			
		||||
.body {
 | 
			
		||||
	height: calc(100% - var(--deckColumnHeaderHeight));
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
	overflow-x: hidden; // Safari does not supports clip
 | 
			
		||||
@@ -394,6 +367,5 @@ function onDrop(ev) {
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	container-type: inline-size;
 | 
			
		||||
	background-color: var(--bg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)">
 | 
			
		||||
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
 | 
			
		||||
	<template #header>
 | 
			
		||||
		<i v-if="column.tl === 'home'" class="ti ti-home"></i>
 | 
			
		||||
		<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
		</p>
 | 
			
		||||
		<p class="desc">{{ $t('disabled-timeline.description') }}</p>
 | 
			
		||||
	</div>
 | 
			
		||||
	<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/>
 | 
			
		||||
	<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')"/>
 | 
			
		||||
</XColumn>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -40,8 +40,6 @@ const emit = defineEmits<{
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
let disabled = $ref(false);
 | 
			
		||||
let indicated = $ref(false);
 | 
			
		||||
let columnActive = $ref(true);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	if (props.column.tl == null) {
 | 
			
		||||
@@ -77,26 +75,6 @@ async function setType() {
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function queueUpdated(q) {
 | 
			
		||||
	if (columnActive) {
 | 
			
		||||
		indicated = q !== 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onNote() {
 | 
			
		||||
	if (!columnActive) {
 | 
			
		||||
		indicated = true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onChangeActiveState(state) {
 | 
			
		||||
	columnActive = state;
 | 
			
		||||
 | 
			
		||||
	if (columnActive) {
 | 
			
		||||
		indicated = false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const menu = [{
 | 
			
		||||
	icon: 'ti ti-pencil',
 | 
			
		||||
	text: i18n.ts.timeline,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="dkgtipfy" :class="{ wallpaper }">
 | 
			
		||||
	<XSidebar v-if="!isMobile" class="sidebar"/>
 | 
			
		||||
<div :class="[$style.root, { [$style.withWallpaper]: wallpaper }]">
 | 
			
		||||
	<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
 | 
			
		||||
 | 
			
		||||
	<MkStickyContainer class="contents">
 | 
			
		||||
	<MkStickyContainer :class="$style.contents">
 | 
			
		||||
		<template #header><XStatusBars :class="$style.statusbars"/></template>
 | 
			
		||||
		<main style="min-width: 0;" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
 | 
			
		||||
			<div :class="$style.content" style="container-type: inline-size;">
 | 
			
		||||
@@ -12,44 +12,71 @@
 | 
			
		||||
		</main>
 | 
			
		||||
	</MkStickyContainer>
 | 
			
		||||
 | 
			
		||||
	<div v-if="isDesktop" ref="widgetsEl" class="widgets">
 | 
			
		||||
		<XWidgets @mounted="attachSticky"/>
 | 
			
		||||
	<div v-if="isDesktop" ref="widgetsEl" :class="$style.widgets">
 | 
			
		||||
		<XWidgets :margin-top="'var(--margin)'" @mounted="attachSticky"/>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<button v-if="!isDesktop && !isMobile" class="widgetButton _button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
 | 
			
		||||
	<button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
 | 
			
		||||
 | 
			
		||||
	<div v-if="isMobile" class="buttons">
 | 
			
		||||
		<button class="button nav _button" @click="drawerMenuShowing = true"><i class="icon ti ti-menu-2"></i><span v-if="menuIndicated" class="indicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button class="button home _button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i class="icon ti ti-home"></i></button>
 | 
			
		||||
		<button class="button notifications _button" @click="mainRouter.push('/my/notifications')"><i class="icon ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button class="button widget _button" @click="widgetsShowing = true"><i class="icon ti ti-apps"></i></button>
 | 
			
		||||
		<button class="button post _button" @click="os.post()"><i class="icon ti ti-pencil"></i></button>
 | 
			
		||||
	<div v-if="isMobile" :class="$style.nav">
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i :class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
 | 
			
		||||
		<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ti ti-apps"></i></button>
 | 
			
		||||
		<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<Transition :name="$store.state.animation ? 'menuDrawer-back' : ''">
 | 
			
		||||
	<Transition
 | 
			
		||||
		:enter-active-class="$store.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
 | 
			
		||||
		:leave-active-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
 | 
			
		||||
		:enter-from-class="$store.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
 | 
			
		||||
		:leave-to-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
 | 
			
		||||
	>
 | 
			
		||||
		<div
 | 
			
		||||
			v-if="drawerMenuShowing"
 | 
			
		||||
			class="menuDrawer-back _modalBg"
 | 
			
		||||
			:class="$style.menuDrawerBg"
 | 
			
		||||
			class="_modalBg"
 | 
			
		||||
			@click="drawerMenuShowing = false"
 | 
			
		||||
			@touchstart.passive="drawerMenuShowing = false"
 | 
			
		||||
		></div>
 | 
			
		||||
	</Transition>
 | 
			
		||||
 | 
			
		||||
	<Transition :name="$store.state.animation ? 'menuDrawer' : ''">
 | 
			
		||||
		<XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/>
 | 
			
		||||
	<Transition
 | 
			
		||||
		:enter-active-class="$store.state.animation ? $style.transition_menuDrawer_enterActive : ''"
 | 
			
		||||
		:leave-active-class="$store.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
 | 
			
		||||
		:enter-from-class="$store.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
 | 
			
		||||
		:leave-to-class="$store.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
 | 
			
		||||
	>
 | 
			
		||||
		<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
 | 
			
		||||
			<XDrawerMenu/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</Transition>
 | 
			
		||||
 | 
			
		||||
	<Transition :name="$store.state.animation ? 'widgetsDrawer-back' : ''">
 | 
			
		||||
	<Transition
 | 
			
		||||
		:enter-active-class="$store.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
 | 
			
		||||
		:leave-active-class="$store.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
 | 
			
		||||
		:enter-from-class="$store.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
 | 
			
		||||
		:leave-to-class="$store.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
 | 
			
		||||
	>
 | 
			
		||||
		<div
 | 
			
		||||
			v-if="widgetsShowing"
 | 
			
		||||
			class="widgetsDrawer-back _modalBg"
 | 
			
		||||
			:class="$style.widgetsDrawerBg"
 | 
			
		||||
			class="_modalBg"
 | 
			
		||||
			@click="widgetsShowing = false"
 | 
			
		||||
			@touchstart.passive="widgetsShowing = false"
 | 
			
		||||
		></div>
 | 
			
		||||
	</Transition>
 | 
			
		||||
 | 
			
		||||
	<Transition :name="$store.state.animation ? 'widgetsDrawer' : ''">
 | 
			
		||||
		<XWidgets v-if="widgetsShowing" class="widgetsDrawer"/>
 | 
			
		||||
	<Transition
 | 
			
		||||
		:enter-active-class="$store.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
 | 
			
		||||
		:leave-active-class="$store.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
 | 
			
		||||
		:enter-from-class="$store.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
 | 
			
		||||
		:leave-to-class="$store.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
 | 
			
		||||
	>
 | 
			
		||||
		<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
 | 
			
		||||
			<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
 | 
			
		||||
			<XWidgets/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</Transition>
 | 
			
		||||
 | 
			
		||||
	<XCommon/>
 | 
			
		||||
@@ -174,75 +201,76 @@ function top() {
 | 
			
		||||
const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.widgetsDrawer-enter-active,
 | 
			
		||||
.widgetsDrawer-leave-active {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transform: translateX(0);
 | 
			
		||||
	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.widgetsDrawer-enter-from,
 | 
			
		||||
.widgetsDrawer-leave-active {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: translateX(240px);
 | 
			
		||||
}
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
$ui-font-size: 1em; // TODO: どこかに集約したい
 | 
			
		||||
$widgets-hide-threshold: 1090px;
 | 
			
		||||
 | 
			
		||||
.widgetsDrawer-back-enter-active,
 | 
			
		||||
.widgetsDrawer-back-leave-active {
 | 
			
		||||
.transition_menuDrawerBg_enterActive,
 | 
			
		||||
.transition_menuDrawerBg_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.widgetsDrawer-back-enter-from,
 | 
			
		||||
.widgetsDrawer-back-leave-active {
 | 
			
		||||
.transition_menuDrawerBg_enterFrom,
 | 
			
		||||
.transition_menuDrawerBg_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menuDrawer-enter-active,
 | 
			
		||||
.menuDrawer-leave-active {
 | 
			
		||||
.transition_menuDrawer_enterActive,
 | 
			
		||||
.transition_menuDrawer_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transform: translateX(0);
 | 
			
		||||
	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.menuDrawer-enter-from,
 | 
			
		||||
.menuDrawer-leave-active {
 | 
			
		||||
.transition_menuDrawer_enterFrom,
 | 
			
		||||
.transition_menuDrawer_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: translateX(-240px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menuDrawer-back-enter-active,
 | 
			
		||||
.menuDrawer-back-leave-active {
 | 
			
		||||
.transition_widgetsDrawerBg_enterActive,
 | 
			
		||||
.transition_widgetsDrawerBg_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.menuDrawer-back-enter-from,
 | 
			
		||||
.menuDrawer-back-leave-active {
 | 
			
		||||
.transition_widgetsDrawerBg_enterFrom,
 | 
			
		||||
.transition_widgetsDrawerBg_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dkgtipfy {
 | 
			
		||||
	$ui-font-size: 1em; // TODO: どこかに集約したい
 | 
			
		||||
	$widgets-hide-threshold: 1090px;
 | 
			
		||||
.transition_widgetsDrawer_enterActive,
 | 
			
		||||
.transition_widgetsDrawer_leaveActive {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	transform: translateX(0);
 | 
			
		||||
	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
}
 | 
			
		||||
.transition_widgetsDrawer_enterFrom,
 | 
			
		||||
.transition_widgetsDrawer_leaveTo {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transform: translateX(240px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.root {
 | 
			
		||||
	min-height: 100dvh;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.wallpaper {
 | 
			
		||||
.withWallpaper {
 | 
			
		||||
	background: var(--wallpaperOverlay);
 | 
			
		||||
	//backdrop-filter: var(--blur, blur(4px));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .sidebar {
 | 
			
		||||
.sidebar {
 | 
			
		||||
	border-right: solid 0.5px var(--divider);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .contents {
 | 
			
		||||
.contents {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	min-width: 0;
 | 
			
		||||
	background: var(--bg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .widgets {
 | 
			
		||||
.widgets {
 | 
			
		||||
	padding: 0 var(--margin);
 | 
			
		||||
	border-left: solid 0.5px var(--divider);
 | 
			
		||||
	background: var(--bg);
 | 
			
		||||
@@ -250,9 +278,9 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
	@media (max-width: $widgets-hide-threshold) {
 | 
			
		||||
		display: none;
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .widgetButton {
 | 
			
		||||
.widgetButton {
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	z-index: 1000;
 | 
			
		||||
@@ -264,13 +292,13 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
	box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
 | 
			
		||||
	font-size: 22px;
 | 
			
		||||
	background: var(--panel);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .widgetsDrawer-back {
 | 
			
		||||
.widgetsDrawerBg {
 | 
			
		||||
	z-index: 1001;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .widgetsDrawer {
 | 
			
		||||
.widgetsDrawer {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	right: 0;
 | 
			
		||||
@@ -281,9 +309,21 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
	overflow: auto;
 | 
			
		||||
	overscroll-behavior: contain;
 | 
			
		||||
	background: var(--bg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .buttons {
 | 
			
		||||
.widgetsCloseButton {
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 370px) {
 | 
			
		||||
	.widgetsCloseButton {
 | 
			
		||||
		display: none;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	z-index: 1000;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
@@ -298,8 +338,9 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
	backdrop-filter: var(--blur, blur(32px));
 | 
			
		||||
	background-color: var(--header);
 | 
			
		||||
	border-top: solid 0.5px var(--divider);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		> .button {
 | 
			
		||||
.navButton {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	aspect-ratio: 1;
 | 
			
		||||
@@ -311,42 +352,46 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
	color: var(--fg);
 | 
			
		||||
 | 
			
		||||
	&:hover {
 | 
			
		||||
				background: var(--X2);
 | 
			
		||||
		background: var(--panelHighlight);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			> .indicator {
 | 
			
		||||
	&:active {
 | 
			
		||||
		background: var(--X2);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.postButton {
 | 
			
		||||
	composes: navButton;
 | 
			
		||||
	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
 | 
			
		||||
	color: var(--fgOnAccent);
 | 
			
		||||
 | 
			
		||||
	&:hover {
 | 
			
		||||
		background: linear-gradient(90deg, var(--X8), var(--X8));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&:active {
 | 
			
		||||
		background: linear-gradient(90deg, var(--X8), var(--X8));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navButtonIcon {
 | 
			
		||||
	font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navButtonIndicator {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	color: var(--indicator);
 | 
			
		||||
	font-size: 16px;
 | 
			
		||||
	animation: blink 1s infinite;
 | 
			
		||||
			}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
			> .icon {
 | 
			
		||||
				font-size: 18px;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:disabled {
 | 
			
		||||
				cursor: default;
 | 
			
		||||
 | 
			
		||||
				> .icon {
 | 
			
		||||
					opacity: 0.5;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&.post {
 | 
			
		||||
				background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
 | 
			
		||||
				color: var(--fgOnAccent);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .menuDrawer-back {
 | 
			
		||||
.menuDrawerBg {
 | 
			
		||||
	z-index: 1001;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	> .menuDrawer {
 | 
			
		||||
.menuDrawer {
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
@@ -358,11 +403,8 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
	overflow: auto;
 | 
			
		||||
	overscroll-behavior: contain;
 | 
			
		||||
	background: var(--navBg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.statusbars {
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	top: 0;
 | 
			
		||||
@@ -370,12 +412,6 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spacer {
 | 
			
		||||
	$widgets-hide-threshold: 1090px;
 | 
			
		||||
 | 
			
		||||
	height: calc(env(safe-area-inset-bottom, 0px) + 96px);
 | 
			
		||||
 | 
			
		||||
	@media (min-width: ($widgets-hide-threshold + 1px)) {
 | 
			
		||||
		display: none;
 | 
			
		||||
	}
 | 
			
		||||
	height: calc(var(--minBottomSpacing));
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="efzpzdvf" :class="{ universal: !classic, classic }">
 | 
			
		||||
	<XWidgets :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
 | 
			
		||||
<div :class="$style.root" :style="{ paddingTop: marginTop }">
 | 
			
		||||
	<XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
 | 
			
		||||
 | 
			
		||||
	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
 | 
			
		||||
	<button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
 | 
			
		||||
@@ -21,10 +21,10 @@ const props = withDefaults(defineProps<{
 | 
			
		||||
	// left = place: leftだけを表示
 | 
			
		||||
	// right = rightとnullを表示
 | 
			
		||||
	place?: 'left' | null | 'right';
 | 
			
		||||
	classic?: boolean;
 | 
			
		||||
	marginTop?: string;
 | 
			
		||||
}>(), {
 | 
			
		||||
	place: null,
 | 
			
		||||
	classic: false,
 | 
			
		||||
	marginTop: '0',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
@@ -81,31 +81,14 @@ function updateWidgets(thisWidgets) {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.efzpzdvf {
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.root {
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	height: min-content;
 | 
			
		||||
	min-height: 100vh;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	&.universal {
 | 
			
		||||
		padding-top: var(--margin);
 | 
			
		||||
 | 
			
		||||
		> * {
 | 
			
		||||
			margin: var(--margin) 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> * {
 | 
			
		||||
.widgets {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
 | 
			
		||||
		&:first-child {
 | 
			
		||||
			margin-top: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .add {
 | 
			
		||||
		margin: 0 auto;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,8 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
 | 
			
		||||
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 | 
			
		||||
import XCalendar from './activity.calendar.vue';
 | 
			
		||||
import XChart from './activity.chart.vue';
 | 
			
		||||
import XCalendar from './WidgetActivity.calendar.vue';
 | 
			
		||||
import XChart from './WidgetActivity.chart.vue';
 | 
			
		||||
import { GetFormResultType } from '@/scripts/form';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import MkContainer from '@/components/MkContainer.vue';
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user