Compare commits
	
		
			43 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 59cb7992e2 | ||
|   | 87b15df47b | ||
|   | 6932d86240 | ||
|   | 87f61e714a | ||
|   | 5762e2d9ba | ||
|   | f0691c8a4f | ||
|   | 6d7e4fe2a1 | ||
|   | 7dc789f470 | ||
|   | 190d1bbf3c | ||
|   | a755dd5f9e | ||
|   | 0846a7b94e | ||
|   | d8be0511f1 | ||
|   | fb07116a4c | ||
|   | fe453c15e3 | ||
|   | 059aeef6a0 | ||
|   | 30e25451d6 | ||
|   | 7f0fd55c9a | ||
|   | 0e9b496deb | ||
|   | 8294c18e70 | ||
|   | 39575b4696 | ||
|   | 29e9801d5c | ||
|   | eaf83bffb0 | ||
|   | bb25ece745 | ||
|   | 754b5629e4 | ||
|   | ee63403548 | ||
|   | 87edeb41da | ||
|   | 41fe804587 | ||
|   | 02466acc4b | ||
|   | 8470a64e6b | ||
|   | 9939e0f9a9 | ||
|   | ce5f552d0c | ||
|   | 7d4c535233 | ||
|   | 57cd0fb93f | ||
|   | a15299ae53 | ||
|   | 1df7abfbb9 | ||
|   | 85a0f696bc | ||
|   | ba3c62bf9c | ||
|   | c17e97b6a6 | ||
|   | 2d80cd0e7b | ||
|   | eedc572f0c | ||
|   | 2de1df3514 | ||
|   | 2d96af1255 | ||
|   | 163325ef89 | 
| @@ -154,3 +154,6 @@ id: 'aid' | ||||
|  | ||||
| # Media Proxy | ||||
| #mediaProxy: https://example.com/proxy | ||||
|  | ||||
| # Sign to ActivityPub GET request (default: false) | ||||
| #signToActivityPubGet: true | ||||
|   | ||||
| @@ -14,8 +14,12 @@ noNotes: "لم يتم العثور على أية ملاحظات" | ||||
| noNotifications: "ليس هناك أية اشعارات" | ||||
| instance: "مثيل الخادم" | ||||
| settings: "الاعدادات" | ||||
| basicSettings: "الاعدادات الأساسية" | ||||
| otherSettings: "إعدادات أخرى" | ||||
| openInWindow: "افتح في نافذة جديدة" | ||||
| profile: "الملف التعريفي" | ||||
| timeline: "الخيط الزمني" | ||||
| noAccountDescription: "لم يكتب هذا المستخدم سيرته بعد." | ||||
| login: "لِج" | ||||
| loggingIn: "جارٍ تسجيل الدخول" | ||||
| logout: "الخروج" | ||||
| @@ -32,18 +36,26 @@ 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 +65,10 @@ followers: "المتابِعين" | ||||
| followsYou: "يتابعك" | ||||
| createList: "إنشاء قائمة" | ||||
| manageLists: "إدارة القوائم" | ||||
| error: "حدث خطأ ما" | ||||
| error: "خطأ" | ||||
| somethingHappened: "حدث خطأ" | ||||
| retry: "حاول مجددًا" | ||||
| pageLoadError: "فشل تحميل الصفحة" | ||||
| enterListName: "اسم القائمة" | ||||
| privacy: "الخصوصية" | ||||
| makeFollowManuallyApprove: "القبول يدويا طلبات الإشتراك" | ||||
| @@ -71,16 +85,24 @@ you: "أنت" | ||||
| clickToShow: "اضغط للعرض" | ||||
| sensitive: "محتوى حساس" | ||||
| add: "إضافة" | ||||
| reaction: "تفاعل" | ||||
| rememberNoteVisibility: "تذكر إعدادت مدى رؤية الملاحظات" | ||||
| attachCancel: "أزل المرفق" | ||||
| enterFileName: "ادخل اسم الملف" | ||||
| mute: "اكتم" | ||||
| unmute: "إلغاء الكتم" | ||||
| block: "احجب" | ||||
| unblock: "إلغاء الحجب" | ||||
| blockConfirm: "أمتأكد من حجب هذا الحساب؟" | ||||
| unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟" | ||||
| selectList: "اختر قائمة" | ||||
| editWidgetsExit: "تم" | ||||
| customEmojis: "إيموجي مخصص" | ||||
| addEmoji: "إضافة إيموجي" | ||||
| cacheRemoteFiles: "خزن مؤقتا الملفات البعيدة" | ||||
| autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الحسابات المتابَعة" | ||||
| addAcount: "إضافة حساب" | ||||
| loginFailed: "فشل الولوج" | ||||
| showOnRemote: "رؤيته على مثيل الخادم البُعدي" | ||||
| general: "الرئيسية" | ||||
| wallpaper: "خلفية الشاشة" | ||||
| @@ -88,6 +110,7 @@ setWallpaper: "استخدم خلفية الشاشة" | ||||
| removeWallpaper: "إزالة خلفية الشاشة" | ||||
| searchWith: "البحث: {q}" | ||||
| youHaveNoLists: "لا تمتلك أية قائمة" | ||||
| followConfirm: "أتريد متابعة {name}؟" | ||||
| proxyAccount: "حساب وكيل البروكسي" | ||||
| host: "المضيف" | ||||
| selectUser: "حدّد مستخدمًا" | ||||
| @@ -96,6 +119,8 @@ annotation: "التعليقات" | ||||
| federation: "الفديرالية" | ||||
| instances: "مثيل الخادم" | ||||
| latestRequestSentAt: "آخر طلب أرسِل في" | ||||
| latestRequestReceivedAt: "آخر طلب تُلقي في" | ||||
| storageUsage: "مساحة التخزين المستخدمة" | ||||
| charts: "المنحنيات البيانية" | ||||
| perHour: "في الساعة" | ||||
| perDay: "في اليوم" | ||||
| @@ -127,7 +152,6 @@ processing: "المعالجة جارية" | ||||
| preview: "معاينة" | ||||
| default: "افتراضي" | ||||
| noCustomEmojis: "ليس هناك إيموجيات" | ||||
| customEmojisOfRemote: "الإيموجيات القادمة مِن مثيلات الخوادم الأخرى" | ||||
| federating: "الفديرالية جارية" | ||||
| blocked: "محجوب" | ||||
| suspended: "مُعلّق" | ||||
| @@ -313,7 +337,6 @@ total: "المجموع" | ||||
| weekOverWeekChanges: "أسبوعيا" | ||||
| dayOverDayChanges: "يوميا" | ||||
| appearance: "المظهر" | ||||
| clinetSettings: "إعدادات التطبيق" | ||||
| accountSettings: "إعدادات الحساب" | ||||
| promotion: "ترقية" | ||||
| promote: "روِّج" | ||||
| @@ -366,6 +389,7 @@ _theme: | ||||
|   make: "إنشاء قالب" | ||||
|   alpha: "الشفافية" | ||||
|   keys: | ||||
|     mention: "أشر الى" | ||||
|     messageBg: "خلفية الدردشة" | ||||
| _sfx: | ||||
|   note: "الملاحظات" | ||||
| @@ -508,7 +532,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 neuem 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" | ||||
| @@ -445,7 +453,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 +484,8 @@ newNoteRecived: "Es gibt neue Notizen" | ||||
| sounds: "Töne" | ||||
| listen: "Anhören" | ||||
| none: "Keine" | ||||
| showInPage: "In Seiten anzeigen" | ||||
| popout: "Pop-Up" | ||||
| volume: "Lautstärke" | ||||
| details: "Details" | ||||
| chooseEmoji: "Wähle ein Emoji" | ||||
| @@ -518,7 +528,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" | ||||
| @@ -564,8 +573,19 @@ 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" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Automatisch aktualisieren" | ||||
|   dialog: "Warnungsfenster zeigen" | ||||
| @@ -576,13 +596,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 +802,7 @@ _widgets: | ||||
|   photos: "Fotos" | ||||
|   digitalClock: "Digitaluhr" | ||||
|   federation: "Föderation" | ||||
|   postForm: "Neue Notiz anfertigen" | ||||
| _cw: | ||||
|   hide: "Ausblenden" | ||||
|   show: "Mehr anzeigen" | ||||
| @@ -1238,14 +1259,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 new 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" | ||||
| @@ -445,7 +453,7 @@ total: "Total" | ||||
| weekOverWeekChanges: "Weekly" | ||||
| dayOverDayChanges: "Daily" | ||||
| appearance: "Appearance" | ||||
| clinetSettings: "Client Settings" | ||||
| clientSettings: "Client settings" | ||||
| accountSettings: "Account Settings" | ||||
| promotion: "Promoted" | ||||
| promote: "Promote" | ||||
| @@ -476,6 +484,8 @@ newNoteRecived: "You've got a new note" | ||||
| sounds: "Sounds" | ||||
| listen: "Listen" | ||||
| none: "None" | ||||
| showInPage: "Show in Pages" | ||||
| popout: "Pop-out" | ||||
| volume: "Volume" | ||||
| details: "Details" | ||||
| chooseEmoji: "Choose an emoji" | ||||
| @@ -518,7 +528,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" | ||||
| @@ -564,8 +573,19 @@ 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" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Automatically reload" | ||||
|   dialog: "Show warning dialog" | ||||
| @@ -576,8 +596,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 +802,7 @@ _widgets: | ||||
|   photos: "Photos" | ||||
|   digitalClock: "Digital clock" | ||||
|   federation: "Federation" | ||||
|   postForm: "Compose a note" | ||||
| _cw: | ||||
|   hide: "Hide" | ||||
|   show: "Load more" | ||||
| @@ -1238,14 +1259,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" | ||||
|   | ||||
| @@ -66,7 +66,6 @@ followers: "Seguidores" | ||||
| followsYou: "Te sigue" | ||||
| createList: "Crear lista" | ||||
| manageLists: "Administrar listas" | ||||
| error: "Ocurrió un problema" | ||||
| retry: "Reintentar" | ||||
| enterListName: "Ingrese nombre de lista" | ||||
| privacy: "Privacidad" | ||||
| @@ -177,7 +176,6 @@ processing: "Procesando" | ||||
| preview: "Vista previa" | ||||
| default: "Predeterminado" | ||||
| noCustomEmojis: "No hay emojis personalizados" | ||||
| customEmojisOfRemote: "Emojis remotos" | ||||
| noJobs: "No hay trabajos" | ||||
| federating: "Federando" | ||||
| blocked: "Bloqueando" | ||||
| @@ -445,7 +443,6 @@ total: "Total" | ||||
| weekOverWeekChanges: "Dif semanal" | ||||
| dayOverDayChanges: "Dif diaria" | ||||
| appearance: "Apariencia" | ||||
| clinetSettings: "Ajustes del cliente" | ||||
| accountSettings: "Ajustes de cuenta" | ||||
| promotion: "Promovido" | ||||
| promote: "Promover" | ||||
| @@ -518,7 +515,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" | ||||
| @@ -566,6 +562,10 @@ 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." | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "Recargar automáticamente" | ||||
|   dialog: "Mostrar diálogo de advertencia" | ||||
| @@ -782,6 +782,7 @@ _widgets: | ||||
|   photos: "Fotos" | ||||
|   digitalClock: "Reloj digital" | ||||
|   federation: "Federación" | ||||
|   postForm: "Formulario" | ||||
| _cw: | ||||
|   hide: "Ocultar" | ||||
|   show: "Ver más" | ||||
| @@ -1244,8 +1245,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" | ||||
| @@ -444,7 +452,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 +482,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 +525,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 +546,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 +558,45 @@ 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" | ||||
| _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 +607,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 +634,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 +719,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 +754,7 @@ _widgets: | ||||
|   photos: "Photos" | ||||
|   digitalClock: "Horloge numérique" | ||||
|   federation: "Fédération" | ||||
|   postForm: "Formulaire à publier" | ||||
| _cw: | ||||
|   hide: "Masquer" | ||||
|   show: "Afficher plus …" | ||||
| @@ -752,6 +796,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 +1210,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" | ||||
|   | ||||
| @@ -585,6 +585,14 @@ regenerateLoginTokenDescription: "ログインに使用される内部トーク | ||||
| setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。" | ||||
| fileIdOrUrl: "ファイルIDまたはURL" | ||||
| chatOpenBehavior: "チャットを開くときの動作" | ||||
| sample: "サンプル" | ||||
| abuseReports: "通報" | ||||
| reportAbuse: "通報" | ||||
| reportAbuseOf: "{name}を通報する" | ||||
| fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" | ||||
| abuseReported: "内容が送信されました。ご報告ありがとうございました。" | ||||
| send: "送信" | ||||
| abuseMarkAsResolved: "対応済みにする" | ||||
|  | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自動でリロード" | ||||
|   | ||||
| @@ -65,7 +65,6 @@ followers: "フォロワー" | ||||
| followsYou: "フォローされとるで" | ||||
| createList: "リスト作る" | ||||
| manageLists: "リストの管理" | ||||
| error: "問題が発生してん" | ||||
| retry: "もっぺんやってみる" | ||||
| enterListName: "リスト名を入れてや" | ||||
| privacy: "プライバシーってなんや?オカンの年齢か?" | ||||
| @@ -173,7 +172,6 @@ processing: "処理しとる" | ||||
| preview: "プレビュー" | ||||
| default: "デフォルト" | ||||
| noCustomEmojis: "絵文字はあらへん" | ||||
| customEmojisOfRemote: "リモートの絵文字" | ||||
| noJobs: "ジョブはあらへん" | ||||
| federating: "連合しとる" | ||||
| blocked: "ブロックしとる" | ||||
|   | ||||
| @@ -66,7 +66,6 @@ followers: "팔로워" | ||||
| followsYou: "당신을 팔로우합니다" | ||||
| createList: "리스트 만들기" | ||||
| manageLists: "리스트 관리" | ||||
| error: "오류가 발생했습니다" | ||||
| retry: "다시 시도" | ||||
| enterListName: "리스트 이름을 입력" | ||||
| privacy: "프라이버시" | ||||
| @@ -177,7 +176,6 @@ processing: "처리중" | ||||
| preview: "미리보기" | ||||
| default: "기본값" | ||||
| noCustomEmojis: "이모지가 없습니다" | ||||
| customEmojisOfRemote: "다른 인스턴스들의 이모지" | ||||
| noJobs: "작업이 없습니다" | ||||
| federating: "연합 중" | ||||
| blocked: "차단됨" | ||||
| @@ -443,7 +441,6 @@ remote: "리모트" | ||||
| total: "합계" | ||||
| weekOverWeekChanges: "지난주보다" | ||||
| dayOverDayChanges: "어제보다" | ||||
| clinetSettings: "클라이언트 설정" | ||||
| accountSettings: "계정 설정" | ||||
| promotion: "프로모션" | ||||
| promote: "프로모션하기" | ||||
| @@ -515,7 +512,6 @@ enableInfiniteScroll: "자동으로 좀 더 보기" | ||||
| visibility: "공개 범위" | ||||
| poll: "투표" | ||||
| useCw: "내용 숨기기" | ||||
| fixedWidgetsPosition: "위젯의 위치 고정" | ||||
| enablePlayer: "플레이어 열기" | ||||
| disablePlayer: "플레이어 닫기" | ||||
| expandTweet: "트윗 확장하기" | ||||
| @@ -708,6 +704,7 @@ _widgets: | ||||
|   photos: "사진" | ||||
|   digitalClock: "디지털 시계" | ||||
|   federation: "연합" | ||||
|   postForm: "글 입력란" | ||||
| _cw: | ||||
|   hide: "숨기기" | ||||
|   show: "더 보기" | ||||
| @@ -1165,7 +1162,6 @@ _notification: | ||||
|     renote: "Renote" | ||||
|     quote: "인용" | ||||
|     reaction: "리액션" | ||||
|     receiveFollowRequest: "팔로우 요청" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "메인 칼럼 항상 표시" | ||||
|   columnAlign: "칼럼 정렬" | ||||
|   | ||||
| @@ -1,2 +1,21 @@ | ||||
| --- | ||||
| _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" | ||||
| _sfx: | ||||
|   notification: "Powiadomienia" | ||||
| _widgets: | ||||
|   notifications: "Powiadomienia" | ||||
| _profile: | ||||
|   username: "Nazwa użytkownika" | ||||
| _deck: | ||||
|   _columns: | ||||
|     notifications: "Powiadomienia" | ||||
|   | ||||
| @@ -1,45 +1,182 @@ | ||||
| --- | ||||
| _lang_: "Русский язык" | ||||
| _lang_: "Русский" | ||||
| introMisskey: "Добро пожаловать! Misskey - это децентрализованный сервис микроблогов с открытым исходным кодом.\nСоздавайте «записи», чтобы поделиться происходящим или рассказать всем о себе 📡\nТакже Вы можете добавить быструю реакцию на все записи с помощью функции «реакция» 👍\nОткройте для себя новый мир 🚀" | ||||
| monthAndDay: "{day}.{month}" | ||||
| search: "Поиск" | ||||
| notifications: "Уведомления" | ||||
| username: "Имя пользователя" | ||||
| password: "Пароль" | ||||
| fetchingAsApObject: "Запрос на федерацию" | ||||
| ok: "Окей" | ||||
| gotIt: "Отлично" | ||||
| cancel: "Отмена" | ||||
| instance: "Экземпляр" | ||||
| enterUsername: "Введите имя пользователя" | ||||
| renotedBy: "{user} репостнул(а)" | ||||
| noNotes: "Нет постов" | ||||
| noNotifications: "Нет уведомлений" | ||||
| instance: "Узел" | ||||
| settings: "Настройки" | ||||
| basicSettings: "Основное" | ||||
| otherSettings: "Прочee" | ||||
| openInWindow: "Открыть в окне" | ||||
| profile: "Профиль" | ||||
| timeline: "Лента" | ||||
| noAccountDescription: "Описание отсутствует" | ||||
| login: "Войти" | ||||
| loggingIn: "Выполняется вход" | ||||
| logout: "Выйти" | ||||
| signup: "Регистрация" | ||||
| uploading: "Загрузка..." | ||||
| save: "Сохранить" | ||||
| users: "Пользователи" | ||||
| addUser: "Добавить пользователя" | ||||
| favorite: "Избранное" | ||||
| favorites: "Избранное" | ||||
| unfavorite: "Удалить из избранных" | ||||
| pin: "Закрепить" | ||||
| unpin: "Открепить" | ||||
| copyContent: "Скопировать содержимое" | ||||
| copyLink: "Скопировать ссылку" | ||||
| delete: "Удалить" | ||||
| deleteAndEdit: "Удалить и отредактировать" | ||||
| deleteAndEditConfirm: "Удалить этот пост и отредактировать заново? Все реакции, репосты и ответы на него также будут удалены." | ||||
| addToList: "Добавить в список" | ||||
| sendMessage: "Отправить сообщение" | ||||
| copyUsername: "Скопировать имя пользователя" | ||||
| searchUser: "Поиск людей" | ||||
| reply: "Ответить" | ||||
| loadMore: "Показать еще" | ||||
| youGotNewFollower: "Новый подписчик" | ||||
| receiveFollowRequest: "Запрос на подписку" | ||||
| followRequestAccepted: "Запрос на подписку принят" | ||||
| mention: "Упоминание" | ||||
| mentions: "Упоминания" | ||||
| directNotes: "Прямые сообщения" | ||||
| importAndExport: "Импорт / Экспорт" | ||||
| files: "Файл" | ||||
| instances: "Экземпляр" | ||||
| import: "Импорт" | ||||
| export: "Экспорт" | ||||
| files: "Файлы" | ||||
| download: "Скачать" | ||||
| driveFileDeleteConfirm: "Удалить файл {name} ? Посты с ним также будут удалены" | ||||
| unfollowConfirm: "Удалить из подписок {name}?" | ||||
| exportRequested: "Вы запросили экспорт. Это может занять некоторое время. По завершению результат будет добавлен на «Диск»." | ||||
| importRequested: "Вы запросили импорт. Это может занять некоторое время." | ||||
| 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: "Реакции" | ||||
| reactionSettingDescription: "Выберите реакции для показа в селекторе реакций" | ||||
| markAsSensitive: "Отметить как NSFW" | ||||
| unmarkAsSensitive: "Снять отметку NSFW" | ||||
| enterFileName: "Введите имя файла" | ||||
| block: "Заблокировать" | ||||
| unblock: "Разблокировать" | ||||
| suspend: "Приостановить" | ||||
| unsuspend: "Возобновить" | ||||
| blockConfirm: "Заблокировать?" | ||||
| unblockConfirm: "Разблокировать?" | ||||
| suspendConfirm: "Приостановить?" | ||||
| unsuspendConfirm: "Возобновить?" | ||||
| selectList: "Выберите список" | ||||
| selectAntenna: "Выберите антенну" | ||||
| selectWidget: "Выберите виджет" | ||||
| editWidgets: "Редактировать виджет" | ||||
| editWidgetsExit: "Завершить" | ||||
| customEmojis: "Кастомные эмодзи" | ||||
| emoji: "Эмодзи" | ||||
| emojiName: "Название эмодзи" | ||||
| emojiUrl: "URL изображения" | ||||
| addEmoji: "Добавить эмодзи" | ||||
| settingGuide: "Рекомендуемые настройки" | ||||
| flagAsBot: "Учётка бота" | ||||
| flagAsCat: "Учётка кота" | ||||
| autoAcceptFollowed: "Принимать подписки автоматически" | ||||
| addAcount: "Добавить учётку" | ||||
| loginFailed: "Ошибка входа" | ||||
| instances: "Узел" | ||||
| remove: "Удалить" | ||||
| nsfw: "NSFW" | ||||
| userList: "Списки" | ||||
| smtpUser: "Имя пользователя" | ||||
| smtpPass: "Пароль" | ||||
| _theme: | ||||
|   keys: | ||||
|     mention: "Упоминание" | ||||
|     renote: "Репост" | ||||
| _sfx: | ||||
|   note: "Посты" | ||||
|   notification: "Уведомления" | ||||
| _widgets: | ||||
|   notifications: "Уведомления" | ||||
|   timeline: "Лента" | ||||
| _cw: | ||||
|   show: "Показать еще" | ||||
| _visibility: | ||||
|   followers: "Подписчики" | ||||
| _profile: | ||||
|   username: "Имя пользователя" | ||||
| _exportOrImport: | ||||
|   followingList: "Подписки" | ||||
|   blockingList: "Заблокировать" | ||||
|   userLists: "Списки" | ||||
| _pages: | ||||
|   script: | ||||
|     categories: | ||||
|       list: "Списки" | ||||
|     blocks: | ||||
|       _join: | ||||
|         arg1: "Списки" | ||||
|       _randomPick: | ||||
|         arg1: "Списки" | ||||
|       _dailyRandomPick: | ||||
|         arg1: "Списки" | ||||
|       _seedRandomPick: | ||||
|         arg2: "Списки" | ||||
|       _pick: | ||||
|         arg1: "Списки" | ||||
|       _listLen: | ||||
|         arg1: "Списки" | ||||
|     types: | ||||
|       array: "Списки" | ||||
| _notification: | ||||
|   youWereFollowed: "Новый подписчик" | ||||
|   _types: | ||||
|     follow: "Подписки" | ||||
|     mention: "Упоминание" | ||||
|     renote: "Репост" | ||||
|     quote: "Цитата" | ||||
|     reaction: "Реакции" | ||||
| _deck: | ||||
|   _columns: | ||||
|     notifications: "Уведомления" | ||||
|     tl: "Лента" | ||||
|     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: "关注者请求需要批准" | ||||
| @@ -106,6 +113,8 @@ unsuspendConfirm: "要解除冻结吗?" | ||||
| selectList: "选择列表" | ||||
| selectAntenna: "天线选择" | ||||
| selectWidget: "选择小工具" | ||||
| editWidgets: "编辑小工具" | ||||
| editWidgetsExit: "完成编辑" | ||||
| customEmojis: "自定义Emoji" | ||||
| emoji: "表情符号" | ||||
| emojiName: "Emoji 名称" | ||||
| @@ -177,7 +186,6 @@ processing: "处理中" | ||||
| preview: "预览" | ||||
| default: "默认" | ||||
| noCustomEmojis: "无自定义Emoji" | ||||
| customEmojisOfRemote: "远程Emoji" | ||||
| noJobs: "没有任务" | ||||
| federating: "联合中" | ||||
| blocked: "已拦截" | ||||
| @@ -286,7 +294,7 @@ dayX: "{day}日" | ||||
| monthX: "{month}月" | ||||
| yearX: "{year}年" | ||||
| pages: "页面" | ||||
| integration: "连携" | ||||
| integration: "关联" | ||||
| connectSerice: "已连接" | ||||
| disconnectSerice: "断开连接" | ||||
| enableLocalTimeline: "启用本地时间线功能" | ||||
| @@ -445,7 +453,7 @@ total: "总计" | ||||
| weekOverWeekChanges: "与前一周相比" | ||||
| dayOverDayChanges: "与前一日相比" | ||||
| appearance: "外观" | ||||
| clinetSettings: "客户端设置" | ||||
| clientSettings: "客户端设置" | ||||
| accountSettings: "账户设置" | ||||
| promotion: "推广" | ||||
| promote: "推广" | ||||
| @@ -476,6 +484,8 @@ newNoteRecived: "有新的帖子" | ||||
| sounds: "声音" | ||||
| listen: "听" | ||||
| none: "空" | ||||
| showInPage: "在页面中显示" | ||||
| popout: "弹窗" | ||||
| volume: "音量" | ||||
| details: "详情" | ||||
| chooseEmoji: "选择表情符号" | ||||
| @@ -518,7 +528,6 @@ enableInfiniteScroll: "启用自动滚动页面模式" | ||||
| visibility: "可见性" | ||||
| poll: "调查问卷" | ||||
| useCw: "隐藏内容" | ||||
| fixedWidgetsPosition: "固定小工具的位置" | ||||
| enablePlayer: "打开播放器" | ||||
| disablePlayer: "关闭播放器" | ||||
| expandTweet: "展开推文" | ||||
| @@ -566,6 +575,17 @@ delayed: "延迟" | ||||
| database: "数据库" | ||||
| channel: "频道" | ||||
| create: "创建" | ||||
| notificationSetting: "通知设置" | ||||
| notificationSettingDesc: "选择要显示的通知类型。" | ||||
| useGlobalSetting: "使用全局设置" | ||||
| useGlobalSettingDesc: "启用时,将使用帐户通知设置。关闭时,则可以单独设置。" | ||||
| other: "其他" | ||||
| regenerateLoginToken: "重新生成登录令牌" | ||||
| regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。" | ||||
| setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。" | ||||
| fileIdOrUrl: "文件ID或者URL" | ||||
| chatOpenBehavior: "聊天窗口打开时的行为" | ||||
| sample: "示例" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自动重载" | ||||
|   dialog: "对话框警告" | ||||
| @@ -782,6 +802,7 @@ _widgets: | ||||
|   photos: "照片" | ||||
|   digitalClock: "数字时钟" | ||||
|   federation: "联邦宇宙" | ||||
|   postForm: "投稿窗口" | ||||
| _cw: | ||||
|   hide: "隐藏" | ||||
|   show: "查看更多" | ||||
| @@ -1244,8 +1265,11 @@ _notification: | ||||
|     renote: "转发" | ||||
|     quote: "引用" | ||||
|     reaction: "回应" | ||||
|     pollVote: "投票" | ||||
|     receiveFollowRequest: "关注请求" | ||||
|     pollVote: "问卷调查已投票" | ||||
|     receiveFollowRequest: "收到关注请求" | ||||
|     followRequestAccepted: "关注请求已接受" | ||||
|     groupInvited: "加入群组邀请" | ||||
|     app: "关联应用的通知" | ||||
| _deck: | ||||
|   alwaysShowMainColumn: "总是显示主列" | ||||
|   columnAlign: "列对齐" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| --- | ||||
| _lang_: "中文(繁體)" | ||||
| _lang_: "繁體中文" | ||||
| introMisskey: "歡迎! Misskey是一個開源的去中心化的社群網站。\n通過「貼文」來分享現在發生的事情吧! 📡\n「反應」功能,可以讓你快速的對大家的「帖子」來表達感情👍\n一起來探索新的世界吧! 🚀" | ||||
| monthAndDay: "{month}月 {day}日" | ||||
| search: "搜尋" | ||||
| @@ -11,7 +11,7 @@ ok: "確定" | ||||
| gotIt: "知道了" | ||||
| cancel: "取消" | ||||
| enterUsername: "輸入使用者名稱" | ||||
| renotedBy: "由{user}轉發" | ||||
| renotedBy: "{user} 轉發了" | ||||
| noNotes: "貼文不可用。" | ||||
| noNotifications: "沒有通知" | ||||
| instance: "實例" | ||||
| @@ -66,7 +66,6 @@ followers: "追隨者" | ||||
| followsYou: "追隨你的人" | ||||
| createList: "建立清單" | ||||
| manageLists: "管理清單" | ||||
| error: "發生錯誤" | ||||
| retry: "重試" | ||||
| enterListName: "輸入清單名稱" | ||||
| privacy: "隱私" | ||||
| @@ -112,7 +111,7 @@ emojiName: "表情符號名稱" | ||||
| emojiUrl: "表情符號URL" | ||||
| addEmoji: "新增表情符號" | ||||
| settingGuide: "推薦設定" | ||||
| cacheRemoteFiles: "遠程文件緩存" | ||||
| cacheRemoteFiles: "緩存非遠程檔案" | ||||
| cacheRemoteFilesDescription: "如果禁用此設定,遠程文件將會被直接連結而非緩存。禁用將節省服務器上的存儲空間,但會因為沒有生成預覽圖而增加流量。" | ||||
| flagAsBot: "此帳戶是Bot" | ||||
| flagAsCat: "此帳戶是Cat" | ||||
| @@ -176,7 +175,6 @@ processing: "處理中" | ||||
| preview: "預覽" | ||||
| default: "預設" | ||||
| noCustomEmojis: "沒有表情符號" | ||||
| customEmojisOfRemote: "來自其他實例的表情符號" | ||||
| noJobs: "沒有任務" | ||||
| federating: "整合搜索中" | ||||
| blocked: "已封鎖" | ||||
| @@ -223,7 +221,7 @@ nUsersRead: "{n}人已讀" | ||||
| tos: "使用條款" | ||||
| start: "開始" | ||||
| home: "首頁" | ||||
| remoteUserCaution: "由於是遠程用戶,信息不完整。" | ||||
| remoteUserCaution: "由於該用戶來自遠端實例,因此資料用戶並未即時更新。" | ||||
| activity: "動態" | ||||
| images: "圖片" | ||||
| birthday: "生日" | ||||
| @@ -293,7 +291,7 @@ disablingTimelinesInfo: "即使您禁用了時間線功能,管理員和協調 | ||||
| registration: "註冊" | ||||
| enableRegistration: "開啟新用戶註冊" | ||||
| invite: "邀請" | ||||
| proxyRemoteFiles: "代理遠程檔案" | ||||
| proxyRemoteFiles: "遠端代理檔案" | ||||
| proxyRemoteFilesDescription: "啟用此設置後,由於超出存儲容量而未保存或刪除的遠程文件將被本地代理,並且將生成預覽圖。這不影響服務器的存儲。" | ||||
| driveCapacityPerLocalAccount: "每個本地用戶的雲端容量" | ||||
| driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量" | ||||
| @@ -316,7 +314,7 @@ antennas: "天線" | ||||
| manageAntennas: "管理天線" | ||||
| name: "名稱" | ||||
| antennaSource: "接收來源" | ||||
| antennaKeywords: "包含的關鍵字" | ||||
| antennaKeywords: "包含關鍵字" | ||||
| antennaExcludeKeywords: "排除關鍵字" | ||||
| antennaKeywordsDescription: "用空格分隔指定AND、用換行符分隔指定OR" | ||||
| notifyAntenna: "通知我有新的貼文" | ||||
| @@ -430,9 +428,15 @@ category: "類別" | ||||
| tags: "標籤" | ||||
| docSource: "文件來源" | ||||
| createAccount: "建立帳戶" | ||||
| regenerate: "再生" | ||||
| fontSize: "字體大小" | ||||
| openImageInNewTab: "於新分頁中開啟圖片" | ||||
| local: "本地" | ||||
| remote: "遠端" | ||||
| total: "合計" | ||||
| clinetSettings: "用戶端設定" | ||||
| objectStoragePrefix: "前綴" | ||||
| objectStorageUseSSL: "使用SSL" | ||||
| objectStorageUseProxy: "使用網路代理" | ||||
| serverLogs: "伺服器日誌" | ||||
| deleteAll: "刪除所有記錄" | ||||
| none: "無" | ||||
| @@ -443,14 +447,19 @@ unableToProcess: "操作無法完成" | ||||
| recentUsed: "最近使用" | ||||
| install: "安裝" | ||||
| uninstall: "解除安裝" | ||||
| installedApps: "已授權的應用程式" | ||||
| lastUsedDate: "最後上線日期" | ||||
| state: "狀態" | ||||
| sort: "排序" | ||||
| ascendingOrder: "昇冪" | ||||
| descendingOrder: "降冪" | ||||
| scratchpad: "暫存記憶體" | ||||
| output: "輸出" | ||||
| script: "腳本" | ||||
| updateRemoteUser: "更新非本地用戶資料" | ||||
| deleteAllFiles: "刪除所有檔案" | ||||
| deleteAllFilesConfirm: "要删除所有檔案吗?" | ||||
| removeAllFollowing: "解除所有追隨" | ||||
| userSuspended: "該用戶已被凍結" | ||||
| userSilenced: "該用戶已被禁言。" | ||||
| sidebar: "側邊列" | ||||
| @@ -468,7 +477,6 @@ enableInfiniteScroll: "啟用自動滾動頁面模式" | ||||
| visibility: "公開範圍" | ||||
| poll: "投票" | ||||
| useCw: "隱藏內容" | ||||
| fixedWidgetsPosition: "固定小工具的位置" | ||||
| enablePlayer: "打開播放器" | ||||
| disablePlayer: "關閉播放器" | ||||
| expandTweet: "展開推文" | ||||
| @@ -497,8 +505,15 @@ smtpHost: "主機" | ||||
| smtpPort: "端口" | ||||
| smtpUser: "使用名稱" | ||||
| smtpPass: "密碼" | ||||
| display: "檢視" | ||||
| copy: "複製" | ||||
| metrics: "指標" | ||||
| logs: "日誌" | ||||
| delayed: "延遲" | ||||
| database: "資料庫" | ||||
| channel: "頻道" | ||||
| create: "新增" | ||||
| notificationSetting: "管理通知" | ||||
| _serverDisconnectedBehavior: | ||||
|   reload: "自動重載" | ||||
|   dialog: "以對話框警告" | ||||
| @@ -515,12 +530,19 @@ _channel: | ||||
|   notesCount: "有{n}個帖子" | ||||
| _sidebar: | ||||
|   icon: "頭像" | ||||
|   hide: "隱藏" | ||||
| _wordMute: | ||||
|   muteWords: "加入靜音文字" | ||||
| _theme: | ||||
|   color: "顏色" | ||||
|   func: "函数" | ||||
|   keys: | ||||
|     bg: "背景" | ||||
|     mention: "提及" | ||||
|     renote: "轉發貼文" | ||||
|     divider: "分割線" | ||||
|     infoBg: "資訊背景" | ||||
|     infoFg: "資訊內容" | ||||
| _sfx: | ||||
|   note: "貼文" | ||||
|   noteMy: "我的貼文" | ||||
| @@ -579,6 +601,8 @@ _permissions: | ||||
|   "write:votes": "投票" | ||||
|   "read:channels": "已查看的頻道" | ||||
|   "write:channels": "操作頻道" | ||||
| _antennaSources: | ||||
|   all: "全部貼文" | ||||
| _weekday: | ||||
|   sunday: "週日" | ||||
|   monday: "週一" | ||||
| @@ -588,33 +612,53 @@ _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: "無期限" | ||||
|   deadlineDate: "截止日期" | ||||
|   deadlineTime: "小時" | ||||
|   votesCount: "{n}票" | ||||
|   totalVotes: "一共{n}票" | ||||
|   vote: "投票" | ||||
|   showResult: "顯示結果" | ||||
|   voted: "已投票" | ||||
|   closed: "已結束" | ||||
| _visibility: | ||||
|   home: "首頁" | ||||
|   followers: "追隨者" | ||||
|   localOnly: "僅限本地" | ||||
| _postForm: | ||||
|   channelPlaceholder: "發佈到頻道" | ||||
| _profile: | ||||
|   name: "名稱" | ||||
|   username: "使用名稱" | ||||
|   metadataLabel: "標籤" | ||||
|   metadataContent: "内容" | ||||
| _exportOrImport: | ||||
|   allNotes: "全部貼文" | ||||
|   followingList: "追隨中" | ||||
|   muteList: "消音" | ||||
|   blockingList: "封鎖" | ||||
|   userLists: "清單" | ||||
| _charts: | ||||
|   remoteNotesIncDec: "非本地貼文的數目增减" | ||||
| _instanceCharts: | ||||
|   cacheSize: "增加或減少快取用量" | ||||
|   cacheSizeTotal: "快取大小總計" | ||||
| @@ -632,6 +676,8 @@ _pages: | ||||
|   unlike: "收回喜歡" | ||||
|   blocks: | ||||
|     image: "圖片" | ||||
|     _post: | ||||
|       text: "内容" | ||||
|     _textareaInput: | ||||
|       name: "變數名稱" | ||||
|     numberInput: "輸入數值" | ||||
| @@ -643,6 +689,9 @@ _pages: | ||||
|       text: "標題" | ||||
|     _button: | ||||
|       text: "標題" | ||||
|       _action: | ||||
|         _dialog: | ||||
|           content: "内容" | ||||
|   script: | ||||
|     categories: | ||||
|       value: "數值 " | ||||
| @@ -782,6 +831,7 @@ _relayStatus: | ||||
|   accepted: "已通過核准" | ||||
|   rejected: "已拒絕" | ||||
| _notification: | ||||
|   youRenoted: "{name} 轉發了你的貼文" | ||||
|   youGotPoll: "{name}已投票" | ||||
|   youWereFollowed: "您有新的追隨者" | ||||
|   yourFollowRequestAccepted: "您的追隨請求已通過" | ||||
|   | ||||
							
								
								
									
										38
									
								
								migration/1603094348345-refine-abuse-user-report.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								migration/1603094348345-refine-abuse-user-report.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| 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" DROP COLUMN "userId"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "targetUserId" character varying(32) NOT NULL`); | ||||
