Compare commits
	
		
			146 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d10ad1b413 | ||
|   | 34063a0b84 | ||
|   | 8c9d975d69 | ||
|   | 03b072b894 | ||
|   | d1bed49808 | ||
|   | 6c3417d9b5 | ||
|   | ba65226460 | ||
|   | 9c9cd168ee | ||
|   | abb3d2a8d9 | ||
|   | 637fe8a04b | ||
|   | be321e95e5 | ||
|   | ed46c1486c | ||
|   | c9fcfc6862 | ||
|   | 8495e37566 | ||
|   | 247bd43ae2 | ||
|   | a6685b1559 | ||
|   | 66c4e8064b | ||
|   | 9d1fa3f202 | ||
|   | a6985d7dc7 | ||
|   | 027c021ac9 | ||
|   | 604205ec09 | ||
|   | 77db016866 | ||
|   | c6a009dbae | ||
|   | 4299e3f90c | ||
|   | 19f4812c03 | ||
|   | d01c465a8d | ||
|   | 4f1409601e | ||
|   | 52cffe0864 | ||
|   | 0866d5c055 | ||
|   | 78c08f6503 | ||
|   | 27d0ac3d75 | ||
|   | a8776002f3 | ||
|   | 31aa008566 | ||
|   | 9d405b4581 | ||
|   | 80c490a18b | ||
|   | 30c9c3739f | ||
|   | ee0e7a09e0 | ||
|   | bfd9577f0d | ||
|   | 0cada4ca76 | ||
|   | a718ccc0b6 | ||
|   | 1fcfd8e645 | ||
|   | c6dd932a0b | ||
|   | b79eed01e0 | ||
|   | 3a7dbe9764 | ||
|   | bef2534fa8 | ||
|   | 888dcd2559 | ||
|   | 2b69fca6bd | ||
|   | 7d088d42b4 | ||
|   | f8ad303b13 | ||
|   | 3c59c6fc9b | ||
|   | 7353d729d7 | ||
|   | 62591e0e7a | ||
|   | 012f15d84b | ||
|   | e57c6f94d2 | ||
|   | 40b27e8ad8 | ||
|   | 055e9f21b7 | ||
|   | d7085b17fe | ||
|   | 0d4d7c9c0c | ||
|   | 99209d36e1 | ||
|   | e2a9a0ff3d | ||
|   | ab166959a4 | ||
|   | ab692cfa3d | ||
|   | c3ae6f3a4a | ||
|   | 5ef4a52bbd | ||
|   | 582768a5e4 | ||
|   | 1852d1cc6f | ||
|   | 7a5a541a4e | ||
|   | 72b03e009c | ||
|   | 1b113c1045 | ||
|   | 54959557ea | ||
|   | d44cb7f256 | ||
|   | 3d063c95d1 | ||
|   | 09cab605fc | ||
|   | 666c8c0498 | ||
|   | d3e764d7f9 | ||
|   | 7060625adf | ||
|   | 21b6e23e98 | ||
|   | a0f794e372 | ||
|   | 9195504329 | ||
|   | 8c5d9dd549 | ||
|   | 580f6a5b6c | ||
|   | 74e76b460b | ||
|   | c4570b37b7 | ||
|   | cd0b0012d9 | ||
|   | c055b4d32d | ||
|   | 75a9ff832a | ||
|   | b64d3af1f3 | ||
|   | fb6605bb40 | ||
|   | 3bfae80fa7 | ||
|   | cb16cb0610 | ||
|   | 0baed1a275 | ||
|   | 42162c8015 | ||
|   | 0fab0c416d | ||
|   | e2e262c8ce | ||
|   | cf6596203b | ||
|   | 471911a54f | ||
|   | 9394f4f540 | ||
|   | 4e968216ad | ||
|   | 84a7a9555f | ||
|   | 8d12fd152b | ||
|   | 629b765abc | ||
|   | 63a89fa84a | ||
|   | a3f89236a0 | ||
|   | 01560abafb | ||
|   | b5698026ba | ||
|   | 6258ce75b7 | ||
|   | 6f34c74027 | ||
|   | 8add4f359b | ||
|   | d8933c135f | ||
|   | eb350e8d6c | ||
|   | 615fedd64d | ||
|   | 25bd82ecaa | ||
|   | e0938e5e3a | ||
|   | ec5e6c8443 | ||
|   | 25d8077474 | ||
|   | 06083f40d9 | ||
|   | ec203f7f79 | ||
|   | 1b30d7d47a | ||
|   | d9be9c958f | ||
|   | ed09796e0d | ||
|   | 4bfa29c0ab | ||
|   | 4804bbb211 | ||
|   | 749102f9c2 | ||
|   | 0bcb1434b0 | ||
|   | 2e537e618c | ||
|   | fe3b7a2ad3 | ||
|   | 90db793fd0 | ||
|   | 7bd2a6ad61 | ||
|   | 745f4d2439 | ||
|   | 254cfaea28 | ||
|   | d4da5a1eea | ||
|   | c0f8297414 | ||
|   | 834cb2ea1a | ||
|   | d82769abd4 | ||
|   | adf01ed4a4 | ||
|   | 09c007b3aa | ||
|   | 526ff177aa | ||
|   | 0e40d4e796 | ||
|   | 172ebab7bd | ||
|   | aa4493fe5c | ||
|   | a68a88f79e | ||
|   | 1de7dc94e1 | ||
|   | 59cb7992e2 | ||
|   | 87b15df47b | ||
|   | 6932d86240 | ||
|   | 87f61e714a | 
| @@ -1 +1 @@ | ||||
| v14.4.0 | ||||
| v15.0.1 | ||||
|   | ||||
							
								
								
									
										2613
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										2613
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +1,4 @@ | ||||
| FROM node:14.4.0-alpine AS base | ||||
| FROM node:15.0.1-alpine AS base | ||||
|  | ||||
| ENV NODE_ENV=production | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| [](http://makeapullrequest.com) | ||||
| [](https://github.com/humanetech-community/awesome-humane-tech) | ||||
|  | ||||
| **A forever evolving, sophisticated microblogging platform.** | ||||
| **A forever evolving, professional microblogging platform.** | ||||
|  | ||||
| <p align="justify"> | ||||
| <a href="https://join.misskey.page/">Misskey</a> is a decentralized microblogging platform born on Earth. | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| --- | ||||
| _lang_: "العربية" | ||||
| introMisskey: "اهلا بك! ميسكي هو منصة تدوين مصغر لا مركزية ومفتوحة المصدر.\nيمكنك مشاركة \"ملاحظات\" عن ما يجري حولك، وإخبار الجميع عن نفسك 📡\nتسمح لك \"الانفعالات\" بتعبير عن شعورك حول ملاحظات الآخرين 👍\nاكتشف عالمًا جديدًا 🚀" | ||||
| monthAndDay: "{day}/{month}" | ||||
| search: "البحث" | ||||
| notifications: "الإشعارات" | ||||
| @@ -14,8 +15,12 @@ noNotes: "لم يتم العثور على أية ملاحظات" | ||||
| noNotifications: "ليس هناك أية اشعارات" | ||||
| instance: "مثيل الخادم" | ||||
| settings: "الاعدادات" | ||||
| basicSettings: "الاعدادات الأساسية" | ||||
| otherSettings: "إعدادات أخرى" | ||||
| openInWindow: "افتح في نافذة جديدة" | ||||
| profile: "الملف التعريفي" | ||||
| timeline: "الخيط الزمني" | ||||
| noAccountDescription: "لم يكتب هذا المستخدم سيرته بعد." | ||||
| login: "لِج" | ||||
| loggingIn: "جارٍ تسجيل الدخول" | ||||
| logout: "الخروج" | ||||
| @@ -28,22 +33,31 @@ favorite: "إضافة إلى المفضلة" | ||||
| favorites: "المفضلات" | ||||
| unfavorite: "إزالة من المفضلة" | ||||
| pin: "دبّسها على الصفحة الشخصية" | ||||
| unpin: "ألغ تثبيتها من ملفك الشخصي" | ||||
| copyContent: "انسخ المحتوى" | ||||
| copyLink: "انسخ الرابط" | ||||
| delete: "حذف" | ||||
| deleteAndEdit: "إزالة وإعادة الصياغة" | ||||
| deleteAndEditConfirm: "أمتأكد من حذف الملاحظة؟ ستفقد كل مشاركاتها، والتفاعلات، والردود عليها." | ||||
| addToList: "أضفه إلى قائمة" | ||||
| sendMessage: "أرسل رسالة" | ||||
| copyUsername: "انسخ اسم المستخدم" | ||||
| searchUser: "ابحث عن مستخدمين" | ||||
| reply: "رد" | ||||
| loadMore: "عرض المزيد" | ||||
| youGotNewFollower: "يتابعك" | ||||
| receiveFollowRequest: "تلقيت طلب متابعة" | ||||
| followRequestAccepted: "قُبل طلب المتابعة" | ||||
| mention: "أشر الى" | ||||
| mentions: "الإشارات" | ||||
| directNotes: "الملاحظات المباشرة" | ||||
| importAndExport: "إستورد / صدر" | ||||
| import: "استيراد" | ||||
| export: "تصدير" | ||||
| files: "الملفات" | ||||
| download: "تنزيل" | ||||
| driveFileDeleteConfirm: "أمتأكد من حذف ملف {name}؟ كل الملاحظات المُرفق بها هذا الملف ستحذف." | ||||
| unfollowConfirm: "أمتأكد من إلغاء متابعة {name}؟" | ||||
| lists: "القوائم" | ||||
| noLists: "ليس لديك أية قائمة" | ||||
| note: "ملاحظة" | ||||
| @@ -53,8 +67,10 @@ followers: "المتابِعين" | ||||
| followsYou: "يتابعك" | ||||
| createList: "إنشاء قائمة" | ||||
| manageLists: "إدارة القوائم" | ||||
| error: "حدث خطأ ما" | ||||
| error: "خطأ" | ||||
| somethingHappened: "حدث خطأ" | ||||
| retry: "حاول مجددًا" | ||||
| pageLoadError: "فشل تحميل الصفحة" | ||||
| enterListName: "اسم القائمة" | ||||
| privacy: "الخصوصية" | ||||
| makeFollowManuallyApprove: "القبول يدويا طلبات الإشتراك" | ||||
| @@ -64,6 +80,7 @@ followRequest: "طلب اشتراك" | ||||
| followRequests: "طلبات الإشتراك" | ||||
| unfollow: "إلغاء الاشتراك" | ||||
| followRequestPending: "طلبات الإشتراك المعلّقة" | ||||
| enterEmoji: "أدخل إيموجي" | ||||
| unrenote: "إلغاء مشاركة الملاحظة" | ||||
| quote: "اقتبس" | ||||
| pinnedNote: "ملاحظة مدبسة" | ||||
| @@ -71,16 +88,26 @@ you: "أنت" | ||||
| clickToShow: "اضغط للعرض" | ||||
| sensitive: "محتوى حساس" | ||||
| add: "إضافة" | ||||
| reaction: "تفاعل" | ||||
| rememberNoteVisibility: "تذكر إعدادت مدى رؤية الملاحظات" | ||||
| attachCancel: "أزل المرفق" | ||||
| enterFileName: "ادخل اسم الملف" | ||||
| mute: "اكتم" | ||||
| unmute: "إلغاء الكتم" | ||||
| block: "احجب" | ||||
| unblock: "إلغاء الحجب" | ||||
| suspend: "علِق" | ||||
| unsuspend: "ألغ التعليق" | ||||
| blockConfirm: "أمتأكد من حجب هذا الحساب؟" | ||||
| unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟" | ||||
| selectList: "اختر قائمة" | ||||
| editWidgetsExit: "تم" | ||||
| customEmojis: "إيموجي مخصص" | ||||
| addEmoji: "إضافة إيموجي" | ||||
| cacheRemoteFiles: "خزن مؤقتا الملفات البعيدة" | ||||
| autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الحسابات المتابَعة" | ||||
| addAcount: "إضافة حساب" | ||||
| loginFailed: "فشل الولوج" | ||||
| showOnRemote: "رؤيته على مثيل الخادم البُعدي" | ||||
| general: "الرئيسية" | ||||
| wallpaper: "خلفية الشاشة" | ||||
| @@ -88,6 +115,7 @@ setWallpaper: "استخدم خلفية الشاشة" | ||||
| removeWallpaper: "إزالة خلفية الشاشة" | ||||
| searchWith: "البحث: {q}" | ||||
| youHaveNoLists: "لا تمتلك أية قائمة" | ||||
| followConfirm: "أتريد متابعة {name}؟" | ||||
| proxyAccount: "حساب وكيل البروكسي" | ||||
| host: "المضيف" | ||||
| selectUser: "حدّد مستخدمًا" | ||||
| @@ -96,6 +124,8 @@ annotation: "التعليقات" | ||||
| federation: "الفديرالية" | ||||
| instances: "مثيل الخادم" | ||||
| latestRequestSentAt: "آخر طلب أرسِل في" | ||||
| latestRequestReceivedAt: "آخر طلب تُلقي في" | ||||
| storageUsage: "مساحة التخزين المستخدمة" | ||||
| charts: "المنحنيات البيانية" | ||||
| perHour: "في الساعة" | ||||
| perDay: "في اليوم" | ||||
| @@ -127,7 +157,6 @@ processing: "المعالجة جارية" | ||||
| preview: "معاينة" | ||||
| default: "افتراضي" | ||||
| noCustomEmojis: "ليس هناك إيموجيات" | ||||
| customEmojisOfRemote: "الإيموجيات القادمة مِن مثيلات الخوادم الأخرى" | ||||
| federating: "الفديرالية جارية" | ||||
| blocked: "محجوب" | ||||
| suspended: "مُعلّق" | ||||
| @@ -256,7 +285,6 @@ unregister: "إلغاء التسجيل" | ||||
| passwordLessLogin: "لِج مِن دون كلمة سرية" | ||||
| resetPassword: "أعد تعيين كلمتك السرية" | ||||
| newPasswordIs: "كلمتك السرية الجديدة هي {password}" | ||||
| autoNoteWatch: "راقب الملاحظات تلقائيا" | ||||
| share: "شارِك" | ||||
| notFound: "غير موجود" | ||||
| help: "المساعدة" | ||||
| @@ -280,6 +308,7 @@ noteOf: "ملاحظات {user}" | ||||
| inviteToGroup: "دعوة إلى فريق" | ||||
| noMessagesYet: "ليس هناك رسائل بعد" | ||||
| newMessageExists: "لقد تلقيت رسالة جديدة" | ||||
| invitations: "دعوة" | ||||
| invitationCode: "رمز الدعوة" | ||||
| checking: "التحقق جارٍ" | ||||
| available: "متوفر" | ||||
| @@ -313,7 +342,6 @@ total: "المجموع" | ||||
| weekOverWeekChanges: "أسبوعيا" | ||||
| dayOverDayChanges: "يوميا" | ||||
| appearance: "المظهر" | ||||
| clinetSettings: "إعدادات التطبيق" | ||||
| accountSettings: "إعدادات الحساب" | ||||
| promotion: "ترقية" | ||||
| promote: "روِّج" | ||||
| @@ -351,6 +379,13 @@ smtpHost: "المضيف" | ||||
| smtpUser: "اسم المستخدم" | ||||
| smtpPass: "الكلمة السرية" | ||||
| display: "المظهر" | ||||
| _mfm: | ||||
|   mention: "أشر الى" | ||||
|   quote: "اقتبس" | ||||
|   emoji: "إيموجي مخصص" | ||||
|   search: "البحث" | ||||
| _reversi: | ||||
|   total: "المجموع" | ||||
| _channel: | ||||
|   featured: "المتداوَلة" | ||||
| _sidebar: | ||||
| @@ -366,6 +401,7 @@ _theme: | ||||
|   make: "إنشاء قالب" | ||||
|   alpha: "الشفافية" | ||||
|   keys: | ||||
|     mention: "أشر الى" | ||||
|     messageBg: "خلفية الدردشة" | ||||
| _sfx: | ||||
|   note: "الملاحظات" | ||||
| @@ -508,7 +544,9 @@ _notification: | ||||
|   youWereFollowed: "يتابعك" | ||||
|   _types: | ||||
|     follow: "المتابَعون" | ||||
|     mention: "أشر الى" | ||||
|     quote: "اقتبس" | ||||
|     reaction: "تفاعل" | ||||
| _deck: | ||||
|   _columns: | ||||
|     notifications: "الإشعارات" | ||||
|   | ||||
| @@ -16,6 +16,9 @@ noNotes: "Keine Notizen" | ||||
| noNotifications: "Keine Benachrichtigungen" | ||||
| instance: "Instanz" | ||||
| settings: "Einstellungen" | ||||
| basicSettings: "Allgemeine Einstellungen" | ||||
| otherSettings: "Andere Einstellungen" | ||||
| openInWindow: "In Fenster öffnen" | ||||
| profile: "Profil" | ||||
| timeline: "Chronik" | ||||
| noAccountDescription: "Dieser Nutzer hat seine Profilbeschreibung noch nicht ausgefüllt." | ||||
| @@ -40,6 +43,7 @@ deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten | ||||
| addToList: "Zu Liste hinzufügen" | ||||
| sendMessage: "Nachricht senden" | ||||
| copyUsername: "Benutzernamen kopieren" | ||||
| searchUser: "Benutzersuche" | ||||
| reply: "Antworten" | ||||
| loadMore: "Mehr anzeigen" | ||||
| youGotNewFollower: "Du hast einen neuen Follower" | ||||
| @@ -66,8 +70,11 @@ followers: "Gefolgt von" | ||||
| followsYou: "Folgt dir" | ||||
| createList: "Liste erstellen" | ||||
| manageLists: "Listen verwalten" | ||||
| error: "Ein Problem ist aufgetreten" | ||||
| error: "Fehler" | ||||
| somethingHappened: "Ein Fehler ist aufgetreten" | ||||
| retry: "Wiederholen" | ||||
| pageLoadError: "Laden der Seite fehlgeschlagen." | ||||
| pageLoadErrorDescription: "Dieser Fehler wird meist durch Netzwerkfehler oder den Browser-Cache verursacht. Versuche den Browser-Cache zu leeren und es nach kurzer Zeit noch einmal zu probieren." | ||||
| enterListName: "Listennamen eingeben" | ||||
| privacy: "Privatsphäre" | ||||
| makeFollowManuallyApprove: "Follow-Anfragen benötigen Bestätigung" | ||||
| @@ -106,6 +113,8 @@ unsuspendConfirm: "Möchtest du die Sperrung dieses Benutzers wirklich aufheben? | ||||
| selectList: "Wähle eine Liste aus" | ||||
| selectAntenna: "Antenne auswählen" | ||||
| selectWidget: "Widget auswählen" | ||||
| editWidgets: "Widgets bearbeiten" | ||||
| editWidgetsExit: "Fertig" | ||||
| customEmojis: "Benutzerdefinierte Emojis" | ||||
| emoji: "Emoji" | ||||
| emojiName: "Emojiname" | ||||
| @@ -177,7 +186,6 @@ processing: "In Bearbeitung" | ||||
| preview: "Vorschau" | ||||
| default: "Standard" | ||||
| noCustomEmojis: "Es existieren keine Emojis" | ||||
| customEmojisOfRemote: "Emojis von anderen Instanzen" | ||||
| noJobs: "Es gibt keine Jobs" | ||||
| federating: "Föderiert" | ||||
| blocked: "Blockiert" | ||||
| @@ -365,8 +373,6 @@ unregister: "Deaktivieren" | ||||
| passwordLessLogin: "Passwortloses Anmelden einrichten" | ||||
| resetPassword: "Passwort zurücksetzen" | ||||
| newPasswordIs: "Das neue Passwort ist \"{password}\"" | ||||
| autoNoteWatch: "Notizen automatisch beobachten" | ||||
| autoNoteWatchDescription: "Werde über Notizen, auf die du reagiert oder geantwortet hast, informiert" | ||||
| reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren" | ||||
| share: "Teilen" | ||||
| notFound: "Nicht gefunden" | ||||
| @@ -404,6 +410,7 @@ noMessagesYet: "Noch keine Nachrichten" | ||||
| newMessageExists: "Du hast eine neue Nachricht" | ||||
| onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden" | ||||
| signinRequired: "Anmeldung erforderlich" | ||||
| invitations: "Einladungen" | ||||
| invitationCode: "Einladungscode" | ||||
| checking: "Wird überprüft..." | ||||
| available: "Verfügbar" | ||||
| @@ -445,7 +452,7 @@ total: "Gesamt" | ||||
| weekOverWeekChanges: "Wöchentlich" | ||||
| dayOverDayChanges: "Täglich" | ||||
| appearance: "Aussehen" | ||||
| clinetSettings: "Client-Einstellungen" | ||||
| clientSettings: "Client-Einstellungen" | ||||
| accountSettings: "Benutzerkonto-Einstellungen" | ||||
| promotion: "Hervorgehoben" | ||||
| promote: "Hervorheben" | ||||
| @@ -476,6 +483,8 @@ newNoteRecived: "Es gibt neue Notizen" | ||||
| sounds: "Töne" | ||||
| listen: "Anhören" | ||||
| none: "Keine" | ||||
| showInPage: "In Seite anzeigen" | ||||
| popout: "Pop-Up" | ||||
| volume: "Lautstärke" | ||||
| details: "Details" | ||||
| chooseEmoji: "Wähle ein Emoji" | ||||
| @@ -518,7 +527,6 @@ enableInfiniteScroll: "Automatisch mehr Notizen laden" | ||||
| visibility: "Sichtbarkeit" | ||||
| poll: "Umfrage" | ||||
| useCw: "Inhalt verstecken" | ||||
| fixedWidgetsPosition: "Widgetposition fixieren" | ||||
| enablePlayer: "Video-Player öffnen" | ||||
| disablePlayer: "Video-Player schließen" | ||||
| expandTweet: "Tweet ausklappen" | ||||
| @@ -532,6 +540,7 @@ pluginInstallWarn: "Installiere nur vertrauenswürdige Plugins." | ||||
| deck: "Deck" | ||||
| undeck: "Deck verlassen" | ||||
| useBlurEffectForModal: "Weichzeichnungseffekt für Modals verwenden" | ||||
| useFullReactionPicker: "Vollständige Reaktionsauswahl nutzen" | ||||
| generateAccessToken: "Zugriffstoken generieren" | ||||
| permission: "Berechtigungen" | ||||
| enableAll: "Alle aktivieren" | ||||
| @@ -564,8 +573,121 @@ overview: "Übersicht" | ||||
| logs: "Logs" | ||||
| delayed: "Verzögert" | ||||
| database: "Datenbank" | ||||
| channel: "Kanal" | ||||
| channel: "Kanäle" | ||||
| create: "Erstellen" | ||||
| notificationSetting: "Benachrichtigungseinstellungen" | ||||
| notificationSettingDesc: "Wähle die Art der anzuzeigenden Benachrichtigung" | ||||
| useGlobalSetting: "Globale Einstellung verwenden" | ||||
| useGlobalSettingDesc: "Wenn dies eingeschaltet ist, werden die Benachrichtigungseinstellungen deines Benutzerkontos verwendet. Wenn dies ausgeschaltet ist, können individuelle Einstellungen vorgenommen werden." | ||||
| other: "Andere" | ||||
| regenerateLoginToken: "Login-Token regenerieren" | ||||
| regenerateLoginTokenDescription: "Den bei Logins intern verwendeten Token regenerieren. Normalerweise wird dies nicht benötigt. Bei Regeneration werden alle Geräte ausgeloggt." | ||||
| setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehrere Einstellungen zu kofigurieren." | ||||
| fileIdOrUrl: "Datei-ID oder URL" | ||||
| chatOpenBehavior: "Verhalten des Chatfensters bei Öffnung" | ||||
| sample: "Beispiel" | ||||
| abuseReports: "Melden" | ||||
| reportAbuse: "Melden" | ||||
| reportAbuseOf: "{name} melden" | ||||
| fillAbuseReportDescription: "Bitte gib Details für diese Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an." | ||||
| abuseReported: "Die Meldung wurde versendet. Vielen Dank." | ||||
| send: "Senden" | ||||
| abuseMarkAsResolved: "Meldung als gelöst markieren" | ||||
| openInNewTab: "In neuem Tab öffnen" | ||||
| openInSideView: "In Seitenansicht öffnen" | ||||
| defaultNavigationBehaviour: "Standardnavigationsverhalten" | ||||
| editTheseSettingsMayBreakAccount: "Bei Bearbeitung dieser Einstellungen besteht die Gefahr, dein Benutzerkonto zu beschädigen." | ||||
| instanceTicker: "Instanz-Informationen von Notizen" | ||||
| waitingFor: "Warte auf {x}" | ||||
| random: "Zufällig" | ||||
| system: "System" | ||||
| switchUi: "UI wechseln" | ||||
| desktop: "Desktop" | ||||
| _mfm: | ||||
|   cheatSheet: "MFM Spickzettel" | ||||
|   intro: "MFM ist eine an vielen Stellen verwendbare und Misskey-exklusive Markup-Sprache. Hier kannst du eine Liste von verfügbarer MFM-Syntax anschauen." | ||||
|   dummy: "Misskey erweitert die Welt des Fediverse" | ||||
|   mention: "Erwähnung" | ||||
|   mentionDescription: "Mit At-Zeichen und Nutzername kann ein individueller Nutzer angegeben werden." | ||||
|   hashtag: "Hashtag" | ||||
|   hashtagDescription: "Mit einer Raute und Text kann ein Hashtag angegeben werden." | ||||
|   url: "URL" | ||||
|   urlDescription: "URLs können angezeigt werden." | ||||
|   link: "Link" | ||||
|   linkDescription: "Ein spezifizierter Textabschnitt kann als URL angezeigt werden." | ||||
|   bold: "Fett" | ||||
|   boldDescription: "Zeichen zur Betonung dicker erscheinen lassen." | ||||
|   small: "Klein" | ||||
|   smallDescription: "Inhalt klein und dünn erscheinen lassen." | ||||
|   center: "Zentrieren" | ||||
|   centerDescription: "Inhalt zentriert anzeigen lassen." | ||||
|   inlineCode: "Code (Eingebettet)" | ||||
|   inlineCodeDescription: "Syntax-Hervorhebung für (Programm-)Code eingebettet anzeigen lassen." | ||||
|   blockCode: "Code (Block)" | ||||
|   blockCodeDescription: "Syntax-Hervorhebung für mehrzeiligen (Programm-)Code als Block anzeigen lassen." | ||||
|   inlineMath: "Mathe (Eingebettet)" | ||||
|   inlineMathDescription: "Mathematische Formeln (KaTeX) eingebettet anzeigen." | ||||
|   blockMath: "Mathe (Block)" | ||||
|   blockMathDescription: "Mehrzeilige mathematische Formeln (KaTeX) als Block einbetten." | ||||
|   quote: "Zitationen" | ||||
|   quoteDescription: "Inhalt als Zitat anzeigen lassen." | ||||
|   emoji: "Benutzerdefinierte Emojis" | ||||
|   emojiDescription: "Emoji-Namen mit Doppelpunkten umschließen, um benutzerdefinierte Emojis anzeigen zu lassen." | ||||
|   search: "Suche" | ||||
|   searchDescription: "Eine vorgefertige Suchanfragebox anzeigen lassen." | ||||
|   flip: "Spiegelung" | ||||
|   flipDescription: "Inhalt horizontal oder vertikal gespiegelt anzeigen lassen." | ||||
|   jelly: "Animation (Dehnen)" | ||||
|   jellyDescription: "Verleiht eine sich dehnende Animation." | ||||
|   tada: "Animation (Tada)" | ||||
|   tadaDescription: "Verleiht eine Animation mit \"Tada!\"-Gefühl" | ||||
|   jump: "Animation (Sprung)" | ||||
|   jumpDescription: "Verleiht eine springende Animation." | ||||
|   bounce: "Animation (Federn)" | ||||
|   bounceDescription: "Erzeugt eine federnde Animation." | ||||
|   shake: "Animation (Zittern)" | ||||
|   shakeDescription: "Verleiht eine zitternde Animation." | ||||
|   twitch: "Animation (Zucken)" | ||||
|   twitchDescription: "Verleiht eine sehr stark zuckende Animation." | ||||
|   spin: "Animation (Rotieren)" | ||||
|   spinDescription: "Verleiht eine rotierende Animation." | ||||
| _reversi: | ||||
|   reversi: "Reversi" | ||||
|   gameSettings: "Spieleinstellungen" | ||||
|   chooseBoard: "Spielbrett auswählen" | ||||
|   blackOrWhite: "Schwarz/Weiß" | ||||
|   blackIs: "{name} spielt Schwarz" | ||||
|   rules: "Regeln" | ||||
|   botSettings: "Optionen des Computergegners" | ||||
|   thisGameIsStartedSoon: "Dieses Spiel beginnt in wenigen Sekunden" | ||||
|   waitingForOther: "Warte auf den Zug des Gegenspielers" | ||||
|   waitingForMe: "Warte auf deinen Zug" | ||||
|   waitingBoth: "Mach dich bereit" | ||||
|   ready: "Bereit" | ||||
|   cancelReady: "Nicht bereit" | ||||
|   opponentTurn: "Zug deines Gegners" | ||||
|   myTurn: "Dein Zug" | ||||
|   turnOf: "Zug von {name}" | ||||
|   pastTurnOf: "Zug von {name}" | ||||
|   surrender: "Aufgeben" | ||||
|   surrendered: "durch Aufgabe" | ||||
|   drawn: "Unentschieden" | ||||
|   won: "{name} hat gesiegt" | ||||
|   black: "Schwarz" | ||||
|   white: "Weiß" | ||||
|   total: "Gesamt" | ||||
|   turnCount: " Zug {count}" | ||||
|   myGames: "Meine Runden" | ||||
|   allGames: "Alle Runden" | ||||
|   ended: "Beendet" | ||||
|   playing: "Laufend" | ||||
|   isLlotheo: "Der mit weniger Steinen gewinnt (Llotheo)" | ||||
|   loopedMap: "Wiederholendes Spielbrett" | ||||
|   canPutEverywhere: "Steine können überall platziert werden" | ||||
| _instanceTicker: | ||||
|   none: "Nie anzeigen" | ||||
|   remote: "Für Benutzer fremder Instanzen anzeigen" | ||||
|   always: "Immer anzeigen" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Automatisch aktualisieren" | ||||
|   dialog: "Warnungsfenster zeigen" | ||||
| @@ -576,13 +698,13 @@ _channel: | ||||
|   setBanner: "Kanalbanner festlegen" | ||||
|   removeBanner: "Kanalbanner entfernen" | ||||
|   featured: "Trends" | ||||
|   owned: "Besitzer" | ||||
|   following: "Folgt" | ||||
|   owned: "Besitzt" | ||||
|   following: "Gefolgt" | ||||
|   usersCount: "{n} Teilnehmer" | ||||
|   notesCount: "{n} Notizen" | ||||
| _sidebar: | ||||
|   full: "Voll" | ||||
|   icon: "Profilbild" | ||||
|   icon: "Symbol" | ||||
|   hide: "Ausblenden" | ||||
| _wordMute: | ||||
|   muteWords: "Wort stummschalten" | ||||
| @@ -782,6 +904,7 @@ _widgets: | ||||
|   photos: "Fotos" | ||||
|   digitalClock: "Digitaluhr" | ||||
|   federation: "Föderation" | ||||
|   postForm: "Neue Notiz anfertigen" | ||||
| _cw: | ||||
|   hide: "Ausblenden" | ||||
|   show: "Mehr anzeigen" | ||||
| @@ -1238,14 +1361,17 @@ _notification: | ||||
|   youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen" | ||||
|   _types: | ||||
|     all: "Alle" | ||||
|     follow: "Folgt" | ||||
|     mention: "Erwähnung" | ||||
|     follow: "Neue Follower" | ||||
|     mention: "Erwähnungen" | ||||
|     reply: "Antworten" | ||||
|     renote: "Renote" | ||||
|     quote: "Zitieren" | ||||
|     renote: "Renotes" | ||||
|     quote: "Zitationen" | ||||
|     reaction: "Reaktionen" | ||||
|     pollVote: "Umfragen" | ||||
|     receiveFollowRequest: "Follow-Anfragen" | ||||
|     pollVote: "Antworten auf Umfragen" | ||||
|     receiveFollowRequest: "Follow-Anfrage erhalten" | ||||
|     followRequestAccepted: "Follow-Anfrage akzeptiert" | ||||
|     groupInvited: "Gruppeneinladung erhalten" | ||||
|     app: "Benachrichtigungen von Apps" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "Hauptspalte immer zeigen" | ||||
|   columnAlign: "Spalten ausrichten" | ||||
|   | ||||
| @@ -16,6 +16,9 @@ noNotes: "No notes" | ||||
| noNotifications: "No notifications" | ||||
| instance: "Instance" | ||||
| settings: "Settings" | ||||
| basicSettings: "Basic Settings" | ||||
| otherSettings: "Other Settings" | ||||
| openInWindow: "Open in window" | ||||
| profile: "Profile" | ||||
| timeline: "Timeline" | ||||
| noAccountDescription: "This user has not written their bio yet." | ||||
| @@ -29,7 +32,7 @@ users: "Users" | ||||
| addUser: "Add a user" | ||||
| favorite: "Favorite" | ||||
| favorites: "Favorites" | ||||
| unfavorite: "Undo favorite" | ||||
| unfavorite: "Unfavorite" | ||||
| pin: "Pin to profile" | ||||
| unpin: "Unpin from profile" | ||||
| copyContent: "Copy contents" | ||||
| @@ -40,6 +43,7 @@ deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? Yo | ||||
| addToList: "Add to list" | ||||
| sendMessage: "Send a message" | ||||
| copyUsername: "Copy username" | ||||
| searchUser: "User search" | ||||
| reply: "Reply" | ||||
| loadMore: "Load more" | ||||
| youGotNewFollower: "Followed you" | ||||
| @@ -66,8 +70,11 @@ followers: "Followers" | ||||
| followsYou: "Follows you" | ||||
| createList: "Create list" | ||||
| manageLists: "Manage lists" | ||||
| error: "Something happened :(" | ||||
| error: "Error" | ||||
| somethingHappened: "An error occurred" | ||||
| retry: "Retry" | ||||
| pageLoadError: "Failed to load page" | ||||
| pageLoadErrorDescription: "This is normally caused by network errors or the browser's cache. Try clearung the cache and then try again after waiting a little while." | ||||
| enterListName: "List name" | ||||
| privacy: "Privacy" | ||||
| makeFollowManuallyApprove: "Follow requests require approval" | ||||
| @@ -106,6 +113,8 @@ unsuspendConfirm: "Are you sure you that want to unsuspend this account?" | ||||
| selectList: "Select a list" | ||||
| selectAntenna: "Select an Antenna" | ||||
| selectWidget: "Select a widget" | ||||
| editWidgets: "Edit widgets" | ||||
| editWidgetsExit: "Done" | ||||
| customEmojis: "Custom Emoji" | ||||
| emoji: "Emoji" | ||||
| emojiName: "Emoji name" | ||||
| @@ -177,7 +186,6 @@ processing: "Processing" | ||||
| preview: "Preview" | ||||
| default: "Default" | ||||
| noCustomEmojis: "There are no emojis" | ||||
| customEmojisOfRemote: "Emojis from other instances" | ||||
| noJobs: "There are no jobs" | ||||
| federating: "Federating" | ||||
| blocked: "Blocked" | ||||
| @@ -365,8 +373,6 @@ unregister: "Unregister" | ||||
| passwordLessLogin: "Set up password-less login" | ||||
| resetPassword: "Reset password" | ||||
| newPasswordIs: "The new password is \"{password}\"" | ||||
| autoNoteWatch: "Watch note automatically" | ||||
| autoNoteWatchDescription: "Get notified about the notes which you reactioned or replied." | ||||
| reduceUiAnimation: "Reduce UI animation" | ||||
| share: "Share" | ||||
| notFound: "Not found" | ||||
| @@ -397,13 +403,14 @@ next: "Next" | ||||
| retype: "Enter again" | ||||
| noteOf: "{user}'s notes" | ||||
| inviteToGroup: "Invite to group" | ||||
| maxNoteTextLength: "Character limit of the note" | ||||
| maxNoteTextLength: "Character limit of notes" | ||||
| quoteAttached: "Quoted" | ||||
| quoteQuestion: "Do you want to append a quote?" | ||||
| noMessagesYet: "No messages yet" | ||||
| newMessageExists: "You've got a new message" | ||||
| onlyOneFileCanBeAttached: "You can only attach one file to a message" | ||||
| signinRequired: "Please sign in" | ||||
| invitations: "Invitations" | ||||
| invitationCode: "Invitation code" | ||||
| checking: "Checking" | ||||
| available: "Available" | ||||
| @@ -445,7 +452,7 @@ total: "Total" | ||||
| weekOverWeekChanges: "Weekly" | ||||
| dayOverDayChanges: "Daily" | ||||
| appearance: "Appearance" | ||||
| clinetSettings: "Client Settings" | ||||
| clientSettings: "Client settings" | ||||
| accountSettings: "Account Settings" | ||||
| promotion: "Promoted" | ||||
| promote: "Promote" | ||||
| @@ -476,6 +483,8 @@ newNoteRecived: "You've got a new note" | ||||
| sounds: "Sounds" | ||||
| listen: "Listen" | ||||
| none: "None" | ||||
| showInPage: "Show in page" | ||||
| popout: "Pop-out" | ||||
| volume: "Volume" | ||||
| details: "Details" | ||||
| chooseEmoji: "Choose an emoji" | ||||
| @@ -518,7 +527,6 @@ enableInfiniteScroll: "Enable infinite scrolling" | ||||
| visibility: "Visiblility" | ||||
| poll: "Poll" | ||||
| useCw: "Hide content" | ||||
| fixedWidgetsPosition: "Make widget position fixed" | ||||
| enablePlayer: "Open video player" | ||||
| disablePlayer: "Close video player" | ||||
| expandTweet: "Expand tweet" | ||||
| @@ -532,6 +540,7 @@ pluginInstallWarn: "Please do not install untrustworthy plugins." | ||||
| deck: "Deck" | ||||
| undeck: "Leave Deck" | ||||
| useBlurEffectForModal: "Use blur effect for modals" | ||||
| useFullReactionPicker: "Use full-size reaction picker" | ||||
| generateAccessToken: "Generate access token" | ||||
| permission: "Permissions" | ||||
| enableAll: "Enable all" | ||||
| @@ -564,8 +573,121 @@ overview: "Overview" | ||||
| logs: "Logs" | ||||
| delayed: "Delayed" | ||||
| database: "Database" | ||||
| channel: "Channel" | ||||
| channel: "Channels" | ||||
| create: "Create" | ||||
| notificationSetting: "Notification settings" | ||||
| notificationSettingDesc: "Select the type of notification to display" | ||||
| useGlobalSetting: "Use global setting" | ||||
| useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made." | ||||
| other: "Other" | ||||
| regenerateLoginToken: "Regenerate login token" | ||||
| regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out." | ||||
| setMultipleBySeparatingWithSpace: "You can set multiple by separating them with spaces." | ||||
| fileIdOrUrl: "File-ID or URL" | ||||
| chatOpenBehavior: "Behavior of the chat window when opened" | ||||
| sample: "Sample" | ||||
| abuseReports: "Reports" | ||||
| reportAbuse: "Report" | ||||
| reportAbuseOf: "Report {name}" | ||||
| fillAbuseReportDescription: "Please fill in the report details. If it is about a specific note, please include its URL." | ||||
| abuseReported: "Your report has been sent. Thank you very much." | ||||
| send: "Send" | ||||
| abuseMarkAsResolved: "Mark report as resolved" | ||||
| openInNewTab: "Open in new tab" | ||||
| openInSideView: "Open in side view" | ||||
| defaultNavigationBehaviour: "Default navigation behavior" | ||||
| editTheseSettingsMayBreakAccount: "Editing these settings may damage your account." | ||||
| instanceTicker: "Instance information of notes" | ||||
| waitingFor: "Waiting for {x}" | ||||
| random: "Random" | ||||
| system: "System" | ||||
| switchUi: "Switch UI" | ||||
| desktop: "Desktop" | ||||
| _mfm: | ||||
|   cheatSheet: "MFM Cheatsheet" | ||||
|   intro: "MFM is a Misskey-exclusive markup language that can be used in many places. Here you can view a list of all available MFM syntax." | ||||
|   dummy: "Misskey expands the world of the Fediverse" | ||||
|   mention: "Mention" | ||||
|   mentionDescription: "Using an At-Symbol and a username, you can specify a specific user." | ||||
|   hashtag: "Hashtag" | ||||
|   hashtagDescription: "Using a number sign and text, you can specify a hashtag." | ||||
|   url: "URL" | ||||
|   urlDescription: "URLs can be displayed." | ||||
|   link: "Link" | ||||
|   linkDescription: "Specific parts of text can be displayed as URL." | ||||
|   bold: "Bold" | ||||
|   boldDescription: "Highlights letters by making them thicker." | ||||
|   small: "Small" | ||||
|   smallDescription: "Displays contents small and thinn." | ||||
|   center: "Center" | ||||
|   centerDescription: "Displays content centered." | ||||
|   inlineCode: "Code (Inline)" | ||||
|   inlineCodeDescription: "Displays inline syntax highlighting for (program-)code." | ||||
|   blockCode: "Code (Block)" | ||||
|   blockCodeDescription: "Displays syntax highlighting for multi-line (program-)code in a block." | ||||
|   inlineMath: "Math (In-line)" | ||||
|   inlineMathDescription: "Display math formulas (KaTeX) in-line" | ||||
|   blockMath: "Math (Block)" | ||||
|   blockMathDescription: "Display multi-line Math formulas (KaTeX) in a block" | ||||
|   quote: "Quote" | ||||
|   quoteDescription: "Displays content as quote." | ||||
|   emoji: "Custom Emoji" | ||||
|   emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed." | ||||
|   search: "Search" | ||||
|   searchDescription: "Displays a search box with pre-entered text." | ||||
|   flip: "Flip" | ||||
|   flipDescription: "Flips content horizontally or vertically." | ||||
|   jelly: "Animation (Jelly)" | ||||
|   jellyDescription: "Infuses a jelly-like animation." | ||||
|   tada: "Animation (Tada)" | ||||
|   tadaDescription: "Infuses a \"Tada!\"-like animation." | ||||
|   jump: "Animation (Jump)" | ||||
|   jumpDescription: "Infuses a jumping animation." | ||||
|   bounce: "Animation (Bounce)" | ||||
|   bounceDescription: "Causes a bouncy animation." | ||||
|   shake: "Animation (Shake)" | ||||
|   shakeDescription: "Infuses a shaking animation." | ||||
|   twitch: "Animation (Twitch)" | ||||
|   twitchDescription: "Infuses a strongly twitching animation." | ||||
|   spin: "Animation (Spin)" | ||||
|   spinDescription: "Infuses a spinning animation." | ||||
| _reversi: | ||||
|   reversi: "Reversi" | ||||
|   gameSettings: "Game settings" | ||||
|   chooseBoard: "Choose a board" | ||||
|   blackOrWhite: "Black/White" | ||||
|   blackIs: "{name} is playing Black" | ||||
|   rules: "Rules" | ||||
|   botSettings: "Bot options" | ||||
|   thisGameIsStartedSoon: "The game will start in a few seconds" | ||||
|   waitingForOther: "Waiting for the opponent's turn" | ||||
|   waitingForMe: "Waiting for your turn" | ||||
|   waitingBoth: "Get ready" | ||||
|   ready: "Ready" | ||||
|   cancelReady: "Cancel ready" | ||||
|   opponentTurn: "Opponent's turn" | ||||
|   myTurn: "Your turn" | ||||
|   turnOf: "{name}'s turn" | ||||
|   pastTurnOf: "{name}'s turn" | ||||
|   surrender: "Surrender" | ||||
|   surrendered: "By surrender" | ||||
|   drawn: "Draw" | ||||
|   won: "{name}'s win" | ||||
|   black: "Black" | ||||
|   white: "White" | ||||
|   total: "Total" | ||||
|   turnCount: "Turn {count}" | ||||
|   myGames: "My rounds" | ||||
|   allGames: "All rounds" | ||||
|   ended: "Ended" | ||||
|   playing: "Currently playing" | ||||
|   isLlotheo: "The one with fewer stones wins (Llotheo)" | ||||
|   loopedMap: "Looped map" | ||||
|   canPutEverywhere: "Tiles are placeable everywhere" | ||||
| _instanceTicker: | ||||
|   none: "Never show" | ||||
|   remote: "Show for remote users" | ||||
|   always: "Always show" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Automatically reload" | ||||
|   dialog: "Show warning dialog" | ||||
| @@ -576,8 +698,8 @@ _channel: | ||||
|   setBanner: "Set banner" | ||||
|   removeBanner: "Remove banner" | ||||
|   featured: "Trending" | ||||
|   owned: "Owner" | ||||
|   following: "Following" | ||||
|   owned: "Owned" | ||||
|   following: "Followed" | ||||
|   usersCount: "{n} Participants" | ||||
|   notesCount: "{n} Notes" | ||||
| _sidebar: | ||||
| @@ -782,6 +904,7 @@ _widgets: | ||||
|   photos: "Photos" | ||||
|   digitalClock: "Digital clock" | ||||
|   federation: "Federation" | ||||
|   postForm: "Compose a note" | ||||
| _cw: | ||||
|   hide: "Hide" | ||||
|   show: "Load more" | ||||
| @@ -1238,14 +1361,17 @@ _notification: | ||||
|   youWereInvitedToGroup: "Invited to group" | ||||
|   _types: | ||||
|     all: "All" | ||||
|     follow: "Following" | ||||
|     mention: "Mention" | ||||
|     follow: "Follows" | ||||
|     mention: "Mentions" | ||||
|     reply: "Replies" | ||||
|     renote: "Renote" | ||||
|     quote: "Quote" | ||||
|     reaction: "Reaction" | ||||
|     pollVote: "Polls" | ||||
|     receiveFollowRequest: "Follow requests" | ||||
|     renote: "Renotes" | ||||
|     quote: "Quotes" | ||||
|     reaction: "Reactions" | ||||
|     pollVote: "Votes on polls" | ||||
|     receiveFollowRequest: "Follow request received" | ||||
|     followRequestAccepted: "Follow request accepted" | ||||
|     groupInvited: "Invited to groups" | ||||
|     app: "Notifications from apps" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "Always show main column" | ||||
|   columnAlign: "Align columns" | ||||
|   | ||||
| @@ -16,6 +16,9 @@ noNotes: "No hay notas" | ||||
| noNotifications: "No hay notificaciones" | ||||
| instance: "Instancia" | ||||
| settings: "Configuración" | ||||
| basicSettings: "Configuración Básica" | ||||
| otherSettings: "Configuración avanzada" | ||||
| openInWindow: "Abrir en una ventana" | ||||
| profile: "Perfil" | ||||
| timeline: "Linea de tiempo" | ||||
| noAccountDescription: "Este usuario no tiene una descripción" | ||||
| @@ -40,6 +43,7 @@ deleteAndEditConfirm: "¿Quieres borrar y editar este nota? Las reacciones, reno | ||||
| addToList: "Agregar a lista" | ||||
| sendMessage: "Énviar mensaje" | ||||
| copyUsername: "Copiar nombre de usuario" | ||||
| searchUser: "Búsqueda de usuarios" | ||||
| reply: "Responder" | ||||
| loadMore: "Ver más" | ||||
| youGotNewFollower: "te ha seguido" | ||||
| @@ -66,8 +70,11 @@ followers: "Seguidores" | ||||
| followsYou: "Te sigue" | ||||
| createList: "Crear lista" | ||||
| manageLists: "Administrar listas" | ||||
| error: "Ocurrió un problema" | ||||
| error: "Error" | ||||
| somethingHappened: "Ocurrió un error" | ||||
| retry: "Reintentar" | ||||
| pageLoadError: "Error al leer la página" | ||||
| pageLoadErrorDescription: "Normalmente es debido a la red o al caché del navegador. Por favor limpie el caché o intente más tarde." | ||||
| enterListName: "Ingrese nombre de lista" | ||||
| privacy: "Privacidad" | ||||
| makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento" | ||||
| @@ -106,6 +113,8 @@ unsuspendConfirm: "¿Quiere dejar de suspender esta cuenta?" | ||||
| selectList: "Seleccione una lista" | ||||
| selectAntenna: "Seleccionar antena" | ||||
| selectWidget: "Seleccionar widget" | ||||
| editWidgets: "Editar widgets" | ||||
| editWidgetsExit: "Terminar edición" | ||||
| customEmojis: "Emojis personalizados" | ||||
| emoji: "Emoji" | ||||
| emojiName: "Nombre del emoji" | ||||
| @@ -177,7 +186,6 @@ processing: "Procesando" | ||||
| preview: "Vista previa" | ||||
| default: "Predeterminado" | ||||
| noCustomEmojis: "No hay emojis personalizados" | ||||
| customEmojisOfRemote: "Emojis remotos" | ||||
| noJobs: "No hay trabajos" | ||||
| federating: "Federando" | ||||
| blocked: "Bloqueando" | ||||
| @@ -365,8 +373,6 @@ unregister: "Cancelar registro" | ||||
| passwordLessLogin: "Iniciar sesión sin contraseña" | ||||
| resetPassword: "Resetear contraseña" | ||||
| newPasswordIs: "La nueva contraseña es \"{password}\"" | ||||
| autoNoteWatch: "Ver nota automáticamente" | ||||
| autoNoteWatchDescription: "Recibe notificaciones sobre las notas de otros usuarios que a los que respondiste y reaccionaste" | ||||
| reduceUiAnimation: "Reducir la animación de la UI" | ||||
| share: "Compartir" | ||||
| notFound: "No se encuentra" | ||||
| @@ -404,6 +410,7 @@ noMessagesYet: "Aún no hay chat" | ||||
| newMessageExists: "Tienes un mensaje nuevo" | ||||
| onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje" | ||||
| signinRequired: "Iniciar sesión" | ||||
| invitations: "Invitar" | ||||
| invitationCode: "Código de invitación" | ||||
| checking: "Comprobando" | ||||
| available: "Disponible" | ||||
| @@ -445,7 +452,7 @@ total: "Total" | ||||
| weekOverWeekChanges: "Dif semanal" | ||||
| dayOverDayChanges: "Dif diaria" | ||||
| appearance: "Apariencia" | ||||
| clinetSettings: "Ajustes del cliente" | ||||
| clientSettings: "Configuración del cliente" | ||||
| accountSettings: "Ajustes de cuenta" | ||||
| promotion: "Promovido" | ||||
| promote: "Promover" | ||||
| @@ -476,6 +483,8 @@ newNoteRecived: "Tienes una nota nuevo" | ||||
| sounds: "Sonidos" | ||||
| listen: "Escuchar" | ||||
| none: "Ninguna" | ||||
| showInPage: "Mostrar en la página" | ||||
| popout: "Popout" | ||||
| volume: "Volumen" | ||||
| details: "Detalles" | ||||
| chooseEmoji: "Elije un emoji" | ||||
| @@ -518,7 +527,6 @@ enableInfiniteScroll: "Activar scroll infinito" | ||||
| visibility: "Visibilidad" | ||||
| poll: "Encuesta" | ||||
| useCw: "Esconder contenidos" | ||||
| fixedWidgetsPosition: "Fijar la posición de los widgets" | ||||
| enablePlayer: "Abrir reproductor" | ||||
| disablePlayer: "Cerrar reproductor" | ||||
| expandTweet: "Expandir tweet" | ||||
| @@ -532,6 +540,7 @@ pluginInstallWarn: "Por favor no instale plugins que no son de confianza" | ||||
| deck: "Deck" | ||||
| undeck: "Quitar deck" | ||||
| useBlurEffectForModal: "Usar efecto borroso en modales" | ||||
| useFullReactionPicker: "Reacción" | ||||
| generateAccessToken: "Generar token de acceso" | ||||
| permission: "Permisos" | ||||
| enableAll: "Activar todo" | ||||
| @@ -566,6 +575,88 @@ delayed: "atrasado" | ||||
| database: "Base de datos" | ||||
| channel: "Canal" | ||||
| create: "Crear" | ||||
| notificationSetting: "Ajustes de Notificaciones" | ||||
| notificationSettingDesc: "Por favor elija el tipo de notificación a mostrar" | ||||
| useGlobalSetting: "Usar ajustes globales" | ||||
| useGlobalSettingDesc: "Al activarse, se usará la configuración de notificaciones de la cuenta, al desactivarse se pueden hacer configuraciones particulares." | ||||
| other: "Otro" | ||||
| regenerateLoginToken: "Regenerar token de login" | ||||
| regenerateLoginTokenDescription: "Regenerar el token usado internamente durante el login. No siempre es necesario hacerlo. Al hacerlo de nuevo, se deslogueará en todos los dispositivos." | ||||
| setMultipleBySeparatingWithSpace: "Puedes añadir mas de uno, separado por espacios." | ||||
| fileIdOrUrl: "Id del archivo o URL" | ||||
| chatOpenBehavior: "Comportamiento al abrir el chat" | ||||
| sample: "Muestra" | ||||
| abuseReports: "Reportes" | ||||
| reportAbuse: "Reportar" | ||||
| reportAbuseOf: "Reportar a {name}" | ||||
| fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta." | ||||
| abuseReported: "Se ha enviado el reporte. Muchas gracias." | ||||
| send: "Enviar" | ||||
| abuseMarkAsResolved: "Marcar reporte como resuelto" | ||||
| openInNewTab: "Abrir en una Nueva Pestaña" | ||||
| openInSideView: "Abrir en una vista al costado" | ||||
| defaultNavigationBehaviour: "Navegación por defecto" | ||||
| editTheseSettingsMayBreakAccount: "Editar estas configuraciones puede dañar su cuenta." | ||||
| instanceTicker: "Información de notas de la instancia" | ||||
| waitingFor: "Esperando a {x}" | ||||
| random: "Aleatorio" | ||||
| system: "Sistema" | ||||
| switchUi: "Cambiar interfaz de usuario" | ||||
| desktop: "Escritorio" | ||||
| _mfm: | ||||
|   cheatSheet: "Hoja de referencia de MFM" | ||||
|   intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de Misskey. Aquí puede ver una lista de sintaxis disponibles en MFM." | ||||
|   mention: "Menciones" | ||||
|   mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar para notificar a un usuario en particular." | ||||
|   hashtag: "Hashtag" | ||||
|   url: "URL" | ||||
|   link: "Vínculo" | ||||
|   bold: "Negrita" | ||||
|   center: "Centrar" | ||||
|   blockCode: "Código (bloque)" | ||||
|   blockCodeDescription: "Código de resaltado de sintaxis, como programas de varias líneas con bloques." | ||||
|   quote: "Citar" | ||||
|   emoji: "Emojis personalizados" | ||||
|   search: "Buscar" | ||||
|   flip: "Echar de un capirotazo" | ||||
|   flipDescription: "Voltea el contenido hacia arriba / abajo o hacia la izquierda / derecha." | ||||
| _reversi: | ||||
|   reversi: "Reversi" | ||||
|   gameSettings: "Configuración del juego" | ||||
|   chooseBoard: "Elegir tablero" | ||||
|   blackOrWhite: "Blancas/Negras" | ||||
|   blackIs: "{name} juega con fichas negras" | ||||
|   rules: "Reglas" | ||||
|   botSettings: "Opciones del bot" | ||||
|   thisGameIsStartedSoon: "El juego empezará en segundos" | ||||
|   waitingForOther: "Esperando el turno del adversario" | ||||
|   waitingForMe: "Esperando mi turno" | ||||
|   waitingBoth: "Prepárate" | ||||
|   ready: "Listo" | ||||
|   cancelReady: "No estoy listo" | ||||
|   opponentTurn: "Turno del adversario" | ||||
|   myTurn: "Mi turno" | ||||
|   turnOf: "Turno de {name}" | ||||
|   pastTurnOf: "Turno de {name}" | ||||
|   surrender: "Rendirse" | ||||
|   surrendered: "Por rendirse" | ||||
|   drawn: "Empate" | ||||
|   won: "{name} ha ganado" | ||||
|   black: "Negro" | ||||
|   white: "Blanco" | ||||
|   total: "Total" | ||||
|   turnCount: "Turno {count}" | ||||
|   myGames: "Mis juegos" | ||||
|   allGames: "Todos los juegos" | ||||
|   ended: "Finalizado" | ||||
|   playing: "Jugando" | ||||
|   isLlotheo: "El que tenga menos fichas gana (LLoTheO)" | ||||
|   loopedMap: "Mapa en bucle" | ||||
|   canPutEverywhere: "Puedes colocar donde quieras" | ||||
| _instanceTicker: | ||||
|   none: "No mostrar" | ||||
|   remote: "Mostrar a usuarios remotos" | ||||
|   always: "Mostrar siempre" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Recargar automáticamente" | ||||
|   dialog: "Mostrar diálogo de advertencia" | ||||
| @@ -782,6 +873,7 @@ _widgets: | ||||
|   photos: "Fotos" | ||||
|   digitalClock: "Reloj digital" | ||||
|   federation: "Federación" | ||||
|   postForm: "Formulario" | ||||
| _cw: | ||||
|   hide: "Ocultar" | ||||
|   show: "Ver más" | ||||
| @@ -1244,8 +1336,11 @@ _notification: | ||||
|     renote: "Renotar" | ||||
|     quote: "Citar" | ||||
|     reaction: "Reacción" | ||||
|     pollVote: "Encuestas" | ||||
|     receiveFollowRequest: "Solicitudes de seguimiento" | ||||
|     pollVote: "Votado en la encuesta" | ||||
|     receiveFollowRequest: "Recibió una solicitud de seguimiento" | ||||
|     followRequestAccepted: "El seguimiento fue aceptado" | ||||
|     groupInvited: "Invitado al grupo" | ||||
|     app: "Notificaciones desde aplicaciones" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "Siempre mostrar la columna principal" | ||||
|   columnAlign: "Alinear columnas" | ||||
|   | ||||
| @@ -16,6 +16,9 @@ noNotes: "Aucune note" | ||||
| noNotifications: "Aucune notification" | ||||
| instance: "Instance" | ||||
| settings: "Paramètres" | ||||
| basicSettings: "Paramètres basiques" | ||||
| otherSettings: "Autres paramètres" | ||||
| openInWindow: "Ouvrir dans une nouvelle fenêtre" | ||||
| profile: "Profil" | ||||
| timeline: "Fil" | ||||
| noAccountDescription: "L’utilisateur·rice n’a pas encore renseigné de biographie de présentation sur son profil." | ||||
| @@ -40,6 +43,7 @@ deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la | ||||
| addToList: "Ajouter à une liste" | ||||
| sendMessage: "Envoyer un message" | ||||
| copyUsername: "Copier le nom d’utilisateur·rice" | ||||
| searchUser: "Chercher un·e utilisateur·rice" | ||||
| reply: "Répondre" | ||||
| loadMore: "Afficher plus …" | ||||
| youGotNewFollower: "Vous suit" | ||||
| @@ -66,8 +70,10 @@ followers: "Abonné·e·s" | ||||
| followsYou: "Vous suit" | ||||
| createList: "Créer une liste" | ||||
| manageLists: "Gérer les listes" | ||||
| error: "Une erreur est survenue" | ||||
| error: "Erreur" | ||||
| somethingHappened: "Une erreur est survenue" | ||||
| retry: "Réessayer" | ||||
| pageLoadError: "Le chargement de la page a échoué" | ||||
| enterListName: "Nom de la liste" | ||||
| privacy: "Confidentialité" | ||||
| makeFollowManuallyApprove: "Accepter manuellement les demandes d’abonnement" | ||||
| @@ -106,6 +112,8 @@ unsuspendConfirm: "Êtes-vous sûr·e de vouloir annuler la suspension de ce com | ||||
| selectList: "Sélectionner une liste" | ||||
| selectAntenna: "Sélectionner une antenne" | ||||
| selectWidget: "Sélectionner un widget" | ||||
| editWidgets: "Modifier les widgets" | ||||
| editWidgetsExit: "Fait" | ||||
| customEmojis: "Émojis personnalisés" | ||||
| emoji: "Émoji" | ||||
| emojiName: "Nom de l’émoji" | ||||
| @@ -177,7 +185,6 @@ processing: "Traitement en cours" | ||||
| preview: "Prévisualisation" | ||||
| default: "Par défaut" | ||||
| noCustomEmojis: "Il n'y a pas d’émoji" | ||||
| customEmojisOfRemote: "Émojis provenant des autres instances" | ||||
| noJobs: "Il n’y a aucune tâche planifiée" | ||||
| federating: "En cours de fédération" | ||||
| blocked: "Bloqué·e" | ||||
| @@ -264,6 +271,7 @@ rename: "Renommer" | ||||
| avatar: "Avatar" | ||||
| banner: "Bannière" | ||||
| nsfw: "Contenu sensible" | ||||
| whenServerDisconnected: "Lorsque la connexion au serveur est perdue" | ||||
| disconnectedFromServer: "Déconnecté·e du serveur" | ||||
| reload: "Rafraîchir" | ||||
| doNothing: "Ignorer" | ||||
| @@ -364,8 +372,6 @@ unregister: "Se désinscrire" | ||||
| passwordLessLogin: "Connectez-vous sans mot de passe" | ||||
| resetPassword: "Réinitialiser mot de passe" | ||||
| newPasswordIs: "Votre nouveau mot de passe est \"{password}\"" | ||||
| autoNoteWatch: "Surveiller les notes automatiquement" | ||||
| autoNoteWatchDescription: "Soyez informé des notes auxquelles vous avez réagi ou répondu." | ||||
| reduceUiAnimation: "Réduire les animations dans l’interface" | ||||
| share: "Partager" | ||||
| notFound: "Non trouvé" | ||||
| @@ -403,6 +409,7 @@ noMessagesYet: "Pas encore discuté" | ||||
| newMessageExists: "Vous avez un nouveau message" | ||||
| onlyOneFileCanBeAttached: "Vous ne pouvez joindre qu’un seul fichier au message" | ||||
| signinRequired: "Veuillez vous connecter" | ||||
| invitations: "Inviter" | ||||
| invitationCode: "Code d’invitation" | ||||
| checking: "Vérification" | ||||
| available: "Disponible" | ||||
| @@ -444,7 +451,7 @@ total: "Total" | ||||
| weekOverWeekChanges: "Diff hebdo" | ||||
| dayOverDayChanges: "Diff quotidien" | ||||
| appearance: "Aspect" | ||||
| clinetSettings: "Paramètres du client" | ||||
| clientSettings: "Paramètres du client" | ||||
| accountSettings: "Paramètres du compte" | ||||
| promotion: "Promu" | ||||
| promote: "Promouvoir" | ||||
| @@ -474,6 +481,7 @@ newNoteRecived: "Vous avez une nouvelle note" | ||||
| sounds: "Sons" | ||||
| listen: "Écouter" | ||||
| none: "Rien" | ||||
| popout: "Fenêtre contextuelle" | ||||
| volume: "Volume" | ||||
| details: "Détails" | ||||
| chooseEmoji: "Choisissez un émoji" | ||||
| @@ -516,7 +524,6 @@ enableInfiniteScroll: "Activer le défilement infini" | ||||
| visibility: "Visibilité" | ||||
| poll: "Sondage" | ||||
| useCw: "Masquer le contenu" | ||||
| fixedWidgetsPosition: "Rendre la position du widget fixe" | ||||
| enablePlayer: "Activer le lecteur vidéo" | ||||
| disablePlayer: "Désactiver le lecteur vidéo" | ||||
| expandTweet: "Étendre le tweet" | ||||
| @@ -538,6 +545,7 @@ tokenRequested: "Autoriser l'accès au compte" | ||||
| pluginTokenRequestedDescription: "Ce plugin pourra utiliser les autorisations définies ici." | ||||
| notificationType: "Type de notifications" | ||||
| edit: "Editer" | ||||
| useStarForReactionFallback: "Utiliser ★ comme alternative si l’émoji de réaction est inconnu" | ||||
| emailConfig: "Configuration du serveur email" | ||||
| enableEmail: "Activer la distribution de courriel" | ||||
| emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas d’oubli." | ||||
| @@ -549,16 +557,56 @@ smtpUser: "Nom d’utilisateur·rice" | ||||
| smtpPass: "Mot de passe" | ||||
| emptyToDisableSmtpAuth: "Laisser le nom d’utilisateur et le mot de passe vides pour désactiver la vérification SMTP" | ||||
| smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP" | ||||
| smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé" | ||||
| testEmail: "Tester la distribution de courriel" | ||||
| wordMute: "Filtre de mots" | ||||
| userSaysSomething: "{name} a dit quelque chose" | ||||
| makeActive: "Activer" | ||||
| display: "Affichage" | ||||
| copy: "Copier" | ||||
| metrics: "Métriques" | ||||
| overview: "Aperçu" | ||||
| logs: "Journaux" | ||||
| delayed: "en retard" | ||||
| database: "Base de données" | ||||
| channel: "Canaux" | ||||
| create: "Créer" | ||||
| notificationSetting: "Paramètres des notifications " | ||||
| notificationSettingDesc: "Sélectionnez le type de notification à afficher" | ||||
| useGlobalSetting: "Utiliser paramètre général" | ||||
| other: "Autre" | ||||
| regenerateLoginToken: "Régénérer le jeton de connexion" | ||||
| setMultipleBySeparatingWithSpace: "Vous pouvez définir plus d’un, séparés par des espaces." | ||||
| fileIdOrUrl: "ID du fichier ou URL" | ||||
| chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouverture" | ||||
| random: "Aléatoire" | ||||
| _mfm: | ||||
|   mention: "Mentionner" | ||||
|   hashtag: "Hashtags" | ||||
|   link: "Lien" | ||||
|   center: "Centrée" | ||||
|   quote: "Citer" | ||||
|   emoji: "Émojis personnalisés" | ||||
|   search: "Rechercher" | ||||
| _reversi: | ||||
|   total: "Total" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Rechargement automatique" | ||||
| _channel: | ||||
|   create: "Créer un canal" | ||||
|   edit: "Éditer le canal" | ||||
|   removeBanner: "Supprimer la bannière" | ||||
|   featured: "Tendances" | ||||
|   usersCount: "{n} Participants" | ||||
|   notesCount: "{n} Notes" | ||||
| _sidebar: | ||||
|   full: "Complet" | ||||
|   icon: "Avatar" | ||||
|   hide: "Masquer" | ||||
| _wordMute: | ||||
|   muteWords: "Mot à mettre en sourdine" | ||||
|   muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR." | ||||
|   mutedNotes: "Notes mises en sourdine" | ||||
| _theme: | ||||
|   explore: "Explorer les thèmes" | ||||
|   install: "Installer un thème" | ||||
| @@ -569,6 +617,8 @@ _theme: | ||||
|   invalid: "Le format du thème n'est pas valide" | ||||
|   make: "Créer un thème" | ||||
|   base: "Base" | ||||
|   addConstant: "Ajouter une constante" | ||||
|   constant: "Constante" | ||||
|   defaultValue: "Valeur par défaut" | ||||
|   color: "Couleur" | ||||
|   key: "Clé " | ||||
| @@ -594,6 +644,7 @@ _theme: | ||||
|     renote: "Renote" | ||||
|     divider: "Séparateur" | ||||
|     infoWarnFg: "Texte d’avertissement" | ||||
|     cwBg: "Arrière-plan du CW" | ||||
|     badge: "Badge" | ||||
|     messageBg: "Arrière plan de la discussion" | ||||
| _sfx: | ||||
| @@ -678,6 +729,8 @@ _permissions: | ||||
|   "write:page-likes": "Mettre à jour les favoris sur les Pages" | ||||
|   "read:user-groups": "Voir les groupes d'utilisateur·rice·s" | ||||
|   "write:user-groups": "Éditer les groupes des utilisateur·rice·s" | ||||
|   "read:channels": "Lire les canaux" | ||||
|   "write:channels": "Modifier les canaux" | ||||
| _auth: | ||||
|   shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?" | ||||
|   shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre compte?" | ||||
| @@ -711,6 +764,7 @@ _widgets: | ||||
|   photos: "Photos" | ||||
|   digitalClock: "Horloge numérique" | ||||
|   federation: "Fédération" | ||||
|   postForm: "Formulaire à publier" | ||||
| _cw: | ||||
|   hide: "Masquer" | ||||
|   show: "Afficher plus …" | ||||
| @@ -752,6 +806,7 @@ _visibility: | ||||
| _postForm: | ||||
|   replyPlaceholder: "Répondre à cette note ..." | ||||
|   quotePlaceholder: "Citez cette note ..." | ||||
|   channelPlaceholder: "Publier vers le canal" | ||||
|   _placeholders: | ||||
|     a: "Quoi de neuf ?" | ||||
|     b: "Quoi de neuf ?" | ||||
| @@ -1165,11 +1220,15 @@ _notification: | ||||
|   yourFollowRequestAccepted: "Votre demande d’abonnement a été accepté" | ||||
|   youWereInvitedToGroup: "Invité au groupe" | ||||
|   _types: | ||||
|     all: "Toutes" | ||||
|     follow: "Abonnements" | ||||
|     mention: "Mentionner" | ||||
|     reply: "Réponses" | ||||
|     renote: "Renote" | ||||
|     quote: "Citer" | ||||
|     reaction: "Réactions" | ||||
|     groupInvited: "Invité aux groupes" | ||||
|     app: "Notifications provenant des apps" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "Toujours afficher la colonne principale" | ||||
|   columnAlign: "Aligner les colonnes" | ||||
|   | ||||
| @@ -15,17 +15,24 @@ const merge = (...args) => args.reduce((a, c) => ({ | ||||
|  | ||||
| const languages = [ | ||||
| 	'ar-SA', | ||||
| 	//'cs-CZ', | ||||
| 	//'da-DK', | ||||
| 	'cs-CZ', | ||||
| 	'da-DK', | ||||
| 	'de-DE', | ||||
| 	'en-US', | ||||
| 	'es-ES', | ||||
| 	'fr-FR', | ||||
| 	'ja-JP', | ||||
| 	'ja-KS', | ||||
| 	'kab-KAB', | ||||
| 	'kn-IN', | ||||
| 	'ko-KR', | ||||
| 	//'nl-NL', | ||||
| 	//'pl-PL', | ||||
| 	'nl-NL', | ||||
| 	'no-NO', | ||||
| 	'pl-PL', | ||||
| 	'pt-PT', | ||||
| 	'ru-RU', | ||||
| 	'ug-CN', | ||||
| 	'uk-UA', | ||||
| 	'zh-CN', | ||||
| 	'zh-TW', | ||||
| ]; | ||||
|   | ||||
| @@ -214,6 +214,7 @@ imageUrl: "画像URL" | ||||
| remove: "削除" | ||||
| removed: "削除しました" | ||||
| removeAreYouSure: "「{x}」を削除しますか?" | ||||
| resetAreYouSure: "リセットしますか?" | ||||
| saved: "保存しました" | ||||
| messaging: "チャット" | ||||
| upload: "アップロード" | ||||
| @@ -373,8 +374,6 @@ unregister: "登録を解除" | ||||
| passwordLessLogin: "パスワード無しログイン" | ||||
| resetPassword: "パスワードをリセット" | ||||
| newPasswordIs: "新しいパスワードは「{password}」です" | ||||
| autoNoteWatch: "ノートの自動ウォッチ" | ||||
| autoNoteWatchDescription: "あなたがリアクションしたり返信したりした他のユーザーのノートに関する通知を受け取るようにします。" | ||||
| reduceUiAnimation: "UIのアニメーションを減らす" | ||||
| share: "共有" | ||||
| notFound: "見つかりません" | ||||
| @@ -412,6 +411,7 @@ noMessagesYet: "まだチャットはありません" | ||||
| newMessageExists: "新しいメッセージがあります" | ||||
| onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです" | ||||
| signinRequired: "ログインしてください" | ||||
| invitations: "招待" | ||||
| invitationCode: "招待コード" | ||||
| checking: "確認しています" | ||||
| available: "利用できます" | ||||
| @@ -541,6 +541,7 @@ pluginInstallWarn: "信頼できないプラグインはインストールしな | ||||
| deck: "デッキ" | ||||
| undeck: "デッキ解除" | ||||
| useBlurEffectForModal: "モーダルにぼかし効果を使用" | ||||
| useFullReactionPicker: "フル機能リアクションピッカーを使用" | ||||
| generateAccessToken: "アクセストークンの発行" | ||||
| permission: "権限" | ||||
| enableAll: "全て有効にする" | ||||
| @@ -586,6 +587,111 @@ setMultipleBySeparatingWithSpace: "スペースで区切って複数設定でき | ||||
| fileIdOrUrl: "ファイルIDまたはURL" | ||||
| chatOpenBehavior: "チャットを開くときの動作" | ||||
| sample: "サンプル" | ||||
| abuseReports: "通報" | ||||
| reportAbuse: "通報" | ||||
| reportAbuseOf: "{name}を通報する" | ||||
| fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" | ||||
| abuseReported: "内容が送信されました。ご報告ありがとうございました。" | ||||
| send: "送信" | ||||
| abuseMarkAsResolved: "対応済みにする" | ||||
| openInNewTab: "新しいタブで開く" | ||||
| openInSideView: "サイドビューで開く" | ||||
| defaultNavigationBehaviour: "デフォルトのナビゲーション" | ||||
| editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウントが破損する可能性があります。" | ||||
| instanceTicker: "ノートのインスタンス情報" | ||||
| waitingFor: "{x}を待っています" | ||||
| random: "ランダム" | ||||
| system: "システム" | ||||
| switchUi: "UI切り替え" | ||||
| desktop: "デスクトップ" | ||||
|  | ||||
| _mfm: | ||||
|   cheatSheet: "MFMチートシート" | ||||
|   intro: "MFMは、Misskey内の様々な場所で使用できる専用のマークアップ言語です。ここでは、MFMで使用可能な構文一覧が確認できます。" | ||||
|   dummy: "MisskeyでFediverseの世界が広がります" | ||||
|   mention: "メンション" | ||||
|   mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。" | ||||
|   hashtag: "ハッシュタグ" | ||||
|   hashtagDescription: "ナンバーサイン + タグで、ハッシュタグを示すことができます。" | ||||
|   url: "URL" | ||||
|   urlDescription: "URLを示すことができます。" | ||||
|   link: "リンク" | ||||
|   linkDescription: "文章の特定の範囲を、URLに紐づけることができます。" | ||||
|   bold: "太字" | ||||
|   boldDescription: "文字を太く表示して強調することができます。" | ||||
|   small: "目立たなく" | ||||
|   smallDescription: "内容を小さく・薄く表示させることができます。" | ||||
|   center: "中央寄せ" | ||||
|   centerDescription: "内容を中央寄せで表示させることができます。" | ||||
|   inlineCode: "コード(インライン)" | ||||
|   inlineCodeDescription: "プログラムなどのコードをインラインでシンタックスハイライトします。" | ||||
|   blockCode: "コード(ブロック)" | ||||
|   blockCodeDescription: "複数行のプログラムなどのコードをブロックでシンタックスハイライトします。" | ||||
|   inlineMath: "数式(インライン)" | ||||
|   inlineMathDescription: "数式(KaTeX)をインラインで表示します。" | ||||
|   blockMath: "数式(ブロック)" | ||||
|   blockMathDescription: "複数行の数式(KaTeX)をブロックで表示します。" | ||||
|   quote: "引用" | ||||
|   quoteDescription: "内容が引用であることを示すことができます。" | ||||
|   emoji: "カスタム絵文字" | ||||
|   emojiDescription: "コロンでカスタム絵文字名を囲むと、カスタム絵文字を表示させることができます。" | ||||
|   search: "検索" | ||||
|   searchDescription: "入力済み検索ボックスを表示させることができます。" | ||||
|   flip: "反転" | ||||
|   flipDescription: "内容を上下または左右に反転させます。" | ||||
|   jelly: "アニメーション(びよんびよん)" | ||||
|   jellyDescription: "びよんびよんするアニメーションを与えます。" | ||||
|   tada: "アニメーション(じゃーん)" | ||||
|   tadaDescription: "ジャーン!という感じのアニメーションを与えます。" | ||||
|   jump: "アニメーション(ジャンプ)" | ||||
|   jumpDescription: "飛び跳ねるようなアニメーションを与えます。" | ||||
|   bounce: "アニメーション(バウンド)" | ||||
|   bounceDescription: "ぽよんぽよん弾むようなアニメーションを与えます。" | ||||
|   shake: "アニメーション(ぶるぶる)" | ||||
|   shakeDescription: "ぶるぶるするアニメーションを与えます。" | ||||
|   twitch: "アニメーション(ブレ)" | ||||
|   twitchDescription: "激しくブレるアニメーションを与えます。" | ||||
|   spin: "アニメーション(回転)" | ||||
|   spinDescription: "回転するアニメーションを与えます。" | ||||
|  | ||||
| _reversi: | ||||
|   reversi: "リバーシ" | ||||
|   gameSettings: "対局の設定" | ||||
|   chooseBoard: "ボードを選択" | ||||
|   blackOrWhite: "先行/後攻" | ||||
|   blackIs: "{name}が黒(先行)" | ||||
|   rules: "ルール" | ||||
|   botSettings: "Botのオプション" | ||||
|   thisGameIsStartedSoon: "対局は数秒後に開始されます" | ||||
|   waitingForOther: "相手の準備が完了するのを待っています" | ||||
|   waitingForMe: "あなたの準備が完了するのを待っています" | ||||
|   waitingBoth: "準備してください" | ||||
|   ready: "準備完了" | ||||
|   cancelReady: "準備を再開" | ||||
|   opponentTurn: "相手のターンです" | ||||
|   myTurn: "あなたのターンです" | ||||
|   turnOf: "{name}のターンです" | ||||
|   pastTurnOf: "{name}のターン" | ||||
|   surrender: "投了" | ||||
|   surrendered: "投了により" | ||||
|   drawn: "引き分け" | ||||
|   won: "{name}の勝ち" | ||||
|   black: "黒" | ||||
|   white: "白" | ||||
|   total: "合計" | ||||
|   turnCount: "{count}ターン目" | ||||
|   myGames: "自分の対局" | ||||
|   allGames: "みんなの対局" | ||||
|   ended: "終了" | ||||
|   playing: "対局中" | ||||
|   isLlotheo: "石の少ない方が勝ち(ロセオ)" | ||||
|   loopedMap: "ループマップ" | ||||
|   canPutEverywhere: "どこでも置けるモード" | ||||
|  | ||||
| _instanceTicker: | ||||
|   none: "表示しない" | ||||
|   remote: "リモートユーザーに表示" | ||||
|   always: "常に表示" | ||||
|  | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自動でリロード" | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| --- | ||||
| _lang_: "日本語 (関西弁)" | ||||
| introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作成しぃ、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素はよ反応を追加することもできます✌\n新しい世界を探検しよう🚀" | ||||
| introMisskey: "ようこそ!Misskeyってのは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作成し、いま起こっとることを共有したり、あんたんこととか皆に伝えていこう📡\n「リアクション」機能で、皆のノートに素はよ反応を追加することもできるんやで✌\n新しい世界を探検してみらん?🚀" | ||||
| monthAndDay: "{month}月 {day}日" | ||||
| search: "探す" | ||||
| notifications: "通知" | ||||
| username: "ユーザー名" | ||||
| password: "パスワード" | ||||
| fetchingAsApObject: "連合に照会中" | ||||
| fetchingAsApObject: "今ちと連合に照会しとるで" | ||||
| ok: "おっけー" | ||||
| gotIt: "ほい" | ||||
| cancel: "やめとくわ" | ||||
| @@ -16,35 +16,40 @@ noNotes: "ノートはあらへん" | ||||
| noNotifications: "通知はあらへん" | ||||
| instance: "インスタンス" | ||||
| settings: "設定" | ||||
| basicSettings: "基本設定" | ||||
| otherSettings: "その他の設定" | ||||
| openInWindow: "ウィンドウで開いてや" | ||||
| profile: "プロフィール" | ||||
| timeline: "タイムライン" | ||||
| noAccountDescription: "自己紹介はあらへん" | ||||
| login: "ログイン" | ||||
| loggingIn: "ログインしとります" | ||||
| loggingIn: "ログインしよるで" | ||||
| logout: "ログアウト" | ||||
| signup: "新規登録" | ||||
| uploading: "アップロードしとります" | ||||
| save: "保存" | ||||
| uploading: "アップロードしよるで" | ||||
| save: "とっとく" | ||||
| users: "ユーザー" | ||||
| addUser: "ユーザー増やす" | ||||
| addUser: "ユーザーを追加や" | ||||
| favorite: "お気に入り" | ||||
| favorites: "お気に入り" | ||||
| unfavorite: "お気に入りやめる" | ||||
| pin: "ピン留め" | ||||
| unpin: "ピン留めやめる" | ||||
| unfavorite: "やっぱ気に入らん" | ||||
| pin: "ピン留めしとく" | ||||
| unpin: "やっぱピン留めせん" | ||||
| copyContent: "内容をコピー" | ||||
| copyLink: "リンクをコピー" | ||||
| delete: "ほかす" | ||||
| deleteAndEdit: "ほかして直す" | ||||
| deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのリアクション、Remote、返信も全部消えんで" | ||||
| deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのリアクション、Renote、返信も全部消えるんやけどそれでもええん?" | ||||
| addToList: "リストに入れたる" | ||||
| sendMessage: "メッセージを送る" | ||||
| copyUsername: "ユーザー名をコピー" | ||||
| searchUser: "ユーザーを検索" | ||||
| reply: "返す" | ||||
| loadMore: "もっとあるやろ!" | ||||
| youGotNewFollower: "フォローされたで" | ||||
| receiveFollowRequest: "フォローリクエストされたで" | ||||
| followRequestAccepted: "フォローが承認されたで" | ||||
| mention: "メンション" | ||||
| mentions: "あんた宛て" | ||||
| directNotes: "ダイレクト投稿" | ||||
| importAndExport: "インポートとエクスポート" | ||||
| @@ -57,7 +62,7 @@ unfollowConfirm: "{name}のフォローを解除してもええんか?" | ||||
| exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。" | ||||
| importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。" | ||||
| lists: "リスト" | ||||
| noLists: "リストはあらへん" | ||||
| noLists: "リストなんてあらへんで" | ||||
| note: "ノート" | ||||
| notes: "ノート" | ||||
| following: "フォロー" | ||||
| @@ -65,32 +70,35 @@ followers: "フォロワー" | ||||
| followsYou: "フォローされとるで" | ||||
| createList: "リスト作る" | ||||
| manageLists: "リストの管理" | ||||
| error: "問題が発生してん" | ||||
| retry: "もっぺんやってみる" | ||||
| error: "エラー" | ||||
| somethingHappened: "なんかアカンことが起こったで" | ||||
| retry: "もっぺんやる?" | ||||
| pageLoadError: "ページの読み込みに失敗してしもうたで…" | ||||
| pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?" | ||||
| enterListName: "リスト名を入れてや" | ||||
| privacy: "プライバシーってなんや?オカンの年齢か?" | ||||
| makeFollowManuallyApprove: "他人のフォローは許可してからや!" | ||||
| privacy: "プライバシーってなんぞや?" | ||||
| makeFollowManuallyApprove: "他人からのフォローは自分が決める" | ||||
| defaultNoteVisibility: "もとからの公開範囲" | ||||
| follow: "フォロー" | ||||
| followRequest: "フォロー許してくれや!言うてみる" | ||||
| followRequests: "フォロー許してくれや!" | ||||
| followRequest: "フォローを頼む" | ||||
| followRequests: "フォローを頼む" | ||||
| unfollow: "フォローやめる" | ||||
| followRequestPending: "フォロー許してくれるん待っとる" | ||||
| enterEmoji: "絵文字を入れてや" | ||||
| renote: "Renote" | ||||
| unrenote: "Renoteやめる" | ||||
| quote: "引用" | ||||
| pinnedNote: "ピン留めされたノート" | ||||
| pinnedNote: "ピン留めされとるノート" | ||||
| you: "あんた" | ||||
| clickToShow: "押してみ、見せたるわ" | ||||
| sensitive: "見たらあかんで" | ||||
| clickToShow: "押したら見えるようになるで" | ||||
| sensitive: "ちょっとアカンやつやで" | ||||
| add: "増やす" | ||||
| reaction: "リアクション" | ||||
| reactionSettingDescription: "リアクションピッカーに出しとくリアクションを選んでや。" | ||||
| rememberNoteVisibility: "公開範囲覚えといて" | ||||
| attachCancel: "くっつけるのやめよか" | ||||
| markAsSensitive: "ちょっと見せられへんわ" | ||||
| unmarkAsSensitive: "別にええんじゃね?" | ||||
| attachCancel: "やっぱ添付やめてくれん?" | ||||
| markAsSensitive: "ちょっとこれはアカン" | ||||
| unmarkAsSensitive: "そこまでアカンことないやろ" | ||||
| enterFileName: "ファイル名を入れてや" | ||||
| mute: "ミュート" | ||||
| unmute: "ミュートやめたる" | ||||
| @@ -98,30 +106,35 @@ block: "ブロック" | ||||
| unblock: "ブロックやめたる" | ||||
| suspend: "凍結" | ||||
| unsuspend: "溶かす" | ||||
| blockConfirm: "ブロックしてしもうてええか?" | ||||
| unblockConfirm: "ブロックすんのやめるけどええか?" | ||||
| blockConfirm: "ブロックしてもええんか?" | ||||
| unblockConfirm: "ブロックやめたるってほんまか?" | ||||
| suspendConfirm: "凍結してしもうてええか?" | ||||
| unsuspendConfirm: "解凍するけどええか?" | ||||
| selectList: "リストを選ぶ" | ||||
| selectAntenna: "アンテナを選ぶ" | ||||
| selectWidget: "ウィジェットを選ぶ" | ||||
| editWidgets: "ウィジェットをいじる" | ||||
| editWidgetsExit: "編集終ったで" | ||||
| customEmojis: "カスタム絵文字" | ||||
| emoji: "絵文字" | ||||
| emojiName: "絵文字名" | ||||
| emojiUrl: "絵文字画像URL" | ||||
| addEmoji: "絵文字を追加" | ||||
| settingGuide: "ええ感じの設定" | ||||
| cacheRemoteFiles: "リモートのファイルをキャッシュする" | ||||
| cacheRemoteFilesDescription: "この設定をチャラにすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されへんので通信量が増加します。" | ||||
| flagAsBot: "Botやでと言っとく" | ||||
| flagAsCat: "Catやでと言っとく" | ||||
| autoAcceptFollowed: "フォローしとるユーザーからのフォロリクは全部勝手にええでって言うで" | ||||
| cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになってしまうんやで? サーバーのストレージは節約できるんやけど、かわりにサムネイルが作られんくなるから通信量が増えるで?" | ||||
| flagAsBot: "Botやで" | ||||
| flagAsCat: "Catやで" | ||||
| autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストには勝手に許可しとくで。" | ||||
| addAcount: "アカウント追加" | ||||
| loginFailed: "ログインに失敗してん" | ||||
| loginFailed: "ログインに失敗してしもうた…" | ||||
| showOnRemote: "リモートで見る" | ||||
| general: "全般" | ||||
| wallpaper: "壁紙" | ||||
| setWallpaper: "壁紙を設定" | ||||
| removeWallpaper: "壁紙ほかす" | ||||
| removeWallpaper: "壁紙を削除" | ||||
| searchWith: "検索: {q}" | ||||
| youHaveNoLists: "リストはあらへん" | ||||
| youHaveNoLists: "リストがあらへんで?" | ||||
| followConfirm: "{name}をフォローしてええか?" | ||||
| proxyAccount: "プロキシアカウント" | ||||
| proxyAccountDescription: "プロキシアカウントは、代わりにフォローしてくれるアカウントや。例えば、551に豚まんが無いときやったり、ユーザーがリモートユーザーをアカウントに入れたとき、リストに入れられたユーザーが誰からもフォローされてないと寂しいやん。寂しいし、アクティビティも配達されへんから、プロキシアカウントがフォローしてくれるで。ええやつやん…" | ||||
| @@ -131,7 +144,7 @@ recipient: "宛先" | ||||
| annotation: "注釈" | ||||
| federation: "連合" | ||||
| instances: "インスタンス" | ||||
| registeredAt: "一見さんになった日" | ||||
| registeredAt: "初観測" | ||||
| latestRequestSentAt: "ちょっと前のリクエスト送信" | ||||
| latestRequestReceivedAt: "ちょっと前のリクエスト受信" | ||||
| latestStatus: "ちょっと前のステータス" | ||||
| @@ -173,7 +186,6 @@ processing: "処理しとる" | ||||
| preview: "プレビュー" | ||||
| default: "デフォルト" | ||||
| noCustomEmojis: "絵文字はあらへん" | ||||
| customEmojisOfRemote: "リモートの絵文字" | ||||
| noJobs: "ジョブはあらへん" | ||||
| federating: "連合しとる" | ||||
| blocked: "ブロックしとる" | ||||
| @@ -259,7 +271,8 @@ copyUrl: "URLをコピー" | ||||
| rename: "名前を変えるで" | ||||
| avatar: "アイコン" | ||||
| banner: "バナー" | ||||
| nsfw: "見たらあかんで" | ||||
| nsfw: "ちょっとアカンやつやで" | ||||
| whenServerDisconnected: "サーバーとの接続が失くなってしもうたとき" | ||||
| disconnectedFromServer: "サーバーが機嫌悪いねん" | ||||
| reload: "リロード" | ||||
| doNothing: "何もせんとく" | ||||
| @@ -295,7 +308,19 @@ proxyRemoteFilesDescription: "この設定を入れると、保存しとらん | ||||
| driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量" | ||||
| driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量" | ||||
| inMb: "メガバイト単位" | ||||
| iconUrl: "アイコン画像のURL" | ||||
| bannerUrl: "バナー画像のURL" | ||||
| basicInfo: "基本情報" | ||||
| pinnedUsers: "ピン留めしたユーザー" | ||||
| pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。" | ||||
| hcaptcha: "hCaptcha(キャプチャ)" | ||||
| enableHcaptcha: "hCaptcha(キャプチャ)をつけとく" | ||||
| hcaptchaSiteKey: "サイトキー" | ||||
| hcaptchaSecretKey: "シークレットキー" | ||||
| recaptcha: "reCAPTCHA" | ||||
| enableRecaptcha: "reCAPTCHA(リキャプチャ)を有効にする" | ||||
| recaptchaSiteKey: "サイトキー" | ||||
| recaptchaSecretKey: "シークレットキー" | ||||
| avoidMultiCaptchaConfirm: "ぎょうさんのCaptchaをつこてしまうと、仲良うせんことがあるんや。他のCaptchaをなおしとこか?別にキャンセルしてもろうたらCaptchaは消されへんで済むけど知らんで。" | ||||
| antennas: "アンテナ" | ||||
| manageAntennas: "アンテナいじる" | ||||
| @@ -348,17 +373,58 @@ unregister: "登録やめる" | ||||
| passwordLessLogin: "パスワード無くてもログインできるようにする" | ||||
| resetPassword: "パスワードをリセット" | ||||
| newPasswordIs: "今度のパスワードは「{password}」や" | ||||
| reduceUiAnimation: "UIの動きやアニメーションを減らしてくれや。" | ||||
| share: "わけわけ" | ||||
| notFound: "見つからへんね" | ||||
| notFoundDescription: "指定されたURLに該当するページはあらへんやった。" | ||||
| uploadFolder: "とりあえずここへアップロード" | ||||
| cacheClear: "キャッシュをほかす" | ||||
| markAsReadAllNotifications: "通知はもう全て読んだわっ" | ||||
| markAsReadAllUnreadNotes: "投稿は全て読んだわっ" | ||||
| markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ" | ||||
| help: "ヘルプ" | ||||
| inputMessageHere: "ここにメッセージ書いてや" | ||||
| close: "さいなら" | ||||
| group: "グループ" | ||||
| groups: "グループ" | ||||
| createGroup: "グループを作るで" | ||||
| ownedGroups: "所有しとるグループ" | ||||
| joinedGroups: "参加しとるグループ" | ||||
| invites: "来てや" | ||||
| groupName: "グループ名" | ||||
| members: "メンバー" | ||||
| transfer: "譲渡" | ||||
| messagingWithUser: "ユーザーとチャット" | ||||
| messagingWithGroup: "グループでチャット" | ||||
| title: "タイトル" | ||||
| text: "テキスト" | ||||
| enable: "有効にするで" | ||||
| next: "次" | ||||
| retype: "もっかい入力" | ||||
| noteOf: "{user}のノート" | ||||
| inviteToGroup: "グループに招く" | ||||
| maxNoteTextLength: "ノートの文字数制限" | ||||
| quoteAttached: "引用付いとるで" | ||||
| quoteQuestion: "引用として添付してもええか?" | ||||
| noMessagesYet: "まだチャットはあらへんで" | ||||
| newMessageExists: "新しいメッセージがきたで" | ||||
| onlyOneFileCanBeAttached: "すまん、メッセージに添付できるファイルはひとつだけなんや。" | ||||
| invitations: "来てや" | ||||
| invitationCode: "招待コード" | ||||
| checking: "確認しとるで" | ||||
| smtpHost: "ホスト" | ||||
| smtpUser: "ユーザー名" | ||||
| smtpPass: "パスワード" | ||||
| _mfm: | ||||
|   mention: "メンション" | ||||
|   quote: "引用" | ||||
|   emoji: "カスタム絵文字" | ||||
|   search: "探す" | ||||
| _sidebar: | ||||
|   icon: "アイコン" | ||||
| _theme: | ||||
|   keys: | ||||
|     mention: "メンション" | ||||
|     renote: "Renote" | ||||
| _sfx: | ||||
|   note: "ノート" | ||||
| @@ -442,6 +508,7 @@ _notification: | ||||
|   youWereFollowed: "フォローされたで" | ||||
|   _types: | ||||
|     follow: "フォロー" | ||||
|     mention: "メンション" | ||||
|     renote: "Renote" | ||||
|     quote: "引用" | ||||
|     reaction: "リアクション" | ||||
|   | ||||
| @@ -35,6 +35,9 @@ userList: "Tibdarin" | ||||
| uiLanguage: "Tutlayt n wegrudem" | ||||
| smtpUser: "Isem n umseqdac" | ||||
| smtpPass: "Awal uffir" | ||||
| _mfm: | ||||
|   mention: "Bder" | ||||
|   search: "Nadi" | ||||
| _theme: | ||||
|   keys: | ||||
|     mention: "Bder" | ||||
|   | ||||
| @@ -56,6 +56,8 @@ instances: "ನಿದರ್ಶನ" | ||||
| remove: "ಅಳಿಸು" | ||||
| smtpUser: "ಬಳಕೆಹೆಸರು" | ||||
| smtpPass: "ಗುಪ್ತಪದ" | ||||
| _mfm: | ||||
|   search: "ಹುಡುಕು" | ||||
| _sfx: | ||||
|   notification: "ಅಧಿಸೂಚನೆಗಳು" | ||||
| _widgets: | ||||
|   | ||||
| @@ -66,7 +66,6 @@ followers: "팔로워" | ||||
| followsYou: "당신을 팔로우합니다" | ||||
| createList: "리스트 만들기" | ||||
| manageLists: "리스트 관리" | ||||
| error: "오류가 발생했습니다" | ||||
| retry: "다시 시도" | ||||
| enterListName: "리스트 이름을 입력" | ||||
| privacy: "프라이버시" | ||||
| @@ -177,7 +176,6 @@ processing: "처리중" | ||||
| preview: "미리보기" | ||||
| default: "기본값" | ||||
| noCustomEmojis: "이모지가 없습니다" | ||||
| customEmojisOfRemote: "다른 인스턴스들의 이모지" | ||||
| noJobs: "작업이 없습니다" | ||||
| federating: "연합 중" | ||||
| blocked: "차단됨" | ||||
| @@ -364,8 +362,6 @@ unregister: "등록 해제" | ||||
| passwordLessLogin: "비밀번호 없이 로그인" | ||||
| resetPassword: "비밀번호 재설정" | ||||
| newPasswordIs: "새로운 비밀번호는 \"{password}\" 입니다" | ||||
| autoNoteWatch: "노트를 자동으로 지켜보기" | ||||
| autoNoteWatchDescription: "리액션하거나 답글을 남긴 다른 유저의 노트에 대한 알림을 받습니다." | ||||
| reduceUiAnimation: "UI의 애니메이션을 줄이기" | ||||
| share: "공유" | ||||
| notFound: "찾을 수 없습니다" | ||||
| @@ -403,6 +399,7 @@ noMessagesYet: "아직 대화가 없습니다" | ||||
| newMessageExists: "새 메시지가 있습니다" | ||||
| onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다" | ||||
| signinRequired: "로그인 해주세요" | ||||
| invitations: "초대" | ||||
| invitationCode: "초대 코드" | ||||
| checking: "확인하는 중입니다" | ||||
| available: "사용 가능합니다" | ||||
| @@ -443,7 +440,6 @@ remote: "리모트" | ||||
| total: "합계" | ||||
| weekOverWeekChanges: "지난주보다" | ||||
| dayOverDayChanges: "어제보다" | ||||
| clinetSettings: "클라이언트 설정" | ||||
| accountSettings: "계정 설정" | ||||
| promotion: "프로모션" | ||||
| promote: "프로모션하기" | ||||
| @@ -515,7 +511,6 @@ enableInfiniteScroll: "자동으로 좀 더 보기" | ||||
| visibility: "공개 범위" | ||||
| poll: "투표" | ||||
| useCw: "내용 숨기기" | ||||
| fixedWidgetsPosition: "위젯의 위치 고정" | ||||
| enablePlayer: "플레이어 열기" | ||||
| disablePlayer: "플레이어 닫기" | ||||
| expandTweet: "트윗 확장하기" | ||||
| @@ -552,6 +547,17 @@ copy: "복사" | ||||
| logs: "로그" | ||||
| database: "데이터베이스" | ||||
| channel: "채널" | ||||
| random: "랜덤" | ||||
| _mfm: | ||||
|   mention: "멘션" | ||||
|   hashtag: "해시태그" | ||||
|   link: "링크" | ||||
|   center: "가운데 정렬" | ||||
|   quote: "인용" | ||||
|   emoji: "커스텀 이모지" | ||||
|   search: "검색" | ||||
| _reversi: | ||||
|   total: "합계" | ||||
| _channel: | ||||
|   create: "채널 생성" | ||||
|   setBanner: "배너 설정" | ||||
| @@ -708,6 +714,7 @@ _widgets: | ||||
|   photos: "사진" | ||||
|   digitalClock: "디지털 시계" | ||||
|   federation: "연합" | ||||
|   postForm: "글 입력란" | ||||
| _cw: | ||||
|   hide: "숨기기" | ||||
|   show: "더 보기" | ||||
| @@ -1165,7 +1172,6 @@ _notification: | ||||
|     renote: "Renote" | ||||
|     quote: "인용" | ||||
|     reaction: "리액션" | ||||
|     receiveFollowRequest: "팔로우 요청" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "메인 칼럼 항상 표시" | ||||
|   columnAlign: "칼럼 정렬" | ||||
|   | ||||
| @@ -1,2 +1,23 @@ | ||||
| --- | ||||
| _lang_: "język polski" | ||||
| search: "Szukaj" | ||||
| notifications: "Powiadomienia" | ||||
| username: "Nazwa użytkownika" | ||||
| password: "Hasło" | ||||
| ok: "OK" | ||||
| gotIt: "Rozumiem!" | ||||
| cancel: "Anuluj" | ||||
| enterUsername: "Wprowadź nazwę użytkownika" | ||||
| smtpUser: "Nazwa użytkownika" | ||||
| smtpPass: "Hasło" | ||||
| _mfm: | ||||
|   search: "Szukaj" | ||||
| _sfx: | ||||
|   notification: "Powiadomienia" | ||||
| _widgets: | ||||
|   notifications: "Powiadomienia" | ||||
| _profile: | ||||
|   username: "Nazwa użytkownika" | ||||
| _deck: | ||||
|   _columns: | ||||
|     notifications: "Powiadomienia" | ||||
|   | ||||
							
								
								
									
										1371
									
								
								locales/ru-RU.yml
									
									
									
									
									
								
							
							
						
						
									
										1371
									
								
								locales/ru-RU.yml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,3 +1,5 @@ | ||||
| --- | ||||
| _lang_: "ياپونچە" | ||||
| search: "ئىزدەش" | ||||
| _mfm: | ||||
|   search: "ئىزدەش" | ||||
|   | ||||
							
								
								
									
										482
									
								
								locales/uk-UA.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										482
									
								
								locales/uk-UA.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,482 @@ | ||||
| --- | ||||
| _lang_: "Українська" | ||||
| monthAndDay: "{month}/{day}" | ||||
| search: "Пошук" | ||||
| notifications: "Сповіщення" | ||||
| username: "Ім'я користувача" | ||||
| password: "Пароль" | ||||
| fetchingAsApObject: "Отримуємо з федіверсу..." | ||||
| ok: "OK" | ||||
| gotIt: "Зрозуміло!" | ||||
| cancel: "Скасувати" | ||||
| enterUsername: "Введіть ім'я користувача" | ||||
| renotedBy: "Поширено {user}" | ||||
| noNotes: "Немає дописів" | ||||
| noNotifications: "Немає сповіщень" | ||||
| instance: "Інстанс" | ||||
| settings: "Налаштування" | ||||
| basicSettings: "Основні налаштування" | ||||
| otherSettings: "Інші налаштування" | ||||
| openInWindow: "Відкрити у вікні" | ||||
| profile: "Профіль" | ||||
| timeline: "Стрічка" | ||||
| noAccountDescription: "Цей користувач ще нічого не написав про себе" | ||||
| login: "Увійти" | ||||
| loggingIn: "Здійснюємо вхід..." | ||||
| logout: "Вийти" | ||||
| signup: "Реєстрація" | ||||
| uploading: "Завантаження..." | ||||
| save: "Зберегти" | ||||
| users: "Користувачі" | ||||
| addUser: "Додати користувача" | ||||
| favorite: "Обране" | ||||
| favorites: "Обране" | ||||
| unfavorite: "Видалити з обраного" | ||||
| pin: "Закріпити" | ||||
| unpin: "Відкріпити" | ||||
| copyContent: "Скопіювати контент" | ||||
| copyLink: "Скопіювати посилання" | ||||
| delete: "Видалити" | ||||
| deleteAndEdit: "Видалити й редагувати" | ||||
| addToList: "Додати до списку" | ||||
| sendMessage: "Надіслати повідомлення" | ||||
| copyUsername: "Скопіювати ім’я користувача" | ||||
| searchUser: "Пошук користувачів" | ||||
| reply: "Відповісти" | ||||
| loadMore: "Показати більше" | ||||
| youGotNewFollower: "У вас новий підписник" | ||||
| receiveFollowRequest: "Отримано запит на підписку" | ||||
| followRequestAccepted: "Запит на підписку прийнято" | ||||
| mention: "Згадка" | ||||
| mentions: "Згадки" | ||||
| directNotes: "Прямі повідомлення" | ||||
| importAndExport: "Імпорт та експорт" | ||||
| import: "Імпорт" | ||||
| export: "Експорт" | ||||
| files: "Файли" | ||||
| download: "Завантажити" | ||||
| unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?" | ||||
| lists: "Списки" | ||||
| noLists: "Немає списків" | ||||
| note: "Дописи" | ||||
| notes: "Дописи" | ||||
| following: "Підписки" | ||||
| followers: "Підписники" | ||||
| followsYou: "Підписаний(-а) на вас" | ||||
| createList: "Створити список" | ||||
| manageLists: "Управління списками" | ||||
| error: "Помилка" | ||||
| somethingHappened: "Щось пішло не так" | ||||
| retry: "Спробувати знову" | ||||
| pageLoadError: "Помилка при завантаженні сторінки" | ||||
| enterListName: "Введіть назву списку" | ||||
| privacy: "Приватність" | ||||
| makeFollowManuallyApprove: "Підтверджувати підписників уручну" | ||||
| defaultNoteVisibility: "Видимість допису за замовчуванням" | ||||
| follow: "Підписка" | ||||
| followRequest: "Запит на підписку" | ||||
| followRequests: "Запити на підписку" | ||||
| unfollow: "Відписатися" | ||||
| followRequestPending: "Очікуючі запити на підписку" | ||||
| enterEmoji: "Введіть емодзі" | ||||
| renote: "Поширити" | ||||
| unrenote: "Відміна поширення" | ||||
| quote: "Цитата" | ||||
| pinnedNote: "Закріплений допис" | ||||
| you: "Ви" | ||||
| clickToShow: "Натисніть для перегляду" | ||||
| sensitive: "NSFW" | ||||
| add: "Додати" | ||||
| reaction: "Реакції" | ||||
| rememberNoteVisibility: "Пам’ятати видимість дописів" | ||||
| attachCancel: "Видалити вкладення" | ||||
| markAsSensitive: "Позначити як NSFW" | ||||
| unmarkAsSensitive: "Зняти позначку NSFW" | ||||
| enterFileName: "Введіть ім'я файлу" | ||||
| mute: "Ігнорувати" | ||||
| unmute: "Показувати" | ||||
| block: "Заблокувати" | ||||
| unblock: "Розблокувати" | ||||
| suspend: "Призупинити" | ||||
| unsuspend: "Відновити" | ||||
| blockConfirm: "Ви впевнені, що хочете заблокувати цей акаунт?" | ||||
| unblockConfirm: "Ви впевнені, що хочете розблокувати цей акаунт?" | ||||
| suspendConfirm: "Ви впевнені, що хочете призупинити цей акаунт?" | ||||
| unsuspendConfirm: "Ви впевнені, що хочете відновити цей акаунт?" | ||||
| selectList: "Виберіть список" | ||||
| selectAntenna: "Виберіть антену" | ||||
| selectWidget: "Виберіть віджет" | ||||
| editWidgets: "Редагувати віджети" | ||||
| editWidgetsExit: "Готово" | ||||
| customEmojis: "Кастомні емоджі" | ||||
| emoji: "Емоджі" | ||||
| emojiName: "Назва емоджі" | ||||
| emojiUrl: "URL емодзі" | ||||
| addEmoji: "Додати емодзі" | ||||
| settingGuide: "Рекомендована конфігурація" | ||||
| cacheRemoteFiles: "Кешувати дані з інших інстансів" | ||||
| flagAsBot: "Акаунт бота" | ||||
| flagAsCat: "Акаунт кота" | ||||
| autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані" | ||||
| addAcount: "Додати акаунт" | ||||
| loginFailed: "Не вдалося увійти" | ||||
| showOnRemote: "Переглянути в оригіналі" | ||||
| general: "Загальне" | ||||
| wallpaper: "Шпалери" | ||||
| setWallpaper: "Встановити шпалери" | ||||
| removeWallpaper: "Прибрати шпалери" | ||||
| searchWith: "Шукати з {q}" | ||||
| youHaveNoLists: "У вас немає списків" | ||||
| followConfirm: "Підписатися на {name}?" | ||||
| proxyAccount: "Проксі-акаунт" | ||||
| host: "Хост" | ||||
| selectUser: "Виберіть користувача" | ||||
| recipient: "Кому" | ||||
| annotation: "Коментар" | ||||
| federation: "Федіверс" | ||||
| instances: "Інстанс" | ||||
| registeredAt: "Приєднався(-лась)" | ||||
| latestRequestSentAt: "Останній запит надіслано" | ||||
| latestRequestReceivedAt: "Останній запит прийнято" | ||||
| latestStatus: "Останній статус" | ||||
| storageUsage: "Використання простіру" | ||||
| charts: "Графіки" | ||||
| perHour: "Щогодини" | ||||
| perDay: "Щоденно" | ||||
| stopActivityDelivery: "Припинити розсилання активності" | ||||
| blockThisInstance: "Заблокувати цей інстанс" | ||||
| operations: "Операції" | ||||
| software: "Програмне забезпечення" | ||||
| version: "Версія" | ||||
| metadata: "Метадані" | ||||
| withNFiles: "файли: {n}" | ||||
| monitor: "Монітор" | ||||
| jobQueue: "Черга завдань" | ||||
| cpuAndMemory: "ЦП та пам'ять" | ||||
| network: "Мережа" | ||||
| disk: "Диск" | ||||
| instanceInfo: "Про цей інстанс" | ||||
| statistics: "Статистика" | ||||
| clearQueue: "Очистити чергу" | ||||
| clearQueueConfirmTitle: "Ви впевнені, що хочете очистити чергу?" | ||||
| clearCachedFiles: "Очистити кеш" | ||||
| clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?" | ||||
| blockedInstances: "Заблоковані інстанси" | ||||
| muteAndBlock: "Ігнор і блокування" | ||||
| mutedUsers: "Ігноровані користувачі" | ||||
| blockedUsers: "Заблоковані користувачі" | ||||
| noUsers: "Немає користувачів" | ||||
| editProfile: "Редагувати профіль" | ||||
| noteDeleteConfirm: "Ви дійсно хочете видалити цей допис?" | ||||
| pinLimitExceeded: "Більше дописів не можна закріпити" | ||||
| done: "Готово" | ||||
| processing: "Обробка" | ||||
| preview: "Передогляд" | ||||
| default: "За умовчанням" | ||||
| noCustomEmojis: "Немає кастомних емоджі" | ||||
| noJobs: "Немає завдань" | ||||
| federating: "Федерується" | ||||
| blocked: "Заблоковано" | ||||
| suspended: "Призупинено" | ||||
| notResponding: "Не відповідає" | ||||
| changePassword: "Змінити пароль" | ||||
| security: "Безпека" | ||||
| currentPassword: "Поточний пароль" | ||||
| newPassword: "Новий пароль" | ||||
| newPasswordRetype: "Новий пароль (повторно)" | ||||
| attachFile: "Вкласти файл" | ||||
| more: "Бiльше!" | ||||
| featured: "Виділено" | ||||
| noSuchUser: "Користувача не знайдено" | ||||
| lookup: "Пошук" | ||||
| announcements: "Оголошення" | ||||
| imageUrl: "URL зображення" | ||||
| remove: "Видалити" | ||||
| removed: "Видалено" | ||||
| saved: "Збережено" | ||||
| messaging: "Чати" | ||||
| upload: "Завантажити" | ||||
| fromDrive: "З диска" | ||||
| fromUrl: "З URL" | ||||
| uploadFromUrl: "Завантажити з URL" | ||||
| explore: "Огляд" | ||||
| games: "Ігри Misskey" | ||||
| noMoreHistory: "Подальшої історії немає" | ||||
| start: "Розпочати" | ||||
| home: "Домівка" | ||||
| activity: "Активність" | ||||
| images: "Зображення" | ||||
| birthday: "День народження" | ||||
| yearsOld: "{age} років" | ||||
| registeredDate: "Приєднався(-лась)" | ||||
| location: "Локація" | ||||
| theme: "Тема" | ||||
| themeForLightMode: "Світла тема" | ||||
| themeForDarkMode: "Темна тема" | ||||
| light: "Світла" | ||||
| dark: "Темна" | ||||
| lightThemes: "Світлі теми" | ||||
| darkThemes: "Темні теми" | ||||
| drive: "Диск" | ||||
| fileName: "Ім'я файлу" | ||||
| selectFile: "Вибрати файл" | ||||
| selectFiles: "Вибрати файли" | ||||
| selectFolder: "Вибрати теку" | ||||
| selectFolders: "Вибрати теки" | ||||
| renameFile: "Перейменувати файл" | ||||
| folderName: "Ім'я теки" | ||||
| createFolder: "Створити теку" | ||||
| renameFolder: "Перейменувати теку" | ||||
| deleteFolder: "Видалити теку" | ||||
| addFile: "Додати файл" | ||||
| emptyDrive: "Диск порожній" | ||||
| emptyFolder: "Тека порожня" | ||||
| unableToDelete: "Видалення неможливе" | ||||
| inputNewFileName: "Введіть ім'я нового файлу" | ||||
| inputNewFolderName: "Введіть ім'я нової теки" | ||||
| hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена" | ||||
| copyUrl: "Копіювати URL" | ||||
| rename: "Перейменувати" | ||||
| avatar: "Аватар" | ||||
| banner: "Банер" | ||||
| nsfw: "NSFW" | ||||
| disconnectedFromServer: "Підключення до сервера було перервано" | ||||
| reload: "Оновити" | ||||
| doNothing: "Нічого не робити" | ||||
| reloadConfirm: "Перезавантажити стрічку?" | ||||
| watch: "Стежити" | ||||
| unwatch: "Не стежити" | ||||
| accept: "Прийняти" | ||||
| reject: "Відхилити" | ||||
| instanceName: "Назва інстансу" | ||||
| instanceDescription: "Описання інстансу" | ||||
| maintainerName: "Ім'я адміністратора" | ||||
| maintainerEmail: "Email адміністратора" | ||||
| tosUrl: "URL умов використання" | ||||
| thisYear: "Рік" | ||||
| thisMonth: "Місяць" | ||||
| today: "День" | ||||
| dayX: "{day}" | ||||
| monthX: "{month}" | ||||
| yearX: "{year}" | ||||
| pages: "Сторінки" | ||||
| integration: "Інтеграція" | ||||
| connectSerice: "Під’єднатися" | ||||
| disconnectSerice: "Відключитися" | ||||
| enableLocalTimeline: "Увімкнути локальну стрічку" | ||||
| enableGlobalTimeline: "Увімкнути глобальну стрічку" | ||||
| registration: "Реєстрація" | ||||
| enableRegistration: "Дозволити реєстрацію" | ||||
| invite: "Запрошення" | ||||
| proxyRemoteFiles: "Проксувати файли з інших інстансів" | ||||
| iconUrl: "URL аватара" | ||||
| bannerUrl: "URL банера" | ||||
| basicInfo: "Основна інформація" | ||||
| pinnedUsers: "Закріплені користувачі" | ||||
| hcaptcha: "hCaptcha" | ||||
| enableHcaptcha: "Увімкнути hCaptcha" | ||||
| hcaptchaSiteKey: "Ключ сайту" | ||||
| hcaptchaSecretKey: "Секретний ключ" | ||||
| recaptcha: "reCAPTCHA" | ||||
| enableRecaptcha: "Увімкнути reCAPTCHA" | ||||
| recaptchaSiteKey: "Ключ сайту" | ||||
| recaptchaSecretKey: "Секретний ключ" | ||||
| antennas: "Антени" | ||||
| manageAntennas: "Налаштування антен" | ||||
| name: "Ім'я" | ||||
| antennaSource: "Джерело антени" | ||||
| antennaKeywords: "Ключові слова антени" | ||||
| antennaExcludeKeywords: "Винятки" | ||||
| serviceworker: "ServiceWorker" | ||||
| enableServiceworker: "Ввімкнути ServiceWorker" | ||||
| caseSensitive: "З урахуванням регістру" | ||||
| notesAndReplies: "Дописи та відповіді" | ||||
| popularUsers: "Популярні користувачі" | ||||
| recentlyUpdatedUsers: "Нещодавно активні користувачі" | ||||
| recentlyRegisteredUsers: "Нещодавно зареєстровані користувачі" | ||||
| recentlyDiscoveredUsers: "Нещодавно знайдені користувачі" | ||||
| exploreUsersCount: "{count} користувачів" | ||||
| exploreFediverse: "Огляд федіверсу" | ||||
| popularTags: "Популярні теги" | ||||
| userList: "Списки" | ||||
| about: "Інформація" | ||||
| aboutMisskey: "Про Misskey" | ||||
| administrator: "Адмін" | ||||
| token: "Токен" | ||||
| twoStepAuthentication: "Двохфакторна аутентифікація" | ||||
| moderator: "Модератор" | ||||
| securityKey: "Ключ захисту" | ||||
| securityKeyName: "Назва ключа" | ||||
| registerSecurityKey: "Зареєструвати ключ захисту" | ||||
| lastUsed: "Востаннє використано" | ||||
| unregister: "Скасувати реєстрацію" | ||||
| passwordLessLogin: "Налаштувати вхід без пароля" | ||||
| resetPassword: "Скинути пароль" | ||||
| newPasswordIs: "Новий пароль: {password}" | ||||
| share: "Поділитись" | ||||
| notFound: "Не знайдено" | ||||
| cacheClear: "Очистити кеш" | ||||
| help: "Допомога" | ||||
| inputMessageHere: "Введіть повідомлення тут" | ||||
| close: "Закрити" | ||||
| group: "Група" | ||||
| groups: "Групи" | ||||
| createGroup: "Створити групу" | ||||
| ownedGroups: "Власні групи" | ||||
| invites: "Запрошення" | ||||
| groupName: "Назва групи" | ||||
| transfer: "Передача" | ||||
| messagingWithUser: "Чат з користувачами" | ||||
| messagingWithGroup: "Чат з групою" | ||||
| title: "Тема" | ||||
| text: "Текст" | ||||
| next: "Далі" | ||||
| retype: "Введіть ще раз" | ||||
| noteOf: "Допис {user}" | ||||
| inviteToGroup: "Запрошення до групи" | ||||
| maxNoteTextLength: "Максимальна довжина допису" | ||||
| quoteAttached: "Цитата" | ||||
| quoteQuestion: "Ви хочете додати цитату?" | ||||
| noMessagesYet: "Ще немає повідомлень" | ||||
| newMessageExists: "Є нові повідомлення" | ||||
| onlyOneFileCanBeAttached: "До повідомлення можна вкласти лише один файл" | ||||
| signinRequired: "Будь ласка, авторизуйтесь" | ||||
| invitations: "Запрошення" | ||||
| invitationCode: "Код запрошення" | ||||
| checking: "Перевірка…" | ||||
| available: "Доступно" | ||||
| unavailable: "Недоступно" | ||||
| usernameInvalidFormat: "літери, цифри та _ є прийнятними" | ||||
| tooShort: "Занадто короткий" | ||||
| tooLong: "Занадто довгий" | ||||
| weakPassword: "Слабкий пароль" | ||||
| strongPassword: "Міцний пароль" | ||||
| passwordMatched: "Все вірно" | ||||
| passwordNotMatched: "Паролі не співпадають" | ||||
| signinWith: "Увійти за допомогою {x}" | ||||
| uiLanguage: "Мова інтерфейсу" | ||||
| aboutX: "Про {x}" | ||||
| useOsNativeEmojis: "Використовувати емодзі ОС" | ||||
| youHaveNoGroups: "Немає груп" | ||||
| noHistory: "Історія порожня" | ||||
| disableAnimatedMfm: "Відключити анімації MFM" | ||||
| doing: "Виконується" | ||||
| category: "Категорія" | ||||
| tags: "Теги" | ||||
| docSource: "Джерело цього документа" | ||||
| createAccount: "Створити акаунт" | ||||
| existingAcount: "Існуючий акаунт" | ||||
| regenerate: "Оновити" | ||||
| fontSize: "Розмір шрифту" | ||||
| noFollowRequests: "Немає запитів на підписку" | ||||
| dashboard: "Панель приладів" | ||||
| local: "Локальні" | ||||
| remote: "Віддалений" | ||||
| total: "Всього" | ||||
| appearance: "Вигляд" | ||||
| clientSettings: "Налаштування клієнта" | ||||
| accountSettings: "Налаштування акаунта" | ||||
| promotion: "Просування" | ||||
| promote: "Просунути" | ||||
| numberOfDays: "Кількість днів" | ||||
| hideThisNote: "Сховати цей допис" | ||||
| showInPage: "Показати на сторінці" | ||||
| sort: "Сортування" | ||||
| ascendingOrder: "За зростанням" | ||||
| descendingOrder: "За спаданням" | ||||
| script: "Скрипт" | ||||
| deleteAllFiles: "Видалити всі файли" | ||||
| deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?" | ||||
| sidebar: "Бокова панель" | ||||
| rooms: "Кімнати" | ||||
| relays: "Ретранслятори" | ||||
| addRelay: "Додати ретранслятор" | ||||
| smtpHost: "Хост" | ||||
| smtpUser: "Ім'я користувача" | ||||
| smtpPass: "Пароль" | ||||
| regenerateLoginToken: "Оновити Login Token" | ||||
| _mfm: | ||||
|   cheatSheet: " Довідка MFM" | ||||
|   mention: "Згадка" | ||||
|   quote: "Цитата" | ||||
|   emoji: "Кастомні емоджі" | ||||
|   search: "Пошук" | ||||
| _reversi: | ||||
|   total: "Всього" | ||||
| _sidebar: | ||||
|   icon: "Аватар" | ||||
| _theme: | ||||
|   keys: | ||||
|     mention: "Згадка" | ||||
|     renote: "Поширити" | ||||
| _sfx: | ||||
|   note: "Дописи" | ||||
|   notification: "Сповіщення" | ||||
|   chat: "Чати" | ||||
| _antennaSources: | ||||
|   homeTimeline: "Дописи тих, на кого ви підписані" | ||||
| _widgets: | ||||
|   notifications: "Сповіщення" | ||||
|   timeline: "Стрічка" | ||||
|   activity: "Активність" | ||||
|   federation: "Федіверс" | ||||
| _cw: | ||||
|   show: "Показати більше" | ||||
| _visibility: | ||||
|   home: "Домівка" | ||||
|   followers: "Підписники" | ||||
|   localOnly: "Лише локально" | ||||
| _postForm: | ||||
|   replyPlaceholder: "Відповідь на допис..." | ||||
| _profile: | ||||
|   name: "Ім'я" | ||||
|   username: "Ім'я користувача" | ||||
| _exportOrImport: | ||||
|   followingList: "Підписки" | ||||
|   muteList: "Ігнорувати" | ||||
|   blockingList: "Заблокувати" | ||||
|   userLists: "Списки" | ||||
| _timelines: | ||||
|   home: "Домівка" | ||||
| _rooms: | ||||
|   _roomType: | ||||
|     default: "За умовчанням" | ||||
|   _furnitures: | ||||
|     monitor: "Монітор" | ||||
| _pages: | ||||
|   blocks: | ||||
|     image: "Зображення" | ||||
|   script: | ||||
|     categories: | ||||
|       list: "Списки" | ||||
|     blocks: | ||||
|       _join: | ||||
|         arg1: "Списки" | ||||
|       _randomPick: | ||||
|         arg1: "Списки" | ||||
|       _dailyRandomPick: | ||||
|         arg1: "Списки" | ||||
|       _seedRandomPick: | ||||
|         arg2: "Списки" | ||||
|       _pick: | ||||
|         arg1: "Списки" | ||||
|       _listLen: | ||||
|         arg1: "Списки" | ||||
|     types: | ||||
|       array: "Списки" | ||||
| _notification: | ||||
|   youRenoted: "{name} поширив(ла) ваш допис" | ||||
|   youWereFollowed: "У вас новий підписник" | ||||
|   _types: | ||||
|     follow: "Підписки" | ||||
|     mention: "Згадка" | ||||
|     renote: "Поширити" | ||||
|     quote: "Цитата" | ||||
|     reaction: "Реакції" | ||||
| _deck: | ||||
|   _columns: | ||||
|     notifications: "Сповіщення" | ||||
|     tl: "Стрічка" | ||||
|     antenna: "Антени" | ||||
|     list: "Списки" | ||||
|     mentions: "Згадки" | ||||
| @@ -16,6 +16,9 @@ noNotes: "没有帖文" | ||||
| noNotifications: "无通知" | ||||
| instance: "实例" | ||||
| settings: "设置" | ||||
| basicSettings: "基本设置" | ||||
| otherSettings: "其他设置" | ||||
| openInWindow: "在新窗口中打开" | ||||
| profile: "个人资料" | ||||
| timeline: "时间线" | ||||
| noAccountDescription: "这个人很懒,没有写自我介绍" | ||||
| @@ -40,6 +43,7 @@ deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回 | ||||
| addToList: "添加至列表" | ||||
| sendMessage: "发送" | ||||
| copyUsername: "复制用户名" | ||||
| searchUser: "搜索用户" | ||||
| reply: "回复" | ||||
| loadMore: "查看更多" | ||||
| youGotNewFollower: "你有新的关注者" | ||||
| @@ -66,8 +70,11 @@ followers: "关注者" | ||||
| followsYou: "关注了你" | ||||
| createList: "创建列表" | ||||
| manageLists: "管理列表" | ||||
| error: "有点小问题" | ||||
| error: "错误" | ||||
| somethingHappened: "出现了问题" | ||||
| retry: "重试" | ||||
| pageLoadError: "页面加载失败。" | ||||
| pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。" | ||||
| enterListName: "输入列表名称" | ||||
| privacy: "隐私" | ||||
| makeFollowManuallyApprove: "关注者请求需要批准" | ||||
| @@ -77,7 +84,7 @@ followRequest: "关注申请" | ||||
| followRequests: "关注申请" | ||||
| unfollow: "取消关注" | ||||
| followRequestPending: "发送关注申请" | ||||
| enterEmoji: "输入Emoji" | ||||
| enterEmoji: "输入表情符号" | ||||
| renote: "转发" | ||||
| unrenote: "取消转发" | ||||
| quote: "引用" | ||||
| @@ -106,11 +113,13 @@ unsuspendConfirm: "要解除冻结吗?" | ||||
| selectList: "选择列表" | ||||
| selectAntenna: "天线选择" | ||||
| selectWidget: "选择小工具" | ||||
| editWidgets: "编辑小工具" | ||||
| editWidgetsExit: "完成编辑" | ||||
| customEmojis: "自定义Emoji" | ||||
| emoji: "表情符号" | ||||
| emojiName: "Emoji 名称" | ||||
| emojiUrl: "emoji 地址" | ||||
| addEmoji: "添加Emoji" | ||||
| emojiName: "表情符号名称" | ||||
| emojiUrl: "表情符号地址" | ||||
| addEmoji: "添加表情符号" | ||||
| settingGuide: "推荐配置" | ||||
| cacheRemoteFiles: "远程文件缓存" | ||||
| cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程实例载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。" | ||||
| @@ -155,7 +164,7 @@ jobQueue: "作业队列" | ||||
| cpuAndMemory: "CPU使用量" | ||||
| network: "网络" | ||||
| disk: "存储" | ||||
| instanceInfo: "实例情报" | ||||
| instanceInfo: "实例信息" | ||||
| statistics: "统计" | ||||
| clearQueue: "清除队列" | ||||
| clearQueueConfirmTitle: "确定清除队列?" | ||||
| @@ -176,8 +185,7 @@ done: "完成" | ||||
| processing: "处理中" | ||||
| preview: "预览" | ||||
| default: "默认" | ||||
| noCustomEmojis: "无自定义Emoji" | ||||
| customEmojisOfRemote: "远程Emoji" | ||||
| noCustomEmojis: "没有自定义表情符号" | ||||
| noJobs: "没有任务" | ||||
| federating: "联合中" | ||||
| blocked: "已拦截" | ||||
| @@ -286,7 +294,7 @@ dayX: "{day}日" | ||||
| monthX: "{month}月" | ||||
| yearX: "{year}年" | ||||
| pages: "页面" | ||||
| integration: "连携" | ||||
| integration: "关联" | ||||
| connectSerice: "已连接" | ||||
| disconnectSerice: "断开连接" | ||||
| enableLocalTimeline: "启用本地时间线功能" | ||||
| @@ -345,7 +353,7 @@ popularTags: "热门标签" | ||||
| userList: "列表" | ||||
| about: "关于" | ||||
| aboutMisskey: "关于 Misskey" | ||||
| aboutMisskeyText: "Misskey是由syuilo于2014年开发的开放源代码软件。" | ||||
| aboutMisskeyText: "Misskey是由syuilo于2014年开发的开源软件。" | ||||
| misskeyMembers: "现在由以下成员进行开发和维护:" | ||||
| misskeySource: "源代码在这里公开:" | ||||
| misskeyTranslation: "与我们一同进行Misskey的翻译工作:" | ||||
| @@ -365,8 +373,6 @@ unregister: "删除账户" | ||||
| passwordLessLogin: "无密码登录" | ||||
| resetPassword: "重置密码" | ||||
| newPasswordIs: "新的密码是「{password}」" | ||||
| autoNoteWatch: "自动关注帖子" | ||||
| autoNoteWatchDescription: "让您能够收到关于「回应」和回复其他用户的帖子的通知。" | ||||
| reduceUiAnimation: "减少UI动画" | ||||
| share: "分享" | ||||
| notFound: "未找到" | ||||
| @@ -404,6 +410,7 @@ noMessagesYet: "现在没有新的聊天" | ||||
| newMessageExists: "新信息" | ||||
| onlyOneFileCanBeAttached: "只能添加一个附件" | ||||
| signinRequired: "请先登录" | ||||
| invitations: "邀请" | ||||
| invitationCode: "邀请码" | ||||
| checking: "正在确认" | ||||
| available: "可用" | ||||
| @@ -423,7 +430,7 @@ or: "或者" | ||||
| uiLanguage: "显示语言" | ||||
| groupInvited: "群组招待" | ||||
| aboutX: "关于 {x}" | ||||
| useOsNativeEmojis: "使用OS原生Emoji" | ||||
| useOsNativeEmojis: "使用OS原生表情符号" | ||||
| youHaveNoGroups: "没有群组" | ||||
| joinOrCreateGroup: "请加入一个现有的群组,或者创建新群组。" | ||||
| noHistory: "没有历史记录" | ||||
| @@ -445,7 +452,7 @@ total: "总计" | ||||
| weekOverWeekChanges: "与前一周相比" | ||||
| dayOverDayChanges: "与前一日相比" | ||||
| appearance: "外观" | ||||
| clinetSettings: "客户端设置" | ||||
| clientSettings: "客户端设置" | ||||
| accountSettings: "账户设置" | ||||
| promotion: "推广" | ||||
| promote: "推广" | ||||
| @@ -476,6 +483,8 @@ newNoteRecived: "有新的帖子" | ||||
| sounds: "声音" | ||||
| listen: "听" | ||||
| none: "空" | ||||
| showInPage: "在页面中显示" | ||||
| popout: "弹窗" | ||||
| volume: "音量" | ||||
| details: "详情" | ||||
| chooseEmoji: "选择表情符号" | ||||
| @@ -518,7 +527,6 @@ enableInfiniteScroll: "启用自动滚动页面模式" | ||||
| visibility: "可见性" | ||||
| poll: "调查问卷" | ||||
| useCw: "隐藏内容" | ||||
| fixedWidgetsPosition: "固定小工具的位置" | ||||
| enablePlayer: "打开播放器" | ||||
| disablePlayer: "关闭播放器" | ||||
| expandTweet: "展开推文" | ||||
| @@ -532,6 +540,7 @@ pluginInstallWarn: "请不要安装不明来源的插件" | ||||
| deck: "Deck" | ||||
| undeck: "取消Deck" | ||||
| useBlurEffectForModal: "模态框使用模糊效果" | ||||
| useFullReactionPicker: "使用全功能的回应工具栏" | ||||
| generateAccessToken: "生成访问令牌" | ||||
| permission: "权限" | ||||
| enableAll: "启用全部" | ||||
| @@ -540,7 +549,7 @@ tokenRequested: "允许访问账户" | ||||
| pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限" | ||||
| notificationType: "通知类型" | ||||
| edit: "编辑" | ||||
| useStarForReactionFallback: "如果回应的颜文字未知,则使用★作为代替" | ||||
| useStarForReactionFallback: "如果回应的是未知表情符号,则使用★作为代替" | ||||
| emailConfig: "邮件服务器设置" | ||||
| enableEmail: "启用发送邮件功能" | ||||
| emailConfigInfo: "用于确认电子邮件和密码重置" | ||||
| @@ -566,6 +575,119 @@ delayed: "延迟" | ||||
| database: "数据库" | ||||
| channel: "频道" | ||||
| create: "创建" | ||||
| notificationSetting: "通知设置" | ||||
| notificationSettingDesc: "选择要显示的通知类型。" | ||||
| useGlobalSetting: "使用全局设置" | ||||
| useGlobalSettingDesc: "启用时,将使用帐户通知设置。关闭时,则可以单独设置。" | ||||
| other: "其他" | ||||
| regenerateLoginToken: "重新生成登录令牌" | ||||
| regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。" | ||||
| setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。" | ||||
| fileIdOrUrl: "文件ID或者URL" | ||||
| chatOpenBehavior: "聊天窗口打开时的行为" | ||||
| sample: "示例" | ||||
| abuseReports: "举报" | ||||
| reportAbuse: "举报" | ||||
| reportAbuseOf: "举报{name}" | ||||
| fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写URL地址。" | ||||
| abuseReported: "内容已发送。感谢您的报告。" | ||||
| send: "发送" | ||||
| abuseMarkAsResolved: "处理完毕" | ||||
| openInNewTab: "在新标签页中打开" | ||||
| openInSideView: "在侧边栏中打开" | ||||
| defaultNavigationBehaviour: "默认导航" | ||||
| editTheseSettingsMayBreakAccount: "编辑这些设置可以会损坏您的账号" | ||||
| instanceTicker: "帖子的实例信息" | ||||
| waitingFor: "等待{x}" | ||||
| random: "随机" | ||||
| system: "系统" | ||||
| switchUi: "切换界面" | ||||
| desktop: "桌面" | ||||
| _mfm: | ||||
|   cheatSheet: "MFM代码速查表" | ||||
|   intro: "MFM是一种在Misskey中的各个位置使用的专用标记语言。在这里您可以看到MFM中可用的语法列表。" | ||||
|   dummy: "通过Misskey扩展Fediverse的世界" | ||||
|   mention: "提及" | ||||
|   mentionDescription: "可以使用 @+用户名 来指示特定用户" | ||||
|   hashtag: "话题标签" | ||||
|   hashtagDescription: "可以使用井号+文字来表示话题标签。" | ||||
|   url: "URL" | ||||
|   urlDescription: "可以表示URL地址。" | ||||
|   link: "链接" | ||||
|   linkDescription: "可以将部分文字和URL关联起来。" | ||||
|   bold: "粗体" | ||||
|   boldDescription: "可以将文字显示为粗体来表示强调。" | ||||
|   small: "缩小" | ||||
|   smallDescription: "可以使内容文字变小、变淡。" | ||||
|   center: "居中" | ||||
|   centerDescription: "可以将内容居中显示。" | ||||
|   inlineCode: "代码(内嵌)" | ||||
|   inlineCodeDescription: "将文字中的程序代码语法高亮显示。" | ||||
|   blockCode: "代码(块)" | ||||
|   blockCodeDescription: "语法高亮显示整块程序代码。" | ||||
|   inlineMath: "数学公式(内嵌)" | ||||
|   inlineMathDescription: "显示内嵌的KaTex公式。" | ||||
|   blockMath: "数学公式(块)" | ||||
|   blockMathDescription: "显示整块的多行KaTex数学公式。" | ||||
|   quote: "引用" | ||||
|   quoteDescription: "可以用来表示引用的内容。" | ||||
|   emoji: "自定义表情符号" | ||||
|   emojiDescription: "可以将自定义表情符号使用冒号括起来,就可以显示自定义表情符号了。" | ||||
|   search: "搜索" | ||||
|   searchDescription: "显示含有搜索内容示例的搜索框。" | ||||
|   flip: "翻转" | ||||
|   flipDescription: "将内容上下或左右翻转。" | ||||
|   jelly: "动画(果冻)" | ||||
|   jellyDescription: "显示果冻一样的动画效果。" | ||||
|   tada: "动画(锵锵)" | ||||
|   tadaDescription: "显示\"锵锵!\"的动画效果。" | ||||
|   jump: "动画(跳动)" | ||||
|   jumpDescription: "显示跳动的动画效果。" | ||||
|   bounce: "动画(弹性)" | ||||
|   bounceDescription: "显示弹性一样的动画效果。" | ||||
|   shake: "动画(摇晃)" | ||||
|   shakeDescription: "显示摇晃的动画效果。" | ||||
|   twitch: "动画(颤抖)" | ||||
|   twitchDescription: "显示强烈颤抖的动画效果。" | ||||
|   spin: "动画(回转)" | ||||
|   spinDescription: "显示回转的动画效果。" | ||||
| _reversi: | ||||
|   reversi: "黑白棋" | ||||
|   gameSettings: "对局设置" | ||||
|   chooseBoard: "棋盘选择" | ||||
|   blackOrWhite: "先手/后手" | ||||
|   blackIs: "{name}执黑(先走)" | ||||
|   rules: "规则" | ||||
|   botSettings: "机器人设置" | ||||
|   thisGameIsStartedSoon: "对局在几秒后开始" | ||||
|   waitingForOther: "等待对手准备" | ||||
|   waitingForMe: "等待您的准备" | ||||
|   waitingBoth: "请准备" | ||||
|   ready: "准备就绪" | ||||
|   cancelReady: "重新准备" | ||||
|   opponentTurn: "对手的会合" | ||||
|   myTurn: "您的回合" | ||||
|   turnOf: "{name}的回合" | ||||
|   pastTurnOf: "{name}的回合" | ||||
|   surrender: "认输 " | ||||
|   surrendered: "对手认输" | ||||
|   drawn: "平局" | ||||
|   won: "{name}获胜" | ||||
|   black: "黑" | ||||
|   white: "白" | ||||
|   total: "总计" | ||||
|   turnCount: "{count}回合" | ||||
|   myGames: "我的对局" | ||||
|   allGames: "所有对局" | ||||
|   ended: "结束" | ||||
|   playing: "对局中" | ||||
|   isLlotheo: "棋子较少一方获胜(LLoTheO规则)" | ||||
|   loopedMap: "循环棋盘" | ||||
|   canPutEverywhere: "可以下在任意位置" | ||||
| _instanceTicker: | ||||
|   none: "不显示" | ||||
|   remote: "显示给远程用户" | ||||
|   always: "始终显示" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自动重载" | ||||
|   dialog: "对话框警告" | ||||
| @@ -782,6 +904,7 @@ _widgets: | ||||
|   photos: "照片" | ||||
|   digitalClock: "数字时钟" | ||||
|   federation: "联邦宇宙" | ||||
|   postForm: "投稿窗口" | ||||
| _cw: | ||||
|   hide: "隐藏" | ||||
|   show: "查看更多" | ||||
| @@ -1244,8 +1367,11 @@ _notification: | ||||
|     renote: "转发" | ||||
|     quote: "引用" | ||||
|     reaction: "回应" | ||||
|     pollVote: "投票" | ||||
|     receiveFollowRequest: "关注请求" | ||||
|     pollVote: "问卷调查已投票" | ||||
|     receiveFollowRequest: "收到关注请求" | ||||
|     followRequestAccepted: "关注请求已接受" | ||||
|     groupInvited: "加入群组邀请" | ||||
|     app: "关联应用的通知" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "总是显示主列" | ||||
|   columnAlign: "列对齐" | ||||
|   | ||||
| @@ -1,21 +1,24 @@ | ||||
| --- | ||||
| _lang_: "中文(繁體)" | ||||
| _lang_: "繁體中文" | ||||
| introMisskey: "歡迎! Misskey是一個開源的去中心化的社群網站。\n通過「貼文」來分享現在發生的事情吧! 📡\n「反應」功能,可以讓你快速的對大家的「帖子」來表達感情👍\n一起來探索新的世界吧! 🚀" | ||||
| monthAndDay: "{month}月 {day}日" | ||||
| search: "搜尋" | ||||
| notifications: "通知" | ||||
| username: "使用名稱" | ||||
| username: "使用者名稱" | ||||
| password: "密碼" | ||||
| fetchingAsApObject: "從 Fediverse 查詢中..." | ||||
| ok: "確定" | ||||
| ok: "OK" | ||||
| gotIt: "知道了" | ||||
| cancel: "取消" | ||||
| enterUsername: "輸入使用者名稱" | ||||
| renotedBy: "由{user}轉發" | ||||
| renotedBy: "{user} 轉發了" | ||||
| noNotes: "貼文不可用。" | ||||
| noNotifications: "沒有通知" | ||||
| instance: "實例" | ||||
| settings: "設定" | ||||
| basicSettings: "基本設定" | ||||
| otherSettings: "其他設定" | ||||
| openInWindow: "在新視窗開啟" | ||||
| profile: "個人檔案" | ||||
| timeline: "時間軸" | ||||
| noAccountDescription: "此用戶還沒有自我介紹" | ||||
| @@ -40,6 +43,7 @@ deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應, | ||||
| addToList: "添加至清單" | ||||
| sendMessage: "發送訊息" | ||||
| copyUsername: "複製用戶名" | ||||
| searchUser: "搜尋用戶" | ||||
| reply: "回覆" | ||||
| loadMore: "瀏覽更多" | ||||
| youGotNewFollower: "您有新的追隨者" | ||||
| @@ -66,8 +70,10 @@ followers: "追隨者" | ||||
| followsYou: "追隨你的人" | ||||
| createList: "建立清單" | ||||
| manageLists: "管理清單" | ||||
| error: "發生錯誤" | ||||
| error: "錯誤" | ||||
| somethingHappened: "發生錯誤" | ||||
| retry: "重試" | ||||
| pageLoadError: "載入頁面失敗" | ||||
| enterListName: "輸入清單名稱" | ||||
| privacy: "隱私" | ||||
| makeFollowManuallyApprove: "手動審核追隨請求" | ||||
| @@ -93,8 +99,8 @@ attachCancel: "移除附件" | ||||
| markAsSensitive: "標記為敏感內容" | ||||
| unmarkAsSensitive: "取消標記為敏感內容" | ||||
| enterFileName: "請輸入檔案名稱" | ||||
| mute: "消音" | ||||
| unmute: "解除消音" | ||||
| mute: "靜音" | ||||
| unmute: "解除靜音" | ||||
| block: "封鎖" | ||||
| unblock: "解除封鎖" | ||||
| suspend: "凍結" | ||||
| @@ -106,16 +112,18 @@ unsuspendConfirm: "確定解凍此帳號?" | ||||
| selectList: "選擇清單" | ||||
| selectAntenna: "選擇天線" | ||||
| selectWidget: "選擇小工具" | ||||
| editWidgets: "編輯小工具" | ||||
| editWidgetsExit: "停止編輯" | ||||
| customEmojis: "自訂表情符號" | ||||
| emoji: "表情符號" | ||||
| emojiName: "表情符號名稱" | ||||
| emojiUrl: "表情符號URL" | ||||
| addEmoji: "新增表情符號" | ||||
| settingGuide: "推薦設定" | ||||
| cacheRemoteFiles: "遠程文件緩存" | ||||
| cacheRemoteFilesDescription: "如果禁用此設定,遠程文件將會被直接連結而非緩存。禁用將節省服務器上的存儲空間,但會因為沒有生成預覽圖而增加流量。" | ||||
| flagAsBot: "此帳戶是Bot" | ||||
| flagAsCat: "此帳戶是Cat" | ||||
| cacheRemoteFiles: "緩存非遠程檔案" | ||||
| cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間。但資料會因直接連線從而產生額外連接數據。" | ||||
| flagAsBot: "此使用者是機器人" | ||||
| flagAsCat: "此使用者是貓" | ||||
| autoAcceptFollowed: "自動許可追隨" | ||||
| addAcount: "新增帳號" | ||||
| loginFailed: "登入失敗" | ||||
| @@ -163,8 +171,8 @@ clearCachedFiles: "清除快取資料" | ||||
| clearCachedFilesConfirm: "確定要清除緩存資料嗎?" | ||||
| blockedInstances: "已封鎖的實例" | ||||
| blockedInstancesDescription: "請逐行輸入需要封鎖的實例。已封鎖的實例將無法與本實例進行通訊。" | ||||
| muteAndBlock: "禁言 / 封鎖" | ||||
| mutedUsers: "已禁言用戶" | ||||
| muteAndBlock: "靜音/封鎖" | ||||
| mutedUsers: "已靜音用戶" | ||||
| blockedUsers: "已封鎖用戶" | ||||
| noUsers: "無用戶" | ||||
| editProfile: "編輯個人檔案" | ||||
| @@ -176,7 +184,6 @@ processing: "處理中" | ||||
| preview: "預覽" | ||||
| default: "預設" | ||||
| noCustomEmojis: "沒有表情符號" | ||||
| customEmojisOfRemote: "來自其他實例的表情符號" | ||||
| noJobs: "沒有任務" | ||||
| federating: "整合搜索中" | ||||
| blocked: "已封鎖" | ||||
| @@ -220,10 +227,11 @@ messageRead: "已讀" | ||||
| noMoreHistory: "沒有更多歷史紀錄" | ||||
| startMessaging: "開始傳送訊息" | ||||
| nUsersRead: "{n}人已讀" | ||||
| agreeTo: "我同意{0}" | ||||
| tos: "使用條款" | ||||
| start: "開始" | ||||
| home: "首頁" | ||||
| remoteUserCaution: "由於是遠程用戶,信息不完整。" | ||||
| remoteUserCaution: "由於該用戶來自遠端實例,因此資料用戶並未即時更新。" | ||||
| activity: "動態" | ||||
| images: "圖片" | ||||
| birthday: "生日" | ||||
| @@ -293,7 +301,7 @@ disablingTimelinesInfo: "即使您禁用了時間線功能,管理員和協調 | ||||
| registration: "註冊" | ||||
| enableRegistration: "開啟新用戶註冊" | ||||
| invite: "邀請" | ||||
| proxyRemoteFiles: "代理遠程檔案" | ||||
| proxyRemoteFiles: "遠端代理檔案" | ||||
| proxyRemoteFilesDescription: "啟用此設置後,由於超出存儲容量而未保存或刪除的遠程文件將被本地代理,並且將生成預覽圖。這不影響服務器的存儲。" | ||||
| driveCapacityPerLocalAccount: "每個本地用戶的雲端容量" | ||||
| driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量" | ||||
| @@ -316,7 +324,7 @@ antennas: "天線" | ||||
| manageAntennas: "管理天線" | ||||
| name: "名稱" | ||||
| antennaSource: "接收來源" | ||||
| antennaKeywords: "包含的關鍵字" | ||||
| antennaKeywords: "包含關鍵字" | ||||
| antennaExcludeKeywords: "排除關鍵字" | ||||
| antennaKeywordsDescription: "用空格分隔指定AND、用換行符分隔指定OR" | ||||
| notifyAntenna: "通知我有新的貼文" | ||||
| @@ -362,8 +370,6 @@ unregister: "刪除賬戶" | ||||
| passwordLessLogin: "設置無密碼登入" | ||||
| resetPassword: "重置密碼" | ||||
| newPasswordIs: "新密碼為「{password}」" | ||||
| autoNoteWatch: "自動追隨貼文" | ||||
| autoNoteWatchDescription: "收到反應或回覆過的貼文的通知" | ||||
| reduceUiAnimation: "減少介面的動態視覺" | ||||
| share: "分享" | ||||
| notFound: "找不到" | ||||
| @@ -401,6 +407,7 @@ noMessagesYet: "沒有訊息" | ||||
| newMessageExists: "有新的訊息" | ||||
| onlyOneFileCanBeAttached: "只能添加一個附件" | ||||
| signinRequired: "請先登入" | ||||
| invitations: "邀請" | ||||
| invitationCode: "邀請碼" | ||||
| checking: "確認中" | ||||
| available: "可用的" | ||||
| @@ -430,12 +437,36 @@ category: "類別" | ||||
| tags: "標籤" | ||||
| docSource: "文件來源" | ||||
| createAccount: "建立帳戶" | ||||
| existingAcount: "現有帳戶" | ||||
| regenerate: "再生" | ||||
| fontSize: "字體大小" | ||||
| openImageInNewTab: "於新分頁中開啟圖片" | ||||
| local: "本地" | ||||
| remote: "遠端" | ||||
| total: "合計" | ||||
| clinetSettings: "用戶端設定" | ||||
| weekOverWeekChanges: "與上週相比" | ||||
| dayOverDayChanges: "與前一日相比" | ||||
| appearance: "外觀" | ||||
| clientSettings: "用戶端設定" | ||||
| accountSettings: "帳號設定" | ||||
| promotion: "推廣貼文" | ||||
| promote: "推廣" | ||||
| numberOfDays: "有效天數" | ||||
| hideThisNote: "隱藏此貼文" | ||||
| showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦" | ||||
| objectStorageBaseUrl: "Base URL" | ||||
| objectStorageBucket: "儲存空間(Bucket)" | ||||
| objectStoragePrefix: "前綴" | ||||
| objectStorageEndpoint: "訪問網域名稱(Endpoint)" | ||||
| objectStorageEndpointDesc: "如要使用AWS S3,請留空。否則請根據伺服器要求以'<host>'或 '<host>:<port>'的形式設定訪問網域名稱(Endpoint)。" | ||||
| objectStorageRegion: "地域(Region)" | ||||
| objectStorageUseSSL: "使用SSL" | ||||
| objectStorageUseProxy: "使用網路代理" | ||||
| serverLogs: "伺服器日誌" | ||||
| deleteAll: "刪除所有記錄" | ||||
| sounds: "音效" | ||||
| none: "無" | ||||
| showInPage: "在頁面中顯示" | ||||
| volume: "音量" | ||||
| details: "詳細資訊" | ||||
| chooseEmoji: "選擇您的表情符號\n" | ||||
| @@ -443,14 +474,21 @@ unableToProcess: "操作無法完成" | ||||
| recentUsed: "最近使用" | ||||
| install: "安裝" | ||||
| uninstall: "解除安裝" | ||||
| installedApps: "已授權的應用程式" | ||||
| nothing: "未發現" | ||||
| installedDate: "安裝時間" | ||||
| lastUsedDate: "最後上線日期" | ||||
| state: "狀態" | ||||
| sort: "排序" | ||||
| ascendingOrder: "昇冪" | ||||
| descendingOrder: "降冪" | ||||
| scratchpad: "暫存記憶體" | ||||
| output: "輸出" | ||||
| script: "腳本" | ||||
| updateRemoteUser: "更新非本地用戶資料" | ||||
| deleteAllFiles: "刪除所有檔案" | ||||
| deleteAllFilesConfirm: "要删除所有檔案吗?" | ||||
| removeAllFollowing: "解除所有追隨" | ||||
| userSuspended: "該用戶已被凍結" | ||||
| userSilenced: "該用戶已被禁言。" | ||||
| sidebar: "側邊列" | ||||
| @@ -468,7 +506,6 @@ enableInfiniteScroll: "啟用自動滾動頁面模式" | ||||
| visibility: "公開範圍" | ||||
| poll: "投票" | ||||
| useCw: "隱藏內容" | ||||
| fixedWidgetsPosition: "固定小工具的位置" | ||||
| enablePlayer: "打開播放器" | ||||
| disablePlayer: "關閉播放器" | ||||
| expandTweet: "展開推文" | ||||
| @@ -488,17 +525,63 @@ tokenRequested: "允許訪問帳號" | ||||
| notificationType: "通知形式" | ||||
| edit: "編輯" | ||||
| useStarForReactionFallback: "以★代替未知的表情符號" | ||||
| emailConfig: "電郵服務器設定" | ||||
| emailConfig: "電子郵件伺服器設定" | ||||
| enableEmail: "啟用發送電郵功能" | ||||
| emailConfigInfo: "用於確認電郵地址及密碼重置" | ||||
| email: "電郵地址" | ||||
| smtpConfig: "SMTP服務器設定" | ||||
| smtpConfig: "SMTP伺服器設定" | ||||
| smtpHost: "主機" | ||||
| smtpPort: "端口" | ||||
| smtpUser: "使用名稱" | ||||
| smtpUser: "使用者名稱" | ||||
| smtpPass: "密碼" | ||||
| emptyToDisableSmtpAuth: "留空使用者名稱和密碼以禁用SMTP驗證。" | ||||
| testEmail: "郵件測試發送" | ||||
| wordMute: "靜音文字" | ||||
| display: "檢視" | ||||
| copy: "複製" | ||||
| metrics: "指標" | ||||
| overview: "概覽" | ||||
| logs: "日誌" | ||||
| delayed: "延遲" | ||||
| database: "資料庫" | ||||
| channel: "頻道" | ||||
| create: "新增" | ||||
| notificationSetting: "通知設定" | ||||
| other: "其他" | ||||
| regenerateLoginTokenDescription: "再生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦再生,所有裝置將會被登出。" | ||||
| sample: "範例 " | ||||
| abuseReports: "檢舉" | ||||
| reportAbuse: "檢舉" | ||||
| reportAbuseOf: "檢舉{name}" | ||||
| send: "發送" | ||||
| openInNewTab: "在新分頁中開啟" | ||||
| random: "隨機" | ||||
| system: "系統" | ||||
| _mfm: | ||||
|   mention: "提及" | ||||
|   hashtag: "#tag" | ||||
|   link: "鏈接" | ||||
|   quote: "引用" | ||||
|   emoji: "自訂表情符號" | ||||
|   search: "搜尋" | ||||
| _reversi: | ||||
|   reversi: "黑白棋" | ||||
|   gameSettings: "對弈設定" | ||||
|   chooseBoard: "選擇棋盤" | ||||
|   rules: "規則" | ||||
|   botSettings: "機器人設定" | ||||
|   opponentTurn: "對手回合" | ||||
|   myTurn: "你的回合" | ||||
|   turnOf: "{name}的回合" | ||||
|   pastTurnOf: "{name}的回合" | ||||
|   surrender: "認輸" | ||||
|   black: "黑" | ||||
|   white: "白" | ||||
|   total: "合計" | ||||
|   ended: "已結束" | ||||
|   playing: "正在對弈" | ||||
| _instanceTicker: | ||||
|   always: "總是顯示" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自動重載" | ||||
|   dialog: "以對話框警告" | ||||
| @@ -506,7 +589,7 @@ _serverDisconnectedBehavior: | ||||
| _channel: | ||||
|   create: "建立頻道" | ||||
|   edit: "編輯頻道" | ||||
|   setBanner: "設置封面圖" | ||||
|   setBanner: "設定橫幅" | ||||
|   removeBanner: "移除封面圖" | ||||
|   featured: "流行" | ||||
|   owned: "管理中" | ||||
| @@ -515,12 +598,33 @@ _channel: | ||||
|   notesCount: "有{n}個帖子" | ||||
| _sidebar: | ||||
|   icon: "頭像" | ||||
|   hide: "隱藏" | ||||
| _wordMute: | ||||
|   muteWords: "加入靜音文字" | ||||
|   mutedNotes: "已靜音的貼文" | ||||
| _theme: | ||||
|   constant: "常數" | ||||
|   defaultValue: "預設值" | ||||
|   color: "顏色" | ||||
|   func: "函数" | ||||
|   argument: "引數" | ||||
|   alpha: "透明度" | ||||
|   darken: "暗度" | ||||
|   lighten: "亮度" | ||||
|   keys: | ||||
|     bg: "背景" | ||||
|     fg: "文本" | ||||
|     shadow: "陰影" | ||||
|     link: "鏈接" | ||||
|     hashtag: "#tag" | ||||
|     mention: "提及" | ||||
|     mentionMe: "提及我" | ||||
|     renote: "轉發貼文" | ||||
|     divider: "分割線" | ||||
|     infoBg: "資訊背景" | ||||
|     infoFg: "資訊內容" | ||||
|     infoWarnBg: "警告背景" | ||||
|     infoWarnFg: "警告字元" | ||||
| _sfx: | ||||
|   note: "貼文" | ||||
|   noteMy: "我的貼文" | ||||
| @@ -557,6 +661,7 @@ _tutorial: | ||||
|   step4_1: "筆記發出去了嗎?" | ||||
|   step4_2: "如果你的貼文有顯示在時間軸上,就代表已經發文成功。" | ||||
|   step5_1: "現在試試看追隨其他人來讓你的時間軸變得更生動吧。" | ||||
|   step5_2: "你可以在{featured}上看到受歡迎的貼文,你也可以選擇從列表中追隨你喜歡的人,或者在{explore}上找到熱門使用者。" | ||||
|   step5_3: "想要追隨其他人,只要點擊他們的頭像並按「追隨」即可。" | ||||
|   step5_4: "如果使用者的名字旁有鎖頭的圖示,代表他們需要手動核准你的追隨請求。" | ||||
|   step6_1: "現在你可以在時間軸上看到其他用戶的貼文" | ||||
| @@ -564,6 +669,8 @@ _tutorial: | ||||
|   step6_3: "在他人的貼文按下「+」的圖示即可選擇想要的表情符號來進行「反應」。" | ||||
|   step7_1: "以上為Misskey的基本操作說明,教學在此告一段落。辛苦了。" | ||||
|   step7_2: "歡迎到{help}來瞭解更多Misskey相關介紹。" | ||||
| _2fa: | ||||
|   registerDevice: "註冊裝置" | ||||
| _permissions: | ||||
|   "read:blocks": "已封鎖用戶名單" | ||||
|   "write:blocks": "編輯已封鎖用戶名單" | ||||
| @@ -572,13 +679,32 @@ _permissions: | ||||
|   "read:favorites": "瀏覽已收藏" | ||||
|   "write:favorites": "編輯收藏清單" | ||||
|   "write:following": "追隨/解除追隨" | ||||
|   "read:messaging": "顯示訊息" | ||||
|   "write:messaging": "撰寫或刪除私人訊息" | ||||
|   "read:mutes": "顯示已靜音列表" | ||||
|   "write:mutes": "編輯已靜音列表" | ||||
|   "write:notes": "撰寫或刪除貼文" | ||||
|   "read:notifications": "查看通知" | ||||
|   "write:notifications": "編輯通知" | ||||
|   "read:reactions": "查看反應" | ||||
|   "write:reactions": "編輯反應" | ||||
|   "write:votes": "投票" | ||||
|   "read:pages": "顯示頁面" | ||||
|   "write:pages": "編輯頁面" | ||||
|   "read:page-likes": "顯示頁面的已喜歡" | ||||
|   "write:page-likes": "編輯頁面上喜歡" | ||||
|   "read:user-groups": "顯示使用者群組" | ||||
|   "write:user-groups": "編輯使用者群組" | ||||
|   "read:channels": "已查看的頻道" | ||||
|   "write:channels": "操作頻道" | ||||
|   "write:channels": "編輯頻道" | ||||
| _auth: | ||||
|   shareAccess: "要授權「“{name}”」存取您的帳戶嗎?" | ||||
| _antennaSources: | ||||
|   all: "全部貼文" | ||||
|   homeTimeline: "來自已追隨使用者的貼文" | ||||
|   users: "來自特定使用者的貼文" | ||||
|   userList: "來自特定清單中的貼文" | ||||
|   userGroup: "來自特定群組的貼文" | ||||
| _weekday: | ||||
|   sunday: "週日" | ||||
|   monday: "週一" | ||||
| @@ -588,63 +714,236 @@ _weekday: | ||||
|   friday: "週五" | ||||
|   saturday: "週六" | ||||
| _widgets: | ||||
|   memo: "備忘錄" | ||||
|   notifications: "通知" | ||||
|   timeline: "時間軸" | ||||
|   calendar: "行事曆" | ||||
|   trends: "發燒貼文" | ||||
|   clock: "時鐘" | ||||
|   rss: "RSS閱讀器" | ||||
|   activity: "動態" | ||||
|   photos: "照片" | ||||
|   digitalClock: "電子時鐘" | ||||
|   federation: "聯邦宇宙" | ||||
| _cw: | ||||
|   hide: "隱藏" | ||||
|   show: "瀏覽更多" | ||||
|   chars: "{count}字元" | ||||
|   files: "{count} 個檔案" | ||||
| _poll: | ||||
|   noOnlyOneChoice: "至少需要兩個選項。" | ||||
|   expiration: "期限" | ||||
|   infinite: "無期限" | ||||
|   at: "結束時間" | ||||
|   deadlineDate: "截止日期" | ||||
|   deadlineTime: "小時" | ||||
|   duration: "時長" | ||||
|   votesCount: "{n}票" | ||||
|   totalVotes: "一共{n}票" | ||||
|   vote: "投票" | ||||
|   showResult: "顯示結果" | ||||
|   voted: "已投票" | ||||
|   closed: "已結束" | ||||
|   remainingDays: "{d}天{h}小時後結束" | ||||
| _visibility: | ||||
|   public: "公開" | ||||
|   home: "首頁" | ||||
|   followers: "追隨者" | ||||
|   specified: "指定使用者" | ||||
|   specifiedDescription: "僅發送至指定使用者" | ||||
|   localOnly: "僅限本地" | ||||
|   localOnlyDescription: "對遠端使用者隱藏" | ||||
| _postForm: | ||||
|   replyPlaceholder: "回覆此貼文..." | ||||
|   quotePlaceholder: "引用此貼文..." | ||||
|   channelPlaceholder: "發佈到頻道" | ||||
|   _placeholders: | ||||
|     a: "今天過得如何?" | ||||
|     b: "有什麼新鮮事嗎?" | ||||
|     c: "有什麼新鮮想法嗎?" | ||||
|     d: "想要發布些什麼嗎?" | ||||
|     e: "寫些什麼吧..." | ||||
|     f: "期待你發佈的內容..." | ||||
| _profile: | ||||
|   name: "名稱" | ||||
|   username: "使用名稱" | ||||
|   username: "使用者名稱" | ||||
|   description: "關於我" | ||||
|   youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag" | ||||
|   metadata: "更多資訊" | ||||
|   metadataLabel: "標籤" | ||||
|   metadataContent: "内容" | ||||
| _exportOrImport: | ||||
|   allNotes: "全部貼文" | ||||
|   followingList: "追隨中" | ||||
|   muteList: "消音" | ||||
|   muteList: "靜音" | ||||
|   blockingList: "封鎖" | ||||
|   userLists: "清單" | ||||
| _charts: | ||||
|   usersIncDec: "使用者増減" | ||||
|   usersTotal: "使用者合共" | ||||
|   activeUsers: "活躍使用者" | ||||
|   notesIncDec: "貼文増減" | ||||
|   localNotesIncDec: "本地貼文増減" | ||||
|   remoteNotesIncDec: "非本地貼文的數目增减" | ||||
|   notesTotal: "貼文合共" | ||||
|   filesIncDec: "檔案増減" | ||||
|   filesTotal: "累計檔案" | ||||
|   storageUsageIncDec: "儲存空間的増減" | ||||
|   storageUsageTotal: "已使用的儲存空間合共" | ||||
| _instanceCharts: | ||||
|   requests: "請求" | ||||
|   users: "使用者増減" | ||||
|   usersTotal: "總計使用者" | ||||
|   notes: "貼文増減" | ||||
|   notesTotal: "累計貼文" | ||||
|   ff: "追隨/追隨者的増減" | ||||
|   ffTotal: "追隨/追隨者累計" | ||||
|   cacheSize: "增加或減少快取用量" | ||||
|   cacheSizeTotal: "快取大小總計" | ||||
|   files: "檔案數量的増減" | ||||
|   filesTotal: "檔案數量總計" | ||||
| _timelines: | ||||
|   home: "首頁" | ||||
|   local: "本地" | ||||
|   social: "社群" | ||||
|   global: "全域" | ||||
| _rooms: | ||||
|   roomOf: "{user}的房間" | ||||
|   addFurniture: "擺放家具" | ||||
|   translate: "移動 " | ||||
|   rotate: "旋轉" | ||||
|   exit: "返回" | ||||
|   remove: "移除" | ||||
|   clear: "全部移除" | ||||
|   clearConfirm: "確定要移除全部家具嗎?" | ||||
|   leaveConfirm: "修改未儲存,是否要離開?" | ||||
|   chooseImage: "選擇圖像" | ||||
|   roomType: "房間種類" | ||||
|   carpetColor: "地板顏色" | ||||
|   _roomType: | ||||
|     default: "預設" | ||||
|     washitsu: "和室" | ||||
|   _furnitures: | ||||
|     milk: "牛奶盒" | ||||
|     bed: "床" | ||||
|     low-table: "咖啡桌" | ||||
|     desk: "書桌" | ||||
|     chair: "椅子" | ||||
|     chair2: "椅子2" | ||||
|     fan: "通風機" | ||||
|     pc: "電腦" | ||||
|     plant: "觀葉植物" | ||||
|     plant2: "觀葉植物2" | ||||
|     eraser: "橡皮擦" | ||||
|     pencil: "鉛筆" | ||||
|     pudding: "布丁" | ||||
|     cardboard-box: "紙板箱" | ||||
|     cardboard-box2: "紙板箱2" | ||||
|     cardboard-box3: "紙板箱3" | ||||
|     book: "讀物" | ||||
|     book2: "讀物2" | ||||
|     piano: "鋼琴" | ||||
|     moon: "月亮" | ||||
|     corkboard: "木栓板" | ||||
|     mousepad: "滑鼠墊" | ||||
|     monitor: "監視器" | ||||
|     keyboard: "鍵盤" | ||||
|     carpet-stripe: "條紋地毯" | ||||
|     bin: "垃圾箱" | ||||
|     cup-noodle: "杯面" | ||||
|     holo-display: "投影機" | ||||
|     energy-drink: "能量飲料" | ||||
|     doll-ai: "小藍的人偶公仔" | ||||
|     banknote: "大疊鈔票" | ||||
| _pages: | ||||
|   newPage: "建立頁面" | ||||
|   editPage: "編輯頁面" | ||||
|   created: "頁面已建立" | ||||
|   updated: "頁面已更新" | ||||
|   deleted: "頁面已被刪除" | ||||
|   editThisPage: "編輯此頁面" | ||||
|   viewSource: "檢視原始碼" | ||||
|   viewPage: "顯示頁面" | ||||
|   like: "喜歡" | ||||
|   unlike: "收回喜歡" | ||||
|   my: "我的頁面" | ||||
|   liked: "已喜歡的頁面" | ||||
|   inspector: "面板檢查" | ||||
|   variables: "變數" | ||||
|   title: "標題" | ||||
|   url: "頁面網址" | ||||
|   font: "字型" | ||||
|   fontSerif: "襯線體" | ||||
|   fontSansSerif: "無襯線體" | ||||
|   inputBlocks: "輸入" | ||||
|   blocks: | ||||
|     text: "文本" | ||||
|     textarea: "文字區域" | ||||
|     section: "區段" | ||||
|     image: "圖片" | ||||
|     button: "按鈕" | ||||
|     if: "如果" | ||||
|     _if: | ||||
|       variable: "變數" | ||||
|     _post: | ||||
|       text: "内容" | ||||
|       canvasId: "畫布ID" | ||||
|     textInput: "插入文字" | ||||
|     _textInput: | ||||
|       name: "變數名稱" | ||||
|       text: "標題" | ||||
|       default: "預設值" | ||||
|     textareaInput: "多行文字输入" | ||||
|     _textareaInput: | ||||
|       name: "變數名稱" | ||||
|       text: "標題" | ||||
|       default: "預設值" | ||||
|     numberInput: "輸入數值" | ||||
|     _numberInput: | ||||
|       name: "變數名稱" | ||||
|     _canvas: | ||||
|       width: "寬度" | ||||
|     _counter: | ||||
|       text: "標題" | ||||
|       default: "預設值" | ||||
|     canvas: "畫布" | ||||
|     _canvas: | ||||
|       id: "畫布ID" | ||||
|       width: "寬度" | ||||
|       height: "高度" | ||||
|     switch: "開關" | ||||
|     _switch: | ||||
|       name: "變數名稱" | ||||
|       text: "標題" | ||||
|       default: "預設值" | ||||
|     counter: "計數器" | ||||
|     _counter: | ||||
|       name: "變數名稱" | ||||
|       text: "標題" | ||||
|       inc: "増加値" | ||||
|     _button: | ||||
|       text: "標題" | ||||
|       colored: "彩色" | ||||
|       action: "按下按鈕後發生的行為" | ||||
|       _action: | ||||
|         _dialog: | ||||
|           content: "内容" | ||||
|         resetRandom: "重設亂數" | ||||
|         pushEvent: "發送事件" | ||||
|         _pushEvent: | ||||
|           event: "事件名稱" | ||||
|           no-variable: "沒有" | ||||
|         callAiScript: "調用AiScript" | ||||
|         _callAiScript: | ||||
|           functionName: "函數名稱" | ||||
|     radioButton: "選項" | ||||
|     _radioButton: | ||||
|       name: "變數名稱" | ||||
|       title: "標題" | ||||
|       default: "預設值" | ||||
|   script: | ||||
|     categories: | ||||
|       logical: "邏輯運算" | ||||
|       operation: "計算" | ||||
|       comparison: "對比" | ||||
|       random: "隨機" | ||||
|       value: "數值 " | ||||
|       fn: "函数" | ||||
|       text: "文本操作" | ||||
| @@ -654,6 +953,8 @@ _pages: | ||||
|       text: "文本" | ||||
|       multiLineText: "文本 (多行)" | ||||
|       textList: "文本列表" | ||||
|       _strLen: | ||||
|         arg1: "文本" | ||||
|       _strPick: | ||||
|         arg1: "文本" | ||||
|         arg2: "字元位置" | ||||
| @@ -667,18 +968,22 @@ _pages: | ||||
|       _add: | ||||
|         arg1: "A" | ||||
|         arg2: "B" | ||||
|       subtract: "减去" | ||||
|       _subtract: | ||||
|         arg1: "A" | ||||
|         arg2: "B" | ||||
|       multiply: "乘" | ||||
|       _multiply: | ||||
|         arg1: "A" | ||||
|         arg2: "B" | ||||
|       divide: "除" | ||||
|       _divide: | ||||
|         arg1: "A" | ||||
|         arg2: "B" | ||||
|       _mod: | ||||
|         arg1: "A" | ||||
|         arg2: "B" | ||||
|       round: "四舍五入" | ||||
|       _round: | ||||
|         arg1: "數值" | ||||
|       eq: "A和B相等" | ||||
| @@ -720,6 +1025,7 @@ _pages: | ||||
|       not: "否" | ||||
|       _not: | ||||
|         arg1: "否" | ||||
|       random: "隨機" | ||||
|       _random: | ||||
|         arg1: "機率" | ||||
|       rannum: "亂數" | ||||
| @@ -762,6 +1068,8 @@ _pages: | ||||
|         arg1: "文字" | ||||
|       _numberToString: | ||||
|         arg1: "數值" | ||||
|       _splitStrByLine: | ||||
|         arg1: "文本" | ||||
|       ref: "變數" | ||||
|       aiScriptVar: "AiScript的變數" | ||||
|       fn: "函数" | ||||
| @@ -777,25 +1085,43 @@ _pages: | ||||
|       array: "清單" | ||||
|       stringArray: "文本列表" | ||||
|     enviromentVariables: "環境變數" | ||||
|     pageVariables: "頁面元素" | ||||
| _relayStatus: | ||||
|   requesting: "等待核准" | ||||
|   accepted: "已通過核准" | ||||
|   rejected: "已拒絕" | ||||
| _notification: | ||||
|   youRenoted: "{name} 轉發了你的貼文" | ||||
|   youGotPoll: "{name}已投票" | ||||
|   youWereFollowed: "您有新的追隨者" | ||||
|   yourFollowRequestAccepted: "您的追隨請求已通過" | ||||
|   youWereInvitedToGroup: "您有新的群組邀請" | ||||
|   _types: | ||||
|     all: "全部 " | ||||
|     follow: "追隨中" | ||||
|     mention: "提及" | ||||
|     reply: "回覆" | ||||
|     renote: "轉發貼文" | ||||
|     quote: "引用" | ||||
|     reaction: "反應" | ||||
|     receiveFollowRequest: "已收到追隨請求" | ||||
|     followRequestAccepted: "追隨請求已接受" | ||||
|     app: "應用程式通知" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "總是顯示主欄" | ||||
|   columnAlign: "對齊欄位" | ||||
|   addColumn: "新增欄位" | ||||
|   swapLeft: "向左移動" | ||||
|   swapRight: "向右移動" | ||||
|   swapUp: "往上移動" | ||||
|   swapDown: "往下移動" | ||||
|   stackLeft: "向左折疊" | ||||
|   popRight: "向右彈出" | ||||
|   _columns: | ||||
|     widgets: "小工具" | ||||
|     notifications: "通知" | ||||
|     tl: "時間軸" | ||||
|     antenna: "天線" | ||||
|     list: "清單" | ||||
|     mentions: "提及" | ||||
|     direct: "指定使用者" | ||||
|   | ||||
							
								
								
									
										32
									
								
								migration/1603094348345-refine-abuse-user-report.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								migration/1603094348345-refine-abuse-user-report.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
|  | ||||
| export class refineAbuseUserReport1603094348345 implements MigrationInterface { | ||||
|     name = 'refineAbuseUserReport1603094348345' | ||||
|  | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_d049123c413e68ca52abe734203"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_d049123c413e68ca52abe73420"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_5cd442c3b2e74fdd99dae20243"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "userId" TO "targetUserId"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "assigneeId" character varying(32)`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "resolved" boolean NOT NULL DEFAULT false`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(2048) NOT NULL DEFAULT '{}'::varchar[]`); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a" ON "abuse_user_report" ("resolved") `); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de" FOREIGN KEY ("assigneeId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); | ||||
|     } | ||||
|  | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(512) NOT NULL DEFAULT '{}'::varchar[]`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "resolved"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "assigneeId"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "targetUserId" TO "userId"`); | ||||
|         await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5cd442c3b2e74fdd99dae20243" ON "abuse_user_report" ("userId", "reporterId") `); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_d049123c413e68ca52abe73420" ON "abuse_user_report" ("userId") `); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_d049123c413e68ca52abe734203" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										20
									
								
								migration/1603095701770-refine-abuse-user-report2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								migration/1603095701770-refine-abuse-user-report2.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
|  | ||||
| export class refineAbuseUserReport21603095701770 implements MigrationInterface { | ||||
|     name = 'refineAbuseUserReport21603095701770' | ||||
|  | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "targetUserHost" character varying(128)`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "reporterHost" character varying(128)`); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_4ebbf7f93cdc10e8d1ef2fc6cd" ON "abuse_user_report" ("targetUserHost") `); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_f8d8b93740ad12c4ce8213a199" ON "abuse_user_report" ("reporterHost") `); | ||||
|     } | ||||
|  | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`DROP INDEX "IDX_f8d8b93740ad12c4ce8213a199"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_4ebbf7f93cdc10e8d1ef2fc6cd"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "reporterHost"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "targetUserHost"`); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										14
									
								
								migration/1603776877564-instance-theme-color.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1603776877564-instance-theme-color.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
|  | ||||
| export class instanceThemeColor1603776877564 implements MigrationInterface { | ||||
|     name = 'instanceThemeColor1603776877564' | ||||
|  | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "instance" ADD "themeColor" character varying(64) DEFAULT null`); | ||||
|     } | ||||
|  | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "themeColor"`); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										14
									
								
								migration/1603781553011-instance-favicon.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1603781553011-instance-favicon.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
|  | ||||
| export class instanceFavicon1603781553011 implements MigrationInterface { | ||||
|     name = 'instanceFavicon1603781553011' | ||||
|  | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "instance" ADD "faviconUrl" character varying(256) DEFAULT null`); | ||||
|     } | ||||
|  | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "faviconUrl"`); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										14
									
								
								migration/1604821689616-delete-auto-watch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1604821689616-delete-auto-watch.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
|  | ||||
| export class deleteAutoWatch1604821689616 implements MigrationInterface { | ||||
|     name = 'deleteAutoWatch1604821689616' | ||||
|  | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "autoWatch"`); | ||||
|     } | ||||
|  | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "user_profile" ADD "autoWatch" boolean NOT NULL DEFAULT false`); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										90
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <syuilotan@yahoo.co.jp>", | ||||
| 	"version": "12.48.3", | ||||
| 	"version": "12.56.0", | ||||
| 	"codename": "indigo", | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| @@ -46,7 +46,7 @@ | ||||
| 		"@koa/multer": "3.0.0", | ||||
| 		"@koa/router": "9.0.1", | ||||
| 		"@sinonjs/fake-timers": "6.0.1", | ||||
| 		"@syuilo/aiscript": "0.11.0", | ||||
| 		"@syuilo/aiscript": "0.11.1", | ||||
| 		"@types/bcryptjs": "2.4.2", | ||||
| 		"@types/bull": "3.14.0", | ||||
| 		"@types/cbor": "5.0.1", | ||||
| @@ -92,58 +92,57 @@ | ||||
| 		"@types/request-stats": "3.0.0", | ||||
| 		"@types/rimraf": "3.0.0", | ||||
| 		"@types/seedrandom": "2.4.28", | ||||
| 		"@types/sharp": "0.25.0", | ||||
| 		"@types/sharp": "0.26.0", | ||||
| 		"@types/sinonjs__fake-timers": "6.0.1", | ||||
| 		"@types/speakeasy": "2.0.5", | ||||
| 		"@types/tinycolor2": "1.4.2", | ||||
| 		"@types/tmp": "0.2.0", | ||||
| 		"@types/uuid": "8.3.0", | ||||
| 		"@types/web-push": "3.3.0", | ||||
| 		"@types/webpack": "4.41.22", | ||||
| 		"@types/webpack": "4.41.24", | ||||
| 		"@types/webpack-stream": "3.2.11", | ||||
| 		"@types/websocket": "1.0.1", | ||||
| 		"@types/ws": "7.2.7", | ||||
| 		"@typescript-eslint/parser": "4.4.0", | ||||
| 		"@vue/compiler-sfc": "3.0.0", | ||||
| 		"@typescript-eslint/parser": "4.6.1", | ||||
| 		"@vue/compiler-sfc": "3.0.2", | ||||
| 		"abort-controller": "3.0.0", | ||||
| 		"apexcharts": "3.22.0", | ||||
| 		"apexcharts": "3.22.1", | ||||
| 		"autobind-decorator": "2.4.0", | ||||
| 		"autosize": "4.0.2", | ||||
| 		"autwh": "0.1.0", | ||||
| 		"aws-sdk": "2.770.0", | ||||
| 		"aws-sdk": "2.787.0", | ||||
| 		"bcryptjs": "2.4.3", | ||||
| 		"blurhash": "1.1.3", | ||||
| 		"bull": "3.18.0", | ||||
| 		"bull": "3.18.1", | ||||
| 		"cafy": "15.2.1", | ||||
| 		"cbor": "5.1.0", | ||||
| 		"chalk": "4.1.0", | ||||
| 		"chart.js": "2.9.3", | ||||
| 		"chart.js": "2.9.4", | ||||
| 		"cli-highlight": "2.1.4", | ||||
| 		"commander": "4.1.1", | ||||
| 		"content-disposition": "0.5.3", | ||||
| 		"core-js": "3.6.5", | ||||
| 		"core-js": "3.7.0", | ||||
| 		"crc-32": "1.2.0", | ||||
| 		"css-loader": "4.3.0", | ||||
| 		"css-loader": "5.0.1", | ||||
| 		"cssnano": "4.1.10", | ||||
| 		"dateformat": "3.0.3", | ||||
| 		"deep-entries": "3.1.0", | ||||
| 		"diskusage": "1.1.3", | ||||
| 		"double-ended-queue": "2.1.0-0", | ||||
| 		"escape-regexp": "0.0.1", | ||||
| 		"eslint": "7.10.0", | ||||
| 		"eslint-plugin-vue": "7.0.1", | ||||
| 		"eslint": "7.12.1", | ||||
| 		"eslint-plugin-vue": "7.1.0", | ||||
| 		"eventemitter3": "4.0.7", | ||||
| 		"feed": "4.2.1", | ||||
| 		"fibers": "5.0.0", | ||||
| 		"file-type": "15.0.1", | ||||
| 		"file-type": "16.0.1", | ||||
| 		"fluent-ffmpeg": "2.1.2", | ||||
| 		"glob": "7.1.6", | ||||
| 		"got": "11.7.0", | ||||
| 		"got": "11.8.0", | ||||
| 		"gulp": "4.0.2", | ||||
| 		"gulp-rename": "2.0.0", | ||||
| 		"gulp-replace": "1.0.0", | ||||
| 		"gulp-sourcemaps": "2.6.5", | ||||
| 		"gulp-terser": "1.4.0", | ||||
| 		"gulp-tslint": "8.1.4", | ||||
| 		"gulp-typescript": "6.0.0-alpha.1", | ||||
| 		"hard-source-webpack-plugin": "0.13.1", | ||||
| @@ -159,8 +158,8 @@ | ||||
| 		"js-yaml": "3.14.0", | ||||
| 		"jsdom": "16.4.0", | ||||
| 		"json5": "2.1.3", | ||||
| 		"json5-loader": "4.0.0", | ||||
| 		"jsonld": "3.1.1", | ||||
| 		"json5-loader": "4.0.1", | ||||
| 		"jsonld": "3.2.0", | ||||
| 		"jsrsasign": "8.0.20", | ||||
| 		"katex": "0.12.0", | ||||
| 		"koa": "2.13.0", | ||||
| @@ -176,24 +175,24 @@ | ||||
| 		"lookup-dns-cache": "2.1.0", | ||||
| 		"markdown-it": "11.0.1", | ||||
| 		"markdown-it-anchor": "6.0.0", | ||||
| 		"mocha": "8.1.3", | ||||
| 		"mocha": "8.2.1", | ||||
| 		"moji": "0.5.1", | ||||
| 		"ms": "2.1.2", | ||||
| 		"multer": "1.4.2", | ||||
| 		"nested-property": "4.0.0", | ||||
| 		"node-fetch": "2.6.1", | ||||
| 		"nodemailer": "6.4.13", | ||||
| 		"nodemailer": "6.4.15", | ||||
| 		"object-assign-deep": "0.4.0", | ||||
| 		"os-utils": "0.0.14", | ||||
| 		"p-cancelable": "2.0.0", | ||||
| 		"parse5": "6.0.1", | ||||
| 		"parsimmon": "1.16.0", | ||||
| 		"pg": "8.4.1", | ||||
| 		"pg": "8.4.2", | ||||
| 		"portscanner": "2.2.0", | ||||
| 		"postcss": "8.1.1", | ||||
| 		"postcss-loader": "4.0.3", | ||||
| 		"prismjs": "1.21.0", | ||||
| 		"probe-image-size": "5.0.0", | ||||
| 		"postcss": "8.1.6", | ||||
| 		"postcss-loader": "4.0.4", | ||||
| 		"prismjs": "1.22.0", | ||||
| 		"probe-image-size": "6.0.0", | ||||
| 		"promise-limit": "2.7.0", | ||||
| 		"promise-sequential": "1.1.1", | ||||
| 		"pug": "2.0.4", | ||||
| @@ -202,8 +201,8 @@ | ||||
| 		"qrcode": "1.4.4", | ||||
| 		"random-seed": "0.3.0", | ||||
| 		"ratelimiter": "3.4.1", | ||||
| 		"re2": "1.15.5", | ||||
| 		"recaptcha-promise": "0.1.3", | ||||
| 		"re2": "1.15.8", | ||||
| 		"recaptcha-promise": "1.0.0", | ||||
| 		"reconnecting-websocket": "4.4.0", | ||||
| 		"redis": "3.0.2", | ||||
| 		"redis-lock": "0.1.4", | ||||
| @@ -215,47 +214,46 @@ | ||||
| 		"rimraf": "3.0.2", | ||||
| 		"rndstr": "1.0.0", | ||||
| 		"s-age": "1.1.2", | ||||
| 		"sass": "1.27.0", | ||||
| 		"sass-loader": "10.0.2", | ||||
| 		"sass": "1.29.0", | ||||
| 		"sass-loader": "10.0.5", | ||||
| 		"seedrandom": "3.0.5", | ||||
| 		"sharp": "0.26.1", | ||||
| 		"sharp": "0.26.2", | ||||
| 		"speakeasy": "2.0.0", | ||||
| 		"stringz": "2.1.0", | ||||
| 		"style-loader": "1.3.0", | ||||
| 		"style-loader": "2.0.0", | ||||
| 		"summaly": "2.4.0", | ||||
| 		"syslog-pro": "1.0.0", | ||||
| 		"systeminformation": "4.27.8", | ||||
| 		"systeminformation": "4.28.1", | ||||
| 		"syuilo-password-strength": "0.0.1", | ||||
| 		"textarea-caret": "3.1.0", | ||||
| 		"three": "0.117.1", | ||||
| 		"tinycolor2": "1.4.2", | ||||
| 		"tmp": "0.2.1", | ||||
| 		"ts-loader": "8.0.4", | ||||
| 		"ts-loader": "8.0.9", | ||||
| 		"ts-node": "9.0.0", | ||||
| 		"tslint": "6.1.3", | ||||
| 		"tslint-sonarts": "1.9.0", | ||||
| 		"typeorm": "0.2.28", | ||||
| 		"typescript": "4.0.3", | ||||
| 		"typeorm": "0.2.29", | ||||
| 		"typescript": "4.0.5", | ||||
| 		"ulid": "2.3.0", | ||||
| 		"url-loader": "4.1.0", | ||||
| 		"url-loader": "4.1.1", | ||||
| 		"uuid": "8.3.1", | ||||
| 		"v-debounce": "0.1.2", | ||||
| 		"vue": "3.0.1", | ||||
| 		"vue": "3.0.2", | ||||
| 		"vue-color": "2.7.1", | ||||
| 		"vue-draggable-next": "1.0.8", | ||||
| 		"vue-i18n": "9.0.0-beta.4", | ||||
| 		"vue-json-pretty": "1.7.0", | ||||
| 		"vue-loader": "16.0.0-beta.7", | ||||
| 		"vue-i18n": "9.0.0-beta.6", | ||||
| 		"vue-json-pretty": "1.7.1", | ||||
| 		"vue-loader": "16.0.0-beta.8", | ||||
| 		"vue-prism-editor": "1.2.2", | ||||
| 		"vue-router": "4.0.0-beta.13", | ||||
| 		"vue-router": "4.0.0-rc.2", | ||||
| 		"vue-style-loader": "4.1.2", | ||||
| 		"vue-svg-inline-loader-corejs3": "1.5.0", | ||||
| 		"vue-template-compiler": "2.6.12", | ||||
| 		"vuex": "4.0.0-beta.4", | ||||
| 		"vuex": "4.0.0-rc.1", | ||||
| 		"vuex-persistedstate": "3.1.0", | ||||
| 		"web-push": "3.4.4", | ||||
| 		"webpack": "5.1.3", | ||||
| 		"webpack-cli": "3.3.12", | ||||
| 		"webpack": "5.4.0", | ||||
| 		"webpack-cli": "4.2.0", | ||||
| 		"websocket": "1.0.32", | ||||
| 		"ws": "7.3.1", | ||||
| 		"xev": "2.0.1" | ||||
|   | ||||
							
								
								
									
										85
									
								
								src/client/components/abuse-report-window.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/client/components/abuse-report-window.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| <template> | ||||
| <XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> | ||||
| 	<template #header> | ||||
| 		<Fa :icon="faExclamationCircle" style="margin-right: 0.5em;"/> | ||||
| 		<i18n-t keypath="reportAbuseOf" tag="span"> | ||||
| 			<template #name> | ||||
| 				<b><MkAcct :user="user"/></b> | ||||
| 			</template> | ||||
| 		</i18n-t> | ||||
| 	</template> | ||||
| 	<div class="dpvffvvy"> | ||||
| 		<div class="_section"> | ||||
| 			<div class="_content"> | ||||
| 				<MkTextarea v-model:value="comment"> | ||||
| 					<span>{{ $t('details') }}</span> | ||||
| 					<template #desc>{{ $t('fillAbuseReportDescription') }}</template> | ||||
| 				</MkTextarea> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div class="_section"> | ||||
| 			<div class="_content"> | ||||
| 				<MkButton @click="send" primary full :disabled="comment.length === 0">{{ $t('send') }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </XWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
| import MkTextarea from '@/components/ui/textarea.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XWindow, | ||||
| 		MkTextarea, | ||||
| 		MkButton, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		user: { | ||||
| 			type: Object, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		initialComment: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['closed'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			comment: this.initialComment || '', | ||||
| 			faExclamationCircle, | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		send() { | ||||
| 			os.apiWithDialog('users/report-abuse', { | ||||
| 				userId: this.user.id, | ||||
| 				comment: this.comment, | ||||
| 			}, undefined, res => { | ||||
| 				os.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('abuseReported') | ||||
| 				}); | ||||
| 				this.$refs.window.close(); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .dpvffvvy { | ||||
| 	--section-padding: 16px; | ||||
| } | ||||
| </style> | ||||
| @@ -28,7 +28,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import { emojilist } from '../../misc/emojilist'; | ||||
| import contains from '@/scripts/contains'; | ||||
| import { twemojiSvgBase } from '../../misc/twemoji-base'; | ||||
| @@ -122,17 +122,13 @@ export default defineComponent({ | ||||
| 			users: [], | ||||
| 			hashtags: [], | ||||
| 			emojis: [], | ||||
| 			items: [], | ||||
| 			select: -1, | ||||
| 			emojilist, | ||||
| 			emojiDb: [] as EmojiDef[] | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		items(): HTMLCollection { | ||||
| 			return (this.$refs.suggests as Element).children; | ||||
| 		}, | ||||
|  | ||||
| 		useOsNativeEmojis(): boolean { | ||||
| 			return this.$store.state.device.useOsNativeEmojis; | ||||
| 		} | ||||
| @@ -148,6 +144,7 @@ export default defineComponent({ | ||||
|  | ||||
| 	updated() { | ||||
| 		this.setPosition(); | ||||
| 		this.items = (this.$refs.suggests as Element | undefined)?.children || []; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| @@ -180,7 +177,7 @@ export default defineComponent({ | ||||
|  | ||||
| 		emojiDefinitions.sort((a, b) => a.name.length - b.name.length); | ||||
|  | ||||
| 		this.emojiDb = emojiDefinitions.concat(emjdb); | ||||
| 		this.emojiDb = markRaw(emojiDefinitions.concat(emjdb)); | ||||
| 		//#endregion | ||||
|  | ||||
| 		this.textarea.addEventListener('keydown', this.onKeydown); | ||||
| @@ -371,6 +368,7 @@ export default defineComponent({ | ||||
|  | ||||
| 		selectNext() { | ||||
| 			if (++this.select >= this.items.length) this.select = 0; | ||||
| 			if (this.items.length === 0) this.select = -1; | ||||
| 			this.applySelect(); | ||||
| 		}, | ||||
|  | ||||
| @@ -384,8 +382,10 @@ export default defineComponent({ | ||||
| 				el.removeAttribute('data-selected'); | ||||
| 			} | ||||
|  | ||||
| 			this.items[this.select].setAttribute('data-selected', 'true'); | ||||
| 			(this.items[this.select] as any).focus(); | ||||
| 			if (this.select !== -1) { | ||||
| 				this.items[this.select].setAttribute('data-selected', 'true'); | ||||
| 				(this.items[this.select] as any).focus(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		chooseUser() { | ||||
|   | ||||
| @@ -2,9 +2,9 @@ | ||||
| <span class="eiwwqkts" :class="{ cat }" :title="acct(user)" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick"> | ||||
| 	<img class="inner" :src="url"/> | ||||
| </span> | ||||
| <router-link class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id"> | ||||
| <MkA class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id"> | ||||
| 	<img class="inner" :src="url"/> | ||||
| </router-link> | ||||
| </MkA> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <router-link :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> | ||||
| 	<div class="banner" v-if="channel.bannerUrl" :style="`background-image: url('${channel.bannerUrl}')`"> | ||||
| <MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> | ||||
| 	<div class="banner" :style="bannerStyle"> | ||||
| 		<div class="fade"></div> | ||||
| 		<div class="name"><Fa :icon="faSatelliteDish"/> {{ channel.name }}</div> | ||||
| 		<div class="status"> | ||||
| @@ -30,7 +30,7 @@ | ||||
| 			{{ $t('updatedAt') }}: <MkTime :time="channel.lastNotedAt"/> | ||||
| 		</span> | ||||
| 	</footer> | ||||
| </router-link> | ||||
| </MkA> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| @@ -45,6 +45,16 @@ export default defineComponent({ | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		bannerStyle() { | ||||
| 			if (this.channel.bannerUrl) { | ||||
| 				return { backgroundImage: `url(${this.channel.bannerUrl})` }; | ||||
| 			} else { | ||||
| 				return { backgroundColor: '#4c5e6d' }; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			faSatelliteDish, faUsers, faPencilAlt, | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<div class="icon" v-if="icon"> | ||||
| 			<Fa :icon="icon"/> | ||||
| 		</div> | ||||
| 		<div class="icon" v-else-if="!input && !select && !user" :class="type"> | ||||
| 		<div class="icon" v-else-if="!input && !select" :class="type"> | ||||
| 			<Fa :icon="faCheck" v-if="type === 'success'"/> | ||||
| 			<Fa :icon="faTimesCircle" v-if="type === 'error'"/> | ||||
| 			<Fa :icon="faExclamationTriangle" v-if="type === 'warning'"/> | ||||
| @@ -12,11 +12,9 @@ | ||||
| 			<Fa :icon="faQuestionCircle" v-if="type === 'question'"/> | ||||
| 			<Fa :icon="faSpinner" pulse v-if="type === 'waiting'"/> | ||||
| 		</div> | ||||
| 		<header v-if="title" v-html="title"></header> | ||||
| 		<header v-if="title == null && user">{{ $t('enterUsername') }}</header> | ||||
| 		<div class="body" v-if="text" v-html="text"></div> | ||||
| 		<header v-if="title"><Mfm :text="title"/></header> | ||||
| 		<div class="body" v-if="text"><Mfm :text="text"/></div> | ||||
| 		<MkInput v-if="input" v-model:value="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput> | ||||
| 		<MkInput v-if="user" v-model:value="userInputValue" autofocus @keydown="onInputKeydown"><template #prefix>@</template></MkInput> | ||||
| 		<MkSelect v-if="select" v-model:value="selectedValue" autofocus> | ||||
| 			<template v-if="select.items"> | ||||
| 				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> | ||||
| @@ -28,8 +26,8 @@ | ||||
| 			</template> | ||||
| 		</MkSelect> | ||||
| 		<div class="buttons" v-if="(showOkButton || showCancelButton) && !actions"> | ||||
| 			<MkButton inline @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user" :disabled="!canOk">{{ (showCancelButton || input || select || user) ? $t('ok') : $t('gotIt') }}</MkButton> | ||||
| 			<MkButton inline @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('cancel') }}</MkButton> | ||||
| 			<MkButton inline @click="ok" v-if="showOkButton" primary :autofocus="!input && !select">{{ (showCancelButton || input || select) ? $t('ok') : $t('gotIt') }}</MkButton> | ||||
| 			<MkButton inline @click="cancel" v-if="showCancelButton || input || select">{{ $t('cancel') }}</MkButton> | ||||
| 		</div> | ||||
| 		<div class="buttons" v-if="actions"> | ||||
| 			<MkButton v-for="action in actions" inline @click="() => { action.callback(); close(); }" :primary="action.primary" :key="action.text">{{ action.text }}</MkButton> | ||||
| @@ -46,8 +44,6 @@ import MkModal from '@/components/ui/modal.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkInput from '@/components/ui/input.vue'; | ||||
| import MkSelect from '@/components/ui/select.vue'; | ||||
| import parseAcct from '../../misc/acct/parse'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| @@ -77,9 +73,6 @@ export default defineComponent({ | ||||
| 		select: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		user: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		icon: { | ||||
| 			required: false | ||||
| 		}, | ||||
| @@ -105,28 +98,12 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			inputValue: this.input && this.input.default ? this.input.default : null, | ||||
| 			userInputValue: null, | ||||
| 			selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, | ||||
| 			canOk: true, | ||||
| 			faTimesCircle, faQuestionCircle, faSpinner, faInfoCircle, faExclamationTriangle, faCheck | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		userInputValue() { | ||||
| 			if (this.user) { | ||||
| 				os.api('users/show', parseAcct(this.userInputValue)).then(u => { | ||||
| 					this.canOk = u != null; | ||||
| 				}).catch(() => { | ||||
| 					this.canOk = false; | ||||
| 				}); | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		if (this.user) this.canOk = false; | ||||
|  | ||||
| 		document.addEventListener('keydown', this.onKeydown); | ||||
| 	}, | ||||
|  | ||||
| @@ -141,21 +118,13 @@ export default defineComponent({ | ||||
| 		}, | ||||
|  | ||||
| 		async ok() { | ||||
| 			if (!this.canOk) return; | ||||
| 			if (!this.showOkButton) return; | ||||
|  | ||||
| 			if (this.user) { | ||||
| 				const user = await os.api('users/show', parseAcct(this.userInputValue)); | ||||
| 				if (user) { | ||||
| 					this.done(false, user); | ||||
| 				} | ||||
| 			} else { | ||||
| 				const result = | ||||
| 					this.input ? this.inputValue : | ||||
| 					this.select ? this.selectedValue : | ||||
| 					true; | ||||
| 				this.done(false, result); | ||||
| 			} | ||||
| 			const result = | ||||
| 				this.input ? this.inputValue : | ||||
| 				this.select ? this.selectedValue : | ||||
| 				true; | ||||
| 			this.done(false, result); | ||||
| 		}, | ||||
|  | ||||
| 		cancel() { | ||||
| @@ -200,15 +169,15 @@ export default defineComponent({ | ||||
| 		font-size: 32px; | ||||
|  | ||||
| 		&.success { | ||||
| 			color: var(--accent); | ||||
| 			color: var(--success); | ||||
| 		} | ||||
|  | ||||
| 		&.error { | ||||
| 			color: #ec4137; | ||||
| 			color: var(--error); | ||||
| 		} | ||||
|  | ||||
| 		&.warning { | ||||
| 			color: #ecb637; | ||||
| 			color: var(--warn); | ||||
| 		} | ||||
|  | ||||
| 		> * { | ||||
|   | ||||
							
								
								
									
										70
									
								
								src/client/components/drive-select-dialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/client/components/drive-select-dialog.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| <template> | ||||
| <XModalWindow ref="dialog" | ||||
| 	:width="800" | ||||
| 	:height="500" | ||||
| 	:with-ok-button="true" | ||||
| 	:ok-button-disabled="(type === 'file') && (selected.length === 0)" | ||||
| 	@click="cancel()" | ||||
| 	@close="cancel()" | ||||
| 	@ok="ok()" | ||||
| 	@closed="$emit('closed')" | ||||
| > | ||||
| 	<template #header> | ||||
| 		{{ multiple ? ((type === 'file') ? $t('selectFiles') : $t('selectFolders')) : ((type === 'file') ? $t('selectFile') : $t('selectFolder')) }} | ||||
| 		<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> | ||||
| 	</template> | ||||
| 	<XDrive :multiple="multiple" @changeSelection="onChangeSelection" @selected="ok()" :select="type"/> | ||||
| </XModalWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import XDrive from './drive.vue'; | ||||
| import XModalWindow from '@/components/ui/modal-window.vue'; | ||||
| import number from '@/filters/number'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XDrive, | ||||
| 		XModalWindow, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 			default: 'file' | ||||
| 		}, | ||||
| 		multiple: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['done', 'closed'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			selected: [] | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		ok() { | ||||
| 			this.$emit('done', this.selected); | ||||
| 			this.$refs.dialog.close(); | ||||
| 		}, | ||||
|  | ||||
| 		cancel() { | ||||
| 			this.$emit('done'); | ||||
| 			this.$refs.dialog.close(); | ||||
| 		}, | ||||
|  | ||||
| 		onChangeSelection(xs) { | ||||
| 			this.selected = xs; | ||||
| 		}, | ||||
|  | ||||
| 		number | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -1,72 +1,44 @@ | ||||
| <template> | ||||
| <XModalWindow ref="dialog" | ||||
| 	:width="800" | ||||
| 	:height="500" | ||||
| 	:with-ok-button="true" | ||||
| 	:ok-button-disabled="(type === 'file') && (selected.length === 0)" | ||||
| 	@click="cancel()" | ||||
| 	@close="cancel()" | ||||
| 	@ok="ok()" | ||||
| <XWindow ref="window" | ||||
| 	:initial-width="800" | ||||
| 	:initial-height="500" | ||||
| 	:can-resize="true" | ||||
| 	@closed="$emit('closed')" | ||||
| > | ||||
| 	<template #header> | ||||
| 		{{ multiple ? ((type === 'file') ? $t('selectFiles') : $t('selectFolders')) : ((type === 'file') ? $t('selectFile') : $t('selectFolder')) }} | ||||
| 		<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> | ||||
| 		{{ $t('drive') }} | ||||
| 	</template> | ||||
| 	<div> | ||||
| 		<XDrive :multiple="multiple" @changeSelection="onChangeSelection" @selected="ok()" :select="type"/> | ||||
| 	</div> | ||||
| </XModalWindow> | ||||
| 	<XDrive :initial-folder="initialFolder"/> | ||||
| </XWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import XDrive from './drive.vue'; | ||||
| import XModalWindow from '@/components/ui/modal-window.vue'; | ||||
| import number from '@/filters/number'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XDrive, | ||||
| 		XModalWindow, | ||||
| 		XWindow, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 			default: 'file' | ||||
| 		initialFolder: { | ||||
| 			type: Object, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		multiple: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['done', 'closed'], | ||||
| 	emits: ['closed'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			selected: [] | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		ok() { | ||||
| 			this.$emit('done', this.selected); | ||||
| 			this.$refs.dialog.close(); | ||||
| 		}, | ||||
|  | ||||
| 		cancel() { | ||||
| 			this.$emit('done'); | ||||
| 			this.$refs.dialog.close(); | ||||
| 		}, | ||||
|  | ||||
| 		onChangeSelection(xs) { | ||||
| 			this.selected = xs; | ||||
| 		}, | ||||
|  | ||||
| 		number | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| <div class="rghtznwe" | ||||
| 	:class="{ draghover }" | ||||
| 	@click="onClick" | ||||
| 	@contextmenu.stop="onContextmenu" | ||||
| 	@mouseover="onMouseover" | ||||
| 	@mouseout="onMouseout" | ||||
| 	@dragover.prevent.stop="onDragover" | ||||
| @@ -27,8 +28,9 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faFolder, faFolderOpen } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { faFolder, faFolderOpen, faTrashAlt, faWindowRestore } from '@fortawesome/free-regular-svg-icons'; | ||||
| import * as os from '@/os'; | ||||
| import { faICursor } from '@fortawesome/free-solid-svg-icons'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| @@ -241,6 +243,28 @@ export default defineComponent({ | ||||
| 				value: this.folder.id | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		onContextmenu(e) { | ||||
| 			os.contextMenu([{ | ||||
| 				text: this.$t('openInWindow'), | ||||
| 				icon: faWindowRestore, | ||||
| 				action: () => { | ||||
| 					os.popup(import('./drive-window.vue'), { | ||||
| 						initialFolder: this.folder | ||||
| 					}, { | ||||
| 					}, 'closed'); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				text: this.$t('rename'), | ||||
| 				icon: faICursor, | ||||
| 				action: this.rename | ||||
| 			}, null, { | ||||
| 				text: this.$t('delete'), | ||||
| 				icon: faTrashAlt, | ||||
| 				danger: true, | ||||
| 				action: this.deleteFolder | ||||
| 			}], e); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -64,7 +64,7 @@ export default defineComponent({ | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		initFolder: { | ||||
| 		initialFolder: { | ||||
| 			type: Object, | ||||
| 			required: false | ||||
| 		}, | ||||
| @@ -151,8 +151,8 @@ export default defineComponent({ | ||||
| 		this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); | ||||
| 		this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted); | ||||
|  | ||||
| 		if (this.initFolder) { | ||||
| 			this.move(this.initFolder); | ||||
| 		if (this.initialFolder) { | ||||
| 			this.move(this.initialFolder); | ||||
| 		} else { | ||||
| 			this.fetch(); | ||||
| 		} | ||||
| @@ -639,6 +639,10 @@ export default defineComponent({ | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .yfudmmck { | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	height: 100%; | ||||
|  | ||||
| 	> nav { | ||||
| 		display: block; | ||||
| 		z-index: 2; | ||||
| @@ -698,6 +702,7 @@ export default defineComponent({ | ||||
| 	} | ||||
|  | ||||
| 	> .main { | ||||
| 		flex: 1; | ||||
| 		overflow: auto; | ||||
|  | ||||
| 		&, * { | ||||
|   | ||||
| @@ -1,75 +1,104 @@ | ||||
| <template> | ||||
| <MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> | ||||
| 	<div class="omfetrab _popup"> | ||||
| 		<header> | ||||
| 			<button v-for="(category, i) in categories" | ||||
| 				class="_button" | ||||
| 				@click="go(category)" | ||||
| 				:class="{ active: category.isActive }" | ||||
| 				:key="i" | ||||
| 			> | ||||
| 				<Fa :icon="category.icon" fixed-width/> | ||||
| 			</button> | ||||
| 		</header> | ||||
|  | ||||
| 	<div class="omfetrab _popup" :class="{ compact }"> | ||||
| 		<input ref="search" class="search" :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$t('search')" @paste.stop="paste" @keyup.enter="done()"> | ||||
| 		<div class="emojis"> | ||||
| 			<template v-if="categories[0].isActive"> | ||||
| 				<header class="category"><Fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header> | ||||
| 				<div class="list"> | ||||
| 					<button v-for="emoji in ($store.state.device.recentEmojis || [])" | ||||
| 			<section class="result"> | ||||
| 				<div v-if="searchResultCustom.length > 0"> | ||||
| 					<button v-for="emoji in searchResultCustom" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji)" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> | ||||
| 						<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
|  | ||||
| 				<header class="category"><Fa :icon="faAsterisk" fixed-width/> {{ $t('customEmojis') }}</header> | ||||
| 			</template> | ||||
|  | ||||
| 			<template v-if="categories.find(x => x.isActive).name"> | ||||
| 				<div class="list"> | ||||
| 					<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)" | ||||
| 				<div v-if="searchResultUnicode.length > 0"> | ||||
| 					<button v-for="emoji in searchResultUnicode" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji)" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 						tabindex="0" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji.char"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
|  | ||||
| 			<div class="index"> | ||||
| 				<section v-if="showPinned"> | ||||
| 					<div> | ||||
| 						<button v-for="emoji in pinned" | ||||
| 							class="_button" | ||||
| 							@click="chosen(emoji, $event)" | ||||
| 							tabindex="0" | ||||
| 						> | ||||
| 							<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</section> | ||||
|  | ||||
| 				<section> | ||||
| 					<header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $t('recentUsed') }}</header> | ||||
| 					<div> | ||||
| 						<button v-for="emoji in $store.state.device.recentlyUsedEmojis" | ||||
| 							class="_button" | ||||
| 							@click="chosen(emoji, $event)" | ||||
| 							:key="emoji" | ||||
| 						> | ||||
| 							<MkEmoji :emoji="emoji" :normal="true"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</section> | ||||
|  | ||||
| 				<div class="arrow"><Fa :icon="faChevronDown"/></div> | ||||
| 			</div> | ||||
|  | ||||
| 			<section v-for="category in customEmojiCategories" :key="'custom:' + category" class="custom"> | ||||
| 				<header class="_acrylic" v-appear="() => visibleCategories[category] = true">{{ category || $t('other') }}</header> | ||||
| 				<div v-if="visibleCategories[category]"> | ||||
| 					<button v-for="emoji in customEmojis.filter(e => e.category === category)" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 					> | ||||
| 						<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</section> | ||||
|  | ||||
| 			<section v-for="category in categories" :key="category.name" class="unicode"> | ||||
| 				<header class="_acrylic" v-appear="() => category.isActive = true"><Fa :icon="category.icon" fixed-width/> {{ category.name }}</header> | ||||
| 				<div v-if="category.isActive"> | ||||
| 					<button v-for="emoji in emojilist.filter(e => e.category === category.name)" | ||||
| 						class="_button" | ||||
| 						:title="emoji.name" | ||||
| 						@click="chosen(emoji, $event)" | ||||
| 						:key="emoji.name" | ||||
| 					> | ||||
| 						<MkEmoji :emoji="emoji.char"/> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 			<template v-else> | ||||
| 				<div v-for="(key, i) in Object.keys(customEmojis)" :key="i"> | ||||
| 					<header class="sub" v-if="key">{{ key }}</header> | ||||
| 					<div class="list"> | ||||
| 						<button v-for="emoji in customEmojis[key]" | ||||
| 							class="_button" | ||||
| 							:title="emoji.name" | ||||
| 							@click="chosen(emoji)" | ||||
| 							:key="emoji.name" | ||||
| 						> | ||||
| 							<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 			</section> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </MkModal> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import { emojilist } from '../../misc/emojilist'; | ||||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||
| import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { groupByX } from '../../prelude/array'; | ||||
| import MkModal from '@/components/ui/modal.vue'; | ||||
| import Particle from '@/components/particle.vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| @@ -80,20 +109,30 @@ export default defineComponent({ | ||||
| 		src: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 		showPinned: { | ||||
| 			required: false, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		compact: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['done', 'closed'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			emojilist, | ||||
| 			emojilist: markRaw(emojilist), | ||||
| 			getStaticImageUrl, | ||||
| 			customEmojis: {}, | ||||
| 			faGlobe, faHistory, | ||||
| 			pinned: this.$store.state.settings.reactions, | ||||
| 			customEmojiCategories: this.$store.getters['instance/emojiCategories'], | ||||
| 			customEmojis: this.$store.state.instance.meta.emojis, | ||||
| 			visibleCategories: {}, | ||||
| 			q: null, | ||||
| 			searchResultCustom: [], | ||||
| 			searchResultUnicode: [], | ||||
| 			faGlobe, faClock, faChevronDown, | ||||
| 			categories: [{ | ||||
| 				icon: faAsterisk, | ||||
| 				isActive: true | ||||
| 			}, { | ||||
| 				name: 'face', | ||||
| 				icon: faLaugh, | ||||
| 				isActive: false | ||||
| @@ -134,38 +173,212 @@ export default defineComponent({ | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		let local = this.$store.state.instance.meta.emojis; | ||||
| 		local = groupByX(local, (x: any) => x.category || ''); | ||||
| 		this.customEmojis = local; | ||||
| 	watch: { | ||||
| 		q() { | ||||
| 			if (this.q == null || this.q === '') { | ||||
| 				this.searchResultCustom = []; | ||||
| 				this.searchResultUnicode = []; | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			const q = this.q.replace(/:/g, ''); | ||||
|  | ||||
| 			const searchCustom = () => { | ||||
| 				const max = 8; | ||||
| 				const emojis = this.customEmojis; | ||||
| 				const matches = new Set(); | ||||
|  | ||||
| 				const exactMatch = emojis.find(e => e.name === q); | ||||
| 				if (exactMatch) matches.add(exactMatch); | ||||
|  | ||||
| 				if (q.includes(' ')) { // AND検索 | ||||
| 					const keywords = q.split(' '); | ||||
|  | ||||
| 					// 名前にキーワードが含まれている | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (keywords.every(keyword => emoji.name.includes(keyword))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					// 名前またはエイリアスにキーワードが含まれている | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 				} else { | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.name.startsWith(q)) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.aliases.some(alias => alias.startsWith(q))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.name.includes(q)) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.aliases.some(alias => alias.includes(q))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				return matches; | ||||
| 			}; | ||||
|  | ||||
| 			const searchUnicode = () => { | ||||
| 				const max = 8; | ||||
| 				const emojis = this.emojilist; | ||||
| 				const matches = new Set(); | ||||
|  | ||||
| 				const exactMatch = emojis.find(e => e.name === q); | ||||
| 				if (exactMatch) matches.add(exactMatch); | ||||
|  | ||||
| 				if (q.includes(' ')) { // AND検索 | ||||
| 					const keywords = q.split(' '); | ||||
|  | ||||
| 					// 名前にキーワードが含まれている | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (keywords.every(keyword => emoji.name.includes(keyword))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					// 名前またはエイリアスにキーワードが含まれている | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 				} else { | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.name.startsWith(q)) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.keywords.some(keyword => keyword.startsWith(q))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.name.includes(q)) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (matches.size >= max) return matches; | ||||
|  | ||||
| 					for (const emoji of emojis) { | ||||
| 						if (emoji.keywords.some(keyword => keyword.includes(q))) { | ||||
| 							matches.add(emoji); | ||||
| 							if (matches.size >= max) break; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				return matches; | ||||
| 			}; | ||||
|  | ||||
| 			this.searchResultCustom = Array.from(searchCustom()); | ||||
| 			this.searchResultUnicode = Array.from(searchUnicode()); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		const isIos = navigator.userAgent.includes('WebKit') && !navigator.userAgent.includes('Chrome'); | ||||
| 		if (!isIos) { | ||||
| 			this.$refs.search.focus({ | ||||
| 				preventScroll: true | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		go(category: any) { | ||||
| 			this.goCategory(category.name); | ||||
| 		getKey(emoji: any) { | ||||
| 			return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); | ||||
| 		}, | ||||
|  | ||||
| 		goCategory(name: string) { | ||||
| 			let matched = false; | ||||
| 			for (const c of this.categories) { | ||||
| 				c.isActive = c.name === name; | ||||
| 				if (c.isActive) { | ||||
| 					matched = true; | ||||
| 				} | ||||
| 		chosen(emoji: any, ev) { | ||||
| 			if (ev) { | ||||
| 				const el = ev.currentTarget || ev.target; | ||||
| 				const rect = el.getBoundingClientRect(); | ||||
| 				const x = rect.left + (el.clientWidth / 2); | ||||
| 				const y = rect.top + (el.clientHeight / 2); | ||||
| 				os.popup(Particle, { x, y }, {}, 'end'); | ||||
| 			} | ||||
| 			if (!matched) { | ||||
| 				this.categories[0].isActive = true; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		chosen(emoji: any) { | ||||
| 			const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`; | ||||
| 			let recents = this.$store.state.device.recentEmojis || []; | ||||
| 			recents = recents.filter((e: any) => getKey(e) !== getKey(emoji)); | ||||
| 			recents.unshift(emoji) | ||||
| 			this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) }); | ||||
| 			this.$emit('done', getKey(emoji)); | ||||
| 			const key = this.getKey(emoji); | ||||
| 			this.$emit('done', key); | ||||
| 			this.$refs.modal.close(); | ||||
|  | ||||
| 			// 最近使った絵文字更新 | ||||
| 			if (!this.pinned.includes(key)) { | ||||
| 				let recents = this.$store.state.device.recentlyUsedEmojis; | ||||
| 				recents = recents.filter((e: any) => e !== key); | ||||
| 				recents.unshift(key); | ||||
| 				this.$store.commit('device/set', { key: 'recentlyUsedEmojis', value: recents.splice(0, 16) }); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		paste(event) { | ||||
| 			const paste = (event.clipboardData || window.clipboardData).getData('text'); | ||||
| 			if (this.done(paste)) { | ||||
| 				event.preventDefault(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		done(query) { | ||||
| 			if (query == null) query = this.q; | ||||
| 			if (query == null) return; | ||||
| 			const q = query.replace(/:/g, ''); | ||||
| 			const exactMatchCustom = this.customEmojis.find(e => e.name === q); | ||||
| 			if (exactMatchCustom) { | ||||
| 				this.chosen(exactMatchCustom); | ||||
| 				return true; | ||||
| 			} | ||||
| 			const exactMatchUnicode = this.emojilist.find(e => e.char === q || e.name === q); | ||||
| 			if (exactMatchUnicode) { | ||||
| 				this.chosen(exactMatchUnicode); | ||||
| 				return true; | ||||
| 			} | ||||
| 			if (this.searchResultCustom.length > 0) { | ||||
| 				this.chosen(this.searchResultCustom[0]); | ||||
| 				return true; | ||||
| 			} | ||||
| 			if (this.searchResultUnicode.length > 0) { | ||||
| 				this.chosen(this.searchResultUnicode[0]); | ||||
| 				return true; | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| @@ -173,87 +386,123 @@ export default defineComponent({ | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .omfetrab { | ||||
| 	width: 350px; | ||||
| 	$eachSize: 40px; | ||||
| 	$pad: 8px; | ||||
|  | ||||
| 	> header { | ||||
| 		display: flex; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	width: ($eachSize * 7) + ($pad * 2); | ||||
| 	contain: content; | ||||
| 	--height: 300px; | ||||
|  | ||||
| 		> button { | ||||
| 			flex: 1; | ||||
| 			padding: 10px 0; | ||||
| 			font-size: 16px; | ||||
| 			transition: color 0.2s ease; | ||||
| 	&.compact { | ||||
| 		width: ($eachSize * 5) + ($pad * 2); | ||||
| 		--height: 210px; | ||||
| 	} | ||||
|  | ||||
| 			&:hover { | ||||
| 				color: var(--textHighlighted); | ||||
| 				transition: color 0s; | ||||
| 			} | ||||
| 	> .search { | ||||
| 		width: 100%; | ||||
| 		padding: 12px; | ||||
| 		box-sizing: border-box; | ||||
| 		font-size: 1em; | ||||
| 		outline: none; | ||||
| 		border: none; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
|  | ||||
| 			&.active { | ||||
| 				color: var(--accent); | ||||
| 				transition: color 0s; | ||||
| 			} | ||||
| 		&:not(.filled) { | ||||
| 			order: 1; | ||||
| 			z-index: 2; | ||||
| 			box-shadow: 0px -1px 0 0px var(--divider); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .emojis { | ||||
| 		height: 300px; | ||||
| 		height: var(--height); | ||||
| 		overflow-y: auto; | ||||
| 		overflow-x: hidden; | ||||
|  | ||||
| 		> header.category { | ||||
| 			position: sticky; | ||||
| 			top: 0; | ||||
| 			left: 0; | ||||
| 			z-index: 1; | ||||
| 			padding: 8px; | ||||
| 			background: var(--panel); | ||||
| 			font-size: 12px; | ||||
| 		scrollbar-width: none; | ||||
|  | ||||
| 		&::-webkit-scrollbar { | ||||
| 			display: none; | ||||
| 		} | ||||
|  | ||||
| 		header.sub { | ||||
| 			padding: 4px 8px; | ||||
| 			font-size: 12px; | ||||
| 		} | ||||
|  | ||||
| 		div.list { | ||||
| 			display: grid; | ||||
| 			grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; | ||||
| 			gap: 4px; | ||||
| 			padding: 8px; | ||||
|  | ||||
| 			> button { | ||||
| 				position: relative; | ||||
| 				padding: 0; | ||||
| 		> .index { | ||||
| 			min-height: var(--height); | ||||
| 			position: relative; | ||||
| 			border-bottom: solid 1px var(--divider); | ||||
| 				 | ||||
| 			> .arrow { | ||||
| 				position: absolute; | ||||
| 				bottom: 0; | ||||
| 				left: 0; | ||||
| 				width: 100%; | ||||
| 				padding: 16px 0; | ||||
| 				text-align: center; | ||||
| 				opacity: 0.5; | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 				&:before { | ||||
| 					content: ''; | ||||
| 					display: block; | ||||
| 					width: 1px; | ||||
| 					height: 0; | ||||
| 					padding-bottom: 100%; | ||||
| 				} | ||||
| 		section { | ||||
| 			> header { | ||||
| 				position: sticky; | ||||
| 				top: 0; | ||||
| 				left: 0; | ||||
| 				z-index: 1; | ||||
| 				padding: 8px; | ||||
| 				font-size: 12px; | ||||
| 			} | ||||
|  | ||||
| 			> div { | ||||
| 				padding: $pad; | ||||
|  | ||||
| 				> button { | ||||
| 					position: relative; | ||||
| 					padding: 0; | ||||
| 					width: $eachSize; | ||||
| 					height: $eachSize; | ||||
| 					border-radius: 4px; | ||||
|  | ||||
| 					&:focus { | ||||
| 						outline: solid 2px var(--focus); | ||||
| 						z-index: 1; | ||||
| 					} | ||||
|  | ||||
| 					&:hover { | ||||
| 						background: rgba(0, 0, 0, 0.05); | ||||
| 					} | ||||
|  | ||||
| 					&:active { | ||||
| 						background: var(--accent); | ||||
| 						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); | ||||
| 					} | ||||
|  | ||||
| 				&:hover { | ||||
| 					> * { | ||||
| 						transform: scale(1.2); | ||||
| 						transition: transform 0s; | ||||
| 						font-size: 24px; | ||||
| 						height: 1.25em; | ||||
| 						vertical-align: -.25em; | ||||
| 						pointer-events: none; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 				> * { | ||||
| 					position: absolute; | ||||
| 					top: 0; | ||||
| 					left: 0; | ||||
| 					width: 100%; | ||||
| 					height: 100%; | ||||
| 					object-fit: contain; | ||||
| 					font-size: 28px; | ||||
| 					transition: transform 0.2s ease; | ||||
| 					pointer-events: none; | ||||
| 			&.result { | ||||
| 				border-bottom: solid 1px var(--divider); | ||||
|  | ||||
| 				&:empty { | ||||
| 					display: none; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			&.unicode { | ||||
| 				min-height: 384px; | ||||
| 			} | ||||
|  | ||||
| 			&.custom { | ||||
| 				min-height: 64px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -2,24 +2,19 @@ | ||||
| <img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt"/> | ||||
| <img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt"/> | ||||
| <span v-else-if="char && useOsNativeEmojis">{{ char }}</span> | ||||
| <span v-else>:{{ name }}:</span> | ||||
| <span v-else>{{ emoji }}</span> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||
| import { twemojiSvgBase } from '../../misc/twemoji-base'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		name: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		emoji: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 			required: true | ||||
| 		}, | ||||
| 		normal: { | ||||
| 			type: Boolean, | ||||
| @@ -50,6 +45,10 @@ export default defineComponent({ | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		isCustom(): boolean { | ||||
| 			return this.emoji.startsWith(':'); | ||||
| 		}, | ||||
|  | ||||
| 		alt(): string { | ||||
| 			return this.customEmoji ? `:${this.customEmoji.name}:` : this.char; | ||||
| 		}, | ||||
| @@ -69,8 +68,8 @@ export default defineComponent({ | ||||
| 	watch: { | ||||
| 		ce: { | ||||
| 			handler() { | ||||
| 				if (this.name) { | ||||
| 					const customEmoji = this.ce.find(x => x.name == this.name); | ||||
| 				if (this.isCustom) { | ||||
| 					const customEmoji = this.ce.find(x => x.name === this.emoji.substr(1, this.emoji.length - 2)); | ||||
| 					if (customEmoji) { | ||||
| 						this.customEmoji = customEmoji; | ||||
| 						this.url = this.$store.state.device.disableShowingAnimatedImages | ||||
| @@ -84,7 +83,7 @@ export default defineComponent({ | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		if (!this.name) { | ||||
| 		if (!this.isCustom) { | ||||
| 			this.char = this.emoji; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -1,102 +0,0 @@ | ||||
| <template> | ||||
| <div class="eqryymyo"> | ||||
| 	<div class="header"> | ||||
| 		<time ref="time" class="_ghost"> | ||||
| 			<span class="yyyymmdd">{{ yyyy }}/{{ mm }}/{{ dd }}</span> | ||||
| 			<br> | ||||
| 			<span class="hhnn">{{ hh }}<span :style="{ visibility: now.getSeconds() % 2 == 0 ? 'visible' : 'hidden' }">:</span>{{ nn }}</span> | ||||
| 		</time> | ||||
| 	</div> | ||||
| 	<div class="content _panel _ghost"> | ||||
| 		<MkClock/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import MkClock from './analog-clock.vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkClock | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			now: new Date(), | ||||
| 			clock: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		yyyy(): number { | ||||
| 			return this.now.getFullYear(); | ||||
| 		}, | ||||
| 		mm(): string { | ||||
| 			return ('0' + (this.now.getMonth() + 1)).slice(-2); | ||||
| 		}, | ||||
| 		dd(): string { | ||||
| 			return ('0' + this.now.getDate()).slice(-2); | ||||
| 		}, | ||||
| 		hh(): string { | ||||
| 			return ('0' + this.now.getHours()).slice(-2); | ||||
| 		}, | ||||
| 		nn(): string { | ||||
| 			return ('0' + this.now.getMinutes()).slice(-2); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.tick(); | ||||
| 		this.clock = setInterval(this.tick, 1000); | ||||
| 	}, | ||||
| 	beforeUnmount() { | ||||
| 		clearInterval(this.clock); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		tick() { | ||||
| 			this.now = new Date(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .eqryymyo { | ||||
| 	display: inline-block; | ||||
| 	overflow: visible; | ||||
|  | ||||
| 	> .header { | ||||
| 		padding: 0 12px; | ||||
| 		padding-top: 4px; | ||||
| 		text-align: center; | ||||
| 		font-size: 12px; | ||||
| 		font-family: Lucida Console, Courier, monospace; | ||||
|  | ||||
| 		&:hover + .content { | ||||
| 			opacity: 1; | ||||
| 		} | ||||
|  | ||||
| 		> time { | ||||
| 			display: table-cell; | ||||
| 			vertical-align: middle; | ||||
| 			height: 48px; | ||||
|  | ||||
| 			> .yyyymmdd { | ||||
| 				opacity: 0.7; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .content { | ||||
| 		opacity: 0; | ||||
| 		display: block; | ||||
| 		position: absolute; | ||||
| 		top: auto; | ||||
| 		right: 0; | ||||
| 		margin: 16px 0 0 0; | ||||
| 		padding: 16px; | ||||
| 		width: 230px; | ||||
| 		transition: opacity 0.2s ease; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -41,10 +41,13 @@ export default defineComponent({ | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .xubzgfga { | ||||
| 	max-width: 1024px; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	height: 100%; | ||||
|  | ||||
| 	> header, | ||||
| 	> footer { | ||||
| 		align-self: center; | ||||
| 		display: inline-block; | ||||
| 		padding: 6px 9px; | ||||
| 		font-size: 90%; | ||||
| @@ -60,7 +63,10 @@ export default defineComponent({ | ||||
|  | ||||
| 	> img { | ||||
| 		display: block; | ||||
| 		max-width: 100%; | ||||
| 		flex: 1; | ||||
| 		min-height: 0; | ||||
| 		object-fit: contain; | ||||
| 		width: 100%; | ||||
| 		cursor: zoom-out; | ||||
| 		image-orientation: from-image; | ||||
| 	} | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { App } from 'vue'; | ||||
|  | ||||
| import mfm from './misskey-flavored-markdown.vue'; | ||||
| import a from './ui/a.vue'; | ||||
| import acct from './acct.vue'; | ||||
| import avatar from './avatar.vue'; | ||||
| import emoji from './emoji.vue'; | ||||
| @@ -10,10 +11,10 @@ import time from './time.vue'; | ||||
| import url from './url.vue'; | ||||
| import loading from './loading.vue'; | ||||
| import error from './error.vue'; | ||||
| import streamIndicator from './stream-indicator.vue'; | ||||
|  | ||||
| export default function(app: App) { | ||||
| 	app.component('Mfm', mfm); | ||||
| 	app.component('MkA', a); | ||||
| 	app.component('MkAcct', acct); | ||||
| 	app.component('MkAvatar', avatar); | ||||
| 	app.component('MkEmoji', emoji); | ||||
| @@ -23,5 +24,4 @@ export default function(app: App) { | ||||
| 	app.component('MkUrl', url); | ||||
| 	app.component('MkLoading', loading); | ||||
| 	app.component('MkError', error); | ||||
| 	app.component('StreamIndicator', streamIndicator); | ||||
| } | ||||
|   | ||||
							
								
								
									
										62
									
								
								src/client/components/instance-ticker.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/client/components/instance-ticker.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| <template> | ||||
| <div class="hpaizdrt" :style="bg"> | ||||
| 	<img v-if="info.faviconUrl" class="icon" :src="info.faviconUrl"/> | ||||
| 	<span class="name">{{ info.name }}</span> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { instanceName } from '@/config'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		instance: { | ||||
| 			type: Object, | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			info: this.instance || { | ||||
| 				faviconUrl: '/favicon.ico', | ||||
| 				name: instanceName, | ||||
| 				themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		bg(): any { | ||||
| 			const themeColor = this.info.themeColor || '#777777'; | ||||
| 			return { | ||||
| 				background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .hpaizdrt { | ||||
| 	$height: 1.1rem; | ||||
|  | ||||
| 	height: $height; | ||||
| 	border-radius: 4px 0 0 4px; | ||||
| 	overflow: hidden; | ||||
| 	color: #fff; | ||||
|  | ||||
| 	> .icon { | ||||
| 		height: 100%; | ||||
| 	} | ||||
|  | ||||
| 	> .name { | ||||
| 		margin-left: 4px; | ||||
| 		line-height: $height; | ||||
| 		font-size: 0.9em; | ||||
| 		vertical-align: top; | ||||
| 		font-weight: bold; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <component :is="self ? 'router-link' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| <component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| 	@mouseover="onMouseover" | ||||
| 	@mouseleave="onMouseleave" | ||||
| 	:title="url" | ||||
| @@ -46,7 +46,7 @@ export default defineComponent({ | ||||
| 			if (!document.body.contains(this.$el)) return; | ||||
| 			if (this.close) return; | ||||
|  | ||||
| 			const { dispose } = os.popup(await import('@/components/url-preview-popup.vue'), { | ||||
| 			const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), { | ||||
| 				url: this.url, | ||||
| 				source: this.$el | ||||
| 			}); | ||||
|   | ||||
| @@ -32,8 +32,6 @@ export default defineComponent({ | ||||
| 		raw: { | ||||
| 			default: false | ||||
| 		}, | ||||
| 		// specify the parent element | ||||
| 		parentElement: {} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| @@ -66,7 +64,7 @@ export default defineComponent({ | ||||
|  | ||||
| 				if (this.$refs.gridOuter) { | ||||
| 					let height = 287; | ||||
| 					const parent = this.parentElement || this.$parent.$el; | ||||
| 					const parent = this.$parent.$el; | ||||
|  | ||||
| 					if (this.$refs.gridOuter.clientHeight) { | ||||
| 						height = this.$refs.gridOuter.clientHeight; | ||||
| @@ -81,11 +79,6 @@ export default defineComponent({ | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		parentElement() { | ||||
| 			this.size(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <template> | ||||
| <router-link class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')"> | ||||
| <MkA class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')"> | ||||
| 	<span class="me" v-if="isMe">{{ $t('you') }}</span> | ||||
| 	<span class="main"> | ||||
| 		<span class="username">@{{ username }}</span> | ||||
| 		<span class="host" v-if="(host != localHost) || $store.state.settings.showFullAcct">@{{ toUnicode(host) }}</span> | ||||
| 	</span> | ||||
| </router-link> | ||||
| </MkA> | ||||
| <a class="ldlomzub" :href="url" target="_blank" rel="noopener" v-else> | ||||
| 	<span class="main"> | ||||
| 		<span class="username">@{{ username }}</span> | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import { concat } from '../../prelude/array'; | ||||
| import MkFormula from './formula.vue'; | ||||
| import MkCode from './code.vue'; | ||||
| import MkGoogle from './google.vue'; | ||||
| import MkA from './ui/a.vue'; | ||||
| import { host } from '@/config'; | ||||
| import { RouterLink } from 'vue-router'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| @@ -77,10 +77,66 @@ export default defineComponent({ | ||||
| 					}, genEl(token.children)); | ||||
| 				} | ||||
|  | ||||
| 				case 'big': { | ||||
| 					return h('strong', { | ||||
| 						style: `display: inline-block; font-size: 150%;` + (this.$store.state.device.animatedMfm ? 'animation: anime-tada 1s linear infinite both;' : ''), | ||||
| 					}, genEl(token.children)); | ||||
| 				case 'fn': { | ||||
| 					// TODO: CSSを文字列で組み立てていくと token.node.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる | ||||
| 					let style; | ||||
| 					switch (token.node.props.name) { | ||||
| 						case 'tada': { | ||||
| 							style = `font-size: 150%;` + (this.$store.state.device.animatedMfm ? 'animation: tada 1s linear infinite both;' : ''); | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'jelly': { | ||||
| 							const speed = token.node.props.args.speed || '1s'; | ||||
| 							style = (this.$store.state.device.animatedMfm ? `animation: mfm-rubberBand ${speed} linear infinite both;` : ''); | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'twitch': { | ||||
| 							const speed = token.node.props.args.speed || '0.5s'; | ||||
| 							style = this.$store.state.device.animatedMfm ? `animation: mfm-twitch ${speed} ease infinite;` : ''; | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'shake': { | ||||
| 							const speed = token.node.props.args.speed || '0.5s'; | ||||
| 							style = this.$store.state.device.animatedMfm ? `animation: mfm-shake ${speed} ease infinite;` : ''; | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'spin': { | ||||
| 							const direction = | ||||
| 								token.node.props.args.left ? 'reverse' : | ||||
| 								token.node.props.args.alternate ? 'alternate' : | ||||
| 								'normal'; | ||||
| 							const anime = | ||||
| 								token.node.props.args.x ? 'mfm-spinX' : | ||||
| 								token.node.props.args.y ? 'mfm-spinY' : | ||||
| 								'mfm-spin'; | ||||
| 							const speed = token.node.props.args.speed || '1.5s'; | ||||
| 							style = this.$store.state.device.animatedMfm ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : ''; | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'jump': { | ||||
| 							style = this.$store.state.device.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : ''; | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'bounce': { | ||||
| 							style = this.$store.state.device.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : ''; | ||||
| 							break; | ||||
| 						} | ||||
| 						case 'flip': { | ||||
| 							const transform = | ||||
| 								(token.node.props.args.h && token.node.props.args.v) ? 'scale(-1, -1)' : | ||||
| 								token.node.props.args.v ? 'scaleY(-1)' : | ||||
| 								'scaleX(-1)'; | ||||
| 							style = `transform: ${transform};`; | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
| 					if (style == null) { | ||||
| 						return h('span', {}, ['[', token.node.props.name, ...genEl(token.children), ']']); | ||||
| 					} else { | ||||
| 						return h('span', { | ||||
| 							style: 'display: inline-block;' + style, | ||||
| 						}, genEl(token.children)); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				case 'small': { | ||||
| @@ -95,36 +151,6 @@ export default defineComponent({ | ||||
| 					}, genEl(token.children))]; | ||||
| 				} | ||||
|  | ||||
| 				case 'motion': { | ||||
| 					return h('span', { | ||||
| 						style: 'display: inline-block;' + (this.$store.state.device.animatedMfm ? 'animation: anime-rubberBand 1s linear infinite both;' : ''), | ||||
| 					}, genEl(token.children)); | ||||
| 				} | ||||
|  | ||||
| 				case 'spin': { | ||||
| 					const direction = | ||||
| 						token.node.props.attr == 'left' ? 'reverse' : | ||||
| 						token.node.props.attr == 'alternate' ? 'alternate' : | ||||
| 						'normal'; | ||||
| 					const style = this.$store.state.device.animatedMfm | ||||
| 						? `animation: anime-spin 1.5s linear infinite; animation-direction: ${direction};` : ''; | ||||
| 					return h('span', { | ||||
| 						style: 'display: inline-block;' + style | ||||
| 					}, genEl(token.children)); | ||||
| 				} | ||||
|  | ||||
| 				case 'jump': { | ||||
| 					return h('span', { | ||||
| 						style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-jump 0.75s linear infinite;' : 'display: inline-block;' | ||||
| 					}, genEl(token.children)); | ||||
| 				} | ||||
|  | ||||
| 				case 'flip': { | ||||
| 					return h('span', { | ||||
| 						style: 'display: inline-block; transform: scaleX(-1);' | ||||
| 					}, genEl(token.children)); | ||||
| 				} | ||||
|  | ||||
| 				case 'url': { | ||||
| 					return [h(MkUrl, { | ||||
| 						key: Math.random(), | ||||
| @@ -150,7 +176,7 @@ export default defineComponent({ | ||||
| 				} | ||||
|  | ||||
| 				case 'hashtag': { | ||||
| 					return [h(RouterLink, { | ||||
| 					return [h(MkA, { | ||||
| 						key: Math.random(), | ||||
| 						to: this.isNote ? `/tags/${encodeURIComponent(token.node.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.node.props.hashtag)}`, | ||||
| 						style: 'color:var(--hashtag);' | ||||
| @@ -186,17 +212,10 @@ export default defineComponent({ | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				case 'title': { | ||||
| 					return [h('div', { | ||||
| 						class: 'title' | ||||
| 					}, genEl(token.children))]; | ||||
| 				} | ||||
|  | ||||
| 				case 'emoji': { | ||||
| 					return [h(MkEmoji, { | ||||
| 						key: Math.random(), | ||||
| 						emoji: token.node.props.emoji, | ||||
| 						name: token.node.props.name, | ||||
| 						emoji: token.node.props.name ? `:${token.node.props.name}:` : token.node.props.emoji, | ||||
| 						customEmojis: this.customEmojis, | ||||
| 						normal: this.plain | ||||
| 					})]; | ||||
|   | ||||
| @@ -13,6 +13,103 @@ export default defineComponent({ | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
| @keyframes mfm-spin { | ||||
| 	0% { transform: rotate(0deg); } | ||||
| 	100% { transform: rotate(360deg); } | ||||
| } | ||||
|  | ||||
| @keyframes mfm-spinX { | ||||
| 	0% { transform: perspective(128px) rotateX(0deg); } | ||||
| 	100% { transform: perspective(128px) rotateX(360deg); } | ||||
| } | ||||
|  | ||||
| @keyframes mfm-spinY { | ||||
| 	0% { transform: perspective(128px) rotateY(0deg); } | ||||
| 	100% { transform: perspective(128px) rotateY(360deg); } | ||||
| } | ||||
|  | ||||
| @keyframes mfm-jump { | ||||
| 	0% { transform: translateY(0); } | ||||
| 	25% { transform: translateY(-16px); } | ||||
| 	50% { transform: translateY(0); } | ||||
| 	75% { transform: translateY(-8px); } | ||||
| 	100% { transform: translateY(0); } | ||||
| } | ||||
|  | ||||
| @keyframes mfm-bounce { | ||||
| 	0% { transform: translateY(0) scale(1, 1); } | ||||
| 	25% { transform: translateY(-16px) scale(1, 1); } | ||||
| 	50% { transform: translateY(0) scale(1, 1); } | ||||
| 	75% { transform: translateY(0) scale(1.5, 0.75); } | ||||
| 	100% { transform: translateY(0) scale(1, 1); } | ||||
| } | ||||
|  | ||||
| // const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`; | ||||
| // let css = ''; | ||||
| // for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } | ||||
| @keyframes mfm-twitch { | ||||
| 	0% { transform: translate(7px, -2px) } | ||||
| 	5% { transform: translate(-3px, 1px) } | ||||
| 	10% { transform: translate(-7px, -1px) } | ||||
| 	15% { transform: translate(0px, -1px) } | ||||
| 	20% { transform: translate(-8px, 6px) } | ||||
| 	25% { transform: translate(-4px, -3px) } | ||||
| 	30% { transform: translate(-4px, -6px) } | ||||
| 	35% { transform: translate(-8px, -8px) } | ||||
| 	40% { transform: translate(4px, 6px) } | ||||
| 	45% { transform: translate(-3px, 1px) } | ||||
| 	50% { transform: translate(2px, -10px) } | ||||
| 	55% { transform: translate(-7px, 0px) } | ||||
| 	60% { transform: translate(-2px, 4px) } | ||||
| 	65% { transform: translate(3px, -8px) } | ||||
| 	70% { transform: translate(6px, 7px) } | ||||
| 	75% { transform: translate(-7px, -2px) } | ||||
| 	80% { transform: translate(-7px, -8px) } | ||||
| 	85% { transform: translate(9px, 3px) } | ||||
| 	90% { transform: translate(-3px, -2px) } | ||||
| 	95% { transform: translate(-10px, 2px) } | ||||
| 	100% { transform: translate(-2px, -6px) } | ||||
| } | ||||
|  | ||||
| // const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`; | ||||
| // let css = ''; | ||||
| // for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } | ||||
| @keyframes mfm-shake { | ||||
| 	0% { transform: translate(-3px, -1px) rotate(-8deg) } | ||||
| 	5% { transform: translate(0px, -1px) rotate(-10deg) } | ||||
| 	10% { transform: translate(1px, -3px) rotate(0deg) } | ||||
| 	15% { transform: translate(1px, 1px) rotate(11deg) } | ||||
| 	20% { transform: translate(-2px, 1px) rotate(1deg) } | ||||
| 	25% { transform: translate(-1px, -2px) rotate(-2deg) } | ||||
| 	30% { transform: translate(-1px, 2px) rotate(-3deg) } | ||||
| 	35% { transform: translate(2px, 1px) rotate(6deg) } | ||||
| 	40% { transform: translate(-2px, -3px) rotate(-9deg) } | ||||
| 	45% { transform: translate(0px, -1px) rotate(-12deg) } | ||||
| 	50% { transform: translate(1px, 2px) rotate(10deg) } | ||||
| 	55% { transform: translate(0px, -3px) rotate(8deg) } | ||||
| 	60% { transform: translate(1px, -1px) rotate(8deg) } | ||||
| 	65% { transform: translate(0px, -1px) rotate(-7deg) } | ||||
| 	70% { transform: translate(-1px, -3px) rotate(6deg) } | ||||
| 	75% { transform: translate(0px, -2px) rotate(4deg) } | ||||
| 	80% { transform: translate(-2px, -1px) rotate(3deg) } | ||||
| 	85% { transform: translate(1px, -3px) rotate(-10deg) } | ||||
| 	90% { transform: translate(1px, 0px) rotate(3deg) } | ||||
| 	95% { transform: translate(-2px, 0px) rotate(-3deg) } | ||||
| 	100% { transform: translate(2px, 1px) rotate(2deg) } | ||||
| } | ||||
|  | ||||
| @keyframes mfm-rubberBand { | ||||
| 	from { transform: scale3d(1, 1, 1); } | ||||
| 	30% { transform: scale3d(1.25, 0.75, 1); } | ||||
| 	40% { transform: scale3d(0.75, 1.25, 1); } | ||||
| 	50% { transform: scale3d(1.15, 0.85, 1); } | ||||
| 	65% { transform: scale3d(0.95, 1.05, 1); } | ||||
| 	75% { transform: scale3d(1.05, 0.95, 1); } | ||||
| 	to { transform: scale3d(1, 1, 1); } | ||||
| } | ||||
| </style> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .havbbuyv { | ||||
| 	white-space: pre-wrap; | ||||
| @@ -42,10 +139,5 @@ export default defineComponent({ | ||||
| 		word-break: break-all; | ||||
| 		padding: 4px 6px; | ||||
| 	} | ||||
|  | ||||
| 	::v-deep(.title) { | ||||
| 		text-align: center; | ||||
| 		border-bottom: solid 1px var(--divider); | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| <template> | ||||
| <header class="kkwtjztg"> | ||||
| 	<router-link class="name" :to="userPage(note.user)" v-user-preview="note.user.id"> | ||||
| 	<MkA class="name" :to="userPage(note.user)" v-user-preview="note.user.id"> | ||||
| 		<MkUserName :user="note.user"/> | ||||
| 	</router-link> | ||||
| 	</MkA> | ||||
| 	<span class="is-bot" v-if="note.user.isBot">bot</span> | ||||
| 	<span class="username"><MkAcct :user="note.user"/></span> | ||||
| 	<span class="admin" v-if="note.user.isAdmin"><Fa :icon="faBookmark"/></span> | ||||
| 	<span class="moderator" v-if="!note.user.isAdmin && note.user.isModerator"><Fa :icon="farBookmark"/></span> | ||||
| 	<div class="info"> | ||||
| 		<span class="mobile" v-if="note.viaMobile"><Fa :icon="faMobileAlt"/></span> | ||||
| 		<router-link class="created-at" :to="notePage(note)"> | ||||
| 		<MkA class="created-at" :to="notePage(note)"> | ||||
| 			<MkTime :time="note.createdAt"/> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 		<span class="visibility" v-if="note.visibility !== 'public'"> | ||||
| 			<Fa v-if="note.visibility === 'home'" :icon="faHome"/> | ||||
| 			<Fa v-if="note.visibility === 'followers'" :icon="faUnlock"/> | ||||
|   | ||||
| @@ -18,9 +18,9 @@ | ||||
| 		<Fa :icon="faRetweet"/> | ||||
| 		<i18n-t keypath="renotedBy" tag="span"> | ||||
| 			<template #user> | ||||
| 				<router-link class="name" :to="userPage(note.user)" v-user-preview="note.userId"> | ||||
| 				<MkA class="name" :to="userPage(note.user)" v-user-preview="note.userId"> | ||||
| 					<MkUserName :user="note.user"/> | ||||
| 				</router-link> | ||||
| 				</MkA> | ||||
| 			</template> | ||||
| 		</i18n-t> | ||||
| 		<div class="info"> | ||||
| @@ -40,7 +40,8 @@ | ||||
| 		<MkAvatar class="avatar" :user="appearNote.user"/> | ||||
| 		<div class="main"> | ||||
| 			<XNoteHeader class="header" :note="appearNote" :mini="true"/> | ||||
| 			<div class="body" ref="noteBody"> | ||||
| 			<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="$store.state.i" :custom-emojis="appearNote.emojis"/> | ||||
| 					<XCwButton v-model:value="showContent" :note="appearNote"/> | ||||
| @@ -48,18 +49,18 @@ | ||||
| 				<div class="content" v-show="appearNote.cw == null || showContent"> | ||||
| 					<div class="text"> | ||||
| 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> | ||||
| 						<router-link class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></router-link> | ||||
| 						<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></MkA> | ||||
| 						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/> | ||||
| 						<a class="rp" v-if="appearNote.renote != null">RN:</a> | ||||
| 					</div> | ||||
| 					<div class="files" v-if="appearNote.files.length > 0"> | ||||
| 						<XMediaList :media-list="appearNote.files" :parent-element="noteBody"/> | ||||
| 						<XMediaList :media-list="appearNote.files"/> | ||||
| 					</div> | ||||
| 					<XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/> | ||||
| 					<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="detail" class="url-preview"/> | ||||
| 					<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div> | ||||
| 				</div> | ||||
| 				<router-link v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</router-link> | ||||
| 				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</MkA> | ||||
| 			</div> | ||||
| 			<footer class="footer"> | ||||
| 				<XReactionsViewer :note="appearNote" ref="reactionsViewer"/> | ||||
| @@ -91,9 +92,9 @@ | ||||
| <div v-else class="_panel muted" @click="muted = false"> | ||||
| 	<i18n-t keypath="userSaysSomething" tag="small"> | ||||
| 		<template #name> | ||||
| 			<router-link class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId"> | ||||
| 			<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId"> | ||||
| 				<MkUserName :user="appearNote.user"/> | ||||
| 			</router-link> | ||||
| 			</MkA> | ||||
| 		</template> | ||||
| 	</i18n-t> | ||||
| </div> | ||||
| @@ -101,7 +102,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, defineAsyncComponent, defineComponent, markRaw, ref } from 'vue'; | ||||
| import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { parse } from '../../mfm/parse'; | ||||
| import { sum, unique } from '../../prelude/array'; | ||||
| @@ -139,12 +140,13 @@ export default defineComponent({ | ||||
| 		XCwButton, | ||||
| 		XPoll, | ||||
| 		MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), | ||||
| 		MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), | ||||
| 	}, | ||||
|  | ||||
| 	inject: { | ||||
| 		inChannel: { | ||||
| 			default: null | ||||
| 		} | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| @@ -174,7 +176,6 @@ export default defineComponent({ | ||||
| 			showContent: false, | ||||
| 			isDeleted: false, | ||||
| 			muted: false, | ||||
| 			noteBody: this.$refs.noteBody, | ||||
| 			faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug, faSatelliteDish | ||||
| 		}; | ||||
| 	}, | ||||
| @@ -258,6 +259,12 @@ export default defineComponent({ | ||||
| 			} else { | ||||
| 				return null; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		showTicker() { | ||||
| 			if (this.$store.state.device.instanceTicker === 'always') return true; | ||||
| 			if (this.$store.state.device.instanceTicker === 'remote' && this.appearNote.user.instance) return true; | ||||
| 			return false; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| @@ -301,8 +308,6 @@ export default defineComponent({ | ||||
| 		if (this.$store.getters.isSignedIn) { | ||||
| 			this.connection.on('_connected_', this.onStreamConnected); | ||||
| 		} | ||||
|  | ||||
| 		this.noteBody = this.$refs.noteBody; | ||||
| 	}, | ||||
|  | ||||
| 	beforeUnmount() { | ||||
| @@ -493,9 +498,9 @@ export default defineComponent({ | ||||
| 		react(viaKeyboard = false) { | ||||
| 			pleaseLogin(); | ||||
| 			this.blur(); | ||||
| 			os.popup(defineAsyncComponent(() => import('@/components/reaction-picker.vue')), { | ||||
| 				showFocus: viaKeyboard, | ||||
| 			os.popup(import('@/components/emoji-picker.vue'), { | ||||
| 				src: this.$refs.reactButton, | ||||
| 				compact: !this.$store.state.device.useFullReactionPicker | ||||
| 			}, { | ||||
| 				done: reaction => { | ||||
| 					if (reaction) { | ||||
| @@ -581,11 +586,6 @@ export default defineComponent({ | ||||
| 				}); | ||||
|  | ||||
| 				menu = [{ | ||||
| 					type: 'link', | ||||
| 					icon: faInfoCircle, | ||||
| 					text: this.$t('details'), | ||||
| 					to: '/notes/' + this.appearNote.id | ||||
| 				}, null, { | ||||
| 					icon: faCopy, | ||||
| 					text: this.$t('copyContent'), | ||||
| 					action: this.copyContent | ||||
| @@ -637,6 +637,21 @@ export default defineComponent({ | ||||
| 					}] | ||||
| 					: [] | ||||
| 				), | ||||
| 				...(this.appearNote.userId != this.$store.state.i.id ? [ | ||||
| 					null, | ||||
| 					{ | ||||
| 						icon: faExclamationCircle, | ||||
| 						text: this.$t('reportAbuse'), | ||||
| 						action: () => { | ||||
| 							const u = `${url}/notes/${this.appearNote.id}`; | ||||
| 							os.popup(import('@/components/abuse-report-window.vue'), { | ||||
| 								user: this.appearNote.user, | ||||
| 								initialComment: `Note: ${u}\n-----\n` | ||||
| 							}, {}, 'closed'); | ||||
| 						} | ||||
| 					}] | ||||
| 					: [] | ||||
| 				), | ||||
| 				...(this.appearNote.userId == this.$store.state.i.id || this.$store.state.i.isModerator || this.$store.state.i.isAdmin ? [ | ||||
| 					null, | ||||
| 					this.appearNote.userId == this.$store.state.i.id ? { | ||||
|   | ||||
| @@ -18,34 +18,34 @@ | ||||
| 	</div> | ||||
| 	<div class="tail"> | ||||
| 		<header> | ||||
| 			<router-link v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></router-link> | ||||
| 			<MkA v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></MkA> | ||||
| 			<span v-else>{{ notification.header }}</span> | ||||
| 			<MkTime :time="notification.createdAt" v-if="withTime"/> | ||||
| 			<MkTime :time="notification.createdAt" v-if="withTime" class="time"/> | ||||
| 		</header> | ||||
| 		<router-link v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Fa :icon="faQuoteLeft"/> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 			<Fa :icon="faQuoteRight"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)"> | ||||
| 			<Fa :icon="faQuoteLeft"/> | ||||
| 			<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/> | ||||
| 			<Fa :icon="faQuoteRight"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 		</router-link> | ||||
| 		<router-link v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 		</MkA> | ||||
| 		<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> | ||||
| 			<Fa :icon="faQuoteLeft"/> | ||||
| 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> | ||||
| 			<Fa :icon="faQuoteRight"/> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 		<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span> | ||||
| 		<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span> | ||||
| 		<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span> | ||||
| @@ -260,7 +260,7 @@ export default defineComponent({ | ||||
| 				overflow: hidden; | ||||
| 			} | ||||
|  | ||||
| 			> .mk-time { | ||||
| 			> .time { | ||||
| 				margin-left: auto; | ||||
| 				font-size: 0.9em; | ||||
| 			} | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1"> | ||||
| <MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1"> | ||||
| 	<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div> | ||||
| 	<article> | ||||
| 		<header> | ||||
| @@ -11,7 +11,7 @@ | ||||
| 			<p>{{ userName(page.user) }}</p> | ||||
| 		</footer> | ||||
| 	</article> | ||||
| </router-link> | ||||
| </MkA> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
|   | ||||
| @@ -1,24 +1,34 @@ | ||||
| <template> | ||||
| <XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> | ||||
| <XWindow ref="window" | ||||
| 	:initial-width="700" | ||||
| 	:initial-height="500" | ||||
| 	:can-resize="true" | ||||
| 	:close-right="true" | ||||
| 	:contextmenu="contextmenu" | ||||
| 	@closed="$emit('closed')" | ||||
| > | ||||
| 	<template #header> | ||||
| 		<XHeader :info="pageInfo" :with-back="false"/> | ||||
| 	</template> | ||||
| 	<template #buttons> | ||||
| 		<button class="_button" @click="expand" v-tooltip="$t('showInPage')"><Fa :icon="faExpandAlt"/></button> | ||||
| 		<button class="_button" @click="popout" v-tooltip="$t('popout')"><Fa :icon="faExternalLinkAlt"/></button> | ||||
| 		<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button> | ||||
| 		<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> | ||||
| 	</template> | ||||
| 	<div style="min-height: 100%; background: var(--bg);"> | ||||
| 	<div class="yrolvcoq" style="min-height: 100%; background: var(--bg);"> | ||||
| 		<component :is="component" v-bind="props" :ref="changePage"/> | ||||
| 	</div> | ||||
| </XWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, markRaw } from 'vue'; | ||||
| import { faExternalLinkAlt, faExpandAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft, faColumns } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
| import XHeader from '@/ui/_common_/header.vue'; | ||||
| import { popout } from '@/scripts/popout'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| import { resolve } from '@/router'; | ||||
| import { url } from '@/config'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| @@ -26,8 +36,22 @@ export default defineComponent({ | ||||
| 		XHeader, | ||||
| 	}, | ||||
|  | ||||
| 	inject: { | ||||
| 		sideViewHook: { | ||||
| 			default: null | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (path) => { | ||||
| 				this.navigate(path); | ||||
| 			} | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		initialUrl: { | ||||
| 		initialPath: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| @@ -38,7 +62,7 @@ export default defineComponent({ | ||||
| 		initialProps: { | ||||
| 			type: Object, | ||||
| 			required: false, | ||||
| 			default: {}, | ||||
| 			default: () => {}, | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| @@ -47,21 +71,53 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			pageInfo: null, | ||||
| 			url: this.initialUrl, | ||||
| 			path: this.initialPath, | ||||
| 			component: this.initialComponent, | ||||
| 			props: this.initialProps, | ||||
| 			faExternalLinkAlt, faExpandAlt, | ||||
| 			history: [], | ||||
| 			faChevronLeft, | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (url, component, props) => { | ||||
| 				this.url = url; | ||||
| 				this.component = markRaw(component); | ||||
| 				this.props = props; | ||||
| 			} | ||||
| 		}; | ||||
| 	computed: { | ||||
| 		url(): string { | ||||
| 			return url + this.path; | ||||
| 		}, | ||||
|  | ||||
| 		contextmenu() { | ||||
| 			return [{ | ||||
| 				type: 'label', | ||||
| 				text: this.path, | ||||
| 			}, { | ||||
| 				icon: faExpandAlt, | ||||
| 				text: this.$t('showInPage'), | ||||
| 				action: this.expand | ||||
| 			}, this.sideViewHook ? { | ||||
| 				icon: faColumns, | ||||
| 				text: this.$t('openInSideView'), | ||||
| 				action: () => { | ||||
| 					this.sideViewHook(this.path); | ||||
| 					this.$refs.window.close(); | ||||
| 				} | ||||
| 			} : undefined, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('popout'), | ||||
| 				action: this.popout | ||||
| 			}, null, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('openInNewTab'), | ||||
| 				action: () => { | ||||
| 					window.open(this.url, '_blank'); | ||||
| 					this.$refs.window.close(); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: faLink, | ||||
| 				text: this.$t('copyLink'), | ||||
| 				action: () => { | ||||
| 					copyToClipboard(this.url); | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| @@ -72,15 +128,33 @@ export default defineComponent({ | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		navigate(path, record = true) { | ||||
| 			if (record) this.history.push(this.path); | ||||
| 			this.path = path; | ||||
| 			const { component, props } = resolve(path); | ||||
| 			this.component = component; | ||||
| 			this.props = props; | ||||
| 		}, | ||||
|  | ||||
| 		back() { | ||||
| 			this.navigate(this.history.pop(), false); | ||||
| 		}, | ||||
|  | ||||
| 		expand() { | ||||
| 			this.$router.push(this.url); | ||||
| 			this.$router.push(this.path); | ||||
| 			this.$refs.window.close(); | ||||
| 		}, | ||||
|  | ||||
| 		popout() { | ||||
| 			popout(this.url, this.$el); | ||||
| 			popout(this.path, this.$el); | ||||
| 			this.$refs.window.close(); | ||||
| 		}, | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .yrolvcoq { | ||||
| 	--section-padding: 16px; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -378,13 +378,13 @@ export default defineComponent({ | ||||
| 			this.saveDraft(); | ||||
| 		}, | ||||
|  | ||||
| 		async setVisibility() { | ||||
| 		setVisibility() { | ||||
| 			if (this.channel) { | ||||
| 				// TODO: information dialog | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			os.popup(await import('./visibility-picker.vue'), { | ||||
| 			os.popup(import('./visibility-picker.vue'), { | ||||
| 				currentVisibility: this.visibility, | ||||
| 				currentLocalOnly: this.localOnly, | ||||
| 				src: this.$refs.visibilityButton | ||||
| @@ -562,6 +562,10 @@ export default defineComponent({ | ||||
| 				}); | ||||
| 			}).catch(err => { | ||||
| 				this.posting = false; | ||||
| 				os.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: err.message + '\n' + (err as any).id, | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <template> | ||||
| <MkEmoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :customEmojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/> | ||||
| <MkEmoji :emoji="reaction" :custom-emojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue';import * as os from '@/os'; | ||||
| import { defineComponent } from 'vue'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
|   | ||||
| @@ -1,214 +0,0 @@ | ||||
| <template> | ||||
| <MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> | ||||
| 	<div class="rdfaahpb _popup" v-hotkey="keymap"> | ||||
| 		<div class="buttons" ref="buttons" :class="{ showFocus }"> | ||||
| 			<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction" v-particle><XReactionIcon :reaction="reaction"/></button> | ||||
| 		</div> | ||||
| 		<input class="text" ref="text" v-model.trim="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText"> | ||||
| 	</div> | ||||
| </MkModal> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { emojiRegex } from '../../misc/emoji-regex'; | ||||
| import XReactionIcon from '@/components/reaction-icon.vue'; | ||||
| import MkModal from '@/components/ui/modal.vue'; | ||||
| import { Autocomplete } from '@/scripts/autocomplete'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XReactionIcon, | ||||
| 		MkModal, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		reactions: { | ||||
| 			required: false | ||||
| 		}, | ||||
|  | ||||
| 		showFocus: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
|  | ||||
| 		src: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['done', 'closed'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			rs: this.reactions || this.$store.state.settings.reactions, | ||||
| 			text: null, | ||||
| 			focus: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		keymap(): any { | ||||
| 			return { | ||||
| 				'esc': this.close, | ||||
| 				'enter|space|plus': this.choose, | ||||
| 				'up|k': this.focusUp, | ||||
| 				'left|h|shift+tab': this.focusLeft, | ||||
| 				'right|l|tab': this.focusRight, | ||||
| 				'down|j': this.focusDown, | ||||
| 				'1': () => this.react(this.rs[0]), | ||||
| 				'2': () => this.react(this.rs[1]), | ||||
| 				'3': () => this.react(this.rs[2]), | ||||
| 				'4': () => this.react(this.rs[3]), | ||||
| 				'5': () => this.react(this.rs[4]), | ||||
| 				'6': () => this.react(this.rs[5]), | ||||
| 				'7': () => this.react(this.rs[6]), | ||||
| 				'8': () => this.react(this.rs[7]), | ||||
| 				'9': () => this.react(this.rs[8]), | ||||
| 				'0': () => this.react(this.rs[9]), | ||||
| 			}; | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		focus(i) { | ||||
| 			this.$refs.buttons.children[i].focus({ | ||||
| 				preventScroll: true | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			this.focus = 0; | ||||
| 		}); | ||||
|  | ||||
| 		// TODO: detach when unmount | ||||
| 		new Autocomplete(this.$refs.text, this, { model: 'text' }); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		close() { | ||||
| 			this.$emit('done'); | ||||
| 			this.$refs.modal.close(); | ||||
| 		}, | ||||
| 	 | ||||
| 		react(reaction) { | ||||
| 			this.$emit('done', reaction); | ||||
| 			this.$refs.modal.close(); | ||||
| 		}, | ||||
|  | ||||
| 		reactText() { | ||||
| 			if (!this.text) return; | ||||
| 			this.react(this.text); | ||||
| 		}, | ||||
|  | ||||
| 		tryReactText() { | ||||
| 			if (!this.text) return; | ||||
| 			if (!this.text.match(emojiRegex)) return; | ||||
| 			this.reactText(); | ||||
| 		}, | ||||
|  | ||||
| 		focusUp() { | ||||
| 			this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5); | ||||
| 		}, | ||||
|  | ||||
| 		focusDown() { | ||||
| 			this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5); | ||||
| 		}, | ||||
|  | ||||
| 		focusRight() { | ||||
| 			this.focus = this.focus == 9 ? 0 : (this.focus + 1); | ||||
| 		}, | ||||
|  | ||||
| 		focusLeft() { | ||||
| 			this.focus = this.focus == 0 ? 9 : (this.focus - 1); | ||||
| 		}, | ||||
|  | ||||
| 		choose() { | ||||
| 			this.$refs.buttons.children[this.focus].click(); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .rdfaahpb { | ||||
| 	> .buttons { | ||||
| 		padding: 6px 6px 0 6px; | ||||
| 		width: 212px; | ||||
| 		box-sizing: border-box; | ||||
| 		text-align: center; | ||||
|  | ||||
| 		@media (max-width: 1025px) { | ||||
| 			padding: 8px 8px 0 8px; | ||||
| 			width: 256px; | ||||
| 		} | ||||
|  | ||||
| 		&.showFocus { | ||||
| 			> button:focus { | ||||
| 				position: relative; | ||||
| 				z-index: 1; | ||||
|  | ||||
| 				&:after { | ||||
| 					content: ""; | ||||
| 					pointer-events: none; | ||||
| 					position: absolute; | ||||
| 					top: 0; | ||||
| 					right: 0; | ||||
| 					bottom: 0; | ||||
| 					left: 0; | ||||
| 					border: 2px solid var(--focus); | ||||
| 					border-radius: 4px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		> button { | ||||
| 			padding: 0; | ||||
| 			width: 40px; | ||||
| 			height: 40px; | ||||
| 			font-size: 24px; | ||||
| 			border-radius: 2px; | ||||
|  | ||||
| 			@media (max-width: 1025px) { | ||||
| 				width: 48px; | ||||
| 				height: 48px; | ||||
| 				font-size: 26px; | ||||
| 			} | ||||
|  | ||||
| 			> * { | ||||
| 				height: 1em; | ||||
| 			} | ||||
|  | ||||
| 			&:hover { | ||||
| 				background: rgba(0, 0, 0, 0.05); | ||||
| 			} | ||||
|  | ||||
| 			&:active { | ||||
| 				background: var(--accent); | ||||
| 				box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .text { | ||||
| 		width: 208px; | ||||
| 		padding: 8px; | ||||
| 		margin: 0 0 6px 0; | ||||
| 		box-sizing: border-box; | ||||
| 		text-align: center; | ||||
| 		font-size: 16px; | ||||
| 		outline: none; | ||||
| 		border: none; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
|  | ||||
| 		@media (max-width: 1025px) { | ||||
| 			width: 256px; | ||||
| 			margin: 4px 0 8px 0; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<div class="bqxuuuey"> | ||||
| 		<div class="info"> | ||||
| 			<div>{{ reaction.replace('@.', '') }}</div> | ||||
| 			<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon"/> | ||||
| 			<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> | ||||
| 		</div> | ||||
| 		<template v-if="users.length <= 10"> | ||||
| 			<b v-for="u in users" :key="u.id" style="margin-right: 12px;"> | ||||
| @@ -66,7 +66,6 @@ export default defineComponent({ | ||||
| 		> .icon { | ||||
| 			display: block; | ||||
| 			width: 60px; | ||||
| 			height: 60px; | ||||
| 			margin: 0 auto; | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -11,33 +11,30 @@ | ||||
| 	<transition name="nav"> | ||||
| 		<nav class="nav" :class="{ iconOnly, hidden }" v-show="showing"> | ||||
| 			<div> | ||||
| 				<button class="item _button account" @click="openAccountMenu" v-if="$store.getters.isSignedIn"> | ||||
| 				<button class="item _button account" @click="openAccountMenu"> | ||||
| 					<MkAvatar :user="$store.state.i" class="avatar"/><MkAcct class="text" :user="$store.state.i"/> | ||||
| 				</button> | ||||
| 				<button class="item _button index active" @click="top()" v-if="$route.name === 'index'"> | ||||
| 					<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span> | ||||
| 				</button> | ||||
| 				<router-link class="item index" active-class="active" to="/" exact v-else> | ||||
| 					<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span> | ||||
| 				</router-link> | ||||
| 				<MkA class="item index" active-class="active" to="/" exact> | ||||
| 					<Fa :icon="faHome" fixed-width/><span class="text">{{ $t('timeline') }}</span> | ||||
| 				</MkA> | ||||
| 				<template v-for="item in menu"> | ||||
| 					<div v-if="item === '-'" class="divider"></div> | ||||
| 					<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'router-link' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to"> | ||||
| 					<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to"> | ||||
| 						<Fa :icon="menuDef[item].icon" fixed-width/><span class="text">{{ $t(menuDef[item].title) }}</span> | ||||
| 						<i v-if="menuDef[item].indicated"><Fa :icon="faCircle"/></i> | ||||
| 					</component> | ||||
| 				</template> | ||||
| 				<div class="divider"></div> | ||||
| 				<button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)" @click="oepnInstanceMenu"> | ||||
| 				<button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$store.state.i.isAdmin || $store.state.i.isModerator" @click="oepnInstanceMenu"> | ||||
| 					<Fa :icon="faServer" fixed-width/><span class="text">{{ $t('instance') }}</span> | ||||
| 				</button> | ||||
| 				<button class="item _button" @click="more"> | ||||
| 					<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span> | ||||
| 					<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i> | ||||
| 				</button> | ||||
| 				<router-link class="item" active-class="active" to="/settings"> | ||||
| 				<MkA class="item" active-class="active" to="/settings"> | ||||
| 					<Fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span> | ||||
| 				</router-link> | ||||
| 				</MkA> | ||||
| 			</div> | ||||
| 		</nav> | ||||
| 	</transition> | ||||
| @@ -46,7 +43,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { host, instanceName } from '@/config'; | ||||
| import { search } from '@/scripts/search'; | ||||
| @@ -74,7 +71,6 @@ export default defineComponent({ | ||||
| 		}, | ||||
|  | ||||
| 		otherNavItemIndicated(): boolean { | ||||
| 			if (!this.$store.getters.isSignedIn) return false; | ||||
| 			for (const def in this.menuDef) { | ||||
| 				if (this.menu.includes(def)) continue; | ||||
| 				if (this.menuDef[def].indicated) return true; | ||||
| @@ -120,10 +116,6 @@ export default defineComponent({ | ||||
| 			this.showing = true; | ||||
| 		}, | ||||
|  | ||||
| 		top() { | ||||
| 			window.scroll({ top: 0, behavior: 'smooth' }); | ||||
| 		}, | ||||
|  | ||||
| 		search() { | ||||
| 			if (this.searching) return; | ||||
|  | ||||
| @@ -217,6 +209,11 @@ export default defineComponent({ | ||||
| 				text: this.$t('announcements'), | ||||
| 				to: '/instance/announcements', | ||||
| 				icon: faBroadcastTower, | ||||
| 			}, { | ||||
| 				type: 'link', | ||||
| 				text: this.$t('abuseReports'), | ||||
| 				to: '/instance/abuses', | ||||
| 				icon: faExclamationCircle, | ||||
| 			}, { | ||||
| 				type: 'link', | ||||
| 				text: this.$t('logs'), | ||||
| @@ -241,7 +238,7 @@ export default defineComponent({ | ||||
| 				icon: faQuestionCircle, | ||||
| 			}, { | ||||
| 				type: 'link', | ||||
| 				text: this.$t('aboutX', { x: instanceName || host }), | ||||
| 				text: this.$t('aboutX', { x: instanceName }), | ||||
| 				to: '/about', | ||||
| 				icon: faInfoCircle, | ||||
| 			}, { | ||||
| @@ -252,8 +249,8 @@ export default defineComponent({ | ||||
| 			}], ev.currentTarget || ev.target); | ||||
| 		}, | ||||
|  | ||||
| 		async addAcount() { | ||||
| 			os.popup(await import('./signin-dialog.vue'), {}, { | ||||
| 		addAcount() { | ||||
| 			os.popup(import('./signin-dialog.vue'), {}, { | ||||
| 				done: res => { | ||||
| 					this.$store.dispatch('addAcount', res); | ||||
| 					os.success(); | ||||
| @@ -261,8 +258,8 @@ export default defineComponent({ | ||||
| 			}, 'closed'); | ||||
| 		}, | ||||
|  | ||||
| 		async createAccount() { | ||||
| 			os.popup(await import('./signup-dialog.vue'), {}, { | ||||
| 		createAccount() { | ||||
| 			os.popup(import('./signup-dialog.vue'), {}, { | ||||
| 				done: res => { | ||||
| 					this.$store.dispatch('addAcount', res); | ||||
| 					this.switchAccountWithToken(res.i); | ||||
| @@ -270,7 +267,7 @@ export default defineComponent({ | ||||
| 			}, 'closed'); | ||||
| 		}, | ||||
|  | ||||
| 		async switchAccount(account: any) { | ||||
| 		switchAccount(account: any) { | ||||
| 			const token = this.$store.state.device.accounts.find((x: any) => x.id === account.id).token; | ||||
| 			this.switchAccountWithToken(token); | ||||
| 		}, | ||||
| @@ -417,9 +414,9 @@ export default defineComponent({ | ||||
| 			> .item { | ||||
| 				position: relative; | ||||
| 				display: block; | ||||
| 				padding-left: 32px; | ||||
| 				padding-left: 24px; | ||||
| 				font-size: $ui-font-size; | ||||
| 				line-height: 3.2rem; | ||||
| 				line-height: 3rem; | ||||
| 				text-overflow: ellipsis; | ||||
| 				overflow: hidden; | ||||
| 				white-space: nowrap; | ||||
|   | ||||
| @@ -7,9 +7,7 @@ | ||||
| > | ||||
| 	<template #header>{{ $t('login') }}</template> | ||||
|  | ||||
| 	<div class="_section"> | ||||
| 		<MkSignin :auto-set="autoSet" @login="onLogin"/> | ||||
| 	</div> | ||||
| 	<MkSignin :auto-set="autoSet" @login="onLogin"/> | ||||
| </XModalWindow> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -1,43 +1,47 @@ | ||||
| <template> | ||||
| <form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> | ||||
| 	<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> | ||||
| 	<div class="normal-signin" v-if="!totpLogin"> | ||||
| 		<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange"> | ||||
| 			<span>{{ $t('username') }}</span> | ||||
| 			<template #prefix>@</template> | ||||
| 			<template #suffix>@{{ host }}</template> | ||||
| 		</MkInput> | ||||
| 		<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required> | ||||
| 			<span>{{ $t('password') }}</span> | ||||
| 			<template #prefix><Fa :icon="faLock"/></template> | ||||
| 		</MkInput> | ||||
| 		<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton> | ||||
| 		<a class="_panelButton" style="margin: 8px auto;" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a> | ||||
| 		<a class="_panelButton" style="margin: 8px auto;" v-if="meta && meta.enableGithubIntegration"  :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a> | ||||
| 		<a class="_panelButton" style="margin: 8px auto;" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a> | ||||
| 	</div> | ||||
| 	<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }"> | ||||
| 		<div v-if="user && user.securityKeys" class="twofa-group tap-group"> | ||||
| 			<p>{{ $t('tapSecurityKey') }}</p> | ||||
| 			<MkButton @click="queryKey" v-if="!queryingKey"> | ||||
| 				{{ $t('retry') }} | ||||
| 			</MkButton> | ||||
| 		</div> | ||||
| 		<div class="or-hr" v-if="user && user.securityKeys"> | ||||
| 			<p class="or-msg">{{ $t('or') }}</p> | ||||
| 		</div> | ||||
| 		<div class="twofa-group totp-group"> | ||||
| 			<p style="margin-bottom:0;">{{ $t('twoStepAuthentication') }}</p> | ||||
| 			<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required> | ||||
| 	<div class="auth _section"> | ||||
| 		<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> | ||||
| 		<div class="normal-signin" v-if="!totpLogin"> | ||||
| 			<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange"> | ||||
| 				<span>{{ $t('username') }}</span> | ||||
| 				<template #prefix>@</template> | ||||
| 				<template #suffix>@{{ host }}</template> | ||||
| 			</MkInput> | ||||
| 			<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required> | ||||
| 				<span>{{ $t('password') }}</span> | ||||
| 				<template #prefix><Fa :icon="faLock"/></template> | ||||
| 			</MkInput> | ||||
| 			<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> | ||||
| 				<span>{{ $t('token') }}</span> | ||||
| 				<template #prefix><Fa :icon="faGavel"/></template> | ||||
| 			</MkInput> | ||||
| 			<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton> | ||||
| 			<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton> | ||||
| 		</div> | ||||
| 		<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }"> | ||||
| 			<div v-if="user && user.securityKeys" class="twofa-group tap-group"> | ||||
| 				<p>{{ $t('tapSecurityKey') }}</p> | ||||
| 				<MkButton @click="queryKey" v-if="!queryingKey"> | ||||
| 					{{ $t('retry') }} | ||||
| 				</MkButton> | ||||
| 			</div> | ||||
| 			<div class="or-hr" v-if="user && user.securityKeys"> | ||||
| 				<p class="or-msg">{{ $t('or') }}</p> | ||||
| 			</div> | ||||
| 			<div class="twofa-group totp-group"> | ||||
| 				<p style="margin-bottom:0;">{{ $t('twoStepAuthentication') }}</p> | ||||
| 				<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required> | ||||
| 					<span>{{ $t('password') }}</span> | ||||
| 					<template #prefix><Fa :icon="faLock"/></template> | ||||
| 				</MkInput> | ||||
| 				<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> | ||||
| 					<span>{{ $t('token') }}</span> | ||||
| 					<template #prefix><Fa :icon="faGavel"/></template> | ||||
| 				</MkInput> | ||||
| 				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="social _section"> | ||||
| 		<a class="_borderButton _vMargin" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a> | ||||
| 		<a class="_borderButton _vMargin" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a> | ||||
| 		<a class="_borderButton _vMargin" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a> | ||||
| 	</div> | ||||
| </form> | ||||
| </template> | ||||
| @@ -203,14 +207,16 @@ export default defineComponent({ | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .eppvobhk { | ||||
| 	> .avatar { | ||||
| 		margin: 0 auto 0 auto; | ||||
| 		width: 64px; | ||||
| 		height: 64px; | ||||
| 		background: #ddd; | ||||
| 		background-position: center; | ||||
| 		background-size: cover; | ||||
| 		border-radius: 100%; | ||||
| 	> .auth { | ||||
| 		> .avatar { | ||||
| 			margin: 0 auto 0 auto; | ||||
| 			width: 64px; | ||||
| 			height: 64px; | ||||
| 			background: #ddd; | ||||
| 			background-position: center; | ||||
| 			background-size: cover; | ||||
| 			border-radius: 100%; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -3,9 +3,9 @@ | ||||
| 	<div class="body"> | ||||
| 		<span v-if="note.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> | ||||
| 		<span v-if="note.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span> | ||||
| 		<router-link class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></router-link> | ||||
| 		<MkA class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></MkA> | ||||
| 		<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/> | ||||
| 		<router-link class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</router-link> | ||||
| 		<MkA class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</MkA> | ||||
| 	</div> | ||||
| 	<details v-if="note.files.length > 0"> | ||||
| 		<summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="pxhvhrfw" v-size="{ max: [500] }"> | ||||
| 	<button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button> | ||||
| 	<button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :disabled="value === item.value" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -23,19 +23,26 @@ export default defineComponent({ | ||||
| <style lang="scss" scoped> | ||||
| .pxhvhrfw { | ||||
| 	display: flex; | ||||
| 	max-width: var(--baseContentWidth); | ||||
| 	margin: 0 auto; | ||||
|  | ||||
| 	> button { | ||||
| 		flex: 1; | ||||
| 		padding: 15px 12px 12px 12px; | ||||
| 		border-bottom: solid 3px transparent; | ||||
|  | ||||
| 		&:disabled { | ||||
| 			opacity: 1 !important; | ||||
| 			cursor: default; | ||||
| 		} | ||||
|  | ||||
| 		&.active { | ||||
| 			color: var(--accent); | ||||
| 			border-bottom-color: var(--accent); | ||||
| 		} | ||||
|  | ||||
| 		&:not(.active):hover { | ||||
| 			color: var(--fgHighlighted); | ||||
| 		} | ||||
|  | ||||
| 		> .icon { | ||||
| 			margin-right: 6px; | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										70
									
								
								src/client/components/taskmanager.api-window.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/client/components/taskmanager.api-window.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| <template> | ||||
| <XWindow ref="window" | ||||
| 	:initial-width="370" | ||||
| 	:initial-height="450" | ||||
| 	:can-resize="true" | ||||
| 	@close="$refs.window.close()" | ||||
| 	@closed="$emit('closed')" | ||||
| > | ||||
| 	<template #header>Req Viewer</template> | ||||
|  | ||||
| 	<div class="rlkneywz"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: 'Request', value: 'req', }, { label: 'Response', value: 'res', }]" style="border-bottom: solid 1px var(--divider);"/> | ||||
|  | ||||
| 		<code v-if="tab === 'req'">{{ reqStr }}</code> | ||||
| 		<code v-if="tab === 'res'">{{ resStr }}</code> | ||||
| 	</div> | ||||
| </XWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import * as JSON5 from 'json5'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
| import MkTab from '@/components/tab.vue'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XWindow, | ||||
| 		MkTab, | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		req: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['closed'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			tab: 'req', | ||||
| 			reqStr: JSON5.stringify(this.req.req, null, '\t'), | ||||
| 			resStr: JSON5.stringify(this.req.res, null, '\t'), | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .rlkneywz { | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	height: 100%; | ||||
|  | ||||
| 	> code { | ||||
| 		display: block; | ||||
| 		flex: 1; | ||||
| 		padding: 8px; | ||||
| 		overflow: auto; | ||||
| 		font-size: 0.9em; | ||||
| 		tab-size: 2; | ||||
| 		white-space: pre; | ||||
| 		font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										231
									
								
								src/client/components/taskmanager.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								src/client/components/taskmanager.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | ||||
| <template> | ||||
| <XWindow ref="window" :initial-width="650" :initial-height="420" :can-resize="true" @closed="$emit('closed')"> | ||||
| 	<template #header> | ||||
| 		<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager | ||||
| 	</template> | ||||
| 	<div class="qljqmnzj"> | ||||
| 		<MkTab v-model:value="tab" :items="[{ label: 'Windows', value: 'windows', }, { label: 'Stream', value: 'stream', }, { label: 'Stream (Pool)', value: 'streamPool', }, { label: 'API', value: 'api', }]" style="border-bottom: solid 1px var(--divider);"/> | ||||
|  | ||||
| 		<div class="content"> | ||||
| 			<div v-if="tab === 'windows'" class="windows" v-follow> | ||||
| 				<div class="header"> | ||||
| 					<div>#ID</div> | ||||
| 					<div>Component</div> | ||||
| 					<div>Action</div> | ||||
| 				</div> | ||||
| 				<div v-for="p in popups"> | ||||
| 					<div>#{{ p.id }}</div> | ||||
| 					<div>{{ p.component.name ? p.component.name : '<anonymous>' }}</div> | ||||
| 					<div><button class="_textButton" @click="killPopup(p)">Kill</button></div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div v-if="tab === 'stream'" class="stream" v-follow> | ||||
| 				<div class="header"> | ||||
| 					<div>#ID</div> | ||||
| 					<div>Ch</div> | ||||
| 					<div>Handle</div> | ||||
| 					<div>In</div> | ||||
| 					<div>Out</div> | ||||
| 				</div> | ||||
| 				<div v-for="c in connections"> | ||||
| 					<div>#{{ c.id }}</div> | ||||
| 					<div>{{ c.channel }}</div> | ||||
| 					<div v-if="c.users !== null">(shared)<span v-if="c.name">{{ ' ' + c.name }}</span></div> | ||||
| 					<div v-else>{{ c.name ? c.name : '<anonymous>' }}</div> | ||||
| 					<div>{{ c.in }}</div> | ||||
| 					<div>{{ c.out }}</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div v-if="tab === 'streamPool'" class="streamPool" v-follow> | ||||
| 				<div class="header"> | ||||
| 					<div>#ID</div> | ||||
| 					<div>Ch</div> | ||||
| 					<div>Users</div> | ||||
| 				</div> | ||||
| 				<div v-for="p in pools"> | ||||
| 					<div>#{{ p.id }}</div> | ||||
| 					<div>{{ p.channel }}</div> | ||||
| 					<div>{{ p.users }}</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div v-if="tab === 'api'" class="api" v-follow> | ||||
| 				<div class="header"> | ||||
| 					<div>#ID</div> | ||||
| 					<div>Endpoint</div> | ||||
| 					<div>State</div> | ||||
| 				</div> | ||||
| 				<div v-for="req in apiRequests" @click="showReq(req)"> | ||||
| 					<div>#{{ req.id }}</div> | ||||
| 					<div>{{ req.endpoint }}</div> | ||||
| 					<div class="state" :class="req.state">{{ req.state }}</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<footer> | ||||
| 			<div><span class="label">Windows</span>{{ popups.length }}</div> | ||||
| 			<div><span class="label">Stream</span>{{ connections.length }}</div> | ||||
| 			<div><span class="label">Stream (Pool)</span>{{ pools.length }}</div> | ||||
| 		</footer> | ||||
| 	</div> | ||||
| </XWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, markRaw, onBeforeUnmount, ref, shallowRef } from 'vue'; | ||||
| import { faTerminal } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XWindow from '@/components/ui/window.vue'; | ||||
| import MkTab from '@/components/tab.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import follow from '@/directives/follow-append'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XWindow, | ||||
| 		MkTab, | ||||
| 		MkButton, | ||||
| 	}, | ||||
|  | ||||
| 	directives: { | ||||
| 		follow | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['closed'], | ||||
|  | ||||
| 	setup() { | ||||
| 		const connections = shallowRef([]); | ||||
| 		const pools = shallowRef([]); | ||||
| 		const refreshStreamInfo = () => { | ||||
| 			console.log(os.stream.sharedConnectionPools, os.stream.sharedConnections, os.stream.nonSharedConnections); | ||||
| 			const conn = os.stream.sharedConnections.map(c => ({ | ||||
| 				id: c.id, name: c.name, channel: c.channel, users: c.pool.users, in: c.inCount, out: c.outCount, | ||||
| 			})).concat(os.stream.nonSharedConnections.map(c => ({ | ||||
| 				id: c.id, name: c.name, channel: c.channel, users: null, in: c.inCount, out: c.outCount, | ||||
| 			}))); | ||||
| 			conn.sort((a, b) => (a.id > b.id) ? 1 : -1); | ||||
| 			connections.value = conn; | ||||
| 			pools.value = os.stream.sharedConnectionPools; | ||||
| 		}; | ||||
| 		const interval = setInterval(refreshStreamInfo, 1000); | ||||
| 		onBeforeUnmount(() => { | ||||
| 			clearInterval(interval); | ||||
| 		}); | ||||
|  | ||||
| 		const killPopup = p => { | ||||
| 			os.popups.value = os.popups.value.filter(x => x !== p); | ||||
| 		}; | ||||
|  | ||||
| 		const showReq = req => { | ||||
| 			os.popup(import('./taskmanager.api-window.vue'), { | ||||
| 				req: req | ||||
| 			}, { | ||||
| 			}, 'closed'); | ||||
| 		}; | ||||
|  | ||||
| 		return { | ||||
| 			tab: ref('stream'), | ||||
| 			popups: os.popups, | ||||
| 			apiRequests: os.apiRequests, | ||||
| 			connections, | ||||
| 			pools, | ||||
| 			killPopup, | ||||
| 			showReq, | ||||
| 			faTerminal, | ||||
| 		}; | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .qljqmnzj { | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	height: 100%; | ||||
| 	font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace; | ||||
|  | ||||
| 	> .content { | ||||
| 		flex: 1; | ||||
| 		overflow: auto; | ||||
|  | ||||
| 		> div { | ||||
| 			display: table; | ||||
| 			width: 100%; | ||||
| 			padding: 16px; | ||||
| 			box-sizing: border-box; | ||||
|  | ||||
| 			> div { | ||||
| 				display: table-row; | ||||
|  | ||||
| 				&:nth-child(even) { | ||||
| 					//background: rgba(0, 0, 0, 0.1); | ||||
| 				} | ||||
|  | ||||
| 				&.header { | ||||
| 					opacity: 0.7; | ||||
| 				} | ||||
|  | ||||
| 				> div { | ||||
| 					display: table-cell; | ||||
| 					white-space: nowrap; | ||||
|  | ||||
| 					&:not(:last-child) { | ||||
| 						padding-right: 8px; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			&.api { | ||||
| 				> div { | ||||
| 					&:not(.header) { | ||||
| 						cursor: pointer; | ||||
|  | ||||
| 						&:hover { | ||||
| 							color: var(--accent); | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					> .state { | ||||
| 						&.pending { | ||||
| 							color: var(--warn); | ||||
| 						} | ||||
|  | ||||
| 						&.success { | ||||
| 							color: var(--success); | ||||
| 						} | ||||
|  | ||||
| 						&.failed { | ||||
| 							color: var(--error); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> footer { | ||||
| 		display: flex; | ||||
| 		width: 100%; | ||||
| 		padding: 8px 16px; | ||||
| 		box-sizing: border-box; | ||||
| 		border-top: solid 1px var(--divider); | ||||
| 		font-size: 0.9em; | ||||
|  | ||||
| 		> div { | ||||
| 			flex: 1; | ||||
|  | ||||
| 			> .label { | ||||
| 				opacity: 0.7; | ||||
| 				margin-right: 0.5em; | ||||
|  | ||||
| 				&:after { | ||||
| 					content: ":"; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <time class="mk-time" :title="absolute"> | ||||
| <time :title="absolute"> | ||||
| 	<template v-if="mode == 'relative'">{{ relative }}</template> | ||||
| 	<template v-else-if="mode == 'absolute'">{{ absolute }}</template> | ||||
| 	<template v-else-if="mode == 'detail'">{{ absolute }} ({{ relative }})</template> | ||||
|   | ||||
							
								
								
									
										133
									
								
								src/client/components/ui/a.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/client/components/ui/a.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| <template> | ||||
| <a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu"> | ||||
| 	<slot></slot> | ||||
| </a> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faExpandAlt, faColumns, faExternalLinkAlt, faLink, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'; | ||||
| import * as os from '@/os'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| import { router } from '@/router'; | ||||
| import { ui, url } from '@/config'; | ||||
| import { popout } from '@/scripts/popout'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	inject: { | ||||
| 		navHook: { | ||||
| 			default: null | ||||
| 		}, | ||||
| 		sideViewHook: { | ||||
| 			default: null | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		to: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		activeClass: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 		}, | ||||
| 		behavior: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		active() { | ||||
| 			if (this.activeClass == null) return false; | ||||
| 			const resolved = router.resolve(this.to); | ||||
| 			if (resolved.path == this.$route.path) return true; | ||||
| 			if (resolved.name == null) return false; | ||||
| 			if (this.$route.name == null) return false; | ||||
| 			return resolved.name == this.$route.name; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onContextmenu(e) { | ||||
| 			if (window.getSelection().toString() !== '') return; | ||||
| 			os.contextMenu([{ | ||||
| 				type: 'label', | ||||
| 				text: this.to, | ||||
| 			}, { | ||||
| 				icon: faWindowMaximize, | ||||
| 				text: this.$t('openInWindow'), | ||||
| 				action: () => { | ||||
| 					os.pageWindow(this.to); | ||||
| 				} | ||||
| 			}, this.sideViewHook ? { | ||||
| 				icon: faColumns, | ||||
| 				text: this.$t('openInSideView'), | ||||
| 				action: () => { | ||||
| 					this.sideViewHook(this.to); | ||||
| 				} | ||||
| 			} : undefined, { | ||||
| 				icon: faExpandAlt, | ||||
| 				text: this.$t('showInPage'), | ||||
| 				action: () => { | ||||
| 					this.$router.push(this.to); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$t('openInNewTab'), | ||||
| 				action: () => { | ||||
| 					window.open(this.to, '_blank'); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: faLink, | ||||
| 				text: this.$t('copyLink'), | ||||
| 				action: () => { | ||||
| 					copyToClipboard(`${url}${this.to}`); | ||||
| 				} | ||||
| 			}], e); | ||||
| 		}, | ||||
|  | ||||
| 		window() { | ||||
| 			os.pageWindow(this.to); | ||||
| 		}, | ||||
|  | ||||
| 		popout() { | ||||
| 			popout(this.to); | ||||
| 		}, | ||||
|  | ||||
| 		nav() { | ||||
| 			if (this.to.startsWith('/my/messaging')) { | ||||
| 				if (this.$store.state.device.chatOpenBehavior === 'window') return this.window(); | ||||
| 				if (this.$store.state.device.chatOpenBehavior === 'popout') return this.popout(); | ||||
| 			} | ||||
|  | ||||
| 			if (this.behavior) { | ||||
| 				if (this.behavior === 'window') { | ||||
| 					return this.window(); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (this.navHook) { | ||||
| 				this.navHook(this.to); | ||||
| 			} else { | ||||
| 				if (this.$store.state.device.defaultSideView && this.sideViewHook && this.to !== '/') { | ||||
| 					return this.sideViewHook(this.to); | ||||
| 				} | ||||
| 				if (this.$store.state.device.deckNavWindow && (ui === 'deck') && this.to !== '/') { | ||||
| 					return this.window(); | ||||
| 				} | ||||
| 				if (ui === 'desktop') { | ||||
| 					return this.window(); | ||||
| 				} | ||||
|  | ||||
| 				if (this.$router.currentRoute.value.path === this.to) { | ||||
| 					window.scroll({ top: 0, behavior: 'smooth' }); | ||||
| 				} else { | ||||
| 					this.$router.push(this.to); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -1,7 +1,9 @@ | ||||
| <template> | ||||
| <div class="nvlagfpb"> | ||||
| 	<MkMenu :items="items" @close="$emit('closed')" class="_popup _shadow" :align="'left'"/> | ||||
| </div> | ||||
| <transition :name="$store.state.device.animation ? 'fade' : ''" appear> | ||||
| 	<div class="nvlagfpb" @contextmenu.prevent.stop="() => {}"> | ||||
| 		<MkMenu :items="items" @close="$emit('closed')" class="_popup _shadow" :align="'left'"/> | ||||
| 	</div> | ||||
| </transition> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| @@ -35,8 +37,30 @@ export default defineComponent({ | ||||
| 		}, | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$el.style.top = this.ev.pageY + 'px'; | ||||
| 		this.$el.style.left = this.ev.pageX + 'px'; | ||||
| 		let left = this.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1 | ||||
| 		let top = this.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1 | ||||
|  | ||||
| 		const width = this.$el.offsetWidth; | ||||
| 		const height = this.$el.offsetHeight; | ||||
|  | ||||
| 		if (left + width - window.pageXOffset > window.innerWidth) { | ||||
| 			left = window.innerWidth - width + window.pageXOffset; | ||||
| 		} | ||||
|  | ||||
| 		if (top + height - window.pageYOffset > window.innerHeight) { | ||||
| 			top = window.innerHeight - height + window.pageYOffset; | ||||
| 		} | ||||
|  | ||||
| 		if (top < 0) { | ||||
| 			top = 0; | ||||
| 		} | ||||
|  | ||||
| 		if (left < 0) { | ||||
| 			left = 0; | ||||
| 		} | ||||
|  | ||||
| 		this.$el.style.top = top + 'px'; | ||||
| 		this.$el.style.left = left + 'px'; | ||||
|  | ||||
| 		for (const el of Array.from(document.querySelectorAll('body *'))) { | ||||
| 			el.addEventListener('mousedown', this.onMousedown); | ||||
| @@ -60,4 +84,14 @@ export default defineComponent({ | ||||
| 	position: absolute; | ||||
| 	z-index: 65535; | ||||
| } | ||||
|  | ||||
| .fade-enter-active, .fade-leave-active { | ||||
| 	transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); | ||||
| 	transform-origin: left top; | ||||
| } | ||||
|  | ||||
| .fade-enter-from, .fade-leave-to { | ||||
| 	opacity: 0; | ||||
| 	transform: scale(0.9); | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -132,7 +132,7 @@ export default defineComponent({ | ||||
| 	&.max-width_500px { | ||||
| 		> header { | ||||
| 			> .title { | ||||
| 				padding: 8px 10px; | ||||
| 				padding: 8px 10px 8px 0; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -12,22 +12,22 @@ | ||||
| 		<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item"> | ||||
| 			<span><MkEllipsis/></span> | ||||
| 		</span> | ||||
| 		<router-link v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item"> | ||||
| 		<MkA v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item"> | ||||
| 			<Fa v-if="item.icon" :icon="item.icon" fixed-width/> | ||||
| 			<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> | ||||
| 			<span>{{ item.text }}</span> | ||||
| 			<i v-if="item.indicate"><Fa :icon="faCircle"/></i> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 		<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item"> | ||||
| 			<Fa v-if="item.icon" :icon="item.icon" fixed-width/> | ||||
| 			<span>{{ item.text }}</span> | ||||
| 			<i v-if="item.indicate"><Fa :icon="faCircle"/></i> | ||||
| 		</a> | ||||
| 		<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item"> | ||||
| 		<button v-else-if="item.type === 'user'" @click="clicked(item.action, $event)" :tabindex="i" class="_button item"> | ||||
| 			<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> | ||||
| 			<i v-if="item.indicate"><Fa :icon="faCircle"/></i> | ||||
| 		</button> | ||||
| 		<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :class="{ danger: item.danger }"> | ||||
| 		<button v-else @click="clicked(item.action, $event)" :tabindex="i" class="_button item" :class="{ danger: item.danger }"> | ||||
| 			<Fa v-if="item.icon" :icon="item.icon" fixed-width/> | ||||
| 			<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> | ||||
| 			<span>{{ item.text }}</span> | ||||
| @@ -115,8 +115,8 @@ export default defineComponent({ | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		clicked(fn) { | ||||
| 			fn(); | ||||
| 		clicked(fn, ev) { | ||||
| 			fn(ev); | ||||
| 			this.close(); | ||||
| 		}, | ||||
| 		close() { | ||||
|   | ||||
| @@ -175,7 +175,7 @@ export default defineComponent({ | ||||
| } | ||||
|  | ||||
| .modal-popup-content-enter-active, .modal-popup-content-leave-active { | ||||
| 	transition: opacity 0.3s, transform 0.3s !important; | ||||
| 	transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) !important; | ||||
| } | ||||
| .modal-popup-content-enter-from, .modal-popup-content-leave-to { | ||||
| 	pointer-events: none; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="cxiknjgy" :class="{ autoMargin }"> | ||||
| <div class="cxiknjgy"> | ||||
| 	<slot :items="items"></slot> | ||||
| 	<div class="empty" v-if="empty" key="_empty_"> | ||||
| 		<slot name="empty"></slot> | ||||
| @@ -31,24 +31,12 @@ export default defineComponent({ | ||||
| 		pagination: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		autoMargin: { | ||||
| 			required: false, | ||||
| 			default: true | ||||
| 		} | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .cxiknjgy { | ||||
| 	&.autoMargin > *:not(:last-child) { | ||||
| 		margin-bottom: 16px; | ||||
|  | ||||
| 		@media (max-width: 500px) { | ||||
| 			margin-bottom: 8px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .more > .button { | ||||
| 		margin-left: auto; | ||||
| 		margin-right: auto; | ||||
|   | ||||
| @@ -18,7 +18,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| @@ -51,7 +50,8 @@ export default defineComponent({ | ||||
| .novjtctn { | ||||
| 	position: relative; | ||||
| 	display: inline-block; | ||||
| 	margin: 0 32px 0 0; | ||||
| 	margin: 8px 20px 0 0; | ||||
| 	text-align: left; | ||||
| 	cursor: pointer; | ||||
| 	transition: all 0.3s; | ||||
|  | ||||
|   | ||||
							
								
								
									
										58
									
								
								src/client/components/ui/radios.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/client/components/ui/radios.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| <script lang="ts"> | ||||
| import { defineComponent, h } from 'vue'; | ||||
| import MkRadio from '@/components/ui/radio.vue'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkRadio | ||||
| 	}, | ||||
| 	props: { | ||||
| 		defs: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		modelValue: { | ||||
| 			required: false | ||||
| 		}, | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			value: this.modelValue, | ||||
| 		} | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		value() { | ||||
| 			this.$emit('update:modelValue', this.value); | ||||
| 		} | ||||
| 	}, | ||||
| 	render() { | ||||
| 		const label = this.$slots.desc(); | ||||
| 		const options = this.$slots.default(); | ||||
|  | ||||
| 		return h('div', { | ||||
| 			class: 'novjtcto' | ||||
| 		}, [ | ||||
| 			h('div', label), | ||||
| 			...options.map(option => h(MkRadio, { | ||||
| 				key: option.props.value, | ||||
| 				value: option.props.value, | ||||
| 				modelValue: this.value, | ||||
| 				'onUpdate:modelValue': value => this.value = value, | ||||
| 			}, option.children)) | ||||
| 		]); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .novjtcto { | ||||
| 	margin: 32px 0; | ||||
|  | ||||
| 	&:first-child { | ||||
| 		margin-top: 0; | ||||
| 	} | ||||
|  | ||||
| 	&:last-child { | ||||
| 		margin-bottom: 0; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -2,12 +2,13 @@ | ||||
| <div class="adhpbeos" :class="{ focused, filled, tall, pre }"> | ||||
| 	<div class="input"> | ||||
| 		<span class="label" ref="label"><slot></slot></span> | ||||
| 		<textarea ref="input" | ||||
| 		<textarea ref="input" :class="{ code }" | ||||
| 			:value="value" | ||||
| 			:required="required" | ||||
| 			:readonly="readonly" | ||||
| 			:pattern="pattern" | ||||
| 			:autocomplete="autocomplete" | ||||
| 			:spellcheck="!code" | ||||
| 			@input="onInput" | ||||
| 			@focus="focused = true" | ||||
| 			@blur="focused = false" | ||||
| @@ -20,7 +21,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| @@ -43,6 +43,10 @@ export default defineComponent({ | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		code: { | ||||
| 			type: Boolean, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		tall: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| @@ -159,6 +163,11 @@ export default defineComponent({ | ||||
| 			outline: none; | ||||
| 			box-shadow: none; | ||||
| 			color: var(--fg); | ||||
|  | ||||
| 			&.code { | ||||
| 				tab-size: 2; | ||||
| 				font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,16 @@ | ||||
| <transition :name="$store.state.device.animation ? 'window' : ''" appear @after-leave="$emit('closed')"> | ||||
| 	<div class="ebkgocck" v-if="showing"> | ||||
| 		<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> | ||||
| 			<div class="header"> | ||||
| 				<button class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 			<div class="header" @contextmenu.prevent.stop="onContextmenu"> | ||||
| 				<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> | ||||
| 				<button v-else class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
|  | ||||
| 				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> | ||||
| 					<slot name="header"></slot> | ||||
| 				</span> | ||||
| 				<slot name="buttons"></slot> | ||||
|  | ||||
| 				<button v-if="closeRight" class="_button" @click="close()"><Fa :icon="faTimes"/></button> | ||||
| 				<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> | ||||
| 			</div> | ||||
| 			<div class="body" v-if="padding"> | ||||
| 				<div class="_section"> | ||||
| @@ -83,6 +87,15 @@ export default defineComponent({ | ||||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		closeRight: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		contextmenu: { | ||||
| 			type: Array, | ||||
| 			required: false, | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['closed'], | ||||
| @@ -106,6 +119,9 @@ export default defineComponent({ | ||||
| 			z: Number(document.defaultView.getComputedStyle(this.$el, null).zIndex) | ||||
| 		}); | ||||
|  | ||||
| 		// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする | ||||
| 		this.top(); | ||||
|  | ||||
| 		window.addEventListener('resize', this.onBrowserResize); | ||||
| 	}, | ||||
|  | ||||
| @@ -127,6 +143,12 @@ export default defineComponent({ | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onContextmenu(e) { | ||||
| 			if (this.contextmenu) { | ||||
| 				os.contextMenu(this.contextmenu, e); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		// 最前面へ移動 | ||||
| 		top() { | ||||
| 			let z = 0; | ||||
| @@ -313,11 +335,13 @@ export default defineComponent({ | ||||
|  | ||||
| 		// 高さを適用 | ||||
| 		applyTransformHeight(height) { | ||||
| 			if (height > window.innerHeight) height = window.innerHeight; | ||||
| 			(this.$el as any).style.height = height + 'px'; | ||||
| 		}, | ||||
|  | ||||
| 		// 幅を適用 | ||||
| 		applyTransformWidth(width) { | ||||
| 			if (width > window.innerWidth) width = window.innerWidth; | ||||
| 			(this.$el as any).style.width = width + 'px'; | ||||
| 		}, | ||||
|  | ||||
| @@ -371,8 +395,6 @@ export default defineComponent({ | ||||
| 		width: 100%; | ||||
|     height: 100%; | ||||
|  | ||||
| 		--section-padding: 16px; | ||||
|  | ||||
| 		> .header { | ||||
| 			$height: 50px; | ||||
| 			display: flex; | ||||
| @@ -380,7 +402,6 @@ export default defineComponent({ | ||||
| 			z-index: 1; | ||||
| 			flex-shrink: 0; | ||||
| 			box-shadow: 0px 1px var(--divider); | ||||
| 			cursor: move; | ||||
| 			user-select: none; | ||||
| 			height: $height; | ||||
|  | ||||
| @@ -400,6 +421,8 @@ export default defineComponent({ | ||||
| 				white-space: nowrap; | ||||
| 				overflow: hidden; | ||||
| 				text-overflow: ellipsis; | ||||
| 				text-align: center; | ||||
| 				cursor: move; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| </div> | ||||
| <div v-else class="mk-url-preview" v-size="{ max: [400, 350] }"> | ||||
| 	<transition name="zoom" mode="out-in"> | ||||
| 		<component :is="self ? 'router-link' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching"> | ||||
| 		<component :is="self ? 'MkA' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching"> | ||||
| 			<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> | ||||
| 				<button class="_button" v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enablePlayer')"><Fa :icon="faPlayCircle"/></button> | ||||
| 			</div> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <component :is="self ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| <component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" | ||||
| 	@mouseover="onMouseover" | ||||
| 	@mouseleave="onMouseleave" | ||||
| > | ||||
| @@ -71,7 +71,7 @@ export default defineComponent({ | ||||
| 			if (!document.body.contains(this.$el)) return; | ||||
| 			if (this.close) return; | ||||
|  | ||||
| 			const { dispose } = os.popup(await import('@/components/url-preview-popup.vue'), { | ||||
| 			const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), { | ||||
| 				url: this.url, | ||||
| 				source: this.$el | ||||
| 			}); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> | ||||
| 	<MkAvatar class="avatar" :user="user" :disable-preview="true"/> | ||||
| 	<div class="title"> | ||||
| 		<router-link class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></router-link> | ||||
| 		<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> | ||||
| 		<p class="username"><MkAcct :user="user"/></p> | ||||
| 	</div> | ||||
| 	<div class="description"> | ||||
| @@ -96,7 +96,7 @@ export default defineComponent({ | ||||
| 			margin: 0; | ||||
| 			line-height: 16px; | ||||
| 			font-size: 0.8em; | ||||
| 			color: var(--text); | ||||
| 			color: var(--fg); | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
| 	} | ||||
| @@ -125,7 +125,7 @@ export default defineComponent({ | ||||
| 			> p { | ||||
| 				margin: 0; | ||||
| 				font-size: 0.7em; | ||||
| 				color: var(--text); | ||||
| 				color: var(--fg); | ||||
| 			} | ||||
|  | ||||
| 			> span { | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 			<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> | ||||
| 			<MkAvatar class="avatar" :user="user" :disable-preview="true"/> | ||||
| 			<div class="title"> | ||||
| 				<router-link class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></router-link> | ||||
| 				<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> | ||||
| 				<p class="username"><MkAcct :user="user"/></p> | ||||
| 			</div> | ||||
| 			<div class="description"> | ||||
| @@ -151,7 +151,7 @@ export default defineComponent({ | ||||
| 				margin: 0; | ||||
| 				line-height: 16px; | ||||
| 				font-size: 0.8em; | ||||
| 				color: var(--text); | ||||
| 				color: var(--fg); | ||||
| 				opacity: 0.7; | ||||
| 			} | ||||
| 		} | ||||
| @@ -159,7 +159,7 @@ export default defineComponent({ | ||||
| 		> .description { | ||||
| 			padding: 0 16px; | ||||
| 			font-size: 0.8em; | ||||
| 			color: var(--text); | ||||
| 			color: var(--fg); | ||||
| 		} | ||||
|  | ||||
| 		> .status { | ||||
| @@ -172,7 +172,7 @@ export default defineComponent({ | ||||
| 				> p { | ||||
| 					margin: 0; | ||||
| 					font-size: 0.7em; | ||||
| 					color: var(--text); | ||||
| 					color: var(--fg); | ||||
| 				} | ||||
|  | ||||
| 				> span { | ||||
|   | ||||
| @@ -14,10 +14,10 @@ | ||||
| 			<MkInput v-model:value="host" class="input" @update:value="search"><span>{{ $t('host') }}</span><template #prefix>@</template></MkInput> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="tbhwbxda _section" :style="users.length > 0 ? 'padding: 0;' : ''"> | ||||
| 	<div class="tbhwbxda _section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }"> | ||||
| 		<div class="users" v-if="users.length > 0"> | ||||
| 			<div class="user" v-for="user in users" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()"> | ||||
| 				<MkAvatar :user="user" class="avatar" :disable-link="true"/> | ||||
| 				<MkAvatar :user="user" class="avatar"/> | ||||
| 				<div class="body"> | ||||
| 					<MkUserName :user="user" class="name"/> | ||||
| 					<MkAcct :user="user" class="acct"/> | ||||
| @@ -28,6 +28,17 @@ | ||||
| 			<span>{{ $t('noUsers') }}</span> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="tbhwbxda _section recent" v-if="username == '' && host == ''"> | ||||
| 		<div class="users"> | ||||
| 			<div class="user" v-for="user in recentUsers" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()"> | ||||
| 				<MkAvatar :user="user" class="avatar"/> | ||||
| 				<div class="body"> | ||||
| 					<MkUserName :user="user" class="name"/> | ||||
| 					<MkAcct :user="user" class="acct"/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </XModalWindow> | ||||
| </template> | ||||
|  | ||||
| @@ -53,18 +64,23 @@ export default defineComponent({ | ||||
| 		return { | ||||
| 			username: '', | ||||
| 			host: '', | ||||
| 			recentUsers: [], | ||||
| 			users: [], | ||||
| 			selected: null, | ||||
| 			faTimes, faCheck | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 	async mounted() { | ||||
| 		this.focus(); | ||||
|  | ||||
| 		this.$nextTick(() => { | ||||
| 			this.focus(); | ||||
| 		}); | ||||
|  | ||||
| 		this.recentUsers = await os.api('users/show', { | ||||
| 			userIds: this.$store.state.device.recentlyUsedUsers | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| @@ -90,6 +106,12 @@ export default defineComponent({ | ||||
| 		ok() { | ||||
| 			this.$emit('ok', this.selected); | ||||
| 			this.$refs.dialog.close(); | ||||
|  | ||||
| 			// 最近使ったユーザー更新 | ||||
| 			let recents = this.$store.state.device.recentlyUsedUsers; | ||||
| 			recents = recents.filter(x => x !== this.selected.id); | ||||
| 			recents.unshift(this.selected.id); | ||||
| 			this.$store.commit('device/set', { key: 'recentlyUsedUsers', value: recents.splice(0, 16) }); | ||||
| 		}, | ||||
|  | ||||
| 		cancel() { | ||||
| @@ -107,6 +129,14 @@ export default defineComponent({ | ||||
| 	overflow: auto; | ||||
| 	height: 100%; | ||||
|  | ||||
| 	&.result.hit { | ||||
| 		padding: 0; | ||||
| 	} | ||||
|  | ||||
| 	&.recent { | ||||
| 		padding: 0; | ||||
| 	} | ||||
|  | ||||
| 	> .inputs { | ||||
| 		> .input { | ||||
| 			display: inline-block; | ||||
|   | ||||
| @@ -6,13 +6,13 @@ | ||||
| 	</div> | ||||
|  | ||||
| 	<div class="users"> | ||||
| 		<router-link v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)"> | ||||
| 		<MkA v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)"> | ||||
| 			<MkAvatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/> | ||||
| 			<div class="body"> | ||||
| 				<MkUserName :user="extract ? extract(item) : item" class="name"/> | ||||
| 				<MkAcct :user="extract ? extract(item) : item" class="acct"/> | ||||
| 			</div> | ||||
| 		</router-link> | ||||
| 		</MkA> | ||||
| 	</div> | ||||
| 	<button class="more _button" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching"> | ||||
| 		<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
|   | ||||
| @@ -12,5 +12,6 @@ export const lang = localStorage.getItem('lang'); | ||||
| export const langs = _LANGS_; | ||||
| export const getLocale = async () => Object.fromEntries((await entries(clientDb.i18n)) as [string, string][]); | ||||
| export const version = _VERSION_; | ||||
| export const instanceName = siteName === 'Misskey' ? null : siteName; | ||||
| export const deckmode = localStorage.getItem('deckmode') === 'true'; | ||||
| export const instanceName = siteName === 'Misskey' ? host : siteName; | ||||
| export const ui = localStorage.getItem('ui'); | ||||
| export const debug = localStorage.getItem('debug') === 'true'; | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/client/directives/follow-append.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/client/directives/follow-append.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { Directive } from 'vue'; | ||||
| import { getScrollContainer, getScrollPosition } from '@/scripts/scroll'; | ||||
|  | ||||
| export default { | ||||
| 	mounted(src, binding, vn) { | ||||
| 		const ro = new ResizeObserver((entries, observer) => { | ||||
| 			const pos = getScrollPosition(src); | ||||
| 			const container = getScrollContainer(src); | ||||
| 			const viewHeight = container.clientHeight; | ||||
| 			const height = container.scrollHeight; | ||||
| 			if (pos + viewHeight > height - 32) { | ||||
| 				container.scrollTop = height; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		ro.observe(src); | ||||
|  | ||||
| 		// TODO: 新たにプロパティを作るのをやめMapを使う | ||||
| 		src._ro_ = ro; | ||||
| 	}, | ||||
|  | ||||
| 	unmounted(src, binding, vn) { | ||||
| 		src._ro_.unobserve(src); | ||||
| 	} | ||||
| } as Directive; | ||||
| @@ -23,13 +23,13 @@ export default { | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		const show = async e => { | ||||
| 		const show = e => { | ||||
| 			if (!document.body.contains(el)) return; | ||||
| 			if (self._close) return; | ||||
| 			if (self.text == null) return; | ||||
|  | ||||
| 			const showing = ref(true); | ||||
| 			popup(await import('@/components/ui/tooltip.vue'), { | ||||
| 			popup(import('@/components/ui/tooltip.vue'), { | ||||
| 				showing, | ||||
| 				text: self.text, | ||||
| 				source: el | ||||
|   | ||||
| @@ -18,13 +18,13 @@ export class UserPreview { | ||||
| 	} | ||||
|  | ||||
| 	@autobind | ||||
| 	private async show() { | ||||
| 	private show() { | ||||
| 		if (!document.body.contains(this.el)) return; | ||||
| 		if (this.promise) return; | ||||
|  | ||||
| 		const showing = ref(true); | ||||
|  | ||||
| 		popup(await import('@/components/user-preview.vue'), { | ||||
| 		popup(import('@/components/user-preview.vue'), { | ||||
| 			showing, | ||||
| 			q: this.user, | ||||
| 			source: this.el | ||||
|   | ||||
| @@ -7,11 +7,10 @@ import '@/style.scss'; | ||||
| import { createApp } from 'vue'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; | ||||
|  | ||||
| import Root from './root.vue'; | ||||
| import widgets from './widgets'; | ||||
| import directives from './directives'; | ||||
| import components from '@/components'; | ||||
| import { version, apiUrl } from '@/config'; | ||||
| import { version, apiUrl, ui } from '@/config'; | ||||
| import { store } from './store'; | ||||
| import { router } from './router'; | ||||
| import { applyTheme } from '@/scripts/theme'; | ||||
| @@ -152,7 +151,13 @@ store.dispatch('instance/fetch').then(() => { | ||||
|  | ||||
| stream.init(store.state.i); | ||||
|  | ||||
| const app = createApp(Root); | ||||
| const app = createApp(await ( | ||||
| 	window.location.search === '?zen' ? import('@/ui/zen.vue') : | ||||
| 	!store.getters.isSignedIn         ? import('@/ui/visitor.vue') : | ||||
| 	ui === 'deck'                     ? import('@/ui/deck.vue') : | ||||
| 	ui === 'desktop'                  ? import('@/ui/desktop.vue') : | ||||
| 	import('@/ui/default.vue') | ||||
| ).then(x => x.default)); | ||||
|  | ||||
| if (_DEV_) { | ||||
| 	app.config.performance = true; | ||||
| @@ -248,7 +253,7 @@ if (store.getters.isSignedIn) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	const main = stream.useSharedConnection('main'); | ||||
| 	const main = stream.useSharedConnection('main', 'System'); | ||||
|  | ||||
| 	// 自分の情報が更新されたとき | ||||
| 	main.on('meUpdated', i => { | ||||
|   | ||||
| @@ -2,37 +2,41 @@ import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vu | ||||
| import { EventEmitter } from 'eventemitter3'; | ||||
| import Stream from '@/scripts/stream'; | ||||
| import { store } from '@/store'; | ||||
| import { apiUrl } from '@/config'; | ||||
| import { apiUrl, debug } from '@/config'; | ||||
| import MkPostFormDialog from '@/components/post-form-dialog.vue'; | ||||
| import MkWaitingDialog from '@/components/waiting-dialog.vue'; | ||||
| import { resolve } from '@/router'; | ||||
|  | ||||
| const ua = navigator.userAgent.toLowerCase(); | ||||
| export const isMobile = /mobile|iphone|ipad|android/.test(ua); | ||||
|  | ||||
| export const stream = new Stream(); | ||||
| export const stream = markRaw(new Stream()); | ||||
|  | ||||
| export const pendingApiRequestsCount = ref(0); | ||||
| let apiRequestsCount = 0; // for debug | ||||
| export const apiRequests = ref([]); // for debug | ||||
|  | ||||
| export const windows = new Map(); | ||||
|  | ||||
| export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) { | ||||
| 	pendingApiRequestsCount.value++; | ||||
|  | ||||
| 	if (_DEV_) { | ||||
| 		performance.mark(_PERF_PREFIX_ + 'api:begin'); | ||||
| 	} | ||||
|  | ||||
| 	const onFinally = () => { | ||||
| 		pendingApiRequestsCount.value--; | ||||
|  | ||||
| 		if (_DEV_) { | ||||
| 			performance.mark(_PERF_PREFIX_ + 'api:end'); | ||||
|  | ||||
| 			performance.measure(_PERF_PREFIX_ + 'api', | ||||
| 				_PERF_PREFIX_ + 'api:begin', | ||||
| 				_PERF_PREFIX_ + 'api:end'); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	const log = debug ? reactive({ | ||||
| 		id: ++apiRequestsCount, | ||||
| 		endpoint, | ||||
| 		req: markRaw(data), | ||||
| 		res: null, | ||||
| 		state: 'pending', | ||||
| 	}) : null; | ||||
| 	if (debug) { | ||||
| 		apiRequests.value.push(log); | ||||
| 		if (apiRequests.value.length > 128) apiRequests.value.shift(); | ||||
| 	} | ||||
|  | ||||
| 	const promise = new Promise((resolve, reject) => { | ||||
| 		// Append a credential | ||||
| 		if (store.getters.isSignedIn) (data as any).i = store.state.i.token; | ||||
| @@ -49,10 +53,21 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st | ||||
|  | ||||
| 			if (res.status === 200) { | ||||
| 				resolve(body); | ||||
| 				if (debug) { | ||||
| 					log.res = markRaw(body); | ||||
| 					log.state = 'success'; | ||||
| 				} | ||||
| 			} else if (res.status === 204) { | ||||
| 				resolve(); | ||||
| 				if (debug) { | ||||
| 					log.state = 'success'; | ||||
| 				} | ||||
| 			} else { | ||||
| 				reject(body.error); | ||||
| 				if (debug) { | ||||
| 					log.res = markRaw(body.error); | ||||
| 					log.state = 'failed'; | ||||
| 				} | ||||
| 			} | ||||
| 		}).catch(reject); | ||||
| 	}); | ||||
| @@ -111,7 +126,8 @@ export function promiseDialog<T extends Promise<any>>( | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||
| 	// NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない) | ||||
| 	popup(MkWaitingDialog, { | ||||
| 		success: success, | ||||
| 		showing: showing, | ||||
| 		text: text, | ||||
| @@ -124,17 +140,20 @@ function isModule(x: any): x is typeof import('*.vue') { | ||||
| 	return x.default != null; | ||||
| } | ||||
|  | ||||
| let popupIdCount = 0; | ||||
| export const popups = ref([]) as Ref<{ | ||||
| 	id: any; | ||||
| 	component: any; | ||||
| 	props: Record<string, any>; | ||||
| }[]>; | ||||
|  | ||||
| export function popup(component: Component | typeof import('*.vue'), props: Record<string, any>, events = {}, disposeEvent?: string) { | ||||
| export async function popup(component: Component | typeof import('*.vue') | Promise<Component | typeof import('*.vue')>, props: Record<string, any>, events = {}, disposeEvent?: string) { | ||||
| 	if (component.then) component = await component; | ||||
|  | ||||
| 	if (isModule(component)) component = component.default; | ||||
| 	markRaw(component); | ||||
|  | ||||
| 	const id = Math.random().toString(); // TODO: uuidとか使う | ||||
| 	const id = ++popupIdCount; | ||||
| 	const dispose = () => { | ||||
| 		if (_DEV_) console.log('os:popup close', id, component, props, events); | ||||
| 		// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? | ||||
| @@ -160,9 +179,10 @@ export function popup(component: Component | typeof import('*.vue'), props: Reco | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| export function pageWindow(url: string, component: Component | typeof import('*.vue'), props: Record<string, any>) { | ||||
| 	popup(defineAsyncComponent(() => import('@/components/page-window.vue')), { | ||||
| 		initialUrl: url, | ||||
| export function pageWindow(path: string) { | ||||
| 	const { component, props } = resolve(path); | ||||
| 	popup(import('@/components/page-window.vue'), { | ||||
| 		initialPath: path, | ||||
| 		initialComponent: markRaw(component), | ||||
| 		initialProps: props, | ||||
| 	}, {}, 'closed'); | ||||
| @@ -170,7 +190,7 @@ export function pageWindow(url: string, component: Component | typeof import('*. | ||||
|  | ||||
| export function dialog(props: Record<string, any>) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), props, { | ||||
| 		popup(import('@/components/dialog.vue'), props, { | ||||
| 			done: result => { | ||||
| 				resolve(result ? result : { canceled: true }); | ||||
| 			}, | ||||
| @@ -184,7 +204,7 @@ export function success() { | ||||
| 		setTimeout(() => { | ||||
| 			showing.value = false; | ||||
| 		}, 1000); | ||||
| 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||
| 		popup(import('@/components/waiting-dialog.vue'), { | ||||
| 			success: true, | ||||
| 			showing: showing | ||||
| 		}, { | ||||
| @@ -196,7 +216,7 @@ export function success() { | ||||
| export function waiting() { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		const showing = ref(true); | ||||
| 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||
| 		popup(import('@/components/waiting-dialog.vue'), { | ||||
| 			success: false, | ||||
| 			showing: showing | ||||
| 		}, { | ||||
| @@ -207,7 +227,7 @@ export function waiting() { | ||||
|  | ||||
| export function form(title, form) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(defineAsyncComponent(() => import('@/components/form-dialog.vue')), { title, form }, { | ||||
| 		popup(import('@/components/form-dialog.vue'), { title, form }, { | ||||
| 			done: result => { | ||||
| 				resolve(result); | ||||
| 			}, | ||||
| @@ -217,7 +237,7 @@ export function form(title, form) { | ||||
|  | ||||
| export async function selectUser() { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(defineAsyncComponent(() => import('@/components/user-select-dialog.vue')), {}, { | ||||
| 		popup(import('@/components/user-select-dialog.vue'), {}, { | ||||
| 			ok: user => { | ||||
| 				resolve(user); | ||||
| 			}, | ||||
| @@ -227,7 +247,7 @@ export async function selectUser() { | ||||
|  | ||||
| export async function selectDriveFile(multiple: boolean) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(defineAsyncComponent(() => import('@/components/drive-window.vue')), { | ||||
| 		popup(import('@/components/drive-select-dialog.vue'), { | ||||
| 			type: 'file', | ||||
| 			multiple | ||||
| 		}, { | ||||
| @@ -242,7 +262,7 @@ export async function selectDriveFile(multiple: boolean) { | ||||
|  | ||||
| export async function selectDriveFolder(multiple: boolean) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(defineAsyncComponent(() => import('@/components/drive-window.vue')), { | ||||
| 		popup(import('@/components/drive-select-dialog.vue'), { | ||||
| 			type: 'folder', | ||||
| 			multiple | ||||
| 		}, { | ||||
| @@ -255,10 +275,11 @@ export async function selectDriveFolder(multiple: boolean) { | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export async function pickEmoji(src?: HTMLElement) { | ||||
| export async function pickEmoji(src?: HTMLElement, opts) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(defineAsyncComponent(() => import('@/components/emoji-picker.vue')), { | ||||
| 			src | ||||
| 		popup(import('@/components/emoji-picker.vue'), { | ||||
| 			src, | ||||
| 			...opts | ||||
| 		}, { | ||||
| 			done: emoji => { | ||||
| 				resolve(emoji); | ||||
| @@ -269,7 +290,8 @@ export async function pickEmoji(src?: HTMLElement) { | ||||
|  | ||||
| export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		const { dispose } = popup(defineAsyncComponent(() => import('@/components/ui/modal-menu.vue')), { | ||||
| 		let dispose; | ||||
| 		popup(import('@/components/ui/modal-menu.vue'), { | ||||
| 			items, | ||||
| 			src, | ||||
| 			align: options?.align, | ||||
| @@ -279,6 +301,8 @@ export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: s | ||||
| 				resolve(); | ||||
| 				dispose(); | ||||
| 			}, | ||||
| 		}).then(res => { | ||||
| 			dispose = res.dispose; | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| @@ -286,7 +310,8 @@ export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: s | ||||
| export function contextMenu(items: any[], ev: MouseEvent) { | ||||
| 	ev.preventDefault(); | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		const { dispose } = popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), { | ||||
| 		let dispose; | ||||
| 		popup(import('@/components/ui/context-menu.vue'), { | ||||
| 			items, | ||||
| 			ev, | ||||
| 		}, { | ||||
| @@ -294,6 +319,8 @@ export function contextMenu(items: any[], ev: MouseEvent) { | ||||
| 				resolve(); | ||||
| 				dispose(); | ||||
| 			}, | ||||
| 		}).then(res => { | ||||
| 			dispose = res.dispose; | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
| @@ -305,11 +332,14 @@ export function post(props: Record<string, any>) { | ||||
| 		//       Vueが渡されたコンポーネントに内部的に__propsというプロパティを生やす影響で、 | ||||
| 		//       複数のpost formを開いたときに場合によってはエラーになる | ||||
| 		//       もちろん複数のpost formを開けること自体Misskeyサイドのバグなのだが | ||||
| 		const { dispose } = popup(MkPostFormDialog, props, { | ||||
| 		let dispose; | ||||
| 		popup(MkPostFormDialog, props, { | ||||
| 			closed: () => { | ||||
| 				resolve(); | ||||
| 				dispose(); | ||||
| 			}, | ||||
| 		}).then(res => { | ||||
| 			dispose = res.dispose; | ||||
| 		}); | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -22,10 +22,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('error'), | ||||
| 					icon: faExclamationTriangle | ||||
| 				}] | ||||
| 				title: this.$t('error'), | ||||
| 				icon: faExclamationTriangle | ||||
| 			}, | ||||
| 			faExclamationTriangle | ||||
| 		}; | ||||
|   | ||||
| @@ -87,10 +87,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('aboutMisskey'), | ||||
| 					icon: null | ||||
| 				}] | ||||
| 				title: this.$t('aboutMisskey'), | ||||
| 				icon: null | ||||
| 			}, | ||||
| 			version, | ||||
| 			faInfoCircle | ||||
|   | ||||
| @@ -37,10 +37,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('about'), | ||||
| 					icon: faInfoCircle | ||||
| 				}] | ||||
| 				title: this.$t('about'), | ||||
| 				icon: faInfoCircle | ||||
| 			}, | ||||
| 			version, | ||||
| 			serverInfo: null, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="_section"> | ||||
| 	<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content" ref="list"> | ||||
| 		<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id"> | ||||
| 		<section class="_card announcement _vMargin" v-for="(announcement, i) in items" :key="announcement.id"> | ||||
| 			<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div> | ||||
| 			<div class="_content"> | ||||
| 				<Mfm :text="announcement.text"/> | ||||
| @@ -31,10 +31,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('announcements'), | ||||
| 					icon: faBroadcastTower | ||||
| 				}] | ||||
| 				title: this.$t('announcements'), | ||||
| 				icon: faBroadcastTower | ||||
| 			}, | ||||
| 			pagination: { | ||||
| 				endpoint: 'announcements', | ||||
|   | ||||
							
								
								
									
										94
									
								
								src/client/pages/api-console.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/client/pages/api-console.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| <template> | ||||
| <div> | ||||
| <section class="_section"> | ||||
| 	<MkInput v-model:value="endpoint" :datalist="endpoints" @update:value="onEndpointChange()"> | ||||
| 		<span>Endpoint</span> | ||||
| 	</MkInput> | ||||
| 	<MkTextarea v-model:value="body" code> | ||||
| 		<span>Params (JSON or JSON5)</span> | ||||
| 	</MkTextarea> | ||||
| 	<MkSwitch v-model:value="withCredential"> | ||||
| 		With credential | ||||
| 	</MkSwitch> | ||||
| 	<MkButton primary full @click="send" :disabled="sending"> | ||||
| 		<template v-if="sending"><MkEllipsis/></template> | ||||
| 		<template v-else><Fa :icon="faPaperPlane"/> Send</template> | ||||
| 	</MkButton> | ||||
| </section> | ||||
| <section class="_section" v-if="res"> | ||||
| 	<MkTextarea v-model:value="res" code readonly tall> | ||||
| 		<span>Response</span> | ||||
| 	</MkTextarea> | ||||
| </section> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faTerminal, faPaperPlane } from '@fortawesome/free-solid-svg-icons'; | ||||
| import * as JSON5 from 'json5'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkInput from '@/components/ui/input.vue'; | ||||
| import MkTextarea from '@/components/ui/textarea.vue'; | ||||
| import MkSwitch from '@/components/ui/switch.vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton, MkInput, MkTextarea, MkSwitch, | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				title: 'API console', | ||||
| 				icon: faTerminal | ||||
| 			}, | ||||
|  | ||||
| 			endpoint: '', | ||||
| 			body: '{}', | ||||
| 			res: null, | ||||
| 			sending: false, | ||||
| 			endpoints: [], | ||||
| 			withCredential: true, | ||||
|  | ||||
| 			faPaperPlane | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		os.api('endpoints').then(endpoints => { | ||||
| 			this.endpoints = endpoints; | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		send() { | ||||
| 			this.sending = true; | ||||
| 			os.api(this.endpoint, JSON5.parse(this.body)).then(res => { | ||||
| 				this.sending = false; | ||||
| 				this.res = JSON5.stringify(res, null, 2); | ||||
| 			}, err => { | ||||
| 				this.sending = false; | ||||
| 				this.res = JSON5.stringify(err, null, 2); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		onEndpointChange() { | ||||
| 			os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => { | ||||
| 				const body = {}; | ||||
| 				for (const p of endpoint.params) { | ||||
| 					body[p.name] = | ||||
| 						p.type === 'String' ? '' : | ||||
| 						p.type === 'Number' ? 0 : | ||||
| 						p.type === 'Boolean' ? false : | ||||
| 						p.type === 'Array' ? [] : | ||||
| 						p.type === 'Object' ? {} : | ||||
| 						null; | ||||
| 				} | ||||
| 				this.body = JSON5.stringify(body, null, 2); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -51,10 +51,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('installedApps'), | ||||
| 					icon: faPlug, | ||||
| 				}], | ||||
| 				title: this.$t('installedApps'), | ||||
| 				icon: faPlug, | ||||
| 			}, | ||||
| 			pagination: { | ||||
| 				endpoint: 'i/apps', | ||||
|   | ||||
| @@ -46,15 +46,11 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: computed(() => this.channelId ? { | ||||
| 				header: [{ | ||||
| 					title: this.$t('_channel.edit'), | ||||
| 					icon: faSatelliteDish, | ||||
| 				}], | ||||
| 				title: this.$t('_channel.edit'), | ||||
| 				icon: faSatelliteDish, | ||||
| 			} : { | ||||
| 				header: [{ | ||||
| 					title: this.$t('_channel.create'), | ||||
| 					icon: faSatelliteDish, | ||||
| 				}], | ||||
| 				title: this.$t('_channel.create'), | ||||
| 				icon: faSatelliteDish, | ||||
| 			}), | ||||
| 			channel: null, | ||||
| 			name: null, | ||||
|   | ||||
| @@ -54,10 +54,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: computed(() => this.channel ? { | ||||
| 				header: [{ | ||||
| 					title: this.channel.name, | ||||
| 					icon: faSatelliteDish, | ||||
| 				}], | ||||
| 				title: this.channel.name, | ||||
| 				icon: faSatelliteDish, | ||||
| 			} : null), | ||||
| 			channel: null, | ||||
| 			showBanner: true, | ||||
|   | ||||
| @@ -43,10 +43,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('channel'), | ||||
| 					icon: faSatelliteDish | ||||
| 				}] | ||||
| 				title: this.$t('channel'), | ||||
| 				icon: faSatelliteDish | ||||
| 			}, | ||||
| 			tab: 'featured', | ||||
| 			featuredPagination: { | ||||
|   | ||||
| @@ -43,10 +43,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.title, | ||||
| 					icon: faFileAlt | ||||
| 				}], | ||||
| 				title: this.title, | ||||
| 				icon: faFileAlt | ||||
| 			}, | ||||
| 			faFileAlt, | ||||
| 			title: '', | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<div class="_content"> | ||||
| 			<ul> | ||||
| 				<li v-for="doc in docs" :key="doc.path"> | ||||
| 					<router-link :to="`/docs/${doc.path}`">{{ doc.title }}</router-link> | ||||
| 					<MkA :to="`/docs/${doc.path}`">{{ doc.title }}</MkA> | ||||
| 				</li> | ||||
| 			</ul> | ||||
| 		</div> | ||||
| @@ -21,10 +21,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('help'), | ||||
| 					icon: faQuestionCircle | ||||
| 				}], | ||||
| 				title: this.$t('help'), | ||||
| 				icon: faQuestionCircle | ||||
| 			}, | ||||
| 			docs: [], | ||||
| 			faQuestionCircle | ||||
|   | ||||
| @@ -18,10 +18,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: computed(() => this.folder ? this.folder.name : this.$t('drive')), | ||||
| 					icon: faCloud, | ||||
| 				}], | ||||
| 				title: computed(() => this.folder ? this.folder.name : this.$t('drive')), | ||||
| 				icon: faCloud, | ||||
| 				action: { | ||||
| 					icon: faEllipsisH, | ||||
| 					handler: this.menu | ||||
|   | ||||
| @@ -38,8 +38,8 @@ | ||||
| 			<template #header><Fa :icon="faHashtag" fixed-width style="margin-right: 0.5em;"/>{{ $t('popularTags') }}</template> | ||||
|  | ||||
| 			<div class="vxjfqztj"> | ||||
| 				<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</router-link> | ||||
| 				<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</router-link> | ||||
| 				<MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA> | ||||
| 				<MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA> | ||||
| 			</div> | ||||
| 		</MkFolder> | ||||
|  | ||||
| @@ -93,10 +93,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('explore'), | ||||
| 					icon: faHashtag | ||||
| 				}], | ||||
| 				title: this.$t('explore'), | ||||
| 				icon: faHashtag | ||||
| 			}, | ||||
| 			pinnedUsers: { endpoint: 'pinned-users' }, | ||||
| 			popularUsers: { endpoint: 'users', limit: 10, noPaging: true, params: { | ||||
|   | ||||
| @@ -19,10 +19,8 @@ export default defineComponent({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('favorites'), | ||||
| 					icon: faStar | ||||
| 				}] | ||||
| 				title: this.$t('favorites'), | ||||
| 				icon: faStar | ||||
| 			}, | ||||
| 			pagination: { | ||||
| 				endpoint: 'i/favorites', | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user