Compare commits
	
		
			53 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 | 
							
								
								
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -61,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 | ||||
| @@ -74,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 | ||||
| @@ -107,6 +112,7 @@ You should also include the user name that made the change. | ||||
| - 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: "الخيط الزمني" | ||||
|   | ||||
| @@ -1200,6 +1200,8 @@ _weekday: | ||||
|   friday: "শুক্রবার" | ||||
|   saturday: "শনিবার" | ||||
| _widgets: | ||||
|   profile: "প্রোফাইল" | ||||
|   instanceInfo: "ইন্সট্যান্সের তথ্য" | ||||
|   memo: "স্টিকি নোট" | ||||
|   notifications: "বিজ্ঞপ্তি" | ||||
|   timeline: "টাইমলাইন" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -1289,6 +1289,8 @@ _weekday: | ||||
|   friday: "Vendredi" | ||||
|   saturday: "Samedi" | ||||
| _widgets: | ||||
|   profile: "Profil" | ||||
|   instanceInfo: "Informations sur l’instance" | ||||
|   memo: "Note collante" | ||||
|   notifications: "Notifications" | ||||
|   timeline: "Fil" | ||||
|   | ||||
| @@ -1206,6 +1206,8 @@ _weekday: | ||||
|   friday: "Jumat" | ||||
|   saturday: "Sabtu" | ||||
| _widgets: | ||||
|   profile: "Profil" | ||||
|   instanceInfo: "Informasi Instansi" | ||||
|   memo: "Catatan memo" | ||||
|   notifications: "Pemberitahuan" | ||||
|   timeline: "Linimasa" | ||||
|   | ||||
| @@ -1296,6 +1296,8 @@ _weekday: | ||||
|   friday: "Venerdì" | ||||
|   saturday: "Sabato" | ||||
| _widgets: | ||||
|   profile: "Profilo" | ||||
|   instanceInfo: "Informazioni sull'istanza" | ||||
|   memo: "Promemoria" | ||||
|   notifications: "Notifiche" | ||||
|   timeline: "Timeline" | ||||
|   | ||||
| @@ -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: "タイムライン" | ||||
|   | ||||
| @@ -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: "타임라인" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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: "Лента" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -327,7 +327,16 @@ 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" | ||||
| @@ -377,7 +386,16 @@ _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" | ||||
| @@ -395,6 +413,7 @@ _profile: | ||||
|   changeAvatar: "Ändra profilbild" | ||||
|   changeBanner: "Ändra banner" | ||||
| _exportOrImport: | ||||
|   allNotes: "Alla noter" | ||||
|   followingList: "Följer" | ||||
|   muteList: "Tysta" | ||||
|   blockingList: "Blockera" | ||||
| @@ -423,5 +442,6 @@ _deck: | ||||
|   _columns: | ||||
|     notifications: "Notifikationer" | ||||
|     tl: "Tidslinje" | ||||
|     antenna: "Antenner" | ||||
|     list: "Listor" | ||||
|     mentions: "Omnämningar" | ||||
|   | ||||
| @@ -1302,6 +1302,8 @@ _weekday: | ||||
|   friday: "วันศุกร์" | ||||
|   saturday: "วันเสาร์" | ||||
| _widgets: | ||||
|   profile: "โปรไฟล์" | ||||
|   instanceInfo: "ข้อมูล อินสแตนซ์" | ||||
|   memo: "โน้ตแปะ" | ||||
|   notifications: "การเเจ้งเตือน" | ||||
|   timeline: "ไทม์ไลน์" | ||||
|   | ||||
| @@ -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: "Стрічка" | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -922,7 +922,8 @@ numberOfLikes: "点赞数" | ||||
| show: "显示" | ||||
| neverShow: "不再显示" | ||||
| remindMeLater: "稍后提醒我" | ||||
| didYouLikeMisskey: "你在Misskey玩得还开心吗?" | ||||
| didYouLikeMisskey: "您喜欢Misskey吗?" | ||||
| pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发,请在有余力的情况下进行捐助!" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" | ||||
|   sensitivity: "检测敏感度" | ||||
| @@ -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: "查看更多" | ||||
|   | ||||
| @@ -797,7 +797,7 @@ squareAvatars: "頭像以方形顯示" | ||||
| sent: "發送" | ||||
| received: "收取" | ||||
| searchResult: "搜尋結果" | ||||
| hashtags: "#tag" | ||||
| hashtags: "標籤" | ||||
| troubleshooting: "故障排除" | ||||
| useBlurEffect: "在 UI 上使用模糊效果" | ||||
| learnMore: "更多資訊" | ||||
| @@ -1159,7 +1159,7 @@ _theme: | ||||
|     navActive: "側邊欄文本 (活動)" | ||||
|     navIndicator: "側邊欄指示符" | ||||
|     link: "鏈接" | ||||
|     hashtag: "#tag" | ||||
|     hashtag: "標籤" | ||||
|     mention: "提到" | ||||
|     mentionMe: "提到了我" | ||||
|     renote: "轉發貼文" | ||||
| @@ -1302,6 +1302,8 @@ _weekday: | ||||
|   friday: "週五" | ||||
|   saturday: "週六" | ||||
| _widgets: | ||||
|   profile: "個人檔案" | ||||
|   instanceInfo: "實例資訊" | ||||
|   memo: "備忘錄" | ||||
|   notifications: "通知" | ||||
|   timeline: "時間軸" | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "13.0.0-beta.36", | ||||
| 	"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`); | ||||
|     } | ||||
| } | ||||
| @@ -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[]; | ||||
|  | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -22,10 +22,9 @@ export const meta = { | ||||
| 					type: 'object', | ||||
| 					optional: false, nullable: false, | ||||
| 					properties: { | ||||
| 						id: { | ||||
| 						name: { | ||||
| 							type: 'string', | ||||
| 							optional: false, nullable: false, | ||||
| 							format: 'id', | ||||
| 						}, | ||||
| 						aliases: { | ||||
| 							type: 'array', | ||||
|   | ||||
| @@ -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 }), | ||||
|   | ||||
| @@ -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 { | ||||
| 		font-size: 24px; | ||||
| .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 { | ||||
| 			margin-top: 8px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> header { | ||||
| 		margin: 0 0 8px 0; | ||||
| 		font-weight: bold; | ||||
| 		font-size: 1.1em; | ||||
|  | ||||
| 		& + .body { | ||||
| 			margin-top: 8px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .body { | ||||
| 		margin: 16px 0 0 0; | ||||
| 	} | ||||
|  | ||||
| 	> .buttons { | ||||
| 		margin-top: 16px; | ||||
|  | ||||
| 		> * { | ||||
| 			margin: 0 8px; | ||||
| 		} | ||||
| 	& + .title { | ||||
| 		margin-top: 8px; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .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; | ||||
|  | ||||
| 	& + .text { | ||||
| 		margin-top: 8px; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .text { | ||||
| 	margin: 16px 0 0 0; | ||||
| } | ||||
|  | ||||
| .buttons { | ||||
| 	margin-top: 16px; | ||||
| 	display: flex; | ||||
| 	gap: 8px; | ||||
| 	flex-wrap: wrap; | ||||
| 	justify-content: center; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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 { | ||||
| 		padding-left: 2px; | ||||
| 		font-size: .9em; | ||||
| 	} | ||||
| <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,143 +200,8 @@ onBeforeUnmount(() => { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .item { | ||||
| 		display: block; | ||||
| 		position: relative; | ||||
| 		padding: 5px 16px; | ||||
| 		width: 100%; | ||||
| 		box-sizing: border-box; | ||||
| 		white-space: nowrap; | ||||
| 		font-size: 0.9em; | ||||
| 		line-height: 20px; | ||||
| 		text-align: left; | ||||
| 		overflow: hidden; | ||||
| 		text-overflow: ellipsis; | ||||
|  | ||||
| 		&:before { | ||||
| 			content: ""; | ||||
| 			display: block; | ||||
| 			position: absolute; | ||||
| 			z-index: -1; | ||||
| 			top: 0; | ||||
| 			left: 0; | ||||
| 			right: 0; | ||||
| 			margin: auto; | ||||
| 			width: calc(100% - 16px); | ||||
| 			height: 100%; | ||||
| 			border-radius: 6px; | ||||
| 		} | ||||
|  | ||||
| 		&:not(:disabled):hover { | ||||
| 			color: var(--accent); | ||||
| 			text-decoration: none; | ||||
|  | ||||
| 			&:before { | ||||
| 				background: var(--accentedBg); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		&.danger { | ||||
| 			color: #ff2a2a; | ||||
|  | ||||
| 			&:hover { | ||||
| 				color: #fff; | ||||
|  | ||||
| 				&:before { | ||||
| 					background: #ff4242; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			&:active { | ||||
| 				color: #fff; | ||||
|  | ||||
| 				&:before { | ||||
| 					background: #d42e2e !important; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		&:active, | ||||
| 		&.active { | ||||
| 			color: var(--fgOnAccent) !important; | ||||
| 			opacity: 1; | ||||
|  | ||||
| 			&:before { | ||||
| 				background: var(--accent) !important; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		&:not(:active):focus-visible { | ||||
| 			box-shadow: 0 0 0 2px var(--focus) inset; | ||||
| 		} | ||||
|  | ||||
| 		&.label { | ||||
| 			pointer-events: none; | ||||
| 			font-size: 0.7em; | ||||
| 			padding-bottom: 4px; | ||||
|  | ||||
| 			> span { | ||||
| 				opacity: 0.7; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		&.pending { | ||||
| 			pointer-events: none; | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
|  | ||||
| 		&.none { | ||||
| 			pointer-events: none; | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
|  | ||||
| 		&.parent { | ||||
| 			display: flex; | ||||
| 			align-items: center; | ||||
| 			cursor: default; | ||||
|  | ||||
| 			> .caret { | ||||
| 				margin-left: auto; | ||||
| 			} | ||||
|  | ||||
| 			&.childShowing { | ||||
| 				color: var(--accent); | ||||
| 				text-decoration: none; | ||||
|  | ||||
| 				&:before { | ||||
| 					background: var(--accentedBg); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		> i { | ||||
| 			margin-right: 5px; | ||||
| 			width: 20px; | ||||
| 		} | ||||
|  | ||||
| 		> .avatar { | ||||
| 			margin-right: 5px; | ||||
| 			width: 20px; | ||||
| 			height: 20px; | ||||
| 		} | ||||
|  | ||||
| 		> .indicator { | ||||
| 			position: absolute; | ||||
| 			top: 5px; | ||||
| 			left: 13px; | ||||
| 			color: var(--indicator); | ||||
| 			font-size: 12px; | ||||
| 			animation: blink 1s infinite; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .divider { | ||||
| 		margin: 8px 0; | ||||
| 		border-top: solid 0.5px var(--divider); | ||||
| 	} | ||||
|  | ||||
| 	&.asDrawer { | ||||
| 		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0; | ||||
| 		padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; | ||||
| 		width: 100%; | ||||
| 		border-radius: 24px; | ||||
| 		border-bottom-right-radius: 0; | ||||
| @@ -351,7 +216,7 @@ onBeforeUnmount(() => { | ||||
| 				border-radius: 12px; | ||||
| 			} | ||||
|  | ||||
| 			> i { | ||||
| 			> .icon { | ||||
| 				margin-right: 14px; | ||||
| 				width: 24px; | ||||
| 			} | ||||
| @@ -362,4 +227,139 @@ onBeforeUnmount(() => { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .item { | ||||
| 	display: block; | ||||
| 	position: relative; | ||||
| 	padding: 5px 16px; | ||||
| 	width: 100%; | ||||
| 	box-sizing: border-box; | ||||
| 	white-space: nowrap; | ||||
| 	font-size: 0.9em; | ||||
| 	line-height: 20px; | ||||
| 	text-align: left; | ||||
| 	overflow: hidden; | ||||
| 	text-overflow: ellipsis; | ||||
|  | ||||
| 	&:before { | ||||
| 		content: ""; | ||||
| 		display: block; | ||||
| 		position: absolute; | ||||
| 		z-index: -1; | ||||
| 		top: 0; | ||||
| 		left: 0; | ||||
| 		right: 0; | ||||
| 		margin: auto; | ||||
| 		width: calc(100% - 16px); | ||||
| 		height: 100%; | ||||
| 		border-radius: 6px; | ||||
| 	} | ||||
|  | ||||
| 	&:not(:disabled):hover { | ||||
| 		color: var(--accent); | ||||
| 		text-decoration: none; | ||||
|  | ||||
| 		&:before { | ||||
| 			background: var(--accentedBg); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&.danger { | ||||
| 		color: #ff2a2a; | ||||
|  | ||||
| 		&:hover { | ||||
| 			color: #fff; | ||||
|  | ||||
| 			&:before { | ||||
| 				background: #ff4242; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		&:active { | ||||
| 			color: #fff; | ||||
|  | ||||
| 			&:before { | ||||
| 				background: #d42e2e !important; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&:active, | ||||
| 	&.active { | ||||
| 		color: var(--fgOnAccent) !important; | ||||
| 		opacity: 1; | ||||
|  | ||||
| 		&:before { | ||||
| 			background: var(--accent) !important; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&:not(:active):focus-visible { | ||||
| 		box-shadow: 0 0 0 2px var(--focus) inset; | ||||
| 	} | ||||
|  | ||||
| 	&.label { | ||||
| 		pointer-events: none; | ||||
| 		font-size: 0.7em; | ||||
| 		padding-bottom: 4px; | ||||
|  | ||||
| 		> span { | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&.pending { | ||||
| 		pointer-events: none; | ||||
| 		opacity: 0.7; | ||||
| 	} | ||||
|  | ||||
| 	&.none { | ||||
| 		pointer-events: none; | ||||
| 		opacity: 0.7; | ||||
| 	} | ||||
|  | ||||
| 	&.parent { | ||||
| 		display: flex; | ||||
| 		align-items: center; | ||||
| 		cursor: default; | ||||
|  | ||||
| 		&.childShowing { | ||||
| 			color: var(--accent); | ||||
| 			text-decoration: none; | ||||
|  | ||||
| 			&:before { | ||||
| 				background: var(--accentedBg); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .icon { | ||||
| 	margin-right: 5px; | ||||
| 	width: 20px; | ||||
| } | ||||
|  | ||||
| .caret { | ||||
| 	margin-left: auto; | ||||
| } | ||||
|  | ||||
| .avatar { | ||||
| 	margin-right: 5px; | ||||
| 	width: 20px; | ||||
| 	height: 20px; | ||||
| } | ||||
|  | ||||
| .indicator { | ||||
| 	position: absolute; | ||||
| 	top: 5px; | ||||
| 	left: 13px; | ||||
| 	color: var(--indicator); | ||||
| 	font-size: 12px; | ||||
| 	animation: blink 1s infinite; | ||||
| } | ||||
|  | ||||
| .divider { | ||||
| 	margin: 8px 0; | ||||
| 	border-top: solid 0.5px var(--divider); | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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,322 +386,259 @@ function readPromo() { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&:hover > .article > .main > .footer > .button { | ||||
| 	&:hover > .article > .main > .footer > .footerButton { | ||||
| 		opacity: 1; | ||||
| 	} | ||||
|  | ||||
| 	> .info { | ||||
| 		display: flex; | ||||
| 		align-items: center; | ||||
| 		padding: 16px 32px 8px 32px; | ||||
| 		line-height: 24px; | ||||
| 		font-size: 90%; | ||||
| 		white-space: pre; | ||||
| 		color: #d28a3f; | ||||
|  | ||||
| 		> i { | ||||
| 			margin-right: 4px; | ||||
| 		} | ||||
|  | ||||
| 		> .hide { | ||||
| 			margin-left: auto; | ||||
| 			color: inherit; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .info + .article { | ||||
| 		padding-top: 8px; | ||||
| 	} | ||||
|  | ||||
| 	> .reply-to { | ||||
| 		opacity: 0.7; | ||||
| 		padding-bottom: 0; | ||||
| 	} | ||||
|  | ||||
| 	> .renote { | ||||
| 		display: flex; | ||||
| 		align-items: center; | ||||
| 		padding: 16px 32px 8px 32px; | ||||
| 		line-height: 28px; | ||||
| 		white-space: pre; | ||||
| 		color: var(--renote); | ||||
|  | ||||
| 		> .avatar { | ||||
| 			flex-shrink: 0; | ||||
| 			display: inline-block; | ||||
| 			width: 28px; | ||||
| 			height: 28px; | ||||
| 			margin: 0 8px 0 0; | ||||
| 			border-radius: 6px; | ||||
| 		} | ||||
|  | ||||
| 		> i { | ||||
| 			margin-right: 4px; | ||||
| 		} | ||||
|  | ||||
| 		> span { | ||||
| 			overflow: hidden; | ||||
| 			flex-shrink: 1; | ||||
| 			text-overflow: ellipsis; | ||||
| 			white-space: nowrap; | ||||
|  | ||||
| 			> .name { | ||||
| 				font-weight: bold; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		> .info { | ||||
| 			margin-left: auto; | ||||
| 			font-size: 0.9em; | ||||
|  | ||||
| 			> .time { | ||||
| 				flex-shrink: 0; | ||||
| 				color: inherit; | ||||
|  | ||||
| 				> .dropdownIcon { | ||||
| 					margin-right: 4px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .renote + .article { | ||||
| 		padding-top: 8px; | ||||
| 	} | ||||
|  | ||||
| 	> .article { | ||||
| 		display: flex; | ||||
| 		padding: 28px 32px 18px; | ||||
|  | ||||
| 		> .avatar { | ||||
| 			flex-shrink: 0; | ||||
| 			display: block; | ||||
| 			margin: 0 14px 8px 0; | ||||
| 			width: 58px; | ||||
| 			height: 58px; | ||||
| 			position: sticky; | ||||
| 			top: calc(22px + var(--stickyTop, 0px)); | ||||
| 			left: 0; | ||||
| 		} | ||||
|  | ||||
| 		> .main { | ||||
| 			flex: 1; | ||||
| 			min-width: 0; | ||||
|  | ||||
| 			> .body { | ||||
| 				container-type: inline-size; | ||||
|  | ||||
| 				> .cw { | ||||
| 					cursor: default; | ||||
| 					display: block; | ||||
| 					margin: 0; | ||||
| 					padding: 0; | ||||
| 					overflow-wrap: break-word; | ||||
|  | ||||
| 					> .text { | ||||
| 						margin-right: 8px; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				> .content { | ||||
| 					&.isLong { | ||||
| 						> .showLess { | ||||
| 							width: 100%; | ||||
| 							margin-top: 1em; | ||||
| 							position: sticky; | ||||
| 							bottom: 1em; | ||||
|  | ||||
| 							> span { | ||||
| 								display: inline-block; | ||||
| 								background: var(--popup); | ||||
| 								padding: 6px 10px; | ||||
| 								font-size: 0.8em; | ||||
| 								border-radius: 999px; | ||||
| 								box-shadow: 0 2px 6px rgb(0 0 0 / 20%); | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					&.collapsed { | ||||
| 						position: relative; | ||||
| 						max-height: 9em; | ||||
| 						overflow: clip; | ||||
|  | ||||
| 						> .fade { | ||||
| 							display: block; | ||||
| 							position: absolute; | ||||
| 							bottom: 0; | ||||
| 							left: 0; | ||||
| 							width: 100%; | ||||
| 							height: 64px; | ||||
| 							background: linear-gradient(0deg, var(--panel), var(--X15)); | ||||
|  | ||||
| 							> span { | ||||
| 								display: inline-block; | ||||
| 								background: var(--panel); | ||||
| 								padding: 6px 10px; | ||||
| 								font-size: 0.8em; | ||||
| 								border-radius: 999px; | ||||
| 								box-shadow: 0 2px 6px rgb(0 0 0 / 20%); | ||||
| 							} | ||||
|  | ||||
| 							&:hover { | ||||
| 								> span { | ||||
| 									background: var(--panelHighlight); | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					> .text { | ||||
| 						overflow-wrap: break-word; | ||||
|  | ||||
| 						> .reply { | ||||
| 							color: var(--accent); | ||||
| 							margin-right: 0.5em; | ||||
| 						} | ||||
|  | ||||
| 						> .rp { | ||||
| 							margin-left: 4px; | ||||
| 							font-style: oblique; | ||||
| 							color: var(--renote); | ||||
| 						} | ||||
|  | ||||
| 						> .translation { | ||||
| 							border: solid 0.5px var(--divider); | ||||
| 							border-radius: var(--radius); | ||||
| 							padding: 12px; | ||||
| 							margin-top: 8px; | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					> .url-preview { | ||||
| 						margin-top: 8px; | ||||
| 					} | ||||
|  | ||||
| 					> .poll { | ||||
| 						font-size: 80%; | ||||
| 					} | ||||
|  | ||||
| 					> .renote { | ||||
| 						padding: 8px 0; | ||||
|  | ||||
| 						> .note { | ||||
| 							padding: 16px; | ||||
| 							border: dashed 1px var(--renote); | ||||
| 							border-radius: 8px; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				> .channel { | ||||
| 					opacity: 0.7; | ||||
| 					font-size: 80%; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			> .footer { | ||||
| 				> .button { | ||||
| 					margin: 0; | ||||
| 					padding: 8px; | ||||
| 					opacity: 0.7; | ||||
|  | ||||
| 					&:not(:last-child) { | ||||
| 						margin-right: 28px; | ||||
| 					} | ||||
|  | ||||
| 					&:hover { | ||||
| 						color: var(--fgHighlighted); | ||||
| 					} | ||||
|  | ||||
| 					> .count { | ||||
| 						display: inline; | ||||
| 						margin: 0 0 0 8px; | ||||
| 						opacity: 0.7; | ||||
| 					} | ||||
|  | ||||
| 					&.reacted { | ||||
| 						color: var(--accent); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .reply { | ||||
| 		border-top: solid 0.5px var(--divider); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @container (max-width: 500px) { | ||||
| 	.tkcbzcuz { | ||||
| 		font-size: 0.9em; | ||||
| .tip { | ||||
| 	display: flex; | ||||
| 	align-items: center; | ||||
| 	padding: 16px 32px 8px 32px; | ||||
| 	line-height: 24px; | ||||
| 	font-size: 90%; | ||||
| 	white-space: pre; | ||||
| 	color: #d28a3f; | ||||
| } | ||||
|  | ||||
| 		> .article { | ||||
| 			> .avatar { | ||||
| 				width: 50px; | ||||
| 				height: 50px; | ||||
| 			} | ||||
| 		} | ||||
| .tip + .article { | ||||
| 	padding-top: 8px; | ||||
| } | ||||
|  | ||||
| .replyTo { | ||||
| 	opacity: 0.7; | ||||
| 	padding-bottom: 0; | ||||
| } | ||||
|  | ||||
| .renote { | ||||
| 	display: flex; | ||||
| 	align-items: center; | ||||
| 	padding: 16px 32px 8px 32px; | ||||
| 	line-height: 28px; | ||||
| 	white-space: pre; | ||||
| 	color: var(--renote); | ||||
|  | ||||
| 	& + .article { | ||||
| 		padding-top: 8px; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .renoteAvatar { | ||||
| 	flex-shrink: 0; | ||||
| 	display: inline-block; | ||||
| 	width: 28px; | ||||
| 	height: 28px; | ||||
| 	margin: 0 8px 0 0; | ||||
| 	border-radius: 6px; | ||||
| } | ||||
|  | ||||
| .renoteText { | ||||
| 	overflow: hidden; | ||||
| 	flex-shrink: 1; | ||||
| 	text-overflow: ellipsis; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
|  | ||||
| .renoteUserName { | ||||
| 	font-weight: bold; | ||||
| } | ||||
|  | ||||
| .renoteInfo { | ||||
| 	margin-left: auto; | ||||
| 	font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| .renoteTime { | ||||
| 	flex-shrink: 0; | ||||
| 	color: inherit; | ||||
| } | ||||
|  | ||||
| .renoteMenu { | ||||
| 	margin-right: 4px; | ||||
| } | ||||
|  | ||||
| .article { | ||||
| 	display: flex; | ||||
| 	padding: 28px 32px 18px; | ||||
| } | ||||
|  | ||||
| .avatar { | ||||
| 	flex-shrink: 0; | ||||
| 	display: block !important; | ||||
| 	margin: 0 14px 8px 0; | ||||
| 	width: 58px; | ||||
| 	height: 58px; | ||||
| 	position: sticky !important; | ||||
| 	top: calc(22px + var(--stickyTop, 0px)); | ||||
| 	left: 0; | ||||
| } | ||||
|  | ||||
| .main { | ||||
| 	flex: 1; | ||||
| 	min-width: 0; | ||||
| } | ||||
|  | ||||
| .cw { | ||||
| 	cursor: default; | ||||
| 	display: block; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| 	overflow-wrap: break-word; | ||||
| } | ||||
|  | ||||
| .showLess { | ||||
| 	width: 100%; | ||||
| 	margin-top: 1em; | ||||
| 	position: sticky; | ||||
| 	bottom: 1em; | ||||
| } | ||||
|  | ||||
| .howLessLabel { | ||||
| 	display: inline-block; | ||||
| 	background: var(--popup); | ||||
| 	padding: 6px 10px; | ||||
| 	font-size: 0.8em; | ||||
| 	border-radius: 999px; | ||||
| 	box-shadow: 0 2px 6px rgb(0 0 0 / 20%); | ||||
| } | ||||
|  | ||||
| .contentCollapsed { | ||||
| 	position: relative; | ||||
| 	max-height: 9em; | ||||
| 	overflow: clip; | ||||
| } | ||||
|  | ||||
| .collapsed { | ||||
| 	display: block; | ||||
| 	position: absolute; | ||||
| 	bottom: 0; | ||||
| 	left: 0; | ||||
| 	width: 100%; | ||||
| 	height: 64px; | ||||
| 	background: linear-gradient(0deg, var(--panel), var(--X15)); | ||||
|  | ||||
| 	&:hover > .collapsedLabel { | ||||
| 		background: var(--panelHighlight); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .collapsedLabel { | ||||
| 	display: inline-block; | ||||
| 	background: var(--panel); | ||||
| 	padding: 6px 10px; | ||||
| 	font-size: 0.8em; | ||||
| 	border-radius: 999px; | ||||
| 	box-shadow: 0 2px 6px rgb(0 0 0 / 20%); | ||||
| } | ||||
|  | ||||
| .text { | ||||
| 	overflow-wrap: break-word; | ||||
| } | ||||
|  | ||||
| .replyIcon { | ||||
| 	color: var(--accent); | ||||
| 	margin-right: 0.5em; | ||||
| } | ||||
|  | ||||
| .translation { | ||||
| 	border: solid 0.5px var(--divider); | ||||
| 	border-radius: var(--radius); | ||||
| 	padding: 12px; | ||||
| 	margin-top: 8px; | ||||
| } | ||||
|  | ||||
| .urlPreview { | ||||
| 	margin-top: 8px; | ||||
| } | ||||
|  | ||||
| .poll { | ||||
| 	font-size: 80%; | ||||
| } | ||||
|  | ||||
| .quote { | ||||
| 	padding: 8px 0; | ||||
| } | ||||
|  | ||||
| .quoteNote { | ||||
| 	padding: 16px; | ||||
| 	border: dashed 1px var(--renote); | ||||
| 	border-radius: 8px; | ||||
| } | ||||
|  | ||||
| .channel { | ||||
| 	opacity: 0.7; | ||||
| 	font-size: 80%; | ||||
| } | ||||
|  | ||||
| .footerButton { | ||||
| 	margin: 0; | ||||
| 	padding: 8px; | ||||
| 	opacity: 0.7; | ||||
|  | ||||
| 	&:not(:last-child) { | ||||
| 		margin-right: 28px; | ||||
| 	} | ||||
|  | ||||
| 	&:hover { | ||||
| 		color: var(--fgHighlighted); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .footerButtonCount { | ||||
| 	display: inline; | ||||
| 	margin: 0 0 0 8px; | ||||
| 	opacity: 0.7; | ||||
| } | ||||
|  | ||||
| @container (max-width: 500px) { | ||||
| 	.root { | ||||
| 		font-size: 0.9em; | ||||
| 	} | ||||
|  | ||||
| 	.avatar { | ||||
| 		width: 50px; | ||||
| 		height: 50px; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @container (max-width: 450px) { | ||||
| 	.tkcbzcuz { | ||||
| 		> .renote { | ||||
| 			padding: 8px 16px 0 16px; | ||||
| 		} | ||||
| 	.renote { | ||||
| 		padding: 8px 16px 0 16px; | ||||
| 	} | ||||
|  | ||||
| 		> .info { | ||||
| 			padding: 8px 16px 0 16px; | ||||
| 		} | ||||
| 	.tip { | ||||
| 		padding: 8px 16px 0 16px; | ||||
| 	} | ||||
|  | ||||
| 		> .article { | ||||
| 			padding: 14px 16px 9px; | ||||
| 	.article { | ||||
| 		padding: 14px 16px 9px; | ||||
| 	} | ||||
|  | ||||
| 			> .avatar { | ||||
| 				margin: 0 10px 8px 0; | ||||
| 				width: 46px; | ||||
| 				height: 46px; | ||||
| 				top: calc(14px + var(--stickyTop, 0px)); | ||||
| 			} | ||||
| 		} | ||||
| 	.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 { | ||||
| 						&:not(:last-child) { | ||||
| 							margin-right: 18px; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 	.footerButton { | ||||
| 		&:not(:last-child) { | ||||
| 			margin-right: 18px; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @container (max-width: 300px) { | ||||
| 	.tkcbzcuz { | ||||
| 		> .article { | ||||
| 			> .avatar { | ||||
| 				width: 44px; | ||||
| 				height: 44px; | ||||
| 			} | ||||
| 	.avatar { | ||||
| 		width: 44px; | ||||
| 		height: 44px; | ||||
| 	} | ||||
|  | ||||
| 			> .main { | ||||
| 				> .footer { | ||||
| 					> .button { | ||||
| 						&:not(:last-child) { | ||||
| 							margin-right: 12px; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 	.footerButton { | ||||
| 		&:not(:last-child) { | ||||
| 			margin-right: 12px; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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,49 +32,49 @@ defineProps<{ | ||||
| }>(); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .kkwtjztg { | ||||
| <style lang="scss" module> | ||||
| .root { | ||||
| 	display: flex; | ||||
| 	align-items: baseline; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
|  | ||||
| 	> .name { | ||||
| 		flex-shrink: 1; | ||||
| 		display: block; | ||||
| 		margin: 0 .5em 0 0; | ||||
| 		padding: 0; | ||||
| 		overflow: hidden; | ||||
| 		font-size: 1em; | ||||
| 		font-weight: bold; | ||||
| 		text-decoration: none; | ||||
| 		text-overflow: ellipsis; | ||||
| .name { | ||||
| 	flex-shrink: 1; | ||||
| 	display: block; | ||||
| 	margin: 0 .5em 0 0; | ||||
| 	padding: 0; | ||||
| 	overflow: hidden; | ||||
| 	font-size: 1em; | ||||
| 	font-weight: bold; | ||||
| 	text-decoration: none; | ||||
| 	text-overflow: ellipsis; | ||||
|  | ||||
| 		&:hover { | ||||
| 			text-decoration: underline; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .is-bot { | ||||
| 		flex-shrink: 0; | ||||
| 		align-self: center; | ||||
| 		margin: 0 .5em 0 0; | ||||
| 		padding: 1px 6px; | ||||
| 		font-size: 80%; | ||||
| 		border: solid 0.5px var(--divider); | ||||
| 		border-radius: 3px; | ||||
| 	} | ||||
|  | ||||
| 	> .username { | ||||
| 		flex-shrink: 9999999; | ||||
| 		margin: 0 .5em 0 0; | ||||
| 		overflow: hidden; | ||||
| 		text-overflow: ellipsis; | ||||
| 	} | ||||
|  | ||||
| 	> .info { | ||||
| 		flex-shrink: 0; | ||||
| 		margin-left: auto; | ||||
| 		font-size: 0.9em; | ||||
| 	&:hover { | ||||
| 		text-decoration: underline; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .isBot { | ||||
| 	flex-shrink: 0; | ||||
| 	align-self: center; | ||||
| 	margin: 0 .5em 0 0; | ||||
| 	padding: 1px 6px; | ||||
| 	font-size: 80%; | ||||
| 	border: solid 0.5px var(--divider); | ||||
| 	border-radius: 3px; | ||||
| } | ||||
|  | ||||
| .username { | ||||
| 	flex-shrink: 9999999; | ||||
| 	margin: 0 .5em 0 0; | ||||
| 	overflow: hidden; | ||||
| 	text-overflow: ellipsis; | ||||
| } | ||||
|  | ||||
| .info { | ||||
| 	flex-shrink: 0; | ||||
| 	margin-left: auto; | ||||
| 	font-size: 0.9em; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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 { | ||||
| 		flex-shrink: 0; | ||||
| 		display: block; | ||||
| 		margin: 0 10px 0 0; | ||||
| 		width: 40px; | ||||
| 		height: 40px; | ||||
| 		border-radius: 8px; | ||||
| 	} | ||||
| .avatar { | ||||
| 	flex-shrink: 0; | ||||
| 	display: block; | ||||
| 	margin: 0 10px 0 0; | ||||
| 	width: 40px; | ||||
| 	height: 40px; | ||||
| 	border-radius: 8px; | ||||
| } | ||||
|  | ||||
| 	> .main { | ||||
| 		flex: 1; | ||||
| 		min-width: 0; | ||||
| .main { | ||||
| 	flex: 1; | ||||
| 	min-width: 0; | ||||
| } | ||||
|  | ||||
| 		> .header { | ||||
| 			margin-bottom: 2px; | ||||
| 		} | ||||
| .header { | ||||
| 	margin-bottom: 2px; | ||||
| } | ||||
|  | ||||
| 		> .body { | ||||
| .cw { | ||||
| 	cursor: default; | ||||
| 	display: block; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| 	overflow-wrap: break-word; | ||||
| } | ||||
|  | ||||
| 			> .cw { | ||||
| 				cursor: default; | ||||
| 				display: block; | ||||
| 				margin: 0; | ||||
| 				padding: 0; | ||||
| 				overflow-wrap: break-word; | ||||
|  | ||||
| 				> .text { | ||||
| 					margin-right: 8px; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			> .content { | ||||
| 				> .text { | ||||
| 					cursor: default; | ||||
| 					margin: 0; | ||||
| 					padding: 0; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| .text { | ||||
| 	cursor: default; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| } | ||||
|  | ||||
| @container (min-width: 350px) { | ||||
| 	.yohlumlk { | ||||
| 		> .avatar { | ||||
| 			margin: 0 10px 0 0; | ||||
| 			width: 44px; | ||||
| 			height: 44px; | ||||
| 		} | ||||
| 	.avatar { | ||||
| 		margin: 0 10px 0 0; | ||||
| 		width: 44px; | ||||
| 		height: 44px; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @container (min-width: 500px) { | ||||
| 	.yohlumlk { | ||||
| 		> .avatar { | ||||
| 			margin: 0 12px 0 0; | ||||
| 			width: 48px; | ||||
| 			height: 48px; | ||||
| 		} | ||||
| 	.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 { | ||||
| 		display: flex; | ||||
| .main { | ||||
| 	display: flex; | ||||
| } | ||||
|  | ||||
| 		> .avatar { | ||||
| 			flex-shrink: 0; | ||||
| 			display: block; | ||||
| 			margin: 0 8px 0 0; | ||||
| 			width: 38px; | ||||
| 			height: 38px; | ||||
| 			border-radius: 8px; | ||||
| 		} | ||||
| .avatar { | ||||
| 	flex-shrink: 0; | ||||
| 	display: block; | ||||
| 	margin: 0 8px 0 0; | ||||
| 	width: 38px; | ||||
| 	height: 38px; | ||||
| 	border-radius: 8px; | ||||
| } | ||||
|  | ||||
| 		> .body { | ||||
| 			flex: 1; | ||||
| 			min-width: 0; | ||||
| .body { | ||||
| 	flex: 1; | ||||
| 	min-width: 0; | ||||
| } | ||||
|  | ||||
| 			> .header { | ||||
| 				margin-bottom: 2px; | ||||
| 			} | ||||
| .header { | ||||
| 	margin-bottom: 2px; | ||||
| } | ||||
|  | ||||
| 			> .body { | ||||
| 				> .cw { | ||||
| 					cursor: default; | ||||
| 					display: block; | ||||
| 					margin: 0; | ||||
| 					padding: 0; | ||||
| 					overflow-wrap: break-word; | ||||
| .cw { | ||||
| 	cursor: default; | ||||
| 	display: block; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| 	overflow-wrap: break-word; | ||||
| } | ||||
|  | ||||
| 					> .text { | ||||
| 						margin-right: 8px; | ||||
| 					} | ||||
| 				} | ||||
| .text { | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| } | ||||
|  | ||||
| 				> .content { | ||||
| 					> .text { | ||||
| 						margin: 0; | ||||
| 						padding: 0; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| .reply, .more { | ||||
| 	border-left: solid 0.5px var(--divider); | ||||
| 	margin-top: 10px; | ||||
| } | ||||
|  | ||||
| 	> .reply, > .more { | ||||
| 		border-left: solid 0.5px var(--divider); | ||||
| 		margin-top: 10px; | ||||
| 	} | ||||
|  | ||||
| 	> .more { | ||||
| 		padding: 10px 0 0 16px; | ||||
| 	} | ||||
| .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,139 +166,135 @@ useTooltip(reactionRef, (showing) => { | ||||
| 	overflow-wrap: break-word; | ||||
| 	display: flex; | ||||
| 	contain: content; | ||||
| } | ||||
|  | ||||
| 	> .head { | ||||
| 		position: sticky; | ||||
| 		top: 0; | ||||
| 		flex-shrink: 0; | ||||
| 		width: 42px; | ||||
| 		height: 42px; | ||||
| 		margin-right: 8px; | ||||
| .head { | ||||
| 	position: sticky; | ||||
| 	top: 0; | ||||
| 	flex-shrink: 0; | ||||
| 	width: 42px; | ||||
| 	height: 42px; | ||||
| 	margin-right: 8px; | ||||
| } | ||||
|  | ||||
| 		> .icon { | ||||
| 			display: block; | ||||
| 			width: 100%; | ||||
| 			height: 100%; | ||||
| 			border-radius: 6px; | ||||
| 		} | ||||
| .icon { | ||||
| 	display: block; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	border-radius: 6px; | ||||
| } | ||||
|  | ||||
| 		> .sub-icon { | ||||
| 			position: absolute; | ||||
| 			z-index: 1; | ||||
| 			bottom: -2px; | ||||
| 			right: -2px; | ||||
| 			width: 20px; | ||||
| 			height: 20px; | ||||
| 			box-sizing: border-box; | ||||
| 			border-radius: 100%; | ||||
| 			background: var(--panel); | ||||
| 			box-shadow: 0 0 0 3px var(--panel); | ||||
| 			font-size: 12px; | ||||
| 			text-align: center; | ||||
| .subIcon { | ||||
| 	position: absolute; | ||||
| 	z-index: 1; | ||||
| 	bottom: -2px; | ||||
| 	right: -2px; | ||||
| 	width: 20px; | ||||
| 	height: 20px; | ||||
| 	box-sizing: border-box; | ||||
| 	border-radius: 100%; | ||||
| 	background: var(--panel); | ||||
| 	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 { | ||||
| 				padding: 3px; | ||||
| 				background: #36aed2; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
|  | ||||
| 			&.renote { | ||||
| 				padding: 3px; | ||||
| 				background: #36d298; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
|  | ||||
| 			&.quote { | ||||
| 				padding: 3px; | ||||
| 				background: #36d298; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
|  | ||||
| 			&.reply { | ||||
| 				padding: 3px; | ||||
| 				background: #007aff; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
|  | ||||
| 			&.mention { | ||||
| 				padding: 3px; | ||||
| 				background: #88a6b7; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
|  | ||||
| 			&.pollEnded { | ||||
| 				padding: 3px; | ||||
| 				background: #88a6b7; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .tail { | ||||
| 		flex: 1; | ||||
| 		min-width: 0; | ||||
|  | ||||
| 		> header { | ||||
| 			display: flex; | ||||
| 			align-items: baseline; | ||||
| 			white-space: nowrap; | ||||
|  | ||||
| 			> .name { | ||||
| 				text-overflow: ellipsis; | ||||
| 				white-space: nowrap; | ||||
| 				min-width: 0; | ||||
| 				overflow: hidden; | ||||
| 			} | ||||
|  | ||||
| 			> .time { | ||||
| 				margin-left: auto; | ||||
| 				font-size: 0.9em; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		> .content { | ||||
| 			> .text { | ||||
| 				white-space: nowrap; | ||||
| 				overflow: hidden; | ||||
| 				text-overflow: ellipsis; | ||||
|  | ||||
| 				> i { | ||||
| 					vertical-align: super; | ||||
| 					font-size: 50%; | ||||
| 					opacity: 0.5; | ||||
| 				} | ||||
|  | ||||
| 				> i:first-child { | ||||
| 					margin-right: 4px; | ||||
| 				} | ||||
|  | ||||
| 				> i:last-child { | ||||
| 					margin-left: 4px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	&:empty { | ||||
| 		display: none; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .t_follow, .t_followRequestAccepted, .t_receiveFollowRequest, .t_groupInvited { | ||||
| 	padding: 3px; | ||||
| 	background: #36aed2; | ||||
| 	pointer-events: none; | ||||
| } | ||||
|  | ||||
| .t_renote { | ||||
| 	padding: 3px; | ||||
| 	background: #36d298; | ||||
| 	pointer-events: none; | ||||
| } | ||||
|  | ||||
| .t_quote { | ||||
| 	padding: 3px; | ||||
| 	background: #36d298; | ||||
| 	pointer-events: none; | ||||
| } | ||||
|  | ||||
| .t_reply { | ||||
| 	padding: 3px; | ||||
| 	background: #007aff; | ||||
| 	pointer-events: none; | ||||
| } | ||||
|  | ||||
| .t_mention { | ||||
| 	padding: 3px; | ||||
| 	background: #88a6b7; | ||||
| 	pointer-events: none; | ||||
| } | ||||
|  | ||||
| .t_pollEnded { | ||||
| 	padding: 3px; | ||||
| 	background: #88a6b7; | ||||
| 	pointer-events: none; | ||||
| } | ||||
|  | ||||
| .tail { | ||||
| 	flex: 1; | ||||
| 	min-width: 0; | ||||
| } | ||||
|  | ||||
| .header { | ||||
| 	display: flex; | ||||
| 	align-items: baseline; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
|  | ||||
| .headerName { | ||||
| 	text-overflow: ellipsis; | ||||
| 	white-space: nowrap; | ||||
| 	min-width: 0; | ||||
| 	overflow: hidden; | ||||
| } | ||||
|  | ||||
| .headerTime { | ||||
| 	margin-left: auto; | ||||
| 	font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| .content { | ||||
| } | ||||
|  | ||||
| .text { | ||||
| 	white-space: nowrap; | ||||
| 	overflow: hidden; | ||||
| 	text-overflow: ellipsis; | ||||
| } | ||||
|  | ||||
| .quote { | ||||
| 	vertical-align: super; | ||||
| 	font-size: 50%; | ||||
| 	opacity: 0.5; | ||||
| } | ||||
|  | ||||
| .quote:first-child { | ||||
| 	margin-right: 4px; | ||||
| } | ||||
|  | ||||
| .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 }"> | ||||
| 		<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/> | ||||
| 	</TransitionGroup> | ||||
| </Transition> | ||||
| <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> | ||||
| </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,74 +69,74 @@ 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 { | ||||
| 	position: absolute; | ||||
| 	bottom: 0; | ||||
| 	left: 0; | ||||
| 	right: 0; | ||||
| 	top: 0; | ||||
| 	border-radius: 100%; | ||||
| 	z-index: 1; | ||||
| 	overflow: clip; | ||||
| 	object-fit: cover; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| } | ||||
|  | ||||
| .indicator { | ||||
| 	position: absolute; | ||||
| 	z-index: 1; | ||||
| 	bottom: 0; | ||||
| 	left: 0; | ||||
| 	width: 20%; | ||||
| 	height: 20%; | ||||
| } | ||||
|  | ||||
| .square { | ||||
| 	border-radius: 20%; | ||||
|  | ||||
| 	> .inner { | ||||
| 		position: absolute; | ||||
| 		bottom: 0; | ||||
| 		left: 0; | ||||
| 		right: 0; | ||||
| 		top: 0; | ||||
| 		border-radius: 100%; | ||||
| 		z-index: 1; | ||||
| 		overflow: clip; | ||||
| 		object-fit: cover; | ||||
| 		width: 100%; | ||||
| 		height: 100%; | ||||
| 	} | ||||
|  | ||||
| 	> .indicator { | ||||
| 		position: absolute; | ||||
| 		z-index: 1; | ||||
| 		bottom: 0; | ||||
| 		left: 0; | ||||
| 		width: 20%; | ||||
| 		height: 20%; | ||||
| 	} | ||||
|  | ||||
| 	&.square { | ||||
| 		border-radius: 20%; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 		> .inner { | ||||
| 			border-radius: 20%; | ||||
| 		} | ||||
| .cat { | ||||
| 	&:before, &:after { | ||||
| 		background: #df548f; | ||||
| 		border: solid 4px currentColor; | ||||
| 		box-sizing: border-box; | ||||
| 		content: ''; | ||||
| 		display: inline-block; | ||||
| 		height: 50%; | ||||
| 		width: 50%; | ||||
| 	} | ||||
|  | ||||
| 	&.cat { | ||||
| 		&:before, &:after { | ||||
| 			background: #df548f; | ||||
| 			border: solid 4px currentColor; | ||||
| 			box-sizing: border-box; | ||||
| 			content: ''; | ||||
| 			display: inline-block; | ||||
| 			height: 50%; | ||||
| 			width: 50%; | ||||
| 		} | ||||
| 	&:before { | ||||
| 		border-radius: 0 75% 75%; | ||||
| 		transform: rotate(37.5deg) skew(30deg); | ||||
| 	} | ||||
|  | ||||
| 	&:after { | ||||
| 		border-radius: 75% 0 75% 75%; | ||||
| 		transform: rotate(-37.5deg) skew(-30deg); | ||||
| 	} | ||||
|  | ||||
| 	&:hover { | ||||
| 		&:before { | ||||
| 			border-radius: 0 75% 75%; | ||||
| 			transform: rotate(37.5deg) skew(30deg); | ||||
| 			animation: earwiggleleft 1s infinite; | ||||
| 		} | ||||
|  | ||||
| 		&:after { | ||||
| 			border-radius: 75% 0 75% 75%; | ||||
| 			transform: rotate(-37.5deg) skew(-30deg); | ||||
| 		} | ||||
|  | ||||
| 		&:hover { | ||||
| 			&:before { | ||||
| 				animation: earwiggleleft 1s infinite; | ||||
| 			} | ||||
|  | ||||
| 			&:after { | ||||
| 				animation: earwiggleright 1s infinite; | ||||
| 			} | ||||
| 			animation: earwiggleright 1s infinite; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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,32 +47,32 @@ 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 { | ||||
| 	height: 2.5em; | ||||
| 	vertical-align: middle; | ||||
| 	transition: transform 0.2s ease; | ||||
|  | ||||
| 	&:hover { | ||||
| 		transform: scale(1.2); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .normal { | ||||
| 	height: 1.25em; | ||||
| 	vertical-align: -0.25em; | ||||
|  | ||||
| 	&.custom { | ||||
| 		height: 2.5em; | ||||
| 		vertical-align: middle; | ||||
| 		transition: transform 0.2s ease; | ||||
|  | ||||
| 		&:hover { | ||||
| 			transform: scale(1.2); | ||||
| 		} | ||||
|  | ||||
| 		&.normal { | ||||
| 			height: 1.25em; | ||||
| 			vertical-align: -0.25em; | ||||
|  | ||||
| 			&:hover { | ||||
| 				transform: none; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&.noStyle { | ||||
| 		height: auto !important; | ||||
| 	&:hover { | ||||
| 		transform: none; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .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 { | ||||
| 		padding-left: 2px; | ||||
| 		font-size: .9em; | ||||
| 	} | ||||
| .icon { | ||||
| 	padding-left: 2px; | ||||
| 	font-size: .9em; | ||||
| } | ||||
|  | ||||
| 	> .self { | ||||
| 		font-weight: bold; | ||||
| 	} | ||||
| .self { | ||||
| 	font-weight: bold; | ||||
| } | ||||
|  | ||||
| 	> .schema { | ||||
| 		opacity: 0.5; | ||||
| 	} | ||||
| .schema { | ||||
| 	opacity: 0.5; | ||||
| } | ||||
|  | ||||
| 	> .hostname { | ||||
| 		font-weight: bold; | ||||
| 	} | ||||
| .hostname { | ||||
| 	font-weight: bold; | ||||
| } | ||||
|  | ||||
| 	> .pathname { | ||||
| 		opacity: 0.8; | ||||
| 	} | ||||
| .pathname { | ||||
| 	opacity: 0.8; | ||||
| } | ||||
|  | ||||
| 	> .query { | ||||
| 		opacity: 0.5; | ||||
| 	} | ||||
| .query { | ||||
| 	opacity: 0.5; | ||||
| } | ||||
|  | ||||
| 	> .hash { | ||||
| 		font-style: italic; | ||||
| 	} | ||||
| .hash { | ||||
| 	font-style: italic; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -2,15 +2,17 @@ | ||||
| <MkStickyContainer> | ||||
| 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> | ||||
| 	<MkSpacer :content-max="800"> | ||||
| 		<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;"> | ||||
| 			<div>{{ relay.inbox }}</div> | ||||
| 			<div class="status"> | ||||
| 				<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i> | ||||
| 				<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i> | ||||
| 				<i v-else class="ti ti-clock icon requesting"></i> | ||||
| 				<span>{{ $t(`_relayStatus.${relay.status}`) }}</span> | ||||
| 		<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"> | ||||
| 					<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i> | ||||
| 					<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i> | ||||
| 					<i v-else class="ti ti-clock icon requesting"></i> | ||||
| 					<span>{{ $t(`_relayStatus.${relay.status}`) }}</span> | ||||
| 				</div> | ||||
| 				<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> | ||||
| 			</div> | ||||
| 			<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> | ||||
| 		</div> | ||||
| 	</MkSpacer> | ||||
| </MkStickyContainer> | ||||
|   | ||||
| @@ -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%; | ||||
|   | ||||
| @@ -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,30 +153,28 @@ definePageMetadata(computed(() => ({ | ||||
| }))); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .cmuxhskf { | ||||
| 	> .new { | ||||
| 		position: sticky; | ||||
| 		top: calc(var(--stickyTop, 0px) + 16px); | ||||
| 		z-index: 1000; | ||||
| 		width: 100%; | ||||
| <style lang="scss" module> | ||||
| .new { | ||||
| 	position: sticky; | ||||
| 	top: calc(var(--stickyTop, 0px) + 16px); | ||||
| 	z-index: 1000; | ||||
| 	width: 100%; | ||||
|  | ||||
| 		> button { | ||||
| 			display: block; | ||||
| 			margin: var(--margin) auto 0 auto; | ||||
| 			padding: 8px 16px; | ||||
| 			border-radius: 32px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .post-form { | ||||
| 		border-radius: var(--radius); | ||||
| 	} | ||||
|  | ||||
| 	> .tl { | ||||
| 		background: var(--bg); | ||||
| 		border-radius: var(--radius); | ||||
| 		overflow: clip; | ||||
| 	> button { | ||||
| 		display: block; | ||||
| 		margin: var(--margin) auto 0 auto; | ||||
| 		padding: 8px 16px; | ||||
| 		border-radius: 32px; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .postForm { | ||||
| 	border-radius: var(--radius); | ||||
| } | ||||
|  | ||||
| .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,31 +101,28 @@ if ($i) { | ||||
| 	padding: 0 32px; | ||||
| 	pointer-events: none; | ||||
| 	container-type: inline-size; | ||||
| } | ||||
|  | ||||
| 	> .notification { | ||||
| 		& + .notification { | ||||
| 			margin-top: 8px; | ||||
| 		} | ||||
| .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 { | ||||
| 				margin-top: 0; | ||||
| 				margin-bottom: 8px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@media (max-width: 500px) { | ||||
| 		bottom: calc(env(safe-area-inset-bottom, 0px) + 92px); | ||||
| 		padding: 0 8px; | ||||
| 	.notification { | ||||
| 		& + .notification { | ||||
| 			margin-top: 0; | ||||
| 			margin-bottom: 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,178 +268,170 @@ async function deleteProfile() { | ||||
| 	height: 100dvh; | ||||
| 	box-sizing: border-box; | ||||
| 	flex: 1; | ||||
| } | ||||
|  | ||||
| 	&.isMobile { | ||||
| 		padding-bottom: 100px; | ||||
| 	} | ||||
| .rootIsMobile { | ||||
| 	padding-bottom: 100px; | ||||
| } | ||||
|  | ||||
| 	> .main { | ||||
| 		flex: 1; | ||||
| 		min-width: 0; | ||||
| 		display: flex; | ||||
| 		flex-direction: column; | ||||
| .main { | ||||
| 	flex: 1; | ||||
| 	min-width: 0; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| } | ||||
|  | ||||
| 		> .columns { | ||||
| 			flex: 1; | ||||
| 			display: flex; | ||||
| 			overflow-x: auto; | ||||
| 			overflow-y: clip; | ||||
| .columns { | ||||
| 	flex: 1; | ||||
| 	display: flex; | ||||
| 	overflow-x: auto; | ||||
| 	overflow-y: clip; | ||||
|  | ||||
| 			&.center { | ||||
| 				> .column:first-of-type { | ||||
| 					margin-left: auto; | ||||
| 				} | ||||
|  | ||||
| 				> .column:last-of-type { | ||||
| 					margin-right: auto; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			> .column { | ||||
| 				flex-shrink: 0; | ||||
| 				border-right: solid var(--deckDividerThickness) var(--deckDivider); | ||||
|  | ||||
| 				&:first-of-type { | ||||
| 					border-left: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 				} | ||||
|  | ||||
| 				&.folder { | ||||
| 					display: flex; | ||||
| 					flex-direction: column; | ||||
|  | ||||
| 					> *:not(:last-of-type) { | ||||
| 						border-bottom: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			> .intro { | ||||
| 				padding: 32px; | ||||
| 				height: min-content; | ||||
| 				text-align: center; | ||||
| 				margin: auto; | ||||
|  | ||||
| 				> .add { | ||||
| 					margin: 1em auto; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			> .sideMenu { | ||||
| 				flex-shrink: 0; | ||||
| 				margin-right: 0; | ||||
| 				margin-left: auto; | ||||
| 				display: flex; | ||||
| 				flex-direction: column; | ||||
| 				justify-content: center; | ||||
| 				width: 32px; | ||||
|  | ||||
| 				> .top, > .middle, > .bottom { | ||||
| 					> .button { | ||||
| 						display: block; | ||||
| 						width: 100%; | ||||
| 						aspect-ratio: 1; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				> .top { | ||||
| 					margin-bottom: auto; | ||||
| 				} | ||||
|  | ||||
| 				> .middle { | ||||
| 					margin-top: auto; | ||||
| 					margin-bottom: auto; | ||||
| 				} | ||||
|  | ||||
| 				> .bottom { | ||||
| 					margin-top: auto; | ||||
| 				} | ||||
| 			} | ||||
| 	&.center { | ||||
| 		> .column:first-of-type { | ||||
| 			margin-left: 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; | ||||
| 				} | ||||
| 			} | ||||
| 		> .column:last-of-type { | ||||
| 			margin-right: auto; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .menu-back { | ||||
| 		z-index: 1001; | ||||
| 	} | ||||
|  | ||||
| 	> .menu { | ||||
| 		position: fixed; | ||||
| 		top: 0; | ||||
| 		left: 0; | ||||
| 		z-index: 1001; | ||||
| 		height: 100dvh; | ||||
| 		width: 240px; | ||||
| 		box-sizing: border-box; | ||||
| 		contain: strict; | ||||
| 		overflow: auto; | ||||
| 		overscroll-behavior: contain; | ||||
| 		background: var(--navBg); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .column { | ||||
| 	flex-shrink: 0; | ||||
| 	border-right: solid var(--deckDividerThickness) var(--deckDivider); | ||||
|  | ||||
| 	&:first-of-type { | ||||
| 		border-left: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .folder { | ||||
| 	composes: column; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
|  | ||||
| 	> *:not(:last-of-type) { | ||||
| 		border-bottom: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .onboarding { | ||||
| 	padding: 32px; | ||||
| 	height: min-content; | ||||
| 	text-align: center; | ||||
| 	margin: auto; | ||||
| } | ||||
|  | ||||
| .sideMenu { | ||||
| 	flex-shrink: 0; | ||||
| 	margin-right: 0; | ||||
| 	margin-left: auto; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	justify-content: center; | ||||
| 	width: 32px; | ||||
| } | ||||
|  | ||||
| .sideMenuButton { | ||||
| 	display: block; | ||||
| 	width: 100%; | ||||
| 	aspect-ratio: 1; | ||||
| } | ||||
|  | ||||
| .sideMenuTop { | ||||
| 	margin-bottom: auto; | ||||
| } | ||||
|  | ||||
| .sideMenuMiddle { | ||||
| 	margin-top: auto; | ||||
| 	margin-bottom: auto; | ||||
| } | ||||
|  | ||||
| .sideMenuBottom { | ||||
| 	margin-top: auto; | ||||
| } | ||||
|  | ||||
| .menuBg { | ||||
| 	z-index: 1001; | ||||
| } | ||||
|  | ||||
| .menu { | ||||
| 	position: fixed; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| 	z-index: 1001; | ||||
| 	height: 100dvh; | ||||
| 	width: 240px; | ||||
| 	box-sizing: border-box; | ||||
| 	contain: strict; | ||||
| 	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,97 +294,78 @@ 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); | ||||
| 			} | ||||
| 			color: var(--fg); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&.paged { | ||||
| 		background: var(--bg) !important; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 	> header { | ||||
| 		position: relative; | ||||
| 		display: flex; | ||||
| 		z-index: 2; | ||||
| 		line-height: var(--deckColumnHeaderHeight); | ||||
| 		height: var(--deckColumnHeaderHeight); | ||||
| 		padding: 0 16px; | ||||
| 		font-size: 0.9em; | ||||
| 		color: var(--panelHeaderFg); | ||||
| 		background: var(--panelHeaderBg); | ||||
| 		box-shadow: 0 1px 0 0 var(--panelHeaderDivider); | ||||
| 		cursor: pointer; | ||||
| .header { | ||||
| 	position: relative; | ||||
| 	display: flex; | ||||
| 	z-index: 2; | ||||
| 	line-height: var(--deckColumnHeaderHeight); | ||||
| 	height: var(--deckColumnHeaderHeight); | ||||
| 	padding: 0 16px; | ||||
| 	font-size: 0.9em; | ||||
| 	color: var(--panelHeaderFg); | ||||
| 	background: var(--panelHeaderBg); | ||||
| 	box-shadow: 0 1px 0 0 var(--panelHeaderDivider); | ||||
| 	cursor: pointer; | ||||
|  | ||||
| 		&, * { | ||||
| 			user-select: none; | ||||
| 		} | ||||
|  | ||||
| 		&.indicated { | ||||
| 			box-shadow: 0 3px 0 0 var(--accent); | ||||
| 		} | ||||
|  | ||||
| 		> .header { | ||||
| 			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 { | ||||
| 			z-index: 1; | ||||
| 			width: var(--deckColumnHeaderHeight); | ||||
| 			line-height: var(--deckColumnHeaderHeight); | ||||
| 			color: var(--faceTextButton); | ||||
|  | ||||
| 			&:hover { | ||||
| 				color: var(--faceTextButtonHover); | ||||
| 			} | ||||
|  | ||||
| 			&:active { | ||||
| 				color: var(--faceTextButtonActive); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		> .toggleActive, > .action { | ||||
| 			margin-left: -16px; | ||||
| 		} | ||||
|  | ||||
| 		> .action { | ||||
| 			z-index: 1; | ||||
| 		} | ||||
|  | ||||
| 		> .action:empty { | ||||
| 			display: none; | ||||
| 		} | ||||
|  | ||||
| 		> .menu { | ||||
| 			margin-left: auto; | ||||
| 			margin-right: -16px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> div { | ||||
| 		height: calc(100% - var(--deckColumnHeaderHeight)); | ||||
| 		overflow-y: auto; | ||||
| 		overflow-x: hidden; // Safari does not supports clip | ||||
| 		overflow-x: clip; | ||||
| 		-webkit-overflow-scrolling: touch; | ||||
| 		box-sizing: border-box; | ||||
| 		container-type: inline-size; | ||||
| 		background-color: var(--bg); | ||||
| 	&, * { | ||||
| 		user-select: none; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .title { | ||||
| 	display: inline-block; | ||||
| 	align-items: center; | ||||
| 	overflow: hidden; | ||||
| 	text-overflow: ellipsis; | ||||
| 	white-space: nowrap; | ||||
| 	width: 100%; | ||||
| } | ||||
|  | ||||
| .toggleActive, | ||||
| .menu { | ||||
| 	z-index: 1; | ||||
| 	width: var(--deckColumnHeaderHeight); | ||||
| 	line-height: var(--deckColumnHeaderHeight); | ||||
| 	color: var(--faceTextButton); | ||||
|  | ||||
| 	&:hover { | ||||
| 		color: var(--faceTextButtonHover); | ||||
| 	} | ||||
|  | ||||
| 	&:active { | ||||
| 		color: var(--faceTextButtonActive); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .toggleActive { | ||||
| 	margin-left: -16px; | ||||
| } | ||||
|  | ||||
| .menu { | ||||
| 	margin-left: auto; | ||||
| 	margin-right: -16px; | ||||
| } | ||||
|  | ||||
| .body { | ||||
| 	height: calc(100% - var(--deckColumnHeaderHeight)); | ||||
| 	overflow-y: auto; | ||||
| 	overflow-x: hidden; // Safari does not supports clip | ||||
| 	overflow-x: clip; | ||||
| 	-webkit-overflow-scrolling: touch; | ||||
| 	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,195 +201,210 @@ 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 { | ||||
| 		background: var(--wallpaperOverlay); | ||||
| 		//backdrop-filter: var(--blur, blur(4px)); | ||||
| 	} | ||||
| .withWallpaper { | ||||
| 	background: var(--wallpaperOverlay); | ||||
| 	//backdrop-filter: var(--blur, blur(4px)); | ||||
| } | ||||
|  | ||||
| 	> .sidebar { | ||||
| 		border-right: solid 0.5px var(--divider); | ||||
| 	} | ||||
| .sidebar { | ||||
| 	border-right: solid 0.5px var(--divider); | ||||
| } | ||||
|  | ||||
| 	> .contents { | ||||
| 		width: 100%; | ||||
| 		min-width: 0; | ||||
| 		background: var(--bg); | ||||
| 	} | ||||
| .contents { | ||||
| 	width: 100%; | ||||
| 	min-width: 0; | ||||
| 	background: var(--bg); | ||||
| } | ||||
|  | ||||
| 	> .widgets { | ||||
| 		padding: 0 var(--margin); | ||||
| 		border-left: solid 0.5px var(--divider); | ||||
| 		background: var(--bg); | ||||
| .widgets { | ||||
| 	padding: 0 var(--margin); | ||||
| 	border-left: solid 0.5px var(--divider); | ||||
| 	background: var(--bg); | ||||
|  | ||||
| 		@media (max-width: $widgets-hide-threshold) { | ||||
| 			display: none; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .widgetButton { | ||||
| 		display: block; | ||||
| 		position: fixed; | ||||
| 		z-index: 1000; | ||||
| 		bottom: 32px; | ||||
| 		right: 32px; | ||||
| 		width: 64px; | ||||
| 		height: 64px; | ||||
| 		border-radius: 100%; | ||||
| 		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 { | ||||
| 		z-index: 1001; | ||||
| 	} | ||||
|  | ||||
| 	> .widgetsDrawer { | ||||
| 		position: fixed; | ||||
| 		top: 0; | ||||
| 		right: 0; | ||||
| 		z-index: 1001; | ||||
| 		height: 100dvh; | ||||
| 		padding: var(--margin) !important; | ||||
| 		box-sizing: border-box; | ||||
| 		overflow: auto; | ||||
| 		overscroll-behavior: contain; | ||||
| 		background: var(--bg); | ||||
| 	} | ||||
|  | ||||
| 	> .buttons { | ||||
| 		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 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); | ||||
|  | ||||
| 		> .button { | ||||
| 			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(--X2); | ||||
| 			} | ||||
|  | ||||
| 			> .indicator { | ||||
| 				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 { | ||||
| 		z-index: 1001; | ||||
| 	} | ||||
|  | ||||
| 	> .menuDrawer { | ||||
| 		position: fixed; | ||||
| 		top: 0; | ||||
| 		left: 0; | ||||
| 		z-index: 1001; | ||||
| 		height: 100dvh; | ||||
| 		width: 240px; | ||||
| 		box-sizing: border-box; | ||||
| 		contain: strict; | ||||
| 		overflow: auto; | ||||
| 		overscroll-behavior: contain; | ||||
| 		background: var(--navBg); | ||||
| 	@media (max-width: $widgets-hide-threshold) { | ||||
| 		display: none; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | ||||
| <style lang="scss" module> | ||||
| .widgetButton { | ||||
| 	display: block; | ||||
| 	position: fixed; | ||||
| 	z-index: 1000; | ||||
| 	bottom: 32px; | ||||
| 	right: 32px; | ||||
| 	width: 64px; | ||||
| 	height: 64px; | ||||
| 	border-radius: 100%; | ||||
| 	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); | ||||
| } | ||||
|  | ||||
| .widgetsDrawerBg { | ||||
| 	z-index: 1001; | ||||
| } | ||||
|  | ||||
| .widgetsDrawer { | ||||
| 	position: fixed; | ||||
| 	top: 0; | ||||
| 	right: 0; | ||||
| 	z-index: 1001; | ||||
| 	height: 100dvh; | ||||
| 	padding: var(--margin) !important; | ||||
| 	box-sizing: border-box; | ||||
| 	overflow: auto; | ||||
| 	overscroll-behavior: contain; | ||||
| 	background: var(--bg); | ||||
| } | ||||
|  | ||||
| .widgetsCloseButton { | ||||
| 	padding: 8px; | ||||
| 	display: block; | ||||
| 	margin: 0 auto; | ||||
| } | ||||
|  | ||||
| @media (min-width: 370px) { | ||||
| 	.widgetsCloseButton { | ||||
| 		display: none; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .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 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; | ||||
| } | ||||
|  | ||||
| .menuDrawerBg { | ||||
| 	z-index: 1001; | ||||
| } | ||||
|  | ||||
| .menuDrawer { | ||||
| 	position: fixed; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| 	z-index: 1001; | ||||
| 	height: 100dvh; | ||||
| 	width: 240px; | ||||
| 	box-sizing: border-box; | ||||
| 	contain: strict; | ||||
| 	overflow: auto; | ||||
| 	overscroll-behavior: contain; | ||||
| 	background: var(--navBg); | ||||
| } | ||||
|  | ||||
| .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; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> * { | ||||
| 		width: 300px; | ||||
|  | ||||
| 		&:first-child { | ||||
| 			margin-top: 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .add { | ||||
| 		margin: 0 auto; | ||||
| 	} | ||||
| .widgets { | ||||
| 	width: 300px; | ||||
| } | ||||
| </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'; | ||||
							
								
								
									
										94
									
								
								packages/frontend/src/widgets/WidgetInstanceInfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								packages/frontend/src/widgets/WidgetInstanceInfo.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| <template> | ||||
| <div class="_panel"> | ||||
| 	<div :class="$style.container" :style="{ backgroundImage: $instance.bannerUrl ? `url(${ $instance.bannerUrl })` : null }"> | ||||
| 		<div :class="$style.iconContainer"> | ||||
| 			<img :src="$instance.iconUrl ?? $instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.icon"/> | ||||
| 		</div> | ||||
| 		<div :class="$style.bodyContainer"> | ||||
| 			<div :class="$style.body"> | ||||
| 				<MkA :class="$style.name" to="/about" behavior="window">{{ $instance.name }}</MkA> | ||||
| 				<div :class="$style.host">{{ host }}</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; | ||||
| import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | ||||
| import { GetFormResultType } from '@/scripts/form'; | ||||
| import { host } from '@/config'; | ||||
|  | ||||
| const name = 'instanceInfo'; | ||||
|  | ||||
| const widgetPropsDef = { | ||||
| }; | ||||
|  | ||||
| type WidgetProps = GetFormResultType<typeof widgetPropsDef>; | ||||
|  | ||||
| // 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない | ||||
| //const props = defineProps<WidgetComponentProps<WidgetProps>>(); | ||||
| //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); | ||||
| const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); | ||||
| const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); | ||||
|  | ||||
| const { widgetProps, configure } = useWidgetPropsManager(name, | ||||
| 	widgetPropsDef, | ||||
| 	props, | ||||
| 	emit, | ||||
| ); | ||||
|  | ||||
| defineExpose<WidgetComponentExpose>({ | ||||
| 	name, | ||||
| 	configure, | ||||
| 	id: props.widget ? props.widget.id : null, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" module> | ||||
| .container { | ||||
| 	position: relative; | ||||
| 	background-size: cover; | ||||
| 	background-position: center; | ||||
| 	display: flex; | ||||
| } | ||||
|  | ||||
| .iconContainer { | ||||
| 	display: inline-block; | ||||
| 	text-align: center; | ||||
| 	padding: 16px; | ||||
| } | ||||
|  | ||||
| .icon { | ||||
| 	display: inline-block; | ||||
| 	width: 60px; | ||||
| 	height: 60px; | ||||
| 	border-radius: 8px; | ||||
| 	box-sizing: border-box; | ||||
| 	border: solid 3px #fff; | ||||
| } | ||||
|  | ||||
| .bodyContainer { | ||||
| 	display: flex; | ||||
| 	align-items: center; | ||||
| 	min-width: 0; | ||||
| 	padding: 0 16px 0 0; | ||||
| } | ||||
|  | ||||
| .body { | ||||
| 	text-overflow: ellipsis; | ||||
| 	overflow: clip; | ||||
| } | ||||
|  | ||||
| .name { | ||||
| 	color: #fff; | ||||
| 	filter: drop-shadow(0 0 4px #000); | ||||
| 	font-weight: bold; | ||||
| } | ||||
|  | ||||
| .host { | ||||
| 	color: #fff; | ||||
| 	filter: drop-shadow(0 0 4px #000); | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										96
									
								
								packages/frontend/src/widgets/WidgetProfile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								packages/frontend/src/widgets/WidgetProfile.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| <template> | ||||
| <div class="_panel"> | ||||
| 	<div :class="$style.container" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> | ||||
| 		<div :class="$style.avatarContainer"> | ||||
| 			<MkAvatar :class="$style.avatar" :user="$i" :disable-link="true" :disable-preview="true"/> | ||||
| 		</div> | ||||
| 		<div :class="$style.bodyContainer"> | ||||
| 			<div :class="$style.body"> | ||||
| 				<MkA :class="$style.name" :to="userPage($i)"> | ||||
| 					<MkUserName :user="$i"/> | ||||
| 				</MkA> | ||||
| 				<div :class="$style.username"><MkAcct :user="$i" detail/></div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; | ||||
| import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | ||||
| import { GetFormResultType } from '@/scripts/form'; | ||||
| import { $i } from '@/account'; | ||||
| import { userPage } from '@/filters/user'; | ||||
|  | ||||
| const name = 'profile'; | ||||
|  | ||||
| const widgetPropsDef = { | ||||
| }; | ||||
|  | ||||
| type WidgetProps = GetFormResultType<typeof widgetPropsDef>; | ||||
|  | ||||
| // 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない | ||||
| //const props = defineProps<WidgetComponentProps<WidgetProps>>(); | ||||
| //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); | ||||
| const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); | ||||
| const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); | ||||
|  | ||||
| const { widgetProps, configure } = useWidgetPropsManager(name, | ||||
| 	widgetPropsDef, | ||||
| 	props, | ||||
| 	emit, | ||||
| ); | ||||
|  | ||||
| defineExpose<WidgetComponentExpose>({ | ||||
| 	name, | ||||
| 	configure, | ||||
| 	id: props.widget ? props.widget.id : null, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" module> | ||||
| .container { | ||||
| 	position: relative; | ||||
| 	background-size: cover; | ||||
| 	background-position: center; | ||||
| 	display: flex; | ||||
| } | ||||
|  | ||||
| .avatarContainer { | ||||
| 	display: inline-block; | ||||
| 	text-align: center; | ||||
| 	padding: 16px; | ||||
| } | ||||
|  | ||||
| .avatar { | ||||
| 	display: inline-block; | ||||
| 	width: 60px; | ||||
| 	height: 60px; | ||||
| 	box-sizing: border-box; | ||||
| 	border: solid 3px #fff; | ||||
| } | ||||
|  | ||||
| .bodyContainer { | ||||
| 	display: flex; | ||||
| 	align-items: center; | ||||
| 	min-width: 0; | ||||
| 	padding: 0 16px 0 0; | ||||
| } | ||||
|  | ||||
| .body { | ||||
| 	text-overflow: ellipsis; | ||||
| 	overflow: clip; | ||||
| } | ||||
|  | ||||
| .name { | ||||
| 	color: #fff; | ||||
| 	filter: drop-shadow(0 0 4px #000); | ||||
| 	font-weight: bold; | ||||
| } | ||||
|  | ||||
| .username { | ||||
| 	color: #fff; | ||||
| 	filter: drop-shadow(0 0 4px #000); | ||||
| } | ||||
| </style> | ||||
| @@ -108,10 +108,7 @@ const tick = () => { | ||||
| 	window.fetch(fetchEndpoint.value, {}) | ||||
| 	.then(res => res.json()) | ||||
| 	.then(feed => { | ||||
| 		if (widgetProps.shuffle) { | ||||
| 			shuffle(feed.items); | ||||
| 		} | ||||
| 		rawItems.value = feed.items; | ||||
| 		rawItems.value = feed.items ?? []; | ||||
| 		fetching.value = false; | ||||
| 		key++; | ||||
| 	}); | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user