|         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`); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId") `); | ||||
|         await queryRunner.query(`CREATE INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a" ON "abuse_user_report" ("resolved") `); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); | ||||
|         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(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a"`); | ||||
|         await queryRunner.query(`DROP INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5"`); | ||||
|         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`); | ||||
|         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" DROP COLUMN "targetUserId"`); | ||||
|         await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "userId" character varying(32) NOT NULL`); | ||||
|         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"`); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <syuilotan@yahoo.co.jp>", | ||||
| 	"version": "12.48.0", | ||||
| 	"version": "12.49.0", | ||||
| 	"codename": "indigo", | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| @@ -138,6 +138,7 @@ | ||||
| 		"file-type": "15.0.1", | ||||
| 		"fluent-ffmpeg": "2.1.2", | ||||
| 		"glob": "7.1.6", | ||||
| 		"got": "11.7.0", | ||||
| 		"gulp": "4.0.2", | ||||
| 		"gulp-rename": "2.0.0", | ||||
| 		"gulp-replace": "1.0.0", | ||||
| @@ -245,7 +246,6 @@ | ||||
| 		"vue-i18n": "9.0.0-beta.4", | ||||
| 		"vue-json-pretty": "1.7.0", | ||||
| 		"vue-loader": "16.0.0-beta.7", | ||||
| 		"vue-prism-component": "1.2.0", | ||||
| 		"vue-prism-editor": "1.2.2", | ||||
| 		"vue-router": "4.0.0-beta.13", | ||||
| 		"vue-style-loader": "4.1.2", | ||||
|   | ||||
							
								
								
									
										3
									
								
								src/client/@types/vuex-shim.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/client/@types/vuex-shim.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -2,10 +2,11 @@ import { ComponentCustomProperties } from 'vue'; | ||||
| import { Store } from 'vuex'; | ||||
|  | ||||
| declare module '@vue/runtime-core' { | ||||
| 	// tslint:disable-next-line:no-empty-interface | ||||
| 	interface State { | ||||
| 	} | ||||
|  | ||||
| 	interface ComponentCustomProperties { | ||||
| 		$store: Store<State> | ||||
| 		$store: Store<State>; | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										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> | ||||
| @@ -1,17 +1,14 @@ | ||||
| <template> | ||||
| <XPrism :inline="inline" :language="prismLang">{{ code }}</XPrism> | ||||
| <code v-if="inline" v-html="html" :class="`language-${prismLang}`"></code> | ||||
| <pre v-else :class="`language-${prismLang}`"><code v-html="html" :class="`language-${prismLang}`"></code></pre> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import 'prismjs'; | ||||
| import 'prismjs/themes/prism-okaidia.css'; | ||||
| import XPrism from 'vue-prism-component';import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XPrism | ||||
| 	}, | ||||
| 	props: { | ||||
| 		code: { | ||||
| 			type: String, | ||||
| @@ -29,6 +26,9 @@ export default defineComponent({ | ||||
| 	computed: { | ||||
| 		prismLang() { | ||||
| 			return Prism.languages[this.lang] ? this.lang : 'js'; | ||||
| 		}, | ||||
| 		html() { | ||||
| 			return Prism.highlight(this.code, Prism.languages[this.prismLang], this.prismLang); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -344,6 +344,7 @@ export default defineComponent({ | ||||
| 		display: flex; | ||||
| 		z-index: 2; | ||||
| 		line-height: $header-height; | ||||
| 		height: $header-height; | ||||
| 		padding: 0 16px; | ||||
| 		font-size: 0.9em; | ||||
| 		color: var(--panelHeaderFg); | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| 		<footer> | ||||
| 			<span>{{ image.type }}</span> | ||||
| 			<span>{{ bytes(image.size) }}</span> | ||||
| 			<span v-if="image.properties?.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span> | ||||
| 			<span v-if="image.properties && image.properties.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span> | ||||
| 		</footer> | ||||
| 	</div> | ||||
| </MkModal> | ||||
|   | ||||
| @@ -38,7 +38,9 @@ export default defineComponent({ | ||||
| 	} | ||||
|  | ||||
| 	> ::v-deep(code) { | ||||
| 		font-size: 0.8em; | ||||
| 		word-break: break-all; | ||||
| 		padding: 4px 6px; | ||||
| 	} | ||||
|  | ||||
| 	::v-deep(.title) { | ||||
|   | ||||
| @@ -101,7 +101,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'; | ||||
| @@ -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(defineAsyncComponent(() => 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 ? { | ||||
| @@ -708,6 +723,7 @@ export default defineComponent({ | ||||
| 			os.modalMenu([{ | ||||
| 				text: this.$t('unrenote'), | ||||
| 				icon: faTrashAlt, | ||||
| 				danger: true, | ||||
| 				action: () => { | ||||
| 					os.api('notes/delete', { | ||||
| 						noteId: this.note.id | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <XWindow ref="window" :initial-width="400" :initial-height="450" :can-resize="true" @closed="$emit('closed')"> | ||||
| <XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> | ||||
| 	<template #header> | ||||
| 		<XHeader :info="pageInfo" :with-back="false"/> | ||||
| 	</template> | ||||
| @@ -7,7 +7,7 @@ | ||||
| 		<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> | ||||
| 	</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> | ||||
| @@ -84,3 +84,9 @@ export default defineComponent({ | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .yrolvcoq { | ||||
| 	--section-padding: 16px; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -5,10 +5,12 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XBlock: defineAsyncComponent(() => import('./page.block.vue')) | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			required: true | ||||
| @@ -23,8 +25,5 @@ export default defineComponent({ | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	beforeCreate() { | ||||
| 		this.$options.components.XBlock = require('./page.block.vue').default; | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -44,14 +44,7 @@ export default defineComponent({ | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		upload() { | ||||
| 			return new Promise((ok) => { | ||||
| 				const dialog = os.dialog({ | ||||
| 					type: 'waiting', | ||||
| 					text: this.$t('uploading') + '...', | ||||
| 					showOkButton: false, | ||||
| 					showCancelButton: false, | ||||
| 					cancelableByBgClick: false | ||||
| 				}); | ||||
| 			const promise = new Promise((ok) => { | ||||
| 				const canvas = this.hpml.canvases[this.value.canvasId]; | ||||
| 				canvas.toBlob(blob => { | ||||
| 					const data = new FormData(); | ||||
| @@ -67,11 +60,12 @@ export default defineComponent({ | ||||
| 					}) | ||||
| 					.then(response => response.json()) | ||||
| 					.then(f => { | ||||
| 						dialog.close(); | ||||
| 						ok(f); | ||||
| 					}) | ||||
| 				}); | ||||
| 			}); | ||||
| 			os.promiseDialog(promise); | ||||
| 			return promise; | ||||
| 		}, | ||||
| 		async post() { | ||||
| 			this.posting = true; | ||||
|   | ||||
| @@ -9,10 +9,13 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XBlock: defineAsyncComponent(() => import('./page.block.vue')) | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			required: true | ||||
| @@ -27,9 +30,6 @@ export default defineComponent({ | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	beforeCreate() { | ||||
| 		this.$options.components.XBlock = require('./page.block.vue').default; | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import MkTextarea from '../ui/textarea.vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import { faHeart } from '@fortawesome/free-regular-svg-icons'; | ||||
| import XBlock from './page.block.vue'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { url } from '@/config'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|   | ||||
| @@ -57,7 +57,6 @@ import MkInput from './ui/input.vue'; | ||||
| import MkSelect from './ui/select.vue'; | ||||
| import MkSwitch from './ui/switch.vue'; | ||||
| import MkButton from './ui/button.vue'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| @@ -78,8 +77,8 @@ export default defineComponent({ | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			choices: ['', ''], | ||||
| 			multiple: false, | ||||
| 			choices: this.poll.choices, | ||||
| 			multiple: this.poll.multiple, | ||||
| 			expiration: 'infinite', | ||||
| 			atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'), | ||||
| 			atTime: '00:00', | ||||
| @@ -90,26 +89,6 @@ export default defineComponent({ | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		poll: { | ||||
| 			handler(poll) { | ||||
| 				if (poll == null) return; | ||||
| 				if (poll.choices.length == 0) return; | ||||
| 				this.choices = poll.choices; | ||||
| 				if (poll.choices.length == 1) this.choices = this.choices.concat(''); | ||||
| 				this.multiple = poll.multiple; | ||||
| 				if (poll.expiresAt) { | ||||
| 					this.expiration = 'at'; | ||||
| 					this.atDate = this.atTime = poll.expiresAt; | ||||
| 				} else if (typeof poll.expiredAfter === 'number') { | ||||
| 					this.expiration = 'after'; | ||||
| 					this.after = poll.expiredAfter; | ||||
| 				} else { | ||||
| 					this.expiration = 'infinite'; | ||||
| 				} | ||||
| 			}, | ||||
| 			deep: true, | ||||
| 			immediate: true | ||||
| 		}, | ||||
| 		choices: { | ||||
| 			handler() { | ||||
| 				this.$emit('updated', this.get()); | ||||
| @@ -136,6 +115,24 @@ export default defineComponent({ | ||||
| 				this.$emit('updated', this.get()); | ||||
| 			}, | ||||
| 		}, | ||||
| 		unit: { | ||||
| 			handler() { | ||||
| 				this.$emit('updated', this.get()); | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		const poll = this.poll; | ||||
| 		if (poll.expiresAt) { | ||||
| 			this.expiration = 'at'; | ||||
| 			this.atDate = this.atTime = poll.expiresAt; | ||||
| 		} else if (typeof poll.expiredAfter === 'number') { | ||||
| 			this.expiration = 'after'; | ||||
| 			this.after = poll.expiredAfter / 1000; | ||||
| 		} else { | ||||
| 			this.expiration = 'infinite'; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')" :position="'top'"> | ||||
| 	<MkPostForm @done="$refs.modal.close()" @esc="$refs.modal.close()" v-bind="$attrs"/> | ||||
| 	<MkPostForm @posted="$refs.modal.close()" @cancel="$refs.modal.close()" @esc="$refs.modal.close()" v-bind="$attrs"/> | ||||
| </MkModal> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -125,7 +125,7 @@ export default defineComponent({ | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	emits: ['posted', 'done', 'esc'], | ||||
| 	emits: ['posted', 'cancel', 'esc'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| @@ -135,8 +135,8 @@ export default defineComponent({ | ||||
| 			poll: null, | ||||
| 			useCw: false, | ||||
| 			cw: null, | ||||
| 			localOnly: false, | ||||
| 			visibility: 'public', | ||||
| 			localOnly: this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.localOnly : this.$store.state.settings.defaultNoteLocalOnly, | ||||
| 			visibility: this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : this.$store.state.settings.defaultNoteVisibility, | ||||
| 			visibleUsers: [], | ||||
| 			autocomplete: null, | ||||
| 			draghover: false, | ||||
| @@ -202,12 +202,6 @@ export default defineComponent({ | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		localOnly() { | ||||
| 			this.$store.commit('deviceUser/setLocalOnly', this.localOnly); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		if (this.initialText) { | ||||
| 			this.text = this.initialText; | ||||
| @@ -239,11 +233,9 @@ export default defineComponent({ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// デフォルト公開範囲 | ||||
| 		if (this.channel == null) { | ||||
| 			this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : this.$store.state.settings.defaultNoteVisibility); | ||||
|  | ||||
| 			this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.localOnly : this.$store.state.settings.defaultNoteLocalOnly; | ||||
| 		if (this.channel) { | ||||
| 			this.visibility = 'public'; | ||||
| 			this.localOnly = true; // TODO: チャンネルが連合するようになった折には消す | ||||
| 		} | ||||
|  | ||||
| 		// 公開以外へのリプライ時は元の公開範囲を引き継ぐ | ||||
| @@ -295,7 +287,7 @@ export default defineComponent({ | ||||
| 					this.text = draft.data.text; | ||||
| 					this.useCw = draft.data.useCw; | ||||
| 					this.cw = draft.data.cw; | ||||
| 					this.applyVisibility(draft.data.visibility); | ||||
| 					this.visibility = draft.data.visibility; | ||||
| 					this.localOnly = draft.data.localOnly; | ||||
| 					this.files = (draft.data.files || []).filter(e => e); | ||||
| 					if (draft.data.poll) { | ||||
| @@ -398,18 +390,20 @@ export default defineComponent({ | ||||
| 				src: this.$refs.visibilityButton | ||||
| 			}, { | ||||
| 				changeVisibility: visibility => { | ||||
| 					this.applyVisibility(visibility); | ||||
| 					this.visibility = visibility; | ||||
| 					if (this.$store.state.settings.rememberNoteVisibility) { | ||||
| 						this.$store.commit('deviceUser/setVisibility', visibility); | ||||
| 					} | ||||
| 				}, | ||||
| 				changeLocalOnly: localOnly => { | ||||
| 					this.localOnly = localOnly; | ||||
| 					if (this.$store.state.settings.rememberNoteVisibility) { | ||||
| 						this.$store.commit('deviceUser/setLocalOnly', localOnly); | ||||
| 					} | ||||
| 				} | ||||
| 			}, 'closed'); | ||||
| 		}, | ||||
|  | ||||
| 		applyVisibility(v: string) { | ||||
| 			this.visibility = (noteVisibilities as unknown as string[]).includes(v) ? v : 'public'; // v11互換性のため | ||||
| 		}, | ||||
|  | ||||
| 		addVisibleUser() { | ||||
| 			os.selectUser().then(user => { | ||||
| 				this.visibleUsers.push(user); | ||||
| @@ -556,23 +550,23 @@ export default defineComponent({ | ||||
| 			this.posting = true; | ||||
| 			os.api('notes/create', data).then(() => { | ||||
| 				this.clear(); | ||||
| 				this.$nextTick(() => { | ||||
| 					this.deleteDraft(); | ||||
| 					this.$emit('posted'); | ||||
| 			}).catch(err => { | ||||
| 			}).then(() => { | ||||
| 				this.posting = false; | ||||
| 				this.$emit('done'); | ||||
| 			}); | ||||
|  | ||||
| 					if (this.text && this.text != '') { | ||||
| 						const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag); | ||||
| 						const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | ||||
| 						localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | ||||
| 					} | ||||
| 					this.posting = false; | ||||
| 				}); | ||||
| 			}).catch(err => { | ||||
| 				this.posting = false; | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		cancel() { | ||||
| 			this.$emit('done'); | ||||
| 			this.$emit('cancel'); | ||||
| 		}, | ||||
|  | ||||
| 		insertMention() { | ||||
|   | ||||
							
								
								
									
										116
									
								
								src/client/components/sample.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/client/components/sample.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| <template> | ||||
| <div class="_card"> | ||||
| 	<div class="_content"> | ||||
| 		<MkInput v-model:value="text"> | ||||
| 			<span>Text</span> | ||||
| 		</MkInput> | ||||
| 		<MkSwitch v-model:value="flag"> | ||||
| 			<span>Switch is now {{ flag ? 'on' : 'off' }}</span> | ||||
| 		</MkSwitch> | ||||
| 		<div style="margin: 32px 0;"> | ||||
| 			<MkRadio v-model="radio" value="misskey">Misskey</MkRadio> | ||||
| 			<MkRadio v-model="radio" value="mastodon">Mastodon</MkRadio> | ||||
| 			<MkRadio v-model="radio" value="pleroma">Pleroma</MkRadio> | ||||
| 		</div> | ||||
| 		<MkButton inline>This is</MkButton> | ||||
| 		<MkButton inline primary>the button</MkButton> | ||||
| 	</div> | ||||
| 	<div class="_content"> | ||||
| 		<Mfm :text="mfm"/> | ||||
| 	</div> | ||||
| 	<div class="_content"> | ||||
| 		<MkButton inline primary @click="openMenu">Open menu</MkButton> | ||||
| 		<MkButton inline primary @click="openDialog">Open dialog</MkButton> | ||||
| 		<MkButton inline primary @click="openForm">Open form</MkButton> | ||||
| 		<MkButton inline primary @click="openDrive">Open drive</MkButton> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkInput from '@/components/ui/input.vue'; | ||||
| import MkSwitch from '@/components/ui/switch.vue'; | ||||
| import MkTextarea from '@/components/ui/textarea.vue'; | ||||
| import MkRadio from '@/components/ui/radio.vue'; | ||||
| import * as os from '@/os'; | ||||
| import * as config from '@/config'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton, | ||||
| 		MkInput, | ||||
| 		MkSwitch, | ||||
| 		MkTextarea, | ||||
| 		MkRadio, | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			text: '', | ||||
| 			flag: false, | ||||
| 			radio: 'misskey', | ||||
| 			mfm: `Hello world! This is an @example mention. BTW you are @${this.$store.state.i.username}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.` | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		async openDialog() { | ||||
| 			os.dialog({ | ||||
| 				type: 'warning', | ||||
| 				title: 'Oh my Aichan', | ||||
| 				text: 'Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		async openForm() { | ||||
| 			os.form('Example form', { | ||||
| 				foo: { | ||||
| 					type: 'boolean', | ||||
| 					default: true, | ||||
| 					label: 'This is a boolean property' | ||||
| 				}, | ||||
| 				bar: { | ||||
| 					type: 'number', | ||||
| 					default: 300, | ||||
| 					label: 'This is a number property' | ||||
| 				}, | ||||
| 				baz: { | ||||
| 					type: 'string', | ||||
| 					default: 'Misskey makes you happy.', | ||||
| 					label: 'This is a string property' | ||||
| 				}, | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		async openDrive() { | ||||
| 			os.selectDriveFile(); | ||||
| 		}, | ||||
|  | ||||
| 		async selectUser() { | ||||
| 			os.selectUser(); | ||||
| 		}, | ||||
|  | ||||
| 		async openMenu(ev) { | ||||
| 			os.modalMenu([{ | ||||
| 				type: 'label', | ||||
| 				text: 'Fruits' | ||||
| 			}, { | ||||
| 				text: 'Create some apples', | ||||
| 				action: () => {}, | ||||
| 			}, { | ||||
| 				text: 'Read some oranges', | ||||
| 				action: () => {}, | ||||
| 			}, { | ||||
| 				text: 'Update some melons', | ||||
| 				action: () => {}, | ||||
| 			}, null, { | ||||
| 				text: 'Delete some bananas', | ||||
| 				danger: true, | ||||
| 				action: () => {}, | ||||
| 			}], ev.currentTarget || ev.target); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -46,7 +46,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'; | ||||
| @@ -217,6 +217,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'), | ||||
|   | ||||
| @@ -26,7 +26,6 @@ | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faChevronDown } from '@fortawesome/free-solid-svg-icons'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
|   | ||||
| @@ -7,7 +7,9 @@ | ||||
| 				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> | ||||
| 					<slot name="header"></slot> | ||||
| 				</span> | ||||
| 				<slot name="buttons"></slot> | ||||
| 				<slot name="buttons"> | ||||
| 					<button class="_button" style="pointer-events: none;"></button> | ||||
| 				</slot> | ||||
| 			</div> | ||||
| 			<div class="body" v-if="padding"> | ||||
| 				<div class="_section"> | ||||
| @@ -313,11 +315,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,15 +375,13 @@ export default defineComponent({ | ||||
| 		width: 100%; | ||||
|     height: 100%; | ||||
|  | ||||
| 		--section-padding: 16px; | ||||
|  | ||||
| 		> .header { | ||||
| 			$height: 50px; | ||||
| 			display: flex; | ||||
| 			position: relative; | ||||
| 			z-index: 1; | ||||
| 			flex-shrink: 0; | ||||
| 			box-shadow: 0px 1px var(--divider); | ||||
| 			cursor: move; | ||||
| 			user-select: none; | ||||
| 			height: $height; | ||||
|  | ||||
| @@ -399,6 +401,8 @@ export default defineComponent({ | ||||
| 				white-space: nowrap; | ||||
| 				overflow: hidden; | ||||
| 				text-overflow: ellipsis; | ||||
| 				text-align: center; | ||||
| 				cursor: move; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -55,11 +55,11 @@ export default defineComponent({ | ||||
| 	props: { | ||||
| 		currentVisibility: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 			required: true | ||||
| 		}, | ||||
| 		currentLocalOnly: { | ||||
| 			type: Boolean, | ||||
| 			required: false | ||||
| 			required: true | ||||
| 		}, | ||||
| 		src: { | ||||
| 			required: false | ||||
| @@ -68,7 +68,7 @@ export default defineComponent({ | ||||
| 	emits: ['change-visibility', 'change-local-only', 'closed'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			v: this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : (this.currentVisibility || this.$store.state.settings.defaultNoteVisibility), | ||||
| 			v: this.currentVisibility, | ||||
| 			localOnly: this.currentLocalOnly, | ||||
| 			faGlobe, faUnlock, faEnvelope, faHome, faBiohazard, faToggleOn, faToggleOff | ||||
| 		} | ||||
| @@ -81,9 +81,6 @@ export default defineComponent({ | ||||
| 	methods: { | ||||
| 		choose(visibility) { | ||||
| 			this.v = visibility; | ||||
| 			if (this.$store.state.settings.rememberNoteVisibility) { | ||||
| 				this.$store.commit('deviceUser/setVisibility', visibility); | ||||
| 			} | ||||
| 			this.$emit('change-visibility', visibility); | ||||
| 			this.$nextTick(() => { | ||||
| 				this.$refs.modal.close(); | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| <template> | ||||
| <MkModal ref="modal" @click="type === 'success' ? done() : () => {}" @closed="$emit('closed')"> | ||||
| 	<div class="iuyakobc" :class="type"> | ||||
| 		<Fa class="icon" v-if="type === 'success'" :icon="faCheck"/> | ||||
| 		<Fa class="icon" v-else-if="type === 'waiting'" :icon="faSpinner" pulse/> | ||||
| <MkModal ref="modal" @click="success ? done() : () => {}" @closed="$emit('closed')"> | ||||
| 	<div class="iuyakobc" :class="{ iconOnly: (text == null) || success }"> | ||||
| 		<Fa class="icon success" v-if="success" :icon="faCheck"/> | ||||
| 		<Fa class="icon waiting" v-else :icon="faSpinner" pulse/> | ||||
| 		<div class="text" v-if="text && !success">{{ text }}<MkEllipsis/></div> | ||||
| 	</div> | ||||
| </MkModal> | ||||
| </template> | ||||
| @@ -18,12 +19,18 @@ export default defineComponent({ | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		type: { | ||||
| 			required: true | ||||
| 		success: { | ||||
| 			type: Boolean, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		showing: { | ||||
| 			required: true | ||||
| 		} | ||||
| 			type: Boolean, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		text: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['done', 'closed'], | ||||
| @@ -57,7 +64,19 @@ export default defineComponent({ | ||||
| 	text-align: center; | ||||
| 	background: var(--panel); | ||||
| 	border-radius: var(--radius); | ||||
| 	width: initial; | ||||
| 	width: 250px; | ||||
| 
 | ||||
| 	&.iconOnly { | ||||
| 		padding: 0; | ||||
| 		width: 96px; | ||||
| 		height: 96px; | ||||
| 
 | ||||
| 		> .icon { | ||||
| 			height: 100%; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .icon { | ||||
| 		font-size: 32px; | ||||
| 
 | ||||
| 		&.success { | ||||
| @@ -65,9 +84,12 @@ export default defineComponent({ | ||||
| 		} | ||||
| 
 | ||||
| 		&.waiting { | ||||
| 		> .icon { | ||||
| 			opacity: 0.7; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .text { | ||||
| 		margin-top: 16px; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -51,7 +51,7 @@ if (_DEV_) { | ||||
| document.addEventListener('touchend', () => {}, { passive: true }); | ||||
|  | ||||
| if (localStorage.getItem('theme') == null) { | ||||
| 	applyTheme(require('@/themes/white.json5')); | ||||
| 	applyTheme(require('@/themes/l-white.json5')); | ||||
| } | ||||
|  | ||||
| //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import Stream from '@/scripts/stream'; | ||||
| import { store } from '@/store'; | ||||
| import { apiUrl } from '@/config'; | ||||
| import MkPostFormDialog from '@/components/post-form-dialog.vue'; | ||||
| import MkWaitingDialog from '@/components/waiting-dialog.vue'; | ||||
|  | ||||
| const ua = navigator.userAgent.toLowerCase(); | ||||
| export const isMobile = /mobile|iphone|ipad|android/.test(ua); | ||||
| @@ -62,17 +63,39 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st | ||||
| 	return promise; | ||||
| } | ||||
|  | ||||
| export function apiWithDialog(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined, onSuccess?: (res: any) => void, onFailure?: (e: Error) => void) { | ||||
| 	const showing = ref(true); | ||||
| 	const state = ref('waiting'); | ||||
|  | ||||
| export function apiWithDialog( | ||||
| 	endpoint: string, | ||||
| 	data: Record<string, any> = {}, | ||||
| 	token?: string | null | undefined, | ||||
| 	onSuccess?: (res: any) => void, | ||||
| 	onFailure?: (e: Error) => void, | ||||
| ) { | ||||
| 	const promise = api(endpoint, data, token); | ||||
| 	promiseDialog(promise, onSuccess, onFailure ? onFailure : (e) => { | ||||
| 		dialog({ | ||||
| 			type: 'error', | ||||
| 			text: e.message + '<br>' + (e as any).id, | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	return promise; | ||||
| } | ||||
|  | ||||
| export function promiseDialog<T extends Promise<any>>( | ||||
| 	promise: T, | ||||
| 	onSuccess?: (res: any) => void, | ||||
| 	onFailure?: (e: Error) => void, | ||||
| 	text?: string, | ||||
| ): T { | ||||
| 	const showing = ref(true); | ||||
| 	const success = ref(false); | ||||
|  | ||||
| 	promise.then(res => { | ||||
| 		if (onSuccess) { | ||||
| 			showing.value = false; | ||||
| 			onSuccess(res); | ||||
| 		} else { | ||||
| 			state.value = 'success'; | ||||
| 			success.value = true; | ||||
| 			setTimeout(() => { | ||||
| 				showing.value = false; | ||||
| 			}, 1000); | ||||
| @@ -89,9 +112,11 @@ export function apiWithDialog(endpoint: string, data: Record<string, any> = {}, | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), { | ||||
| 		type: state, | ||||
| 		showing: showing | ||||
| 	// NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない) | ||||
| 	popup(MkWaitingDialog, { | ||||
| 		success: success, | ||||
| 		showing: showing, | ||||
| 		text: text, | ||||
| 	}, {}, 'closed'); | ||||
|  | ||||
| 	return promise; | ||||
| @@ -161,8 +186,8 @@ export function success() { | ||||
| 		setTimeout(() => { | ||||
| 			showing.value = false; | ||||
| 		}, 1000); | ||||
| 		popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), { | ||||
| 			type: 'success', | ||||
| 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||
| 			success: true, | ||||
| 			showing: showing | ||||
| 		}, { | ||||
| 			done: () => resolve(), | ||||
| @@ -173,8 +198,8 @@ export function success() { | ||||
| export function waiting() { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		const showing = ref(true); | ||||
| 		popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), { | ||||
| 			type: 'waiting', | ||||
| 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||
| 			success: false, | ||||
| 			showing: showing | ||||
| 		}, { | ||||
| 			done: () => resolve(), | ||||
| @@ -278,6 +303,10 @@ export function contextMenu(items: any[], ev: MouseEvent) { | ||||
| export function post(props: Record<string, any>) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない | ||||
| 		// NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、 | ||||
| 		//       Vueが渡されたコンポーネントに内部的に__propsというプロパティを生やす影響で、 | ||||
| 		//       複数のpost formを開いたときに場合によってはエラーになる | ||||
| 		//       もちろん複数のpost formを開けること自体Misskeyサイドのバグなのだが | ||||
| 		const { dispose } = popup(MkPostFormDialog, props, { | ||||
| 			closed: () => { | ||||
| 				resolve(); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div v-if="channel"> | ||||
| 	<div class="wpgynlbz _panel _vMargin" :class="{ hide: !showBanner }"> | ||||
| 		<XChannelFollow-button :channel="channel" :full="true" class="subscribe"/> | ||||
| <div v-if="channel" class="_section"> | ||||
| 	<div class="wpgynlbz _content _panel _vMargin" :class="{ hide: !showBanner }"> | ||||
| 		<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/> | ||||
| 		<button class="_button toggle" @click="() => showBanner = !showBanner"> | ||||
| 			<template v-if="showBanner"><Fa :icon="faAngleUp"/></template> | ||||
| 			<template v-else><Fa :icon="faAngleDown"/></template> | ||||
| @@ -10,8 +10,8 @@ | ||||
| 		</div> | ||||
| 		<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner"> | ||||
| 			<div class="status"> | ||||
| 				<div><Fa :icon="faUsers" fixed-width/><i18n path="_channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></i18n></div> | ||||
| 				<div><Fa :icon="faPencilAlt" fixed-width/><i18n path="_channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></i18n></div> | ||||
| 				<div><Fa :icon="faUsers" fixed-width/><i18n-t keypath="_channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></i18n-t></div> | ||||
| 				<div><Fa :icon="faPencilAlt" fixed-width/><i18n-t keypath="_channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></i18n-t></div> | ||||
| 			</div> | ||||
| 			<div class="fade"></div> | ||||
| 		</div> | ||||
| @@ -20,9 +20,9 @@ | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
| 	<XPostForm :channel="channel" class="post-form _panel _vMargin" fixed/> | ||||
| 	<XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed/> | ||||
|  | ||||
| 	<XTimeline class="_vMargin" src="channel" :channel="channelId" @before="before" @after="after"/> | ||||
| 	<XTimeline class="_content _vMargin" src="channel" :channel="channelId" @before="before" @after="after"/> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -91,6 +91,8 @@ export default defineComponent({ | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .wpgynlbz { | ||||
| 	position: relative; | ||||
|  | ||||
| 	> .subscribe { | ||||
| 		position: absolute; | ||||
| 		z-index: 1; | ||||
|   | ||||
| @@ -6,26 +6,24 @@ | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
| import parseAcct from '../../misc/acct/parse'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	created() { | ||||
| 		const acct = new URL(location.href).searchParams.get('acct'); | ||||
| 		if (acct == null) return; | ||||
|  | ||||
| 		const dialog = os.dialog({ | ||||
| 			type: 'waiting', | ||||
| 			text: this.$t('fetchingAsApObject') + '...', | ||||
| 			showOkButton: false, | ||||
| 			showCancelButton: false, | ||||
| 			cancelableByBgClick: false | ||||
| 		}); | ||||
| 		let promise; | ||||
|  | ||||
| 		if (acct.startsWith('https://')) { | ||||
| 			os.api('ap/show', { | ||||
| 			promise = os.api('ap/show', { | ||||
| 				uri: acct | ||||
| 			}).then(res => { | ||||
| 			}); | ||||
| 			promise.then(res => { | ||||
| 				if (res.type == 'User') { | ||||
| 					this.follow(res.object); | ||||
| 				} else if (res.type === 'Note') { | ||||
| 					this.$router.push(`/notes/${res.object.id}`); | ||||
| 				} else { | ||||
| 					os.dialog({ | ||||
| 						type: 'error', | ||||
| @@ -34,30 +32,15 @@ export default defineComponent({ | ||||
| 						window.close(); | ||||
| 					}); | ||||
| 				} | ||||
| 			}).catch(e => { | ||||
| 				os.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}).then(() => { | ||||
| 					window.close(); | ||||
| 				}); | ||||
| 			}).finally(() => { | ||||
| 				dialog.close(); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			os.api('users/show', parseAcct(acct)).then(user => { | ||||
| 			promise = os.api('users/show', parseAcct(acct)); | ||||
| 			promise.then(user => { | ||||
| 				this.follow(user); | ||||
| 			}).catch(e => { | ||||
| 				os.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}).then(() => { | ||||
| 					window.close(); | ||||
| 				}); | ||||
| 			}).finally(() => { | ||||
| 				dialog.close(); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
| 		os.promiseDialog(promise, null, null, this.$t('fetchingAsApObject')); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| @@ -73,19 +56,8 @@ export default defineComponent({ | ||||
| 				return; | ||||
| 			} | ||||
| 			 | ||||
| 			os.api('following/create', { | ||||
| 			os.apiWithDialog('following/create', { | ||||
| 				userId: user.id | ||||
| 			}).then(() => { | ||||
| 				os.success().then(() => { | ||||
| 					window.close(); | ||||
| 				}); | ||||
| 			}).catch(e => { | ||||
| 				os.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}).then(() => { | ||||
| 					window.close(); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										163
									
								
								src/client/pages/instance/abuses.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/client/pages/instance/abuses.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| <template> | ||||
| <div class=""> | ||||
| 	<div class="_section reports"> | ||||
| 		<div class="_content"> | ||||
| 			<div class="inputs" style="display: flex;"> | ||||
| 				<MkSelect v-model:value="state" style="margin: 0; flex: 1;"> | ||||
| 					<template #label>{{ $t('state') }}</template> | ||||
| 					<option value="all">{{ $t('all') }}</option> | ||||
| 					<option value="unresolved">{{ $t('unresolved') }}</option> | ||||
| 					<option value="resolved">{{ $t('resolved') }}</option> | ||||
| 				</MkSelect> | ||||
| 				<MkSelect v-model:value="targetUserOrigin" style="margin: 0; flex: 1;"> | ||||
| 					<template #label>{{ $t('targetUserOrigin') }}</template> | ||||
| 					<option value="combined">{{ $t('all') }}</option> | ||||
| 					<option value="local">{{ $t('local') }}</option> | ||||
| 					<option value="remote">{{ $t('remote') }}</option> | ||||
| 				</MkSelect> | ||||
| 				<MkSelect v-model:value="reporterOrigin" style="margin: 0; flex: 1;"> | ||||
| 					<template #label>{{ $t('reporterOrigin') }}</template> | ||||
| 					<option value="combined">{{ $t('all') }}</option> | ||||
| 					<option value="local">{{ $t('local') }}</option> | ||||
| 					<option value="remote">{{ $t('remote') }}</option> | ||||
| 				</MkSelect> | ||||
| 			</div> | ||||
| 			<!-- TODO | ||||
| 			<div class="inputs" style="display: flex; padding-top: 1.2em;"> | ||||
| 				<MkInput v-model:value="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:value="$refs.reports.reload()"> | ||||
| 					<span>{{ $t('username') }}</span> | ||||
| 				</MkInput> | ||||
| 				<MkInput v-model:value="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:value="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'"> | ||||
| 					<span>{{ $t('host') }}</span> | ||||
| 				</MkInput> | ||||
| 			</div> | ||||
| 			--> | ||||
|  | ||||
| 			<MkPagination :pagination="pagination" #default="{items}" ref="reports" :auto-margin="false" style="margin-top: var(--margin);"> | ||||
| 				<div class="bcekxzvu _card _vMargin" v-for="report in items" :key="report.id"> | ||||
| 					<div class="_content target"> | ||||
| 						<MkAvatar class="avatar" :user="report.targetUser"/> | ||||
| 						<div class="info"> | ||||
| 							<MkUserName class="name" :user="report.targetUser"/> | ||||
| 							<div class="acct">@{{ acct(report.targetUser) }}</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<div class="_content"> | ||||
| 						<div> | ||||
| 							<Mfm :text="report.comment"/> | ||||
| 						</div> | ||||
| 						<hr> | ||||
| 						<div>Reporter: <MkAcct :user="report.reporter"/></div> | ||||
| 						<div><MkTime :time="report.createdAt"/></div> | ||||
| 					</div> | ||||
| 					<div class="_footer"> | ||||
| 						<div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div> | ||||
| 						<MkButton @click="resolve(report)" primary v-if="!report.resolved">{{ $t('abuseMarkAsResolved') }}</MkButton> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faPlus, faUsers, faSearch, faBookmark, faMicrophoneSlash, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faSnowflake, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons'; | ||||
| import parseAcct from '../../../misc/acct/parse'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkInput from '@/components/ui/input.vue'; | ||||
| import MkSelect from '@/components/ui/select.vue'; | ||||
| import MkPagination from '@/components/ui/pagination.vue'; | ||||
| import { acct } from '../../filters/user'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton, | ||||
| 		MkInput, | ||||
| 		MkSelect, | ||||
| 		MkPagination, | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 					title: this.$t('abuseReports'), | ||||
| 					icon: faExclamationCircle | ||||
| 				}], | ||||
| 			}, | ||||
| 			searchUsername: '', | ||||
| 			searchHost: '', | ||||
| 			state: 'unresolved', | ||||
| 			reporterOrigin: 'combined', | ||||
| 			targetUserOrigin: 'combined', | ||||
| 			pagination: { | ||||
| 				endpoint: 'admin/abuse-user-reports', | ||||
| 				limit: 10, | ||||
| 				params: () => ({ | ||||
| 					state: this.state, | ||||
| 					reporterOrigin: this.reporterOrigin, | ||||
| 					targetUserOrigin: this.targetUserOrigin, | ||||
| 				}), | ||||
| 			}, | ||||
| 			faPlus, faUsers, faSearch, faBookmark, farBookmark, faMicrophoneSlash, faSnowflake | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	watch: { | ||||
| 		state() { | ||||
| 			this.$refs.reports.reload(); | ||||
| 		}, | ||||
|  | ||||
| 		reporterOrigin() { | ||||
| 			this.$refs.reports.reload(); | ||||
| 		}, | ||||
|  | ||||
| 		targetUserOrigin() { | ||||
| 			this.$refs.reports.reload(); | ||||
| 		}, | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		acct, | ||||
|  | ||||
| 		resolve(report) { | ||||
| 			os.apiWithDialog('admin/resolve-abuse-user-report', { | ||||
| 				reportId: report.id, | ||||
| 			}).then(() => { | ||||
| 				this.$refs.reports.removeItem(item => item.id === report.id); | ||||
| 			}); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .bcekxzvu { | ||||
| 	> .target { | ||||
| 		display: flex; | ||||
| 		width: 100%; | ||||
| 		box-sizing: border-box; | ||||
| 		text-align: left; | ||||
| 		align-items: center; | ||||
| 					 | ||||
| 		> .avatar { | ||||
| 			width: 42px; | ||||
| 			height: 42px; | ||||
| 		} | ||||
|  | ||||
| 		> .info { | ||||
| 			margin-left: 0.3em; | ||||
| 			padding: 0 8px; | ||||
| 			flex: 1; | ||||
|  | ||||
| 			> .name { | ||||
| 				font-weight: bold; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| @@ -15,10 +15,8 @@ | ||||
| 						<button class="emoji _panel _button" v-for="emoji in items" :key="emoji.id" @click="edit(emoji)"> | ||||
| 							<img :src="emoji.url" class="img" :alt="emoji.name"/> | ||||
| 							<div class="body"> | ||||
| 								<span class="name">{{ emoji.name }}</span> | ||||
| 								<span class="info"> | ||||
| 									<span class="category">{{ emoji.category }}</span> | ||||
| 								</span> | ||||
| 								<div class="name">{{ emoji.name }}</div> | ||||
| 								<div class="info">{{ emoji.category }}</div> | ||||
| 							</div> | ||||
| 						</button> | ||||
| 					</div> | ||||
| @@ -36,8 +34,8 @@ | ||||
| 						<div class="emoji _panel _button" v-for="emoji in items" :key="emoji.id" @click="remoteMenu(emoji, $event)"> | ||||
| 							<img :src="emoji.url" class="img" :alt="emoji.name"/> | ||||
| 							<div class="body"> | ||||
| 								<span class="name">{{ emoji.name }}</span> | ||||
| 								<span class="info">{{ emoji.host }}</span> | ||||
| 								<div class="name">{{ emoji.name }}</div> | ||||
| 								<div class="info">{{ emoji.host }}</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| @@ -106,24 +104,13 @@ export default defineComponent({ | ||||
| 		async add(e) { | ||||
| 			const files = await selectFile(e.currentTarget || e.target, null, true); | ||||
|  | ||||
| 			const dialog = os.dialog({ | ||||
| 				type: 'waiting', | ||||
| 				text: this.$t('doing') + '...', | ||||
| 				showOkButton: false, | ||||
| 				showCancelButton: false, | ||||
| 				cancelableByBgClick: false | ||||
| 			}); | ||||
| 			 | ||||
| 			Promise.all(files.map(file => os.api('admin/emoji/add', { | ||||
| 			const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { | ||||
| 				fileId: file.id, | ||||
| 			}))) | ||||
| 			.then(() => { | ||||
| 			}))); | ||||
| 			promise.then(() => { | ||||
| 				this.$refs.emojis.reload(); | ||||
| 				os.success(); | ||||
| 			}) | ||||
| 			.finally(() => { | ||||
| 				dialog.cancel(); | ||||
| 			}); | ||||
| 			os.promiseDialog(promise); | ||||
| 		}, | ||||
|  | ||||
| 		async edit(emoji) { | ||||
| @@ -193,13 +180,14 @@ export default defineComponent({ | ||||
| 						overflow: hidden; | ||||
|  | ||||
| 						> .name { | ||||
| 							display: block; | ||||
| 							text-overflow: ellipsis; | ||||
| 							overflow: hidden; | ||||
| 						} | ||||
|  | ||||
| 						> .info { | ||||
| 							opacity: 0.5; | ||||
| 							text-overflow: ellipsis; | ||||
| 							overflow: hidden; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| @@ -233,14 +221,12 @@ export default defineComponent({ | ||||
| 						overflow: hidden; | ||||
|  | ||||
| 						> .name { | ||||
| 							display: block; | ||||
| 							text-overflow: ellipsis; | ||||
| 							overflow: hidden; | ||||
| 						} | ||||
|  | ||||
| 						> .info { | ||||
| 							opacity: 0.5; | ||||
| 							display: block; | ||||
| 							text-overflow: ellipsis; | ||||
| 							overflow: hidden; | ||||
| 						} | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|  | ||||
| 	<section class="_section info"> | ||||
| 		<div class="_content"> | ||||
| 			<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()" style="margin:0;"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput> | ||||
| 			<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput> | ||||
| 		</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkSwitch v-model:value="enableLocalTimeline" @update:value="save()">{{ $t('enableLocalTimeline') }}</MkSwitch> | ||||
| @@ -91,8 +91,8 @@ | ||||
| 			<MkInfo>{{ $t('emptyToDisableSmtpAuth') }}</MkInfo> | ||||
| 			<MkSwitch v-model:value="smtpSecure" :disabled="!enableEmail">{{ $t('smtpSecure') }}<template #desc>{{ $t('smtpSecureInfo') }}</template></MkSwitch> | ||||
| 			<div> | ||||
| 				<MkButton :disabled="!enableEmail" inline @click="testEmail()">{{ $t('testEmail') }}</MkButton> | ||||
| 			  <MkButton :disabled="!enableEmail" primary inline @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton> | ||||
| 				<MkButton :disabled="!enableEmail" inline @click="testEmail()">{{ $t('testEmail') }}</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</section> | ||||
| @@ -131,7 +131,7 @@ | ||||
| 			<MkSwitch v-model:value="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></MkSwitch> | ||||
| 			<MkSwitch v-model:value="proxyRemoteFiles">{{ $t('proxyRemoteFiles') }}<template #desc>{{ $t('proxyRemoteFilesDescription') }}</template></MkSwitch> | ||||
| 			<MkInput v-model:value="localDriveCapacityMb" type="number">{{ $t('driveCapacityPerLocalAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></MkInput> | ||||
| 			<MkInput v-model:value="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" style="margin-bottom: 0;">{{ $t('driveCapacityPerRemoteAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></MkInput> | ||||
| 			<MkInput v-model:value="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">{{ $t('driveCapacityPerRemoteAccount') }}<template #suffix>MB</template><template #desc>{{ $t('inMb') }}</template></MkInput> | ||||
| 		</div> | ||||
| 		<div class="_footer"> | ||||
| 			<MkButton primary @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton> | ||||
| @@ -169,7 +169,7 @@ | ||||
| 	<section class="_section"> | ||||
| 		<div class="_title"><Fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div> | ||||
| 		<div class="_content"> | ||||
| 			<MkInput :value="proxyAccount ? proxyAccount.username : null" style="margin: 0;" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput> | ||||
| 			<MkInput :value="proxyAccount ? proxyAccount.username : null" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput> | ||||
| 			<MkButton primary @click="chooseProxyAccount">{{ $t('chooseProxyAccount') }}</MkButton> | ||||
| 		</div> | ||||
| 	</section> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="thvuemwp" :class="{ isMe }"> | ||||
| <div class="thvuemwp" :class="{ isMe }" v-size="{ max: [400, 500] }"> | ||||
| 	<MkAvatar class="avatar" :user="message.user"/> | ||||
| 	<div class="content"> | ||||
| 		<div class="balloon" :class="{ noText: message.text == null }" > | ||||
| @@ -92,11 +92,6 @@ export default defineComponent({ | ||||
| 		width: 54px; | ||||
| 		height: 54px; | ||||
| 		transition: all 0.1s ease; | ||||
|  | ||||
| 		@media (max-width: 400px) { | ||||
| 			width: 48px; | ||||
| 			height: 48px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	> .content { | ||||
| @@ -175,14 +170,6 @@ export default defineComponent({ | ||||
| 					font-size: 1em; | ||||
| 					color: rgba(#000, 0.8); | ||||
|  | ||||
| 					@media (max-width: 500px) { | ||||
| 						padding: 8px 16px; | ||||
| 					} | ||||
|  | ||||
| 					@media (max-width: 400px) { | ||||
| 						font-size: 0.9em; | ||||
| 					} | ||||
|  | ||||
| 					& + .file { | ||||
| 						> a { | ||||
| 							border-radius: 0 0 16px 16px; | ||||
| @@ -326,5 +313,34 @@ export default defineComponent({ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&.max-width_400px { | ||||
| 		> .avatar { | ||||
| 			width: 48px; | ||||
| 			height: 48px; | ||||
| 		} | ||||
|  | ||||
| 		> .content { | ||||
| 			> .balloon { | ||||
| 				> .content { | ||||
| 					> .text { | ||||
| 						font-size: 0.9em; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	&.max-width_500px { | ||||
| 		> .content { | ||||
| 			> .balloon { | ||||
| 				> .content { | ||||
| 					> .text { | ||||
| 						padding: 8px 16px; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -8,8 +8,8 @@ | ||||
|  | ||||
| 		<div class="_section"> | ||||
| 			<div class="_content"> | ||||
| 				<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" style="margin-bottom: var(--margin)"/> | ||||
| 				<XNote v-model:note="note" :key="note.id" :detail="true"/> | ||||
| 				<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_vMargin"/> | ||||
| 				<XNote v-model:note="note" :key="note.id" :detail="true" class="_vMargin"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XContainer from '../page-editor.container.vue'; | ||||
| @@ -34,7 +34,8 @@ import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XContainer, MkSelect | ||||
| 		XContainer, MkSelect, | ||||
| 		XBlocks: defineAsyncComponent(() => import('../page-editor.blocks.vue')), | ||||
| 	}, | ||||
|  | ||||
| 	inject: ['getPageBlockList'], | ||||
| @@ -54,10 +55,6 @@ export default defineComponent({ | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	beforeCreate() { | ||||
| 		this.$options.components.XBlocks = require('../page-editor.blocks.vue').default | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		if (this.value.children == null) this.value.children = []; | ||||
| 		if (this.value.var === undefined) this.value.var = null; | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; | ||||
| @@ -26,7 +26,8 @@ import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XContainer | ||||
| 		XContainer, | ||||
| 		XBlocks: defineAsyncComponent(() => import('../page-editor.blocks.vue')), | ||||
| 	}, | ||||
|  | ||||
| 	inject: ['getPageBlockList'], | ||||
| @@ -46,10 +47,6 @@ export default defineComponent({ | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	beforeCreate() { | ||||
| 		this.$options.components.XBlocks = require('../page-editor.blocks.vue').default | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		if (this.value.title == null) this.value.title = null; | ||||
| 		if (this.value.children == null) this.value.children = []; | ||||
|   | ||||
| @@ -28,7 +28,6 @@ | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faBars, faAngleUp, faAngleDown } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faTrashAlt } from '@fortawesome/free-regular-svg-icons'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
|   | ||||
| @@ -56,7 +56,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineAsyncComponent, defineComponent } from 'vue'; | ||||
| import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import XContainer from './page-editor.container.vue'; | ||||
| @@ -66,7 +66,8 @@ import * as os from '@/os'; | ||||
|  | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XContainer, MkTextarea | ||||
| 		XContainer, MkTextarea, | ||||
| 		XV: defineAsyncComponent(() => import('./page-editor.script-block.vue')), | ||||
| 	}, | ||||
|  | ||||
| 	inject: ['getScriptBlockList'], | ||||
| @@ -135,10 +136,6 @@ export default defineComponent({ | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	beforeCreate() { | ||||
| 		this.$options.components.XV = require('./page-editor.script-block.vue').default; | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		if (this.value.value == null) this.value.value = null; | ||||
|  | ||||
|   | ||||
| @@ -2,18 +2,20 @@ | ||||
| <div> | ||||
| 	<MkTab v-model:value="tab" :items="[{ label: $t('_pages.my'), value: 'my', icon: faEdit }, { label: $t('_pages.liked'), value: 'liked', icon: faHeart }]"/> | ||||
|  | ||||
| 	<div class="rknalgpo my" v-if="tab === 'my'"> | ||||
| 	<div class="_section"> | ||||
| 		<div class="rknalgpo _content my" v-if="tab === 'my'"> | ||||
| 			<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton> | ||||
| 			<MkPagination :pagination="myPagesPagination" #default="{items}"> | ||||
| 				<MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
|  | ||||
| 	<div class="rknalgpo" v-if="tab === 'liked'"> | ||||
| 		<div class="rknalgpo _content" v-if="tab === 'liked'"> | ||||
| 			<MkPagination :pagination="likedPagesPagination" #default="{items}"> | ||||
| 				<MkPagePreview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -64,8 +66,6 @@ export default defineComponent({ | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .rknalgpo { | ||||
| 	padding: 16px; | ||||
|  | ||||
| 	&.my .ckltabjg:first-child { | ||||
| 		margin-top: 16px; | ||||
| 	} | ||||
|   | ||||
| @@ -44,6 +44,7 @@ export default defineComponent({ | ||||
| 				this.exportTarget == 'following' ? 'i/export-following' : | ||||
| 				this.exportTarget == 'blocking' ? 'i/export-blocking' : | ||||
| 				this.exportTarget == 'user-lists' ? 'i/export-user-lists' : | ||||
| 				this.exportTarget == 'mute' ? 'i/export-mute' : | ||||
| 				null, {}) | ||||
| 			.then(() => { | ||||
| 				os.dialog({ | ||||
| @@ -69,31 +70,15 @@ export default defineComponent({ | ||||
| 			data.append('file', file); | ||||
| 			data.append('i', this.$store.state.i.token); | ||||
|  | ||||
| 			const dialog = os.dialog({ | ||||
| 				type: 'waiting', | ||||
| 				text: this.$t('uploading') + '...', | ||||
| 				showOkButton: false, | ||||
| 				showCancelButton: false, | ||||
| 				cancelableByBgClick: false | ||||
| 			}); | ||||
|  | ||||
| 			fetch(apiUrl + '/drive/files/create', { | ||||
| 			const promise = fetch(apiUrl + '/drive/files/create', { | ||||
| 				method: 'POST', | ||||
| 				body: data | ||||
| 			}) | ||||
| 			.then(response => response.json()) | ||||
| 			.then(f => { | ||||
| 				this.reqImport(f); | ||||
| 			}) | ||||
| 			.catch(e => { | ||||
| 				os.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| 			}) | ||||
| 			.finally(() => { | ||||
| 				dialog.close(); | ||||
| 			}); | ||||
| 			os.promiseDialog(promise); | ||||
| 		}, | ||||
|  | ||||
| 		reqImport(file) { | ||||
|   | ||||
| @@ -106,6 +106,14 @@ | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="_card _vMargin"> | ||||
| 			<div class="_title">Waiting dialog</div> | ||||
| 			<div class="_content"> | ||||
| 				<MkButton inline @click="openWaitingDialog()">icon only</MkButton> | ||||
| 				<MkButton inline @click="openWaitingDialog('Doing')">with text</MkButton> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="_card _vMargin"> | ||||
| 			<div class="_title">Messaging window</div> | ||||
| 			<div class="_content"> | ||||
| @@ -224,6 +232,13 @@ export default defineComponent({ | ||||
| 			os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue'))); | ||||
| 		}, | ||||
|  | ||||
| 		openWaitingDialog(text?) { | ||||
| 			const promise = new Promise((resolve, reject) => { | ||||
| 				setTimeout(resolve, 2000); | ||||
| 			}); | ||||
| 			os.promiseDialog(promise, null, null, text); | ||||
| 		}, | ||||
|  | ||||
| 		resetTutorial() { | ||||
| 			this.$store.dispatch('settings/set', { key: 'tutorial', value: 0 }); | ||||
| 		}, | ||||
|   | ||||
| @@ -75,6 +75,12 @@ | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</section> | ||||
| 	<section class="_section"> | ||||
| 		<details class="_content"> | ||||
| 			<summary>{{ $t('sample') }}</summary> | ||||
| 			<MkSample/> | ||||
| 		</details> | ||||
| 	</section> | ||||
| 	<section class="_section"> | ||||
| 		<div class="_content"> | ||||
| 			<MkButton inline @click="preview">{{ $t('preview') }}</MkButton> | ||||
| @@ -88,16 +94,17 @@ | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faPalette, faChevronDown, faKeyboard } from '@fortawesome/free-solid-svg-icons'; | ||||
| import * as JSON5 from 'json5'; | ||||
| import { toUnicode } from 'punycode'; | ||||
|  | ||||
| import MkRadio from '@/components/ui/radio.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkInput from '@/components/ui/input.vue'; | ||||
| import MkTextarea from '@/components/ui/textarea.vue'; | ||||
| import MkSelect from '@/components/ui/select.vue'; | ||||
| import MkSample from '@/components/sample.vue'; | ||||
|  | ||||
| import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@/scripts/theme-editor'; | ||||
| import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } from '@/scripts/theme'; | ||||
| import { toUnicode } from 'punycode'; | ||||
| import { host } from '@/config'; | ||||
| import * as os from '@/os'; | ||||
|  | ||||
| @@ -107,7 +114,8 @@ export default defineComponent({ | ||||
| 		MkButton, | ||||
| 		MkInput, | ||||
| 		MkTextarea, | ||||
| 		MkSelect | ||||
| 		MkSelect, | ||||
| 		MkSample, | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
|   | ||||
| @@ -49,47 +49,63 @@ export default defineComponent({ | ||||
| 			menuOpened: false, | ||||
| 			queue: 0, | ||||
| 			width: 0, | ||||
| 			INFO: { | ||||
| 				header: [{ | ||||
| 			INFO: computed(() => { | ||||
| 				const header = [{ | ||||
| 					id: 'home', | ||||
| 					title: null, | ||||
| 					tooltip: this.$t('_timelines.home'), | ||||
| 					icon: faHome, | ||||
| 					onClick: () => { this.src = 'home'; this.saveSrc(); }, | ||||
| 					selected: computed(() => this.src === 'home') | ||||
| 				}, { | ||||
| 				}]; | ||||
|  | ||||
| 				if (!this.$store.state.instance.meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin) { | ||||
| 					header.push({ | ||||
| 						id: 'local', | ||||
| 						title: null, | ||||
| 						tooltip: this.$t('_timelines.local'), | ||||
| 						icon: faComments, | ||||
| 						onClick: () => { this.src = 'local'; this.saveSrc(); }, | ||||
| 						selected: computed(() => this.src === 'local') | ||||
| 				}, { | ||||
| 					}); | ||||
|  | ||||
| 					header.push({ | ||||
| 						id: 'social', | ||||
| 						title: null, | ||||
| 						tooltip: this.$t('_timelines.social'), | ||||
| 						icon: faShareAlt, | ||||
| 						onClick: () => { this.src = 'social'; this.saveSrc(); }, | ||||
| 						selected: computed(() => this.src === 'social') | ||||
| 				}, { | ||||
| 					}); | ||||
| 				} | ||||
|  | ||||
| 				if (!this.$store.state.instance.meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin) { | ||||
| 					header.push({ | ||||
| 						id: 'global', | ||||
| 						title: null, | ||||
| 						tooltip: this.$t('_timelines.global'), | ||||
| 						icon: faGlobe, | ||||
| 						onClick: () => { this.src = 'global'; this.saveSrc(); }, | ||||
| 						selected: computed(() => this.src === 'global') | ||||
| 				}, { | ||||
| 					}); | ||||
| 				} | ||||
|  | ||||
| 				header.push({ | ||||
| 					id: 'other', | ||||
| 					title: null, | ||||
| 					icon: faEllipsisH, | ||||
| 					onClick: this.choose, | ||||
| 					indicate: computed(() => this.$store.state.i.hasUnreadAntenna || this.$store.state.i.hasUnreadChannel) | ||||
| 				}], | ||||
| 				}); | ||||
|  | ||||
| 				return { | ||||
| 					header, | ||||
| 					action: { | ||||
| 						icon: faPencilAlt, | ||||
| 						handler: () => os.post() | ||||
| 					} | ||||
| 			}, | ||||
| 				}; | ||||
| 			}), | ||||
| 			faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faComments, faListUl, faSatellite, faSatelliteDish, faCircle | ||||
| 		}; | ||||
| 	}, | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| <template> | ||||
| <div class="mk-user-page" v-if="user" v-size="{ max: [500] }"> | ||||
| 	<MkRemoteCaution v-if="user.host != null" :href="user.url" style="margin-bottom: var(--margin)"/> | ||||
|  | ||||
| 	<!-- TODO --> | ||||
| 	<!-- <div class="punished" v-if="user.isSuspended"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSuspended') }}</div> --> | ||||
| 	<!-- <div class="punished" v-if="user.isSilenced"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSilenced') }}</div> --> | ||||
|  | ||||
| 	<div class="profile _section _fitBottom"> | ||||
| 		<div class="_content" :key="user.id"> | ||||
| 		<MkRemoteCaution v-if="user.host != null" :href="user.url" class="_content _vMargin"/> | ||||
|  | ||||
| 		<div class="_content _vMargin" :key="user.id"> | ||||
| 			<div class="banner-container" :style="style"> | ||||
| 				<div class="banner" ref="banner" :style="style"></div> | ||||
| 				<div class="fade"></div> | ||||
| @@ -85,8 +85,8 @@ | ||||
|  | ||||
| 	<router-view :user="user"></router-view> | ||||
| 	<template v-if="$route.name == 'user'"> | ||||
| 		<div class="_section" v-if="user.pinnedNotes.length > 0"> | ||||
| 			<div class="_content _vMargin"> | ||||
| 		<div class="_section"> | ||||
| 			<div class="_content _vMargin" v-if="user.pinnedNotes.length > 0"> | ||||
| 				<XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/> | ||||
| 			</div> | ||||
| 			<MkFolder :body-togglable="true" class="_content _vMargin" persist-key="user-images"> | ||||
|   | ||||
| @@ -86,6 +86,7 @@ export const router = createRouter({ | ||||
| 		{ path: '/instance/federation', component: page('instance/federation') }, | ||||
| 		{ path: '/instance/relays', component: page('instance/relays') }, | ||||
| 		{ path: '/instance/announcements', component: page('instance/announcements') }, | ||||
| 		{ path: '/instance/abuses', component: page('instance/abuses') }, | ||||
| 		{ path: '/notes/:note', name: 'note', component: page('note') }, | ||||
| 		{ path: '/tags/:tag', component: page('tag') }, | ||||
| 		{ path: '/auth/:token', component: page('auth') }, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash, faPlug, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faSnowflake, faEnvelope } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| @@ -102,6 +102,12 @@ export function getUserMenu(user) { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	async function reportAbuse() { | ||||
| 		os.popup(await import('@/components/abuse-report-window.vue'), { | ||||
| 			user: user, | ||||
| 		}, {}, 'closed'); | ||||
| 	} | ||||
|  | ||||
| 	async function getConfirmed(text: string): Promise<boolean> { | ||||
| 		const confirm = await os.dialog({ | ||||
| 			type: 'warning', | ||||
| @@ -157,6 +163,12 @@ export function getUserMenu(user) { | ||||
| 			action: toggleBlock | ||||
| 		}]); | ||||
|  | ||||
| 		menu = menu.concat([null, { | ||||
| 			icon: faExclamationCircle, | ||||
| 			text: i18n.global.t('reportAbuse'), | ||||
| 			action: reportAbuse | ||||
| 		}]); | ||||
|  | ||||
| 		if (store.getters.isSignedIn && (store.state.i.isAdmin || store.state.i.isModerator)) { | ||||
| 			menu = menu.concat([null, { | ||||
| 				icon: faMicrophoneSlash, | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { createAiScriptEnv } from '../aiscript/api'; | ||||
| import { collectPageVars } from '../collect-page-vars'; | ||||
| import { initLib } from './lib'; | ||||
| import * as os from '@/os'; | ||||
| import { markRaw, ref, Ref } from 'vue'; | ||||
|  | ||||
| type Fn = { | ||||
| 	slots: string[]; | ||||
| @@ -23,7 +24,7 @@ export class Hpml { | ||||
| 	public aiscript?: AiScript; | ||||
| 	private pageVarUpdatedCallback; | ||||
| 	public canvases: Record<string, HTMLCanvasElement> = {}; | ||||
| 	public vars: Record<string, any>; | ||||
| 	public vars: Ref<Record<string, any>> = ref({}); | ||||
| 	public page: Record<string, any>; | ||||
|  | ||||
| 	private opts: { | ||||
| @@ -38,7 +39,7 @@ export class Hpml { | ||||
| 		this.opts = opts; | ||||
|  | ||||
| 		if (this.opts.enableAiScript) { | ||||
| 			this.aiscript = new AiScript({ ...createAiScriptEnv({ | ||||
| 			this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({ | ||||
| 				storageKey: 'pages:' + this.page.id | ||||
| 			}), ...initLib(this)}, { | ||||
| 				in: (q) => { | ||||
| @@ -56,7 +57,7 @@ export class Hpml { | ||||
| 				}, | ||||
| 				log: (type, params) => { | ||||
| 				}, | ||||
| 			}); | ||||
| 			})); | ||||
|  | ||||
| 			this.aiscript.scope.opts.onUpdated = (name, value) => { | ||||
| 				this.eval(); | ||||
| @@ -89,7 +90,7 @@ export class Hpml { | ||||
| 	@autobind | ||||
| 	public eval() { | ||||
| 		try { | ||||
| 			this.vars = this.evaluateVars(); | ||||
| 			this.vars.value = this.evaluateVars(); | ||||
| 		} catch (e) { | ||||
| 			//this.onError(e); | ||||
| 		} | ||||
| @@ -99,7 +100,7 @@ export class Hpml { | ||||
| 	public interpolate(str: string) { | ||||
| 		if (str == null) return null; | ||||
| 		return str.replace(/{(.+?)}/g, match => { | ||||
| 			const v = this.vars ? this.vars[match.slice(1, -1).trim()] : null; | ||||
| 			const v = this.vars[match.slice(1, -1).trim()]; | ||||
| 			return v == null ? 'NULL' : v.toString(); | ||||
| 		}); | ||||
| 	} | ||||
|   | ||||
| @@ -13,7 +13,7 @@ export function popout(path: string, w?: HTMLElement) { | ||||
| 			`width=${width}, height=${height}, top=${y}, left=${x}`); | ||||
| 	} else { | ||||
| 		const width = 400; | ||||
| 		const height = 450; | ||||
| 		const height = 500; | ||||
| 		const x = window.top.outerHeight / 2 + window.top.screenY - (height / 2); | ||||
| 		const y = window.top.outerWidth / 2 + window.top.screenX - (width / 2); | ||||
| 		window.open(url, url, | ||||
|   | ||||
| @@ -48,28 +48,19 @@ export async function search(q?: string | null | undefined) { | ||||
| 	} | ||||
|  | ||||
| 	if (q.startsWith('https://')) { | ||||
| 		const dialog = os.dialog({ | ||||
| 			type: 'waiting', | ||||
| 			text: i18n.global.t('fetchingAsApObject') + '...', | ||||
| 			showOkButton: false, | ||||
| 			showCancelButton: false, | ||||
| 			cancelableByBgClick: false | ||||
| 		}); | ||||
|  | ||||
| 		try { | ||||
| 			const res = await os.api('ap/show', { | ||||
| 		const promise = os.api('ap/show', { | ||||
| 			uri: q | ||||
| 		}); | ||||
| 			dialog.cancel(); | ||||
|  | ||||
| 		os.promiseDialog(promise, null, null, i18n.global.t('fetchingAsApObject')); | ||||
|  | ||||
| 		const res = await promise; | ||||
|  | ||||
| 		if (res.type === 'User') { | ||||
| 			router.push(`/@${res.object.username}@${res.object.host}`); | ||||
| 		} else if (res.type === 'Note') { | ||||
| 			router.push(`/notes/${res.object.id}`); | ||||
| 		} | ||||
| 		} catch (e) { | ||||
| 			dialog.cancel(); | ||||
| 			// TODO: Show error | ||||
| 		} | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|   | ||||
| @@ -6,7 +6,7 @@ export type Theme = { | ||||
| 	author: string; | ||||
| 	desc?: string; | ||||
| 	base?: 'dark' | 'light'; | ||||
| 	props: { [key: string]: string }; | ||||
| 	props: Record<string, string>; | ||||
| }; | ||||
|  | ||||
| export const lightTheme: Theme = require('../themes/_light.json5'); | ||||
| @@ -15,18 +15,19 @@ export const darkTheme: Theme = require('../themes/_dark.json5'); | ||||
| export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); | ||||
|  | ||||
| export const builtinThemes = [ | ||||
| 	require('../themes/white.json5'), | ||||
| 	require('../themes/black.json5'), | ||||
| 	require('../themes/lilac.json5'), | ||||
| 	require('../themes/halloween.json5'), | ||||
| 	require('../themes/city.json5'), | ||||
| 	require('../themes/rainy.json5'), | ||||
| 	require('../themes/urban.json5'), | ||||
| 	require('../themes/cafe.json5'), | ||||
| 	require('../themes/chocolate.json5'), | ||||
| 	require('../themes/danboard.json5'), | ||||
| 	require('../themes/olive.json5'), | ||||
| 	require('../themes/ocean.json5'), | ||||
| 	require('../themes/l-white.json5'), | ||||
| 	require('../themes/l-red.json5'), | ||||
| 	require('../themes/l-green.json5'), | ||||
| 	require('../themes/l-blue.json5'), | ||||
| 	require('../themes/l-apricot.json5'), | ||||
|  | ||||
| 	require('../themes/d-black.json5'), | ||||
| 	require('../themes/d-red.json5'), | ||||
| 	require('../themes/d-green.json5'), | ||||
| 	require('../themes/d-blue.json5'), | ||||
| 	require('../themes/d-persimmon.json5'), | ||||
|  | ||||
| 	require('../themes/d-battery-saver.json5'), | ||||
| ] as Theme[]; | ||||
|  | ||||
| let timeout = null; | ||||
|   | ||||
| @@ -61,7 +61,7 @@ export const defaultDeviceSettings = { | ||||
| 	accounts: [], | ||||
| 	recentEmojis: [], | ||||
| 	themes: [], | ||||
| 	darkTheme: '8c539dc1-0fab-4d47-9194-39c508e9bfe1', | ||||
| 	darkTheme: '8050783a-7f63-445a-b270-36d0f6ba1677', | ||||
| 	lightTheme: '4eea646f-7afa-4645-83e9-83af0333cd37', | ||||
| 	darkMode: false, | ||||
| 	deckMode: false, | ||||
|   | ||||
| @@ -109,6 +109,11 @@ textarea, input { | ||||
| 	-webkit-tap-highlight-color: transparent; | ||||
| } | ||||
|  | ||||
| optgroup, option { | ||||
| 	background: var(--panel); | ||||
| 	color: var(--fg); | ||||
| } | ||||
|  | ||||
| hr { | ||||
| 	margin: var(--margin) 0 var(--margin) 0; | ||||
| 	border: none; | ||||
| @@ -319,6 +324,7 @@ hr { | ||||
|  | ||||
| 	> ._title, | ||||
| 	> ._content { | ||||
| 		box-sizing: border-box; | ||||
| 		max-width: var(--baseContentWidth); | ||||
| 		margin: 0 auto; | ||||
| 	} | ||||
|   | ||||
| @@ -1,20 +0,0 @@ | ||||
| { | ||||
| 	id: 'bd6577b4-8154-4a2d-b7ce-7bf59f1fa3f5', | ||||
|  | ||||
| 	name: 'Chocolate', | ||||
| 	author: 'syuilo', | ||||
| 	desc: 'So sweet', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(199, 69, 32)', | ||||
| 		bg: 'rgb(35, 25, 21)', | ||||
| 		fg: 'rgb(216, 208, 199)', | ||||
| 		panel: 'rgb(64, 39, 27)', | ||||
| 		renote: '@accent', | ||||
| 		link: '@accent', | ||||
| 		mention: '@accent', | ||||
| 		hashtag: '@accent', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| { | ||||
| 	id: '8e4aa0ab-a439-43c8-b67d-16d5c03936de', | ||||
|  | ||||
| 	name: 'City', | ||||
| 	author: 'Zheneha', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(255, 153, 0)', | ||||
| 		panel: 'rgb(30, 30, 30)', | ||||
| 		bg: 'rgb(0, 0, 0)', | ||||
| 		fg: 'rgb(255, 255, 255)', | ||||
| 		infoFg: '@accent', | ||||
| 		infoBg: 'rgb(0, 0, 0)', | ||||
| 		header: 'rgb(37, 37, 37)', | ||||
| 		mention: '@accent', | ||||
| 		hashtag: '@accent', | ||||
| 		link: '@accent', | ||||
| 		renote: 'rgb(118, 179, 40)', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,9 +1,8 @@ | ||||
| { | ||||
| 	id: '8c539dc1-0fab-4d47-9194-39c508e9bfe1', | ||||
| 
 | ||||
| 	name: 'Black', | ||||
| 	name: 'Battery Saver', | ||||
| 	author: 'syuilo', | ||||
| 	desc: 'Basic dark theme', | ||||
| 
 | ||||
| 	base: 'dark', | ||||
| 
 | ||||
							
								
								
									
										29
									
								
								src/client/themes/d-black.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/client/themes/d-black.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
| 	id: '8050783a-7f63-445a-b270-36d0f6ba1677', | ||||
|  | ||||
| 	name: 'Mi Black', | ||||
| 	author: 'syuilo', | ||||
| 	desc: 'Default light theme', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		bg: '#272727', | ||||
| 		fg: 'rgb(199, 209, 216)', | ||||
| 		fgHighlighted: '#fff', | ||||
| 		divider: 'rgba(255, 255, 255, 0.14)', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderBg: '@panel', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		infoFg: '@accent', | ||||
| 		infoBg: 'rgb(0, 0, 0)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: '#363636', | ||||
| 		renote: '@accent', | ||||
| 		mention: '#da6d35', | ||||
| 		mentionMe: '#d44c4c', | ||||
| 		hashtag: '#4cb8d4', | ||||
| 		link: '@accent', | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/client/themes/d-blue.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/client/themes/d-blue.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
| 	id: 'ab4eb6d5-dcc0-4457-8a3c-98aad8ea3979', | ||||
|  | ||||
| 	name: 'Mi D Blue', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(81 185 189)', | ||||
| 		bg: 'rgb(54, 54, 54)', | ||||
| 		fg: 'rgb(199, 209, 216)', | ||||
| 		fgHighlighted: '#fff', | ||||
| 		divider: 'rgba(255, 255, 255, 0.14)', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderBg: '@panel', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		infoFg: '@accent', | ||||
| 		infoBg: 'rgb(0, 0, 0)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: 'rgb(71, 71, 71)', | ||||
| 		renote: '@accent', | ||||
| 		mention: '#da6d35', | ||||
| 		mentionMe: '#d44c4c', | ||||
| 		hashtag: '#4cb8d4', | ||||
| 		link: '@accent', | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/client/themes/d-green.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/client/themes/d-green.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
| 	id: '326dc4bf-29d9-45b4-889e-bdc33e84919b', | ||||
|  | ||||
| 	name: 'Mi D Green', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(152, 196, 69)', | ||||
| 		bg: 'rgb(54, 54, 54)', | ||||
| 		fg: 'rgb(199, 209, 216)', | ||||
| 		fgHighlighted: '#fff', | ||||
| 		divider: 'rgba(255, 255, 255, 0.14)', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderBg: '@panel', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		infoFg: '@accent', | ||||
| 		infoBg: 'rgb(0, 0, 0)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: 'rgb(71, 71, 71)', | ||||
| 		renote: '@accent', | ||||
| 		mention: '#da6d35', | ||||
| 		mentionMe: '#d44c4c', | ||||
| 		hashtag: '#4cb8d4', | ||||
| 		link: '@accent', | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/client/themes/d-persimmon.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/client/themes/d-persimmon.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
| 	id: 'c503d768-7c70-4db2-a4e6-08264304bc8d', | ||||
|  | ||||
| 	name: 'Ai Persimmon', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(206, 102, 65)', | ||||
| 		bg: 'rgb(41, 43, 41)', | ||||
| 		fg: '#cdd8c7', | ||||
| 		fgHighlighted: '#fff', | ||||
| 		divider: 'rgba(255, 255, 255, 0.14)', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderBg: '@panel', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		infoFg: '@accent', | ||||
| 		infoBg: 'rgb(0, 0, 0)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: '#1f211f', | ||||
| 		renote: '@accent', | ||||
| 		mention: '@accent', | ||||
| 		mentionMe: '#de6161', | ||||
| 		hashtag: '#68bad0', | ||||
| 		link: '#a1c758', | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/client/themes/d-red.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/client/themes/d-red.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
| 	id: '60960086-26da-4f3c-bb0c-f6a4f89e0f60', | ||||
|  | ||||
| 	name: 'Mi D Red', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(196 115 69)', | ||||
| 		bg: 'rgb(54, 54, 54)', | ||||
| 		fg: 'rgb(199, 209, 216)', | ||||
| 		fgHighlighted: '#fff', | ||||
| 		divider: 'rgba(255, 255, 255, 0.14)', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderBg: '@panel', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		infoFg: '@accent', | ||||
| 		infoBg: 'rgb(0, 0, 0)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: 'rgb(71, 71, 71)', | ||||
| 		renote: '@accent', | ||||
| 		mention: '#da6d35', | ||||
| 		mentionMe: '#d44c4c', | ||||
| 		hashtag: '#4cb8d4', | ||||
| 		link: '@accent', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| { | ||||
| 	id: '17587283-dd92-4a2c-a22c-be0637c9e22a', | ||||
|  | ||||
| 	name: 'Danboard', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(218, 141, 49)', | ||||
| 		bg: 'rgb(218, 212, 190)', | ||||
| 		fg: 'rgb(115, 108, 92)', | ||||
| 		panel: 'rgb(236, 232, 220)', | ||||
| 		renote: 'rgb(100, 152, 106)', | ||||
| 		link: 'rgb(100, 152, 106)', | ||||
| 		mention: '@accent', | ||||
| 		hashtag: 'rgb(100, 152, 106)', | ||||
| 		header: 'rgba(239, 227, 213, 0.75)', | ||||
| 		navBg: 'rgb(216, 206, 182)', | ||||
| 		inputBorder: 'rgba(0, 0, 0, 0.1)', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| { | ||||
| 	id: '42e4f09b-67d5-498c-af7d-29faa54745b0', | ||||
|  | ||||
| 	name: 'Halloween', | ||||
| 	author: 'syuilo', | ||||
| 	desc: 'Hello, Happy Halloween!', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: '#d67036', | ||||
| 		panel: '#1f1d30', | ||||
| 		bg: '#0f0e17', | ||||
| 		fg: '#b1bee3', | ||||
| 		renote: '@accent', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,14 +1,14 @@ | ||||
| { | ||||
| 	id: '0ff48d43-aab3-46e7-ab12-8492110d2e2b', | ||||
| 
 | ||||
| 	name: 'Cafe', | ||||
| 	name: 'Ai Apricot', | ||||
| 	author: 'syuilo', | ||||
| 
 | ||||
| 	base: 'light', | ||||
| 
 | ||||
| 	props: { | ||||
| 		accent: 'rgb(234, 154, 82)', | ||||
| 		bg: '#DDD9D1', | ||||
| 		bg: '#e6e5e2', | ||||
| 		fg: 'rgb(149, 143, 139)', | ||||
| 		panel: '#EEECE8', | ||||
| 		renote: '@accent', | ||||
							
								
								
									
										21
									
								
								src/client/themes/l-blue.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/client/themes/l-blue.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
| 	id: 'ad18a23b-6af6-4af0-9ed4-600568250574', | ||||
|  | ||||
| 	name: 'Mi L Blue', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: '#4dbccc', | ||||
| 		bg: '#fff', | ||||
| 		fg: '#5d5d5d', | ||||
| 		divider: 'rgb(223, 223, 223)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: '@bg', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		messageBg: '#dedede', | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/client/themes/l-green.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/client/themes/l-green.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
| 	id: 'a55af79a-12bf-4f8d-a0cc-718957ad59b4', | ||||
|  | ||||
| 	name: 'Mi L Green', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: '#8bcc4d', | ||||
| 		bg: '#fff', | ||||
| 		fg: '#5d5d5d', | ||||
| 		divider: 'rgb(223, 223, 223)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: '@bg', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		messageBg: '#dedede', | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/client/themes/l-red.json5
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/client/themes/l-red.json5
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
| 	id: '957db7cb-30fb-4c80-bf0b-04198e7ae7e3', | ||||
|  | ||||
| 	name: 'Mi L Red', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: '#fb734d', | ||||
| 		bg: '#fff', | ||||
| 		fg: '#5d5d5d', | ||||
| 		divider: 'rgb(223, 223, 223)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: '@bg', | ||||
| 		panel: '@bg', | ||||
| 		panelShadow: '" 0 0 0 1px var(--divider)', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		messageBg: '#dedede', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,16 +1,20 @@ | ||||
| { | ||||
| 	id: '4eea646f-7afa-4645-83e9-83af0333cd37', | ||||
| 
 | ||||
| 	name: 'White', | ||||
| 	name: 'Mi White', | ||||
| 	author: 'syuilo', | ||||
| 	desc: 'Basic light theme', | ||||
| 	desc: 'Default light theme', | ||||
| 
 | ||||
| 	base: 'light', | ||||
| 
 | ||||
| 	props: { | ||||
| 		bg: '#F6F7F7', | ||||
| 		bg: '#f9f9f9', | ||||
| 		fg: '#636b71', | ||||
| 		divider: 'rgb(223, 223, 223)', | ||||
| 		header: ':alpha<0.7<@bg', | ||||
| 		navBg: '@bg', | ||||
| 		panel: '#fff', | ||||
| 		panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 		messageBg: '#dedede', | ||||
| 	}, | ||||
| @@ -1,20 +0,0 @@ | ||||
| { | ||||
| 	id: 'e9c8c01d-9c15-48d0-9b5c-3d00843b5b36', | ||||
|  | ||||
| 	name: 'Lilac', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(206, 147, 191)', | ||||
| 		bg: 'rgb(253, 242, 243)', | ||||
| 		fg: 'rgb(161, 139, 146)', | ||||
| 		divider: '#ece7e7', | ||||
| 		renote: '@accent', | ||||
| 		link: '@accent', | ||||
| 		mention: '@accent', | ||||
| 		hashtag: '@accent', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| { | ||||
| 	id: '7e5e263e-c6c1-44e4-a3d2-39198e3cddb8', | ||||
|  | ||||
| 	name: 'Ocean', | ||||
| 	author: 'simirall', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: '#1da1f2', | ||||
| 		bg: '#10171E', | ||||
| 		fg: '#fdfdfd', | ||||
| 		panel: '#15202B', | ||||
| 		header: 'rgba(20, 32, 43, 0.75)', | ||||
| 		renote: '#17bf63', | ||||
| 		link: '@accent', | ||||
| 		mention: '@accent', | ||||
| 		hashtag: '@accent', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| { | ||||
| 	id: '0d92cf9c-ed9e-42fe-b715-be4899f54d12', | ||||
|  | ||||
| 	name: 'Olive', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(158, 177, 95)', | ||||
| 		bg: 'rgb(230, 230, 223)', | ||||
| 		fg: 'rgb(103, 115, 92)', | ||||
| 		panel: 'rgb(243, 241, 233)', | ||||
| 		renote: '@accent', | ||||
| 		link: '@accent', | ||||
| 		mention: '@accent', | ||||
| 		hashtag: '@accent', | ||||
| 		header: 'rgba(211, 214, 200, 0.75)', | ||||
| 		navBg: 'rgb(220, 219, 206)', | ||||
| 		inputBorder: 'rgba(0, 0, 0, 0.1)', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| { | ||||
| 	id: '2d7d1479-acb8-4e2e-85bb-565a2d8e6966', | ||||
|  | ||||
| 	name: 'Rainy', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'light', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(147, 199, 206)', | ||||
| 		bg: 'rgb(220, 229, 232)', | ||||
| 		fg: 'rgb(139, 153, 161)', | ||||
| 		renote: '@accent', | ||||
| 		panelHeaderDivider: '@divider', | ||||
| 	}, | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| { | ||||
| 	id: 'b9392635-8c3d-4397-aaf7-796e49781899', | ||||
|  | ||||
| 	name: 'Urban', | ||||
| 	author: 'syuilo', | ||||
|  | ||||
| 	base: 'dark', | ||||
|  | ||||
| 	props: { | ||||
| 		accent: 'rgb(212, 104, 48)', | ||||
| 		panel: 'rgb(38, 44, 53)', | ||||
| 		bg: 'rgb(26, 29, 33)', | ||||
| 		fg: 'rgb(199, 209, 216)', | ||||
| 		shadow: 'rgba(0, 0, 0, 0.2)', | ||||
| 		header: 'rgba(51, 64, 72, 0.75)', | ||||
| 		renote: '@accent', | ||||
| 	}, | ||||
| } | ||||
| @@ -293,6 +293,10 @@ export default defineComponent({ | ||||
|  | ||||
| 			> .spacer { | ||||
| 				height: 82px; | ||||
|  | ||||
| 				@media (min-width: ($widgets-hide-threshold + 1px)) { | ||||
| 					display: none; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -117,7 +117,6 @@ export default defineComponent({ | ||||
| .mk-app { | ||||
| 	$header-height: 52px; | ||||
| 	$ui-font-size: 1em; // TODO: どこかに集約したい | ||||
| 	$widgets-hide-threshold: 1090px; | ||||
|  | ||||
| 	// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ | ||||
| 	min-height: calc(var(--vh, 1vh) * 100); | ||||
|   | ||||
| @@ -58,6 +58,8 @@ export type Source = { | ||||
| 	}; | ||||
|  | ||||
| 	mediaProxy?: string; | ||||
|  | ||||
| 	signToActivityPubGet?: boolean; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import fetch, { HeadersInit } from 'node-fetch'; | ||||
| import { HttpProxyAgent } from 'http-proxy-agent'; | ||||
| import { HttpsProxyAgent } from 'https-proxy-agent'; | ||||
| import config from '../config'; | ||||
| import { URL } from 'url'; | ||||
|  | ||||
| export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: HeadersInit) { | ||||
| 	const res = await fetch(url, { | ||||
| @@ -69,14 +70,14 @@ const _https = new https.Agent({ | ||||
|  * Get http proxy or non-proxy agent | ||||
|  */ | ||||
| export const httpAgent = config.proxy | ||||
| 	? new HttpProxyAgent(config.proxy) | ||||
| 	? new HttpProxyAgent(config.proxy) as unknown as http.Agent | ||||
| 	: _http; | ||||
|  | ||||
| /** | ||||
|  * Get https proxy or non-proxy agent | ||||
|  */ | ||||
| export const httpsAgent = config.proxy | ||||
| 	? new HttpsProxyAgent(config.proxy) | ||||
| 	? new HttpsProxyAgent(config.proxy) as unknown as https.Agent | ||||
| 	: _https; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { User } from './user'; | ||||
| import { id } from '../id'; | ||||
|  | ||||
| @Entity() | ||||
| @Index(['userId', 'reporterId'], { unique: true }) | ||||
| export class AbuseUserReport { | ||||
| 	@PrimaryColumn(id()) | ||||
| 	public id: string; | ||||
| @@ -16,13 +15,13 @@ export class AbuseUserReport { | ||||
|  | ||||
| 	@Index() | ||||
| 	@Column(id()) | ||||
| 	public userId: User['id']; | ||||
| 	public targetUserId: User['id']; | ||||
|  | ||||
| 	@ManyToOne(type => User, { | ||||
| 		onDelete: 'CASCADE' | ||||
| 	}) | ||||
| 	@JoinColumn() | ||||
| 	public user: User | null; | ||||
| 	public targetUser: User | null; | ||||
|  | ||||
| 	@Index() | ||||
| 	@Column(id()) | ||||
| @@ -34,8 +33,42 @@ export class AbuseUserReport { | ||||
| 	@JoinColumn() | ||||
| 	public reporter: User | null; | ||||
|  | ||||
| 	@Column({ | ||||
| 		...id(), | ||||
| 		nullable: true | ||||
| 	}) | ||||
| 	public assigneeId: User['id'] | null; | ||||
|  | ||||
| 	@ManyToOne(type => User, { | ||||
| 		onDelete: 'SET NULL' | ||||
| 	}) | ||||
| 	@JoinColumn() | ||||
| 	public assignee: User | null; | ||||
|  | ||||
| 	@Index() | ||||
| 	@Column('boolean', { | ||||
| 		default: false | ||||
| 	}) | ||||
| 	public resolved: boolean; | ||||
|  | ||||
| 	@Column('varchar', { | ||||
| 		length: 512, | ||||
| 		length: 2048, | ||||
| 	}) | ||||
| 	public comment: string; | ||||
|  | ||||
| 	//#region Denormalized fields | ||||
| 	@Index() | ||||
| 	@Column('varchar', { | ||||
| 		length: 128, nullable: true, | ||||
| 		comment: '[Denormalized]' | ||||
| 	}) | ||||
| 	public targetUserHost: string | null; | ||||
|  | ||||
| 	@Index() | ||||
| 	@Column('varchar', { | ||||
| 		length: 128, nullable: true, | ||||
| 		comment: '[Denormalized]' | ||||
| 	}) | ||||
| 	public reporterHost: string | null; | ||||
| 	//#endregion | ||||
| } | ||||
|   | ||||
| @@ -15,14 +15,19 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> { | ||||
| 			id: report.id, | ||||
| 			createdAt: report.createdAt, | ||||
| 			comment: report.comment, | ||||
| 			resolved: report.resolved, | ||||
| 			reporterId: report.reporterId, | ||||
| 			userId: report.userId, | ||||
| 			targetUserId: report.targetUserId, | ||||
| 			assigneeId: report.assigneeId, | ||||
| 			reporter: Users.pack(report.reporter || report.reporterId, null, { | ||||
| 				detail: true | ||||
| 			}), | ||||
| 			user: Users.pack(report.user || report.userId, null, { | ||||
| 			targetUser: Users.pack(report.targetUser || report.targetUserId, null, { | ||||
| 				detail: true | ||||
| 			}), | ||||
| 			assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, { | ||||
| 				detail: true | ||||
| 			}) : null, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -19,8 +19,10 @@ export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => { | ||||
| 	await AbuseUserReports.insert({ | ||||
| 		id: genId(), | ||||
| 		createdAt: new Date(), | ||||
| 		userId: users[0].id, | ||||
| 		targetUserId: users[0].id, | ||||
| 		targetUserHost: users[0].host, | ||||
| 		reporterId: actor.id, | ||||
| 		reporterHost: actor.host, | ||||
| 		comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}` | ||||
| 	}); | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import * as http from 'http'; | ||||
| import * as https from 'https'; | ||||
| import { sign } from 'http-signature'; | ||||
| import * as crypto from 'crypto'; | ||||
| @@ -7,6 +8,9 @@ import { ILocalUser } from '../../models/entities/user'; | ||||
| import { UserKeypairs } from '../../models'; | ||||
| import { ensure } from '../../prelude/ensure'; | ||||
| import { getAgentByUrl } from '../../misc/fetch'; | ||||
| import { URL } from 'url'; | ||||
| import got from 'got'; | ||||
| import * as Got from 'got'; | ||||
|  | ||||
| export default async (user: ILocalUser, url: string, object: any) => { | ||||
| 	const timeout = 10 * 1000; | ||||
| @@ -62,3 +66,96 @@ export default async (user: ILocalUser, url: string, object: any) => { | ||||
| 		req.end(data); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Get AP object with http-signature | ||||
|  * @param user http-signature user | ||||
|  * @param url URL to fetch | ||||
|  */ | ||||
| export async function signedGet(url: string, user: ILocalUser) { | ||||
| 	const timeout = 10 * 1000; | ||||
|  | ||||
| 	const keypair = await UserKeypairs.findOne({ | ||||
| 		userId: user.id | ||||
| 	}).then(ensure); | ||||
|  | ||||
| 	const req = got.get<any>(url, { | ||||
| 		headers: { | ||||
| 			'Accept': 'application/activity+json, application/ld+json', | ||||
| 			'User-Agent': config.userAgent, | ||||
| 		}, | ||||
| 		responseType: 'json', | ||||
| 		timeout, | ||||
| 		hooks: { | ||||
| 			beforeRequest: [ | ||||
| 				options => { | ||||
| 					options.request = (url: URL, opt: http.RequestOptions, callback?: (response: any) => void) => { | ||||
| 						// Select custom agent by URL | ||||
| 						opt.agent = getAgentByUrl(url, false); | ||||
|  | ||||
| 						// Wrap original https?.request | ||||
| 						const requestFunc = url.protocol === 'http:' ? http.request : https.request; | ||||
| 						const clientRequest = requestFunc(url, opt, callback) as http.ClientRequest; | ||||
|  | ||||
| 						// HTTP-Signature | ||||
| 						sign(clientRequest, { | ||||
| 							authorizationHeaderName: 'Signature', | ||||
| 							key: keypair.privateKey, | ||||
| 							keyId: `${config.url}/users/${user.id}#main-key`, | ||||
| 							headers: ['(request-target)', 'host', 'date', 'accept'] | ||||
| 						}); | ||||
|  | ||||
| 						return clientRequest; | ||||
| 					}; | ||||
| 				}, | ||||
| 			], | ||||
| 		}, | ||||
| 		retry: 0, | ||||
| 	}); | ||||
|  | ||||
| 	const res = await receiveResponce(req, 10 * 1024 * 1024); | ||||
|  | ||||
| 	return res.body; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Receive response (with size limit) | ||||
|  * @param req Request | ||||
|  * @param maxSize size limit | ||||
|  */ | ||||
| export async function receiveResponce<T>(req: Got.CancelableRequest<Got.Response<T>>, maxSize: number) { | ||||
| 	// 応答ヘッダでサイズチェック | ||||
| 	req.on('response', (res: Got.Response) => { | ||||
| 		const contentLength = res.headers['content-length']; | ||||
| 		if (contentLength != null) { | ||||
| 			const size = Number(contentLength); | ||||
| 			if (size > maxSize) { | ||||
| 				req.cancel(); | ||||
| 			} | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// 受信中のデータでサイズチェック | ||||
| 	req.on('downloadProgress', (progress: Got.Progress) => { | ||||
| 		if (progress.transferred > maxSize) { | ||||
| 			req.cancel(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// 応答取得 with ステータスコードエラーの整形 | ||||
| 	const res = await req.catch(e => { | ||||
| 		if (e.name === 'HTTPError') { | ||||
| 			const statusCode = (e as Got.HTTPError).response.statusCode; | ||||
| 			const statusMessage = (e as Got.HTTPError).response.statusMessage; | ||||
| 			throw { | ||||
| 				name: `StatusError`, | ||||
| 				statusCode, | ||||
| 				message: `${statusCode} ${statusMessage}`, | ||||
| 			}; | ||||
| 		} else { | ||||
| 			throw e; | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	return res; | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,13 @@ | ||||
| import config from '../../config'; | ||||
| import { getJson } from '../../misc/fetch'; | ||||
| import { ILocalUser } from '../../models/entities/user'; | ||||
| import { getInstanceActor } from '../../services/instance-actor'; | ||||
| import { signedGet } from './request'; | ||||
| import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type'; | ||||
|  | ||||
| export default class Resolver { | ||||
| 	private history: Set<string>; | ||||
| 	private user?: ILocalUser; | ||||
|  | ||||
| 	constructor() { | ||||
| 		this.history = new Set(); | ||||
| @@ -39,7 +44,13 @@ export default class Resolver { | ||||
|  | ||||
| 		this.history.add(value); | ||||
|  | ||||
| 		const object = await getJson(value, 'application/activity+json, application/ld+json'); | ||||
| 		if (config.signToActivityPubGet && !this.user) { | ||||
| 			this.user = await getInstanceActor(); | ||||
| 		} | ||||
|  | ||||
| 		const object = this.user | ||||
| 			? await signedGet(value, this.user) | ||||
| 			: await getJson(value, 'application/activity+json, application/ld+json'); | ||||
|  | ||||
| 		if (object == null || ( | ||||
| 			Array.isArray(object['@context']) ? | ||||
|   | ||||
| @@ -23,12 +23,50 @@ export const meta = { | ||||
| 		untilId: { | ||||
| 			validator: $.optional.type(ID), | ||||
| 		}, | ||||
|  | ||||
| 		state: { | ||||
| 			validator: $.optional.nullable.str, | ||||
| 			default: null, | ||||
| 		}, | ||||
|  | ||||
| 		reporterOrigin: { | ||||
| 			validator: $.optional.str.or([ | ||||
| 				'combined', | ||||
| 				'local', | ||||
| 				'remote', | ||||
| 			]), | ||||
| 			default: 'combined' | ||||
| 		}, | ||||
|  | ||||
| 		targetUserOrigin: { | ||||
| 			validator: $.optional.str.or([ | ||||
| 				'combined', | ||||
| 				'local', | ||||
| 				'remote', | ||||
| 			]), | ||||
| 			default: 'combined' | ||||
| 		}, | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default define(meta, async (ps) => { | ||||
| 	const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); | ||||
|  | ||||
| 	switch (ps.state) { | ||||
| 		case 'resolved': query.andWhere('report.resolved = TRUE'); break; | ||||
| 		case 'unresolved': query.andWhere('report.resolved = FALSE'); break; | ||||
| 	} | ||||
|  | ||||
| 	switch (ps.reporterOrigin) { | ||||
| 		case 'local': query.andWhere('report.reporterHost IS NULL'); break; | ||||
| 		case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break; | ||||
| 	} | ||||
|  | ||||
| 	switch (ps.targetUserOrigin) { | ||||
| 		case 'local': query.andWhere('report.targetUserHost IS NULL'); break; | ||||
| 		case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; | ||||
| 	} | ||||
|  | ||||
| 	const reports = await query.take(ps.limit!).getMany(); | ||||
|  | ||||
| 	return await AbuseUserReports.packMany(reports); | ||||
|   | ||||
| @@ -16,12 +16,15 @@ export const meta = { | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps) => { | ||||
| export default define(meta, async (ps, me) => { | ||||
| 	const report = await AbuseUserReports.findOne(ps.reportId); | ||||
| 
 | ||||
| 	if (report == null) { | ||||
| 		throw new Error('report not found'); | ||||
| 	} | ||||
| 
 | ||||
| 	await AbuseUserReports.delete(report.id); | ||||
| 	await AbuseUserReports.update(report.id, { | ||||
| 		resolved: true, | ||||
| 		assigneeId: me.id, | ||||
| 	}); | ||||
| }); | ||||
| @@ -69,6 +69,7 @@ export default define(meta, async (ps, me) => { | ||||
| 		throw new ApiError(meta.errors.accessDenied); | ||||
| 	} | ||||
|  | ||||
| 	// tslint:disable-next-line:no-unnecessary-initializer | ||||
| 	let banner = undefined; | ||||
| 	if (ps.bannerId != null) { | ||||
| 		banner = await DriveFiles.findOne({ | ||||
|   | ||||
| @@ -26,7 +26,7 @@ export const meta = { | ||||
| 		}, | ||||
|  | ||||
| 		comment: { | ||||
| 			validator: $.str.range(1, 3000), | ||||
| 			validator: $.str.range(1, 2048), | ||||
| 			desc: { | ||||
| 				'ja-JP': '迷惑行為の詳細' | ||||
| 			} | ||||
| @@ -72,9 +72,11 @@ export default define(meta, async (ps, me) => { | ||||
| 	const report = await AbuseUserReports.save({ | ||||
| 		id: genId(), | ||||
| 		createdAt: new Date(), | ||||
| 		userId: user.id, | ||||
| 		targetUserId: user.id, | ||||
| 		targetUserHost: user.host, | ||||
| 		reporterId: me.id, | ||||
| 		comment: ps.comment | ||||
| 		reporterHost: null, | ||||
| 		comment: ps.comment, | ||||
| 	}); | ||||
|  | ||||
| 	// Publish event to moderators | ||||
| @@ -90,7 +92,7 @@ export default define(meta, async (ps, me) => { | ||||
| 		for (const moderator of moderators) { | ||||
| 			publishAdminStream(moderator.id, 'newAbuseUserReport', { | ||||
| 				id: report.id, | ||||
| 				userId: report.userId, | ||||
| 				targetUserId: report.targetUserId, | ||||
| 				reporterId: report.reporterId, | ||||
| 				comment: report.comment | ||||
| 			}); | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/services/instance-actor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/services/instance-actor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import { createSystemUser } from './create-system-user'; | ||||
| import { ILocalUser } from '../models/entities/user'; | ||||
| import { Users } from '../models'; | ||||
|  | ||||
| const ACTOR_USERNAME = 'instance.actor' as const; | ||||
|  | ||||
| export async function getInstanceActor(): Promise<ILocalUser> { | ||||
| 	const user = await Users.findOne({ | ||||
| 		host: null, | ||||
| 		username: ACTOR_USERNAME | ||||
| 	}); | ||||
|  | ||||
| 	if (user) return user as ILocalUser; | ||||
|  | ||||
| 	const created = await createSystemUser(ACTOR_USERNAME); | ||||
| 	return created as ILocalUser; | ||||
| } | ||||
| @@ -15,8 +15,9 @@ import { getFileInfo } from '../src/misc/get-file-info'; | ||||
| describe('Get file info', () => { | ||||
| 	it('Empty file', async (async () => { | ||||
| 		const path = `${__dirname}/resources/emptyfile`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 0, | ||||
| 			md5: 'd41d8cd98f00b204e9800998ecf8427e', | ||||
| @@ -26,14 +27,14 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: undefined, | ||||
| 			height: undefined, | ||||
| 			blurhash: undefined | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('Generic JPEG', async (async () => { | ||||
| 		const path = `${__dirname}/resources/Lenna.jpg`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 25360, | ||||
| 			md5: '091b3f259662aa31e2ffef4519951168', | ||||
| @@ -43,14 +44,14 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 512, | ||||
| 			height: 512, | ||||
| 			blurhash: 'yFLxJjH[NE}@^PRiN_}Y=aVZNvFxxZ#SwIt7Eg%KIp-ospv~Nex[R6t3xZI:iwt6kWxDafoySgsAfR$*oyM|S2t7$iV[tQNbaKn%xt' | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('Generic APNG', async (async () => { | ||||
| 		const path = `${__dirname}/resources/anime.png`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 1868, | ||||
| 			md5: '08189c607bea3b952704676bb3c979e0', | ||||
| @@ -60,14 +61,14 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			blurhash: 'y8S?Mr-;=~~Xs;%foL?bWVs;xbR%NFay^ms;I-InI-xbs;%gofj[I-s;-WxbI-WUayxb$,NFR*~Wa{R%xbayNFI.oMj[oMNFWB$,WU' | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('Generic AGIF', async (async () => { | ||||
| 		const path = `${__dirname}/resources/anime.gif`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 2248, | ||||
| 			md5: '32c47a11555675d9267aee1a86571e7e', | ||||
| @@ -77,14 +78,14 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			blurhash: 'y8S?Mr-;=~~Xs;%foL?bWVs;xbR%NFay^ms;I-InI-xbs;%gofj[I-s;-WxbI-WUayxb$,NFR*~Wa{R%xbayNFI.oMj[oMNFWB$,WU' | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('PNG with alpha', async (async () => { | ||||
| 		const path = `${__dirname}/resources/with-alpha.png`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 3772, | ||||
| 			md5: 'f73535c3e1e27508885b69b10cf6e991', | ||||
| @@ -94,14 +95,14 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			blurhash: 'y74P29kDpdp{k?VDZ#krkCaefkf6fQf5HXZ$krkqadaKaJkCaKkXfkkCf5fkQ8kXZ#VDaKk?krZ~kCf6kDf6f5f6U]krZ#Z#aekrkq' | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('Generic SVG', async (async () => { | ||||
| 		const path = `${__dirname}/resources/image.svg`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 505, | ||||
| 			md5: 'b6f52b4b021e7b92cdd04509c7267965', | ||||
| @@ -111,15 +112,15 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			blurhash: 'yMEKyd1U1?=nZN-2EwofR*oHnijYX6S50J=m]WEVl9JE$SR*xHR;XSX8nQxB-WS6Nts*aKskWnaxR%s*i_n~X6S5=#NgOAs*enoIWU' | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('SVG with XML definition', async (async () => { | ||||
| 		// https://github.com/syuilo/misskey/issues/4413 | ||||
| 		const path = `${__dirname}/resources/with-xml-def.svg`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 544, | ||||
| 			md5: '4b7a346cde9ccbeb267e812567e33397', | ||||
| @@ -129,14 +130,14 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 256, | ||||
| 			height: 256, | ||||
| 			blurhash: 'yMEKyd1U1?=nZN-2EwofR*oHnijYX6S50J=m]WEVl9JE$SR*xHR;XSX8nQxB-WS6Nts*aKskWnaxR%s*i_n~X6S5=#NgOAs*enoIWU' | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	it('Dimension limit', async (async () => { | ||||
| 		const path = `${__dirname}/resources/25000x25000.png`; | ||||
| 		const info = await getFileInfo(path); | ||||
| 		const info = await getFileInfo(path) as any; | ||||
| 		delete info.warnings; | ||||
| 		delete info.blurhash; | ||||
| 		assert.deepStrictEqual(info, { | ||||
| 			size: 75933, | ||||
| 			md5: '268c5dde99e17cf8fe09f1ab3f97df56', | ||||
| @@ -146,7 +147,6 @@ describe('Get file info', () => { | ||||
| 			}, | ||||
| 			width: 25000, | ||||
| 			height: 25000, | ||||
| 			blurhash: undefined | ||||
| 		}); | ||||
| 	})); | ||||
| }); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user