Compare commits

..

58 Commits

Author SHA1 Message Date
syuilo
629b765abc 12.51.0 2020-10-27 18:29:15 +09:00
syuilo
63a89fa84a カスタム絵文字がつぶれる問題を修正 2020-10-27 18:28:37 +09:00
syuilo
a3f89236a0 New Crowdin updates (#6760)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Japanese, Kansai)
2020-10-27 18:24:34 +09:00
syuilo
01560abafb Fix #6762 2020-10-27 18:21:52 +09:00
MeiMei
b5698026ba Fix emojilist.json (#6764) 2020-10-27 18:15:35 +09:00
syuilo
6258ce75b7 Reversi (#6765)
* wip

* wip

* wip

* wip

* Update game.setting.vue

* wip

* wip

* Update game.setting.vue

* wip

* Update game.board.vue

* wip

* Update sidebar.ts
2020-10-27 18:11:41 +09:00
syuilo
6f34c74027 Update popup animation 2020-10-27 16:46:13 +09:00
syuilo
8add4f359b 🎨 2020-10-27 16:45:24 +09:00
syuilo
d8933c135f リモートインスタンス情報を強制更新するAPIを追加 2020-10-27 16:45:14 +09:00
syuilo
eb350e8d6c Better favicon detection 2020-10-27 16:44:54 +09:00
syuilo
615fedd64d Instance Ticker 2020-10-27 16:16:59 +09:00
syuilo
25bd82ecaa Default behavior option for MkA component 2020-10-27 13:53:47 +09:00
syuilo
e0938e5e3a Add animation of context menu 2020-10-25 23:22:27 +09:00
syuilo
ec5e6c8443 APIコンソール 2020-10-25 16:11:08 +09:00
syuilo
25d8077474 Fix bug 2020-10-25 15:45:47 +09:00
syuilo
06083f40d9 🎨 2020-10-25 12:47:40 +09:00
syuilo
ec203f7f79 Use MFM instead of v-html to avoid XSS 2020-10-25 12:25:13 +09:00
syuilo
1b30d7d47a Clean up 2020-10-25 11:29:10 +09:00
syuilo
d9be9c958f 投稿失敗したときにエラー表示するように 2020-10-25 11:19:20 +09:00
syuilo
ed09796e0d 🎨 2020-10-25 11:06:55 +09:00
syuilo
4bfa29c0ab コンテキストメニューの位置計算を改善 2020-10-25 11:01:03 +09:00
syuilo
4804bbb211 インポート/エクスポート設定を復活 2020-10-25 10:48:33 +09:00
syuilo
749102f9c2 ヘッダーにもコンテキストメニュー追加 2020-10-25 09:26:19 +09:00
syuilo
0bcb1434b0 Refactor 2020-10-25 09:15:20 +09:00
syuilo
2e537e618c 12.50.0 2020-10-25 01:30:38 +09:00
syuilo
fe3b7a2ad3 New Crowdin updates (#6756)
* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)
2020-10-25 01:24:55 +09:00
syuilo
90db793fd0 regesit 2020-10-25 01:24:01 +09:00
syuilo
7bd2a6ad61 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-10-25 01:23:41 +09:00
syuilo
745f4d2439 regedit 2020-10-25 01:23:23 +09:00
syuilo
254cfaea28 自前ルーティング (#6759)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
2020-10-25 01:21:41 +09:00
syuilo
d4da5a1eea Update dependencies 🚀 2020-10-24 11:12:29 +09:00
syuilo
c0f8297414 Fix migration bug 2020-10-23 17:46:31 +09:00
syuilo
834cb2ea1a 12.49.1 2020-10-22 23:31:11 +09:00
syuilo
d82769abd4 New Crowdin updates (#6748)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)
2020-10-22 23:30:54 +09:00
syuilo
adf01ed4a4 Fix #6749 (#6754) 2020-10-22 23:10:23 +09:00
syuilo
09c007b3aa Update dependencies 🚀 2020-10-22 23:09:03 +09:00
syuilo
526ff177aa Update dependenceis 🚀 2020-10-21 22:20:03 +09:00
syuilo
0e40d4e796 Clean up 2020-10-21 21:50:24 +09:00
syuilo
172ebab7bd Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-10-21 19:36:50 +09:00
syuilo
aa4493fe5c Fix api permission definition 2020-10-21 19:36:47 +09:00
syuilo
a68a88f79e Update README.md 2020-10-20 17:32:17 +09:00
syuilo
1de7dc94e1 Delete CHANGELOG.md 2020-10-19 20:55:19 +09:00
syuilo
59cb7992e2 12.49.0 2020-10-19 19:43:30 +09:00
syuilo
87b15df47b Auto adjust window size 2020-10-19 19:42:55 +09:00
syuilo
6932d86240 New Crowdin updates (#6667)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (English)
2020-10-19 19:39:05 +09:00
syuilo
87f61e714a Resolve #6087 2020-10-19 19:29:04 +09:00
syuilo
5762e2d9ba 12.48.3 2020-10-19 15:06:26 +09:00
syuilo
f0691c8a4f 🎨 2020-10-19 15:05:29 +09:00
syuilo
6d7e4fe2a1 fb07116a4 のコミット忘れ 2020-10-19 14:50:57 +09:00
MeiMei
7dc789f470 Fix: optional chaining (#6747) 2020-10-19 14:48:56 +09:00
syuilo
190d1bbf3c デフォルト公開範囲が機能していない問題を修正 2020-10-19 14:46:55 +09:00
syuilo
a755dd5f9e Add note 2020-10-19 14:46:32 +09:00
syuilo
0846a7b94e Remove unused themes 2020-10-19 13:20:00 +09:00
syuilo
d8be0511f1 Fix design 2020-10-19 13:17:37 +09:00
syuilo
fb07116a4c 🎨 2020-10-19 13:17:11 +09:00
takonomura
fe453c15e3 ページのセクション内などが表示されない問題を修正 (#6746) 2020-10-19 09:30:21 +09:00
takonomura
059aeef6a0 MFM のバッククオートで囲ったコードが表示されないのを修正 (#6741) 2020-10-19 08:37:07 +09:00
okpierre
30e25451d6 Update settings.vue (#6742) 2020-10-19 08:23:50 +09:00
161 changed files with 5738 additions and 3960 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=github)](http://makeapullrequest.com)
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
**A forever evolving, sophisticated microblogging platform.**
**A forever evolving, professional microblogging platform.**
<p align="justify">
<a href="https://join.misskey.page/">Misskey</a> is a decentralized microblogging platform born on Earth.

View File

@@ -1,5 +1,6 @@
---
_lang_: "العربية"
introMisskey: "اهلا بك! ميسكي هو منصة تدوين مصغر لا مركزية ومفتوحة المصدر.\nيمكنك مشاركة \"ملاحظات\" عن ما يجري حولك، وإخبار الجميع عن نفسك 📡\nتسمح لك \"الانفعالات\" بتعبير عن شعورك حول ملاحظات الآخرين 👍\nاكتشف عالمًا جديدًا 🚀"
monthAndDay: "{day}/{month}"
search: "البحث"
notifications: "الإشعارات"
@@ -14,8 +15,12 @@ noNotes: "لم يتم العثور على أية ملاحظات"
noNotifications: "ليس هناك أية اشعارات"
instance: "مثيل الخادم"
settings: "الاعدادات"
basicSettings: "الاعدادات الأساسية"
otherSettings: "إعدادات أخرى"
openInWindow: "افتح في نافذة جديدة"
profile: "الملف التعريفي"
timeline: "الخيط الزمني"
noAccountDescription: "لم يكتب هذا المستخدم سيرته بعد."
login: "لِج"
loggingIn: "جارٍ تسجيل الدخول"
logout: "الخروج"
@@ -28,22 +33,31 @@ favorite: "إضافة إلى المفضلة"
favorites: "المفضلات"
unfavorite: "إزالة من المفضلة"
pin: "دبّسها على الصفحة الشخصية"
unpin: "ألغ تثبيتها من ملفك الشخصي"
copyContent: "انسخ المحتوى"
copyLink: "انسخ الرابط"
delete: "حذف"
deleteAndEdit: "إزالة وإعادة الصياغة"
deleteAndEditConfirm: "أمتأكد من حذف الملاحظة؟ ستفقد كل مشاركاتها، والتفاعلات، والردود عليها."
addToList: "أضفه إلى قائمة"
sendMessage: "أرسل رسالة"
copyUsername: "انسخ اسم المستخدم"
searchUser: "ابحث عن مستخدمين"
reply: "رد"
loadMore: "عرض المزيد"
youGotNewFollower: "يتابعك"
receiveFollowRequest: "تلقيت طلب متابعة"
followRequestAccepted: "قُبل طلب المتابعة"
mention: "أشر الى"
mentions: "الإشارات"
directNotes: "الملاحظات المباشرة"
importAndExport: "إستورد / صدر"
import: "استيراد"
export: "تصدير"
files: "الملفات"
download: "تنزيل"
driveFileDeleteConfirm: "أمتأكد من حذف ملف {name}؟ كل الملاحظات المُرفق بها هذا الملف ستحذف."
unfollowConfirm: "أمتأكد من إلغاء متابعة {name}؟"
lists: "القوائم"
noLists: "ليس لديك أية قائمة"
note: "ملاحظة"
@@ -53,8 +67,10 @@ followers: "المتابِعين"
followsYou: "يتابعك"
createList: "إنشاء قائمة"
manageLists: "إدارة القوائم"
error: "حدث خطأ ما"
error: "خطأ"
somethingHappened: "حدث خطأ"
retry: "حاول مجددًا"
pageLoadError: "فشل تحميل الصفحة"
enterListName: "اسم القائمة"
privacy: "الخصوصية"
makeFollowManuallyApprove: "القبول يدويا طلبات الإشتراك"
@@ -64,6 +80,7 @@ followRequest: "طلب اشتراك"
followRequests: "طلبات الإشتراك"
unfollow: "إلغاء الاشتراك"
followRequestPending: "طلبات الإشتراك المعلّقة"
enterEmoji: "أدخل إيموجي"
unrenote: "إلغاء مشاركة الملاحظة"
quote: "اقتبس"
pinnedNote: "ملاحظة مدبسة"
@@ -71,16 +88,26 @@ you: "أنت"
clickToShow: "اضغط للعرض"
sensitive: "محتوى حساس"
add: "إضافة"
reaction: "تفاعل"
rememberNoteVisibility: "تذكر إعدادت مدى رؤية الملاحظات"
attachCancel: "أزل المرفق"
enterFileName: "ادخل اسم الملف"
mute: "اكتم"
unmute: "إلغاء الكتم"
block: "احجب"
unblock: "إلغاء الحجب"
suspend: "علِق"
unsuspend: "ألغ التعليق"
blockConfirm: "أمتأكد من حجب هذا الحساب؟"
unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟"
selectList: "اختر قائمة"
editWidgetsExit: "تم"
customEmojis: "إيموجي مخصص"
addEmoji: "إضافة إيموجي"
cacheRemoteFiles: "خزن مؤقتا الملفات البعيدة"
autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الحسابات المتابَعة"
addAcount: "إضافة حساب"
loginFailed: "فشل الولوج"
showOnRemote: "رؤيته على مثيل الخادم البُعدي"
general: "الرئيسية"
wallpaper: "خلفية الشاشة"
@@ -88,6 +115,7 @@ setWallpaper: "استخدم خلفية الشاشة"
removeWallpaper: "إزالة خلفية الشاشة"
searchWith: "البحث: {q}"
youHaveNoLists: "لا تمتلك أية قائمة"
followConfirm: "أتريد متابعة {name}؟"
proxyAccount: "حساب وكيل البروكسي"
host: "المضيف"
selectUser: "حدّد مستخدمًا"
@@ -96,6 +124,8 @@ annotation: "التعليقات"
federation: "الفديرالية"
instances: "مثيل الخادم"
latestRequestSentAt: "آخر طلب أرسِل في"
latestRequestReceivedAt: "آخر طلب تُلقي في"
storageUsage: "مساحة التخزين المستخدمة"
charts: "المنحنيات البيانية"
perHour: "في الساعة"
perDay: "في اليوم"
@@ -127,7 +157,6 @@ processing: "المعالجة جارية"
preview: "معاينة"
default: "افتراضي"
noCustomEmojis: "ليس هناك إيموجيات"
customEmojisOfRemote: "الإيموجيات القادمة مِن مثيلات الخوادم الأخرى"
federating: "الفديرالية جارية"
blocked: "محجوب"
suspended: "مُعلّق"
@@ -280,6 +309,7 @@ noteOf: "ملاحظات {user}"
inviteToGroup: "دعوة إلى فريق"
noMessagesYet: "ليس هناك رسائل بعد"
newMessageExists: "لقد تلقيت رسالة جديدة"
invitations: "دعوة"
invitationCode: "رمز الدعوة"
checking: "التحقق جارٍ"
available: "متوفر"
@@ -313,7 +343,6 @@ total: "المجموع"
weekOverWeekChanges: "أسبوعيا"
dayOverDayChanges: "يوميا"
appearance: "المظهر"
clinetSettings: "إعدادات التطبيق"
accountSettings: "إعدادات الحساب"
promotion: "ترقية"
promote: "روِّج"
@@ -351,6 +380,8 @@ smtpHost: "المضيف"
smtpUser: "اسم المستخدم"
smtpPass: "الكلمة السرية"
display: "المظهر"
_reversi:
total: "المجموع"
_channel:
featured: "المتداوَلة"
_sidebar:
@@ -366,6 +397,7 @@ _theme:
make: "إنشاء قالب"
alpha: "الشفافية"
keys:
mention: "أشر الى"
messageBg: "خلفية الدردشة"
_sfx:
note: "الملاحظات"
@@ -508,7 +540,9 @@ _notification:
youWereFollowed: "يتابعك"
_types:
follow: "المتابَعون"
mention: "أشر الى"
quote: "اقتبس"
reaction: "تفاعل"
_deck:
_columns:
notifications: "الإشعارات"

View File

@@ -16,6 +16,9 @@ noNotes: "Keine Notizen"
noNotifications: "Keine Benachrichtigungen"
instance: "Instanz"
settings: "Einstellungen"
basicSettings: "Allgemeine Einstellungen"
otherSettings: "Andere Einstellungen"
openInWindow: "In Fenster öffnen"
profile: "Profil"
timeline: "Chronik"
noAccountDescription: "Dieser Nutzer hat seine Profilbeschreibung noch nicht ausgefüllt."
@@ -40,6 +43,7 @@ deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten
addToList: "Zu Liste hinzufügen"
sendMessage: "Nachricht senden"
copyUsername: "Benutzernamen kopieren"
searchUser: "Benutzersuche"
reply: "Antworten"
loadMore: "Mehr anzeigen"
youGotNewFollower: "Du hast einen neuen Follower"
@@ -66,8 +70,11 @@ followers: "Gefolgt von"
followsYou: "Folgt dir"
createList: "Liste erstellen"
manageLists: "Listen verwalten"
error: "Ein Problem ist aufgetreten"
error: "Fehler"
somethingHappened: "Ein Fehler ist aufgetreten"
retry: "Wiederholen"
pageLoadError: "Laden der Seite fehlgeschlagen."
pageLoadErrorDescription: "Dieser Fehler wird meist durch Netzwerkfehler oder den Browser-Cache verursacht. Versuche den Browser-Cache zu leeren und es nach kurzer Zeit noch einmal zu probieren."
enterListName: "Listennamen eingeben"
privacy: "Privatsphäre"
makeFollowManuallyApprove: "Follow-Anfragen benötigen Bestätigung"
@@ -106,6 +113,8 @@ unsuspendConfirm: "Möchtest du die Sperrung dieses Benutzers wirklich aufheben?
selectList: "Wähle eine Liste aus"
selectAntenna: "Antenne auswählen"
selectWidget: "Widget auswählen"
editWidgets: "Widgets bearbeiten"
editWidgetsExit: "Fertig"
customEmojis: "Benutzerdefinierte Emojis"
emoji: "Emoji"
emojiName: "Emojiname"
@@ -177,7 +186,6 @@ processing: "In Bearbeitung"
preview: "Vorschau"
default: "Standard"
noCustomEmojis: "Es existieren keine Emojis"
customEmojisOfRemote: "Emojis von anderen Instanzen"
noJobs: "Es gibt keine Jobs"
federating: "Föderiert"
blocked: "Blockiert"
@@ -404,6 +412,7 @@ noMessagesYet: "Noch keine Nachrichten"
newMessageExists: "Du hast eine neue Nachricht"
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
signinRequired: "Anmeldung erforderlich"
invitations: "Einladen"
invitationCode: "Einladungscode"
checking: "Wird überprüft..."
available: "Verfügbar"
@@ -445,7 +454,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 +485,8 @@ newNoteRecived: "Es gibt neue Notizen"
sounds: "Töne"
listen: "Anhören"
none: "Keine"
showInPage: "In Seite anzeigen"
popout: "Pop-Up"
volume: "Lautstärke"
details: "Details"
chooseEmoji: "Wähle ein Emoji"
@@ -518,7 +529,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 +574,38 @@ overview: "Übersicht"
logs: "Logs"
delayed: "Verzögert"
database: "Datenbank"
channel: "Kanal"
channel: "Kanäle"
create: "Erstellen"
notificationSetting: "Benachrichtigungseinstellungen"
notificationSettingDesc: "Wähle die Art der anzuzeigenden Benachrichtigung"
useGlobalSetting: "Globale Einstellung verwenden"
useGlobalSettingDesc: "Wenn dies eingeschaltet ist, werden die Benachrichtigungseinstellungen deines Benutzerkontos verwendet. Wenn dies ausgeschaltet ist, können individuelle Einstellungen vorgenommen werden."
other: "Andere"
regenerateLoginToken: "Login-Token regenerieren"
regenerateLoginTokenDescription: "Den bei Logins intern verwendeten Token regenerieren. Normalerweise wird dies nicht benötigt. Bei Regeneration werden alle Geräte ausgeloggt."
setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehrere Einstellungen zu kofigurieren."
fileIdOrUrl: "Datei-ID oder URL"
chatOpenBehavior: "Verhalten des Chatfensters bei Öffnung"
sample: "Beispiel"
abuseReports: "Melden"
reportAbuse: "Melden"
reportAbuseOf: "{name} melden"
fillAbuseReportDescription: "Bitte gib Details für diese Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an."
abuseReported: "Die Meldung wurde versendet. Vielen Dank."
send: "Senden"
abuseMarkAsResolved: "Meldung als gelöst markieren"
openInNewTab: "In neuem Tab öffnen"
openInSideView: "In Seitenansicht öffnen"
defaultNavigationBehaviour: "Standardnavigationsverhalten"
editTheseSettingsMayBreakAccount: "Bei Bearbeitung dieser Einstellungen besteht die Gefahr, dein Benutzerkonto zu beschädigen."
instanceTicker: "Instanz-Informationen der Notiz"
random: "Zufällig"
_reversi:
total: "Gesamt"
_instanceTicker:
none: "Nie anzeigen"
remote: "Für Benutzer fremder Instanzen anzeigen"
always: "Immer anzeigen"
_serverDisconnectedBehavior:
reload: "Automatisch aktualisieren"
dialog: "Warnungsfenster zeigen"
@@ -576,13 +616,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 +822,7 @@ _widgets:
photos: "Fotos"
digitalClock: "Digitaluhr"
federation: "Föderation"
postForm: "Neue Notiz anfertigen"
_cw:
hide: "Ausblenden"
show: "Mehr anzeigen"
@@ -1238,14 +1279,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"

View File

@@ -16,6 +16,9 @@ noNotes: "No notes"
noNotifications: "No notifications"
instance: "Instance"
settings: "Settings"
basicSettings: "Basic Settings"
otherSettings: "Other Settings"
openInWindow: "Open in window"
profile: "Profile"
timeline: "Timeline"
noAccountDescription: "This user has not written their bio yet."
@@ -29,7 +32,7 @@ users: "Users"
addUser: "Add a user"
favorite: "Favorite"
favorites: "Favorites"
unfavorite: "Undo favorite"
unfavorite: "Unfavorite"
pin: "Pin to profile"
unpin: "Unpin from profile"
copyContent: "Copy contents"
@@ -40,6 +43,7 @@ deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? Yo
addToList: "Add to list"
sendMessage: "Send a message"
copyUsername: "Copy username"
searchUser: "User search"
reply: "Reply"
loadMore: "Load more"
youGotNewFollower: "Followed you"
@@ -66,8 +70,11 @@ followers: "Followers"
followsYou: "Follows you"
createList: "Create list"
manageLists: "Manage lists"
error: "Something happened :("
error: "Error"
somethingHappened: "An error occurred"
retry: "Retry"
pageLoadError: "Failed to load page"
pageLoadErrorDescription: "This is normally caused by network errors or the browser's cache. Try clearung the cache and then try again after waiting a little while."
enterListName: "List name"
privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval"
@@ -106,6 +113,8 @@ unsuspendConfirm: "Are you sure you that want to unsuspend this account?"
selectList: "Select a list"
selectAntenna: "Select an Antenna"
selectWidget: "Select a widget"
editWidgets: "Edit widgets"
editWidgetsExit: "Done"
customEmojis: "Custom Emoji"
emoji: "Emoji"
emojiName: "Emoji name"
@@ -177,7 +186,6 @@ processing: "Processing"
preview: "Preview"
default: "Default"
noCustomEmojis: "There are no emojis"
customEmojisOfRemote: "Emojis from other instances"
noJobs: "There are no jobs"
federating: "Federating"
blocked: "Blocked"
@@ -404,6 +412,7 @@ noMessagesYet: "No messages yet"
newMessageExists: "You've got a new message"
onlyOneFileCanBeAttached: "You can only attach one file to a message"
signinRequired: "Please sign in"
invitations: "Invite"
invitationCode: "Invitation code"
checking: "Checking"
available: "Available"
@@ -445,7 +454,7 @@ total: "Total"
weekOverWeekChanges: "Weekly"
dayOverDayChanges: "Daily"
appearance: "Appearance"
clinetSettings: "Client Settings"
clientSettings: "Client settings"
accountSettings: "Account Settings"
promotion: "Promoted"
promote: "Promote"
@@ -476,6 +485,8 @@ newNoteRecived: "You've got a new note"
sounds: "Sounds"
listen: "Listen"
none: "None"
showInPage: "Show in page"
popout: "Pop-out"
volume: "Volume"
details: "Details"
chooseEmoji: "Choose an emoji"
@@ -518,7 +529,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 +574,38 @@ overview: "Overview"
logs: "Logs"
delayed: "Delayed"
database: "Database"
channel: "Channel"
channel: "Channels"
create: "Create"
notificationSetting: "Notification settings"
notificationSettingDesc: "Select the type of notification to display"
useGlobalSetting: "Use global setting"
useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made."
other: "Other"
regenerateLoginToken: "Regenerate login token"
regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
setMultipleBySeparatingWithSpace: "You can set multiple by separating them with spaces."
fileIdOrUrl: "File-ID or URL"
chatOpenBehavior: "Behavior of the chat window when opened"
sample: "Sample"
abuseReports: "Reports"
reportAbuse: "Report"
reportAbuseOf: "Report {name}"
fillAbuseReportDescription: "Please fill in the report details. If it is about a specific note, please include its URL."
abuseReported: "Your report has been sent. Thank you very much."
send: "Send"
abuseMarkAsResolved: "Mark report as resolved"
openInNewTab: "Open in new tab"
openInSideView: "Open in side view"
defaultNavigationBehaviour: "Default navigation behavior"
editTheseSettingsMayBreakAccount: "Editing these settings may damage your account."
instanceTicker: "Instance information of the Note"
random: "Random"
_reversi:
total: "Total"
_instanceTicker:
none: "Never show"
remote: "Show for remote users"
always: "Always show"
_serverDisconnectedBehavior:
reload: "Automatically reload"
dialog: "Show warning dialog"
@@ -576,8 +616,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 +822,7 @@ _widgets:
photos: "Photos"
digitalClock: "Digital clock"
federation: "Federation"
postForm: "Compose a note"
_cw:
hide: "Hide"
show: "Load more"
@@ -1238,14 +1279,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"

View File

@@ -16,6 +16,9 @@ noNotes: "No hay notas"
noNotifications: "No hay notificaciones"
instance: "Instancia"
settings: "Configuración"
basicSettings: "Configuración Básica"
otherSettings: "Configuración avanzada"
openInWindow: "Abrir en una ventana"
profile: "Perfil"
timeline: "Linea de tiempo"
noAccountDescription: "Este usuario no tiene una descripción"
@@ -40,6 +43,7 @@ deleteAndEditConfirm: "¿Quieres borrar y editar este nota? Las reacciones, reno
addToList: "Agregar a lista"
sendMessage: "Énviar mensaje"
copyUsername: "Copiar nombre de usuario"
searchUser: "Búsqueda de usuarios"
reply: "Responder"
loadMore: "Ver más"
youGotNewFollower: "te ha seguido"
@@ -66,8 +70,11 @@ followers: "Seguidores"
followsYou: "Te sigue"
createList: "Crear lista"
manageLists: "Administrar listas"
error: "Ocurrió un problema"
error: "Error"
somethingHappened: "Ocurrió un error"
retry: "Reintentar"
pageLoadError: "Error al leer la página"
pageLoadErrorDescription: "Normalmente es debido a la red o al caché del navegador. Por favor limpie el caché o intente más tarde."
enterListName: "Ingrese nombre de lista"
privacy: "Privacidad"
makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento"
@@ -106,6 +113,8 @@ unsuspendConfirm: "¿Quiere dejar de suspender esta cuenta?"
selectList: "Seleccione una lista"
selectAntenna: "Seleccionar antena"
selectWidget: "Seleccionar widget"
editWidgets: "Editar widgets"
editWidgetsExit: "Terminar edición"
customEmojis: "Emojis personalizados"
emoji: "Emoji"
emojiName: "Nombre del emoji"
@@ -177,7 +186,6 @@ processing: "Procesando"
preview: "Vista previa"
default: "Predeterminado"
noCustomEmojis: "No hay emojis personalizados"
customEmojisOfRemote: "Emojis remotos"
noJobs: "No hay trabajos"
federating: "Federando"
blocked: "Bloqueando"
@@ -404,6 +412,7 @@ noMessagesYet: "Aún no hay chat"
newMessageExists: "Tienes un mensaje nuevo"
onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje"
signinRequired: "Iniciar sesión"
invitations: "Invitar"
invitationCode: "Código de invitación"
checking: "Comprobando"
available: "Disponible"
@@ -445,7 +454,7 @@ total: "Total"
weekOverWeekChanges: "Dif semanal"
dayOverDayChanges: "Dif diaria"
appearance: "Apariencia"
clinetSettings: "Ajustes del cliente"
clientSettings: "Configuración del cliente"
accountSettings: "Ajustes de cuenta"
promotion: "Promovido"
promote: "Promover"
@@ -476,6 +485,8 @@ newNoteRecived: "Tienes una nota nuevo"
sounds: "Sonidos"
listen: "Escuchar"
none: "Ninguna"
showInPage: "Mostrar en la página"
popout: "Popout"
volume: "Volumen"
details: "Detalles"
chooseEmoji: "Elije un emoji"
@@ -518,7 +529,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 +576,31 @@ delayed: "atrasado"
database: "Base de datos"
channel: "Canal"
create: "Crear"
notificationSetting: "Ajustes de Notificaciones"
notificationSettingDesc: "Por favor elija el tipo de notificación a mostrar"
useGlobalSetting: "Usar ajustes globales"
useGlobalSettingDesc: "Al activarse, se usará la configuración de notificaciones de la cuenta, al desactivarse se pueden hacer configuraciones particulares."
other: "Otro"
regenerateLoginToken: "Regenerar token de login"
regenerateLoginTokenDescription: "Regenerar el token usado internamente durante el login. No siempre es necesario hacerlo. Al hacerlo de nuevo, se deslogueará en todos los dispositivos."
setMultipleBySeparatingWithSpace: "Puedes añadir mas de uno, separado por espacios."
fileIdOrUrl: "Id del archivo o URL"
chatOpenBehavior: "Comportamiento al abrir el chat"
sample: "Muestra"
abuseReports: "Reportes"
reportAbuse: "Reportar"
reportAbuseOf: "Reportar a {name}"
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta."
abuseReported: "Se ha enviado el reporte. Muchas gracias."
send: "Enviar"
abuseMarkAsResolved: "Marcar reporte como resuelto"
openInNewTab: "Abrir en una Nueva Pestaña"
openInSideView: "Abrir en una vista al costado"
defaultNavigationBehaviour: "Navegación por defecto"
editTheseSettingsMayBreakAccount: "Editar estas configuraciones puede dañar su cuenta."
random: "Aleatorio"
_reversi:
total: "Total"
_serverDisconnectedBehavior:
reload: "Recargar automáticamente"
dialog: "Mostrar diálogo de advertencia"
@@ -782,6 +817,7 @@ _widgets:
photos: "Fotos"
digitalClock: "Reloj digital"
federation: "Federación"
postForm: "Formulario"
_cw:
hide: "Ocultar"
show: "Ver más"
@@ -1244,8 +1280,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"

View File

@@ -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: "Lutilisateur·rice na 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 dutilisateur·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 dabonnement"
@@ -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 ny 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"
@@ -403,6 +411,7 @@ noMessagesYet: "Pas encore discuté"
newMessageExists: "Vous avez un nouveau message"
onlyOneFileCanBeAttached: "Vous ne pouvez joindre quun seul fichier au message"
signinRequired: "Veuillez vous connecter"
invitations: "Inviter"
invitationCode: "Code dinvitation"
checking: "Vérification"
available: "Disponible"
@@ -444,7 +453,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 +483,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 +526,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 +547,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 doubli."
@@ -549,16 +559,48 @@ smtpUser: "Nom dutilisateur·rice"
smtpPass: "Mot de passe"
emptyToDisableSmtpAuth: "Laisser le nom dutilisateur 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 dun, séparés par des espaces."
fileIdOrUrl: "ID du fichier ou URL"
chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouverture"
random: "Aléatoire"
_reversi:
total: "Total"
_serverDisconnectedBehavior:
reload: "Rechargement automatique"
_channel:
create: "Créer un canal"
edit: "Éditer le canal"
removeBanner: "Supprimer la bannière"
featured: "Tendances"
usersCount: "{n} Participants"
notesCount: "{n} Notes"
_sidebar:
full: "Complet"
icon: "Avatar"
hide: "Masquer"
_wordMute:
muteWords: "Mot à mettre en sourdine"
muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
mutedNotes: "Notes mises en sourdine"
_theme:
explore: "Explorer les thèmes"
install: "Installer un thème"
@@ -569,6 +611,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 +638,7 @@ _theme:
renote: "Renote"
divider: "Séparateur"
infoWarnFg: "Texte davertissement"
cwBg: "Arrière-plan du CW"
badge: "Badge"
messageBg: "Arrière plan de la discussion"
_sfx:
@@ -678,6 +723,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 +758,7 @@ _widgets:
photos: "Photos"
digitalClock: "Horloge numérique"
federation: "Fédération"
postForm: "Formulaire à publier"
_cw:
hide: "Masquer"
show: "Afficher plus …"
@@ -752,6 +800,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 +1214,15 @@ _notification:
yourFollowRequestAccepted: "Votre demande dabonnement 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"

View File

@@ -412,6 +412,7 @@ noMessagesYet: "まだチャットはありません"
newMessageExists: "新しいメッセージがあります"
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
signinRequired: "ログインしてください"
invitations: "招待"
invitationCode: "招待コード"
checking: "確認しています"
available: "利用できます"
@@ -586,6 +587,59 @@ setMultipleBySeparatingWithSpace: "スペースで区切って複数設定でき
fileIdOrUrl: "ファイルIDまたはURL"
chatOpenBehavior: "チャットを開くときの動作"
sample: "サンプル"
abuseReports: "通報"
reportAbuse: "通報"
reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のートがある場合はそのURLも記入してください。"
abuseReported: "内容が送信されました。ご報告ありがとうございました。"
send: "送信"
abuseMarkAsResolved: "対応済みにする"
openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション"
editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウントが破損する可能性があります。"
instanceTicker: "ノートのインスタンス情報"
waitingFor: "{x}を待っています"
random: "ランダム"
_reversi:
reversi: "リバーシ"
gameSettings: "対局の設定"
chooseBoard: "ボードを選択"
blackOrWhite: "先行/後攻"
blackIs: "{name}が黒(先行)"
rules: "ルール"
botSettings: "Botのオプション"
thisGameIsStartedSoon: "対局は数秒後に開始されます"
waitingForOther: "相手の準備が完了するのを待っています"
waitingForMe: "あなたの準備が完了するのを待っています"
waitingBoth: "準備してください"
ready: "準備完了"
cancelReady: "準備を再開"
opponentTurn: "相手のターンです"
myTurn: "あなたのターンです"
turnOf: "{name}のターンです"
pastTurnOf: "{name}のターン"
surrender: "投了"
surrendered: "投了により"
drawn: "引き分け"
won: "{name}の勝ち"
black: "黒"
white: "白"
total: "合計"
turnCount: "{count}ターン目"
myGames: "自分の対局"
allGames: "みんなの対局"
ended: "終了"
playing: "対局中"
isLlotheo: "石の少ない方が勝ち(ロセオ)"
loopedMap: "ループマップ"
canPutEverywhere: "どこでも置けるモード"
_instanceTicker:
none: "表示しない"
remote: "リモートユーザーに表示"
always: "常に表示"
_serverDisconnectedBehavior:
reload: "自動でリロード"

View File

@@ -65,7 +65,6 @@ followers: "フォロワー"
followsYou: "フォローされとるで"
createList: "リスト作る"
manageLists: "リストの管理"
error: "問題が発生してん"
retry: "もっぺんやってみる"
enterListName: "リスト名を入れてや"
privacy: "プライバシーってなんや?オカンの年齢か?"
@@ -173,7 +172,6 @@ processing: "処理しとる"
preview: "プレビュー"
default: "デフォルト"
noCustomEmojis: "絵文字はあらへん"
customEmojisOfRemote: "リモートの絵文字"
noJobs: "ジョブはあらへん"
federating: "連合しとる"
blocked: "ブロックしとる"
@@ -352,6 +350,7 @@ notFoundDescription: "指定されたURLに該当するページはあらへん
close: "さいなら"
joinedGroups: "参加しとるグループ"
invites: "来てや"
invitations: "来てや"
smtpHost: "ホスト"
smtpUser: "ユーザー名"
smtpPass: "パスワード"

View File

@@ -66,7 +66,6 @@ followers: "팔로워"
followsYou: "당신을 팔로우합니다"
createList: "리스트 만들기"
manageLists: "리스트 관리"
error: "오류가 발생했습니다"
retry: "다시 시도"
enterListName: "리스트 이름을 입력"
privacy: "프라이버시"
@@ -177,7 +176,6 @@ processing: "처리중"
preview: "미리보기"
default: "기본값"
noCustomEmojis: "이모지가 없습니다"
customEmojisOfRemote: "다른 인스턴스들의 이모지"
noJobs: "작업이 없습니다"
federating: "연합 중"
blocked: "차단됨"
@@ -403,6 +401,7 @@ noMessagesYet: "아직 대화가 없습니다"
newMessageExists: "새 메시지가 있습니다"
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
signinRequired: "로그인 해주세요"
invitations: "초대"
invitationCode: "초대 코드"
checking: "확인하는 중입니다"
available: "사용 가능합니다"
@@ -443,7 +442,6 @@ remote: "리모트"
total: "합계"
weekOverWeekChanges: "지난주보다"
dayOverDayChanges: "어제보다"
clinetSettings: "클라이언트 설정"
accountSettings: "계정 설정"
promotion: "프로모션"
promote: "프로모션하기"
@@ -515,7 +513,6 @@ enableInfiniteScroll: "자동으로 좀 더 보기"
visibility: "공개 범위"
poll: "투표"
useCw: "내용 숨기기"
fixedWidgetsPosition: "위젯의 위치 고정"
enablePlayer: "플레이어 열기"
disablePlayer: "플레이어 닫기"
expandTweet: "트윗 확장하기"
@@ -552,6 +549,9 @@ copy: "복사"
logs: "로그"
database: "데이터베이스"
channel: "채널"
random: "랜덤"
_reversi:
total: "합계"
_channel:
create: "채널 생성"
setBanner: "배너 설정"
@@ -708,6 +708,7 @@ _widgets:
photos: "사진"
digitalClock: "디지털 시계"
federation: "연합"
postForm: "글 입력란"
_cw:
hide: "숨기기"
show: "더 보기"
@@ -1165,7 +1166,6 @@ _notification:
renote: "Renote"
quote: "인용"
reaction: "리액션"
receiveFollowRequest: "팔로우 요청"
_deck:
alwaysShowMainColumn: "메인 칼럼 항상 표시"
columnAlign: "칼럼 정렬"

View File

@@ -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"

File diff suppressed because it is too large Load Diff

View File

@@ -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: "启用本地时间线功能"
@@ -404,6 +412,7 @@ noMessagesYet: "现在没有新的聊天"
newMessageExists: "新信息"
onlyOneFileCanBeAttached: "只能添加一个附件"
signinRequired: "请先登录"
invitations: "邀请"
invitationCode: "邀请码"
checking: "正在确认"
available: "可用"
@@ -445,7 +454,7 @@ total: "总计"
weekOverWeekChanges: "与前一周相比"
dayOverDayChanges: "与前一日相比"
appearance: "外观"
clinetSettings: "客户端设置"
clientSettings: "客户端设置"
accountSettings: "账户设置"
promotion: "推广"
promote: "推广"
@@ -476,6 +485,8 @@ newNoteRecived: "有新的帖子"
sounds: "声音"
listen: "听"
none: "空"
showInPage: "在页面中显示"
popout: "弹窗"
volume: "音量"
details: "详情"
chooseEmoji: "选择表情符号"
@@ -518,7 +529,6 @@ enableInfiniteScroll: "启用自动滚动页面模式"
visibility: "可见性"
poll: "调查问卷"
useCw: "隐藏内容"
fixedWidgetsPosition: "固定小工具的位置"
enablePlayer: "打开播放器"
disablePlayer: "关闭播放器"
expandTweet: "展开推文"
@@ -566,6 +576,31 @@ delayed: "延迟"
database: "数据库"
channel: "频道"
create: "创建"
notificationSetting: "通知设置"
notificationSettingDesc: "选择要显示的通知类型。"
useGlobalSetting: "使用全局设置"
useGlobalSettingDesc: "启用时,将使用帐户通知设置。关闭时,则可以单独设置。"
other: "其他"
regenerateLoginToken: "重新生成登录令牌"
regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。"
setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。"
fileIdOrUrl: "文件ID或者URL"
chatOpenBehavior: "聊天窗口打开时的行为"
sample: "示例"
abuseReports: "举报"
reportAbuse: "举报"
reportAbuseOf: "举报{name}"
fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子请同时填写URL地址。"
abuseReported: "内容已发送。感谢您的报告。"
send: "发送"
abuseMarkAsResolved: "处理完毕"
openInNewTab: "在新标签页中打开"
openInSideView: "在侧边栏中打开"
defaultNavigationBehaviour: "默认导航"
editTheseSettingsMayBreakAccount: "编辑这些设置可以会损坏您的账号"
random: "随机"
_reversi:
total: "总计"
_serverDisconnectedBehavior:
reload: "自动重载"
dialog: "对话框警告"
@@ -782,6 +817,7 @@ _widgets:
photos: "照片"
digitalClock: "数字时钟"
federation: "联邦宇宙"
postForm: "投稿窗口"
_cw:
hide: "隐藏"
show: "查看更多"
@@ -1244,8 +1280,11 @@ _notification:
renote: "转发"
quote: "引用"
reaction: "回应"
pollVote: "投票"
receiveFollowRequest: "关注请求"
pollVote: "问卷调查已投票"
receiveFollowRequest: "收到关注请求"
followRequestAccepted: "关注请求已接受"
groupInvited: "加入群组邀请"
app: "关联应用的通知"
_deck:
alwaysShowMainColumn: "总是显示主列"
columnAlign: "列对齐"

View File

@@ -1,21 +1,24 @@
---
_lang_: "中文(繁體)"
_lang_: "繁體中文"
introMisskey: "歡迎! Misskey是一個開源的去中心化的社群網站。\n通過「貼文」來分享現在發生的事情吧 📡\n「反應」功能可以讓你快速的對大家的「帖子」來表達感情👍\n一起來探索新的世界吧 🚀"
monthAndDay: "{month}月 {day}日"
search: "搜尋"
notifications: "通知"
username: "使用名稱"
username: "使用名稱"
password: "密碼"
fetchingAsApObject: "從 Fediverse 查詢中..."
ok: "確定"
ok: "OK"
gotIt: "知道了"
cancel: "取消"
enterUsername: "輸入使用者名稱"
renotedBy: "{user}轉發"
renotedBy: "{user} 轉發"
noNotes: "貼文不可用。"
noNotifications: "沒有通知"
instance: "實例"
settings: "設定"
basicSettings: "基本設定"
otherSettings: "其他設定"
openInWindow: "在新視窗開啟"
profile: "個人檔案"
timeline: "時間軸"
noAccountDescription: "此用戶還沒有自我介紹"
@@ -40,6 +43,7 @@ deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應,
addToList: "添加至清單"
sendMessage: "發送訊息"
copyUsername: "複製用戶名"
searchUser: "搜尋用戶"
reply: "回覆"
loadMore: "瀏覽更多"
youGotNewFollower: "您有新的追隨者"
@@ -66,8 +70,10 @@ followers: "追隨者"
followsYou: "追隨你的人"
createList: "建立清單"
manageLists: "管理清單"
error: "發生錯誤"
error: "錯誤"
somethingHappened: "發生錯誤"
retry: "重試"
pageLoadError: "載入頁面失敗"
enterListName: "輸入清單名稱"
privacy: "隱私"
makeFollowManuallyApprove: "手動審核追隨請求"
@@ -106,16 +112,18 @@ unsuspendConfirm: "確定解凍此帳號?"
selectList: "選擇清單"
selectAntenna: "選擇天線"
selectWidget: "選擇小工具"
editWidgets: "編輯小工具"
editWidgetsExit: "停止編輯"
customEmojis: "自訂表情符號"
emoji: "表情符號"
emojiName: "表情符號名稱"
emojiUrl: "表情符號URL"
addEmoji: "新增表情符號"
settingGuide: "推薦設定"
cacheRemoteFiles: "遠程文件緩存"
cacheRemoteFilesDescription: "如果禁用此設定,遠程文件將會被直接連結而非緩存。禁用將節省服務器上的存儲空間,但會因為沒有生成預覽圖而增加流量。"
flagAsBot: "此帳戶是Bot"
flagAsCat: "此帳戶是Cat"
cacheRemoteFiles: "緩存非遠程檔案"
cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間。但資料會因直接連線從而產生額外連接數據。"
flagAsBot: "此使用者是機器人"
flagAsCat: "此使用者是貓"
autoAcceptFollowed: "自動許可追隨"
addAcount: "新增帳號"
loginFailed: "登入失敗"
@@ -176,7 +184,6 @@ processing: "處理中"
preview: "預覽"
default: "預設"
noCustomEmojis: "沒有表情符號"
customEmojisOfRemote: "來自其他實例的表情符號"
noJobs: "沒有任務"
federating: "整合搜索中"
blocked: "已封鎖"
@@ -220,10 +227,11 @@ messageRead: "已讀"
noMoreHistory: "沒有更多歷史紀錄"
startMessaging: "開始傳送訊息"
nUsersRead: "{n}人已讀"
agreeTo: "我同意{0}"
tos: "使用條款"
start: "開始"
home: "首頁"
remoteUserCaution: "由於是遠程用戶,信息不完整。"
remoteUserCaution: "由於該用戶來自遠端實例,因此資料用戶並未即時更新。"
activity: "動態"
images: "圖片"
birthday: "生日"
@@ -293,7 +301,7 @@ disablingTimelinesInfo: "即使您禁用了時間線功能,管理員和協調
registration: "註冊"
enableRegistration: "開啟新用戶註冊"
invite: "邀請"
proxyRemoteFiles: "代理遠程檔案"
proxyRemoteFiles: "遠端代理檔案"
proxyRemoteFilesDescription: "啟用此設置後,由於超出存儲容量而未保存或刪除的遠程文件將被本地代理,並且將生成預覽圖。這不影響服務器的存儲。"
driveCapacityPerLocalAccount: "每個本地用戶的雲端容量"
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量"
@@ -316,7 +324,7 @@ antennas: "天線"
manageAntennas: "管理天線"
name: "名稱"
antennaSource: "接收來源"
antennaKeywords: "包含關鍵字"
antennaKeywords: "包含關鍵字"
antennaExcludeKeywords: "排除關鍵字"
antennaKeywordsDescription: "用空格分隔指定AND、用換行符分隔指定OR"
notifyAntenna: "通知我有新的貼文"
@@ -401,6 +409,7 @@ noMessagesYet: "沒有訊息"
newMessageExists: "有新的訊息"
onlyOneFileCanBeAttached: "只能添加一個附件"
signinRequired: "請先登入"
invitations: "邀請"
invitationCode: "邀請碼"
checking: "確認中"
available: "可用的"
@@ -430,12 +439,34 @@ category: "類別"
tags: "標籤"
docSource: "文件來源"
createAccount: "建立帳戶"
existingAcount: "現有帳戶"
regenerate: "再生"
fontSize: "字體大小"
openImageInNewTab: "於新分頁中開啟圖片"
local: "本地"
remote: "遠端"
total: "合計"
clinetSettings: "用戶端設定"
weekOverWeekChanges: "與上週相比"
dayOverDayChanges: "與前一日相比"
appearance: "外觀"
clientSettings: "用戶端設定"
accountSettings: "帳號設定"
promotion: "推廣貼文"
numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文"
objectStorageBaseUrl: "Base URL"
objectStorageBucket: "儲存空間Bucket"
objectStoragePrefix: "前綴"
objectStorageEndpoint: "訪問網域名稱Endpoint"
objectStorageEndpointDesc: "如要使用AWS S3請留空。否則請根據伺服器要求以'<host>'或 '<host>:<port>'的形式設定訪問網域名稱Endpoint。"
objectStorageRegion: "地域Region"
objectStorageUseSSL: "使用SSL"
objectStorageUseProxy: "使用網路代理"
serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄"
sounds: "音效"
none: "無"
showInPage: "在頁面中顯示"
volume: "音量"
details: "詳細資訊"
chooseEmoji: "選擇您的表情符號\n"
@@ -443,14 +474,21 @@ unableToProcess: "操作無法完成"
recentUsed: "最近使用"
install: "安裝"
uninstall: "解除安裝"
installedApps: "已授權的應用程式"
nothing: "未發現"
installedDate: "安裝時間"
lastUsedDate: "最後上線日期"
state: "狀態"
sort: "排序"
ascendingOrder: "昇冪"
descendingOrder: "降冪"
scratchpad: "暫存記憶體"
output: "輸出"
script: "腳本"
updateRemoteUser: "更新非本地用戶資料"
deleteAllFiles: "刪除所有檔案"
deleteAllFilesConfirm: "要删除所有檔案吗?"
removeAllFollowing: "解除所有追隨"
userSuspended: "該用戶已被凍結"
userSilenced: "該用戶已被禁言。"
sidebar: "側邊列"
@@ -468,7 +506,6 @@ enableInfiniteScroll: "啟用自動滾動頁面模式"
visibility: "公開範圍"
poll: "投票"
useCw: "隱藏內容"
fixedWidgetsPosition: "固定小工具的位置"
enablePlayer: "打開播放器"
disablePlayer: "關閉播放器"
expandTweet: "展開推文"
@@ -488,17 +525,37 @@ tokenRequested: "允許訪問帳號"
notificationType: "通知形式"
edit: "編輯"
useStarForReactionFallback: "以★代替未知的表情符號"
emailConfig: "電郵服務器設定"
emailConfig: "電子郵件伺服器設定"
enableEmail: "啟用發送電郵功能"
emailConfigInfo: "用於確認電郵地址及密碼重置"
email: "電郵地址"
smtpConfig: "SMTP服器設定"
smtpConfig: "SMTP服器設定"
smtpHost: "主機"
smtpPort: "端口"
smtpUser: "使用名稱"
smtpUser: "使用名稱"
smtpPass: "密碼"
emptyToDisableSmtpAuth: "留空使用者名稱和密碼以禁用SMTP驗證。"
testEmail: "郵件測試發送"
display: "檢視"
copy: "複製"
metrics: "指標"
overview: "概覽"
logs: "日誌"
delayed: "延遲"
database: "資料庫"
channel: "頻道"
create: "新增"
notificationSetting: "通知設定"
other: "其他"
sample: "範例 "
abuseReports: "檢舉"
reportAbuse: "檢舉"
reportAbuseOf: "檢舉{name}"
send: "發送"
openInNewTab: "在新分頁中開啟"
random: "隨機"
_reversi:
total: "合計"
_serverDisconnectedBehavior:
reload: "自動重載"
dialog: "以對話框警告"
@@ -506,7 +563,7 @@ _serverDisconnectedBehavior:
_channel:
create: "建立頻道"
edit: "編輯頻道"
setBanner: "設置封面圖"
setBanner: "設定橫幅"
removeBanner: "移除封面圖"
featured: "流行"
owned: "管理中"
@@ -515,12 +572,23 @@ _channel:
notesCount: "有{n}個帖子"
_sidebar:
icon: "頭像"
hide: "隱藏"
_wordMute:
muteWords: "加入靜音文字"
_theme:
constant: "常數"
color: "顏色"
func: "函数"
keys:
bg: "背景"
fg: "文本"
mention: "提及"
renote: "轉發貼文"
divider: "分割線"
infoBg: "資訊背景"
infoFg: "資訊內容"
infoWarnBg: "警告背景"
infoWarnFg: "警告字元"
_sfx:
note: "貼文"
noteMy: "我的貼文"
@@ -557,6 +625,7 @@ _tutorial:
step4_1: "筆記發出去了嗎?"
step4_2: "如果你的貼文有顯示在時間軸上,就代表已經發文成功。"
step5_1: "現在試試看追隨其他人來讓你的時間軸變得更生動吧。"
step5_2: "你可以在{featured}上看到受歡迎的貼文,你也可以選擇從列表中追隨你喜歡的人,或者在{explore}上找到熱門使用者。"
step5_3: "想要追隨其他人,只要點擊他們的頭像並按「追隨」即可。"
step5_4: "如果使用者的名字旁有鎖頭的圖示,代表他們需要手動核准你的追隨請求。"
step6_1: "現在你可以在時間軸上看到其他用戶的貼文"
@@ -577,8 +646,16 @@ _permissions:
"read:reactions": "查看反應"
"write:reactions": "編輯反應"
"write:votes": "投票"
"read:user-groups": "顯示使用者群組"
"write:user-groups": "編輯使用者群組"
"read:channels": "已查看的頻道"
"write:channels": "操作頻道"
_antennaSources:
all: "全部貼文"
homeTimeline: "來自已追隨使用者的貼文"
users: "來自特定使用者的貼文"
userList: "來自特定清單中的貼文"
userGroup: "來自特定群組的貼文"
_weekday:
sunday: "週日"
monday: "週一"
@@ -588,63 +665,220 @@ _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:
public: "公開"
home: "首頁"
followers: "追隨者"
specified: "指定使用者"
specifiedDescription: "僅發送至指定使用者"
localOnly: "僅限本地"
localOnlyDescription: "對遠端使用者隱藏"
_postForm:
replyPlaceholder: "回覆此貼文..."
quotePlaceholder: "引用此貼文..."
channelPlaceholder: "發佈到頻道"
_placeholders:
a: "今天過得如何?"
b: "有什麼新鮮事嗎?"
c: "有什麼新鮮想法嗎?"
d: "想要發布些什麼嗎?"
e: "寫些什麼吧..."
f: "期待你發佈的內容..."
_profile:
name: "名稱"
username: "使用名稱"
username: "使用名稱"
description: "關於我"
youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag"
metadata: "更多資訊"
metadataLabel: "標籤"
metadataContent: "内容"
_exportOrImport:
allNotes: "全部貼文"
followingList: "追隨中"
muteList: "消音"
blockingList: "封鎖"
userLists: "清單"
_charts:
usersIncDec: "使用者増減"
usersTotal: "使用者合共"
activeUsers: "活躍使用者"
notesIncDec: "貼文増減"
localNotesIncDec: "本地貼文増減"
remoteNotesIncDec: "非本地貼文的數目增减"
notesTotal: "貼文合共"
filesIncDec: "檔案増減"
filesTotal: "累計檔案"
storageUsageIncDec: "儲存空間的増減"
storageUsageTotal: "已使用的儲存空間合共"
_instanceCharts:
requests: "請求"
users: "使用者増減"
usersTotal: "總計使用者"
notes: "貼文増減"
notesTotal: "累計貼文"
ff: "追隨/追隨者的増減"
ffTotal: "追隨/追隨者累計"
cacheSize: "增加或減少快取用量"
cacheSizeTotal: "快取大小總計"
files: "檔案數量的増減"
filesTotal: "檔案數量總計"
_timelines:
home: "首頁"
local: "本地"
social: "社群"
global: "全域"
_rooms:
roomOf: "{user}的房間"
addFurniture: "擺放家具"
translate: "移動 "
rotate: "旋轉"
exit: "返回"
remove: "移除"
clear: "全部移除"
clearConfirm: "確定要移除全部家具嗎?"
leaveConfirm: "修改未儲存,是否要離開?"
chooseImage: "選擇圖像"
roomType: "房間種類"
carpetColor: "地板顏色"
_roomType:
default: "預設"
washitsu: "和室"
_furnitures:
milk: "牛奶盒"
bed: "床"
low-table: "咖啡桌"
desk: "書桌"
chair: "椅子"
chair2: "椅子2"
fan: "通風機"
pc: "電腦"
plant: "觀葉植物"
plant2: "觀葉植物2"
eraser: "橡皮擦"
pencil: "鉛筆"
pudding: "布丁"
cardboard-box: "紙板箱"
cardboard-box2: "紙板箱2"
cardboard-box3: "紙板箱3"
book: "讀物"
book2: "讀物2"
piano: "鋼琴"
moon: "月亮"
corkboard: "木栓板"
mousepad: "滑鼠墊"
monitor: "監視器"
keyboard: "鍵盤"
carpet-stripe: "條紋地毯"
bin: "垃圾箱"
cup-noodle: "杯面"
holo-display: "投影機"
energy-drink: "能量飲料"
doll-ai: "小藍的人偶公仔"
banknote: "大疊鈔票"
_pages:
newPage: "建立頁面"
editPage: "編輯頁面"
created: "頁面已建立"
updated: "頁面已更新"
deleted: "頁面已被刪除"
editThisPage: "編輯此頁面"
viewSource: "檢視原始碼"
viewPage: "顯示頁面"
like: "喜歡"
unlike: "收回喜歡"
my: "我的頁面"
liked: "已喜歡的頁面"
inspector: "面板檢查"
variables: "變數"
title: "標題"
url: "頁面網址"
fontSerif: "襯線體"
fontSansSerif: "無襯線體"
inputBlocks: "輸入"
blocks:
section: "區段"
image: "圖片"
button: "按鈕"
if: "如果"
_if:
variable: "變數"
_post:
text: "内容"
_textInput:
name: "變數名稱"
text: "標題"
_textareaInput:
name: "變數名稱"
text: "標題"
numberInput: "輸入數值"
_numberInput:
name: "變數名稱"
text: "標題"
_canvas:
width: "寬度"
_counter:
height: "高度"
switch: "開關"
_switch:
name: "變數名稱"
text: "標題"
default: "預設值"
counter: "計數器"
_counter:
name: "變數名稱"
text: "標題"
inc: "増加値"
_button:
text: "標題"
colored: "彩色"
_action:
_dialog:
content: "内容"
resetRandom: "重設亂數"
pushEvent: "發送事件"
_pushEvent:
event: "事件名稱"
no-variable: "沒有"
callAiScript: "調用AiScript"
_callAiScript:
functionName: "函數名稱"
radioButton: "選項"
_radioButton:
name: "變數名稱"
title: "標題"
script:
categories:
logical: "邏輯運算"
operation: "計算"
comparison: "對比"
random: "隨機"
value: "數值 "
fn: "函数"
text: "文本操作"
@@ -667,18 +901,22 @@ _pages:
_add:
arg1: "A"
arg2: "B"
subtract: "减去"
_subtract:
arg1: "A"
arg2: "B"
multiply: "乘"
_multiply:
arg1: "A"
arg2: "B"
divide: "除"
_divide:
arg1: "A"
arg2: "B"
_mod:
arg1: "A"
arg2: "B"
round: "四舍五入"
_round:
arg1: "數值"
eq: "A和B相等"
@@ -720,6 +958,7 @@ _pages:
not: "否"
_not:
arg1: "否"
random: "隨機"
_random:
arg1: "機率"
rannum: "亂數"
@@ -782,20 +1021,31 @@ _relayStatus:
accepted: "已通過核准"
rejected: "已拒絕"
_notification:
youRenoted: "{name} 轉發了你的貼文"
youGotPoll: "{name}已投票"
youWereFollowed: "您有新的追隨者"
yourFollowRequestAccepted: "您的追隨請求已通過"
youWereInvitedToGroup: "您有新的群組邀請"
_types:
all: "全部 "
follow: "追隨中"
mention: "提及"
reply: "回覆"
renote: "轉發貼文"
quote: "引用"
reaction: "反應"
_deck:
swapLeft: "向左移動"
swapRight: "向右移動"
swapUp: "往上移動"
swapDown: "往下移動"
stackLeft: "向左折疊"
popRight: "向右彈出"
_columns:
widgets: "小工具"
notifications: "通知"
tl: "時間軸"
antenna: "天線"
list: "清單"
mentions: "提及"
direct: "指定使用者"

View File

@@ -0,0 +1,32 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class refineAbuseUserReport1603094348345 implements MigrationInterface {
name = 'refineAbuseUserReport1603094348345'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_d049123c413e68ca52abe734203"`);
await queryRunner.query(`DROP INDEX "IDX_d049123c413e68ca52abe73420"`);
await queryRunner.query(`DROP INDEX "IDX_5cd442c3b2e74fdd99dae20243"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "userId" TO "targetUserId"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "assigneeId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "resolved" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(2048) NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`CREATE INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a" ON "abuse_user_report" ("resolved") `);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de" FOREIGN KEY ("assigneeId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de"`);
await queryRunner.query(`DROP INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(512) NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "resolved"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "assigneeId"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "targetUserId" TO "userId"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5cd442c3b2e74fdd99dae20243" ON "abuse_user_report" ("userId", "reporterId") `);
await queryRunner.query(`CREATE INDEX "IDX_d049123c413e68ca52abe73420" ON "abuse_user_report" ("userId") `);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_d049123c413e68ca52abe734203" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
}

View 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"`);
}
}

View File

@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class instanceThemeColor1603776877564 implements MigrationInterface {
name = 'instanceThemeColor1603776877564'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "instance" ADD "themeColor" character varying(64) DEFAULT null`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "themeColor"`);
}
}

View File

@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class instanceFavicon1603781553011 implements MigrationInterface {
name = 'instanceFavicon1603781553011'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "instance" ADD "faviconUrl" character varying(256) DEFAULT null`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "faviconUrl"`);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.48.2",
"version": "12.51.0",
"codename": "indigo",
"repository": {
"type": "git",
@@ -46,7 +46,7 @@
"@koa/multer": "3.0.0",
"@koa/router": "9.0.1",
"@sinonjs/fake-timers": "6.0.1",
"@syuilo/aiscript": "0.11.0",
"@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.14.0",
"@types/cbor": "5.0.1",
@@ -92,7 +92,7 @@
"@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.0",
"@types/seedrandom": "2.4.28",
"@types/sharp": "0.25.0",
"@types/sharp": "0.26.0",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/speakeasy": "2.0.5",
"@types/tinycolor2": "1.4.2",
@@ -104,7 +104,7 @@
"@types/websocket": "1.0.1",
"@types/ws": "7.2.7",
"@typescript-eslint/parser": "4.4.0",
"@vue/compiler-sfc": "3.0.0",
"@vue/compiler-sfc": "3.0.2",
"abort-controller": "3.0.0",
"apexcharts": "3.22.0",
"autobind-decorator": "2.4.0",
@@ -123,19 +123,19 @@
"content-disposition": "0.5.3",
"core-js": "3.6.5",
"crc-32": "1.2.0",
"css-loader": "4.3.0",
"css-loader": "5.0.0",
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-entries": "3.1.0",
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1",
"eslint": "7.10.0",
"eslint-plugin-vue": "7.0.1",
"eslint": "7.11.0",
"eslint-plugin-vue": "7.1.0",
"eventemitter3": "4.0.7",
"feed": "4.2.1",
"fibers": "5.0.0",
"file-type": "15.0.1",
"file-type": "16.0.0",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"got": "11.7.0",
@@ -159,8 +159,8 @@
"js-yaml": "3.14.0",
"jsdom": "16.4.0",
"json5": "2.1.3",
"json5-loader": "4.0.0",
"jsonld": "3.1.1",
"json5-loader": "4.0.1",
"jsonld": "3.2.0",
"jsrsasign": "8.0.20",
"katex": "0.12.0",
"koa": "2.13.0",
@@ -190,9 +190,9 @@
"parsimmon": "1.16.0",
"pg": "8.4.1",
"portscanner": "2.2.0",
"postcss": "8.1.1",
"postcss-loader": "4.0.3",
"prismjs": "1.21.0",
"postcss": "8.1.3",
"postcss-loader": "4.0.4",
"prismjs": "1.22.0",
"probe-image-size": "5.0.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
@@ -202,8 +202,8 @@
"qrcode": "1.4.4",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.15.5",
"recaptcha-promise": "0.1.3",
"re2": "1.15.8",
"recaptcha-promise": "1.0.0",
"reconnecting-websocket": "4.4.0",
"redis": "3.0.2",
"redis-lock": "0.1.4",
@@ -216,47 +216,45 @@
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.27.0",
"sass-loader": "10.0.2",
"sass-loader": "10.0.4",
"seedrandom": "3.0.5",
"sharp": "0.26.1",
"sharp": "0.26.2",
"speakeasy": "2.0.0",
"stringz": "2.1.0",
"style-loader": "1.3.0",
"style-loader": "2.0.0",
"summaly": "2.4.0",
"syslog-pro": "1.0.0",
"systeminformation": "4.27.8",
"systeminformation": "4.27.10",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.117.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "8.0.4",
"ts-loader": "8.0.6",
"ts-node": "9.0.0",
"tslint": "6.1.3",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.28",
"typescript": "4.0.3",
"ulid": "2.3.0",
"url-loader": "4.1.0",
"url-loader": "4.1.1",
"uuid": "8.3.1",
"v-debounce": "0.1.2",
"vue": "3.0.1",
"vue": "3.0.2",
"vue-color": "2.7.1",
"vue-draggable-next": "1.0.8",
"vue-i18n": "9.0.0-beta.4",
"vue-i18n": "9.0.0-beta.6",
"vue-json-pretty": "1.7.0",
"vue-loader": "16.0.0-beta.7",
"vue-prism-component": "1.2.0",
"vue-loader": "16.0.0-beta.8",
"vue-prism-editor": "1.2.2",
"vue-router": "4.0.0-beta.13",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader-corejs3": "1.5.0",
"vue-template-compiler": "2.6.12",
"vuex": "4.0.0-beta.4",
"vuex-persistedstate": "3.1.0",
"web-push": "3.4.4",
"webpack": "5.1.3",
"webpack-cli": "3.3.12",
"webpack": "5.2.0",
"webpack-cli": "4.1.0",
"websocket": "1.0.32",
"ws": "7.3.1",
"xev": "2.0.1"

View 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>

View File

@@ -2,9 +2,9 @@
<span class="eiwwqkts" :class="{ cat }" :title="acct(user)" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick">
<img class="inner" :src="url"/>
</span>
<router-link class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id">
<MkA class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id">
<img class="inner" :src="url"/>
</router-link>
</MkA>
</template>
<script lang="ts">

View File

@@ -1,5 +1,5 @@
<template>
<router-link :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<div class="banner" v-if="channel.bannerUrl" :style="`background-image: url('${channel.bannerUrl}')`">
<div class="fade"></div>
<div class="name"><Fa :icon="faSatelliteDish"/> {{ channel.name }}</div>
@@ -30,7 +30,7 @@
{{ $t('updatedAt') }}: <MkTime :time="channel.lastNotedAt"/>
</span>
</footer>
</router-link>
</MkA>
</template>
<script lang="ts">

View File

@@ -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);
}
}
});

View File

@@ -4,7 +4,7 @@
<div class="icon" v-if="icon">
<Fa :icon="icon"/>
</div>
<div class="icon" v-else-if="!input && !select && !user" :class="type">
<div class="icon" v-else-if="!input && !select" :class="type">
<Fa :icon="faCheck" v-if="type === 'success'"/>
<Fa :icon="faTimesCircle" v-if="type === 'error'"/>
<Fa :icon="faExclamationTriangle" v-if="type === 'warning'"/>
@@ -12,11 +12,9 @@
<Fa :icon="faQuestionCircle" v-if="type === 'question'"/>
<Fa :icon="faSpinner" pulse v-if="type === 'waiting'"/>
</div>
<header v-if="title" v-html="title"></header>
<header v-if="title == null && user">{{ $t('enterUsername') }}</header>
<div class="body" v-if="text" v-html="text"></div>
<header v-if="title"><Mfm :text="title"/></header>
<div class="body" v-if="text"><Mfm :text="text"/></div>
<MkInput v-if="input" v-model:value="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput>
<MkInput v-if="user" v-model:value="userInputValue" autofocus @keydown="onInputKeydown"><template #prefix>@</template></MkInput>
<MkSelect v-if="select" v-model:value="selectedValue" autofocus>
<template v-if="select.items">
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
@@ -28,8 +26,8 @@
</template>
</MkSelect>
<div class="buttons" v-if="(showOkButton || showCancelButton) && !actions">
<MkButton inline @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user" :disabled="!canOk">{{ (showCancelButton || input || select || user) ? $t('ok') : $t('gotIt') }}</MkButton>
<MkButton inline @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('cancel') }}</MkButton>
<MkButton inline @click="ok" v-if="showOkButton" primary :autofocus="!input && !select">{{ (showCancelButton || input || select) ? $t('ok') : $t('gotIt') }}</MkButton>
<MkButton inline @click="cancel" v-if="showCancelButton || input || select">{{ $t('cancel') }}</MkButton>
</div>
<div class="buttons" v-if="actions">
<MkButton v-for="action in actions" inline @click="() => { action.callback(); close(); }" :primary="action.primary" :key="action.text">{{ action.text }}</MkButton>
@@ -46,8 +44,6 @@ import MkModal from '@/components/ui/modal.vue';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/ui/input.vue';
import MkSelect from '@/components/ui/select.vue';
import parseAcct from '../../misc/acct/parse';
import * as os from '@/os';
export default defineComponent({
components: {
@@ -77,9 +73,6 @@ export default defineComponent({
select: {
required: false
},
user: {
required: false
},
icon: {
required: false
},
@@ -105,28 +98,12 @@ export default defineComponent({
data() {
return {
inputValue: this.input && this.input.default ? this.input.default : null,
userInputValue: null,
selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null,
canOk: true,
faTimesCircle, faQuestionCircle, faSpinner, faInfoCircle, faExclamationTriangle, faCheck
};
},
watch: {
userInputValue() {
if (this.user) {
os.api('users/show', parseAcct(this.userInputValue)).then(u => {
this.canOk = u != null;
}).catch(() => {
this.canOk = false;
});
}
},
},
mounted() {
if (this.user) this.canOk = false;
document.addEventListener('keydown', this.onKeydown);
},
@@ -141,21 +118,13 @@ export default defineComponent({
},
async ok() {
if (!this.canOk) return;
if (!this.showOkButton) return;
if (this.user) {
const user = await os.api('users/show', parseAcct(this.userInputValue));
if (user) {
this.done(false, user);
}
} else {
const result =
this.input ? this.inputValue :
this.select ? this.selectedValue :
true;
this.done(false, result);
}
const result =
this.input ? this.inputValue :
this.select ? this.selectedValue :
true;
this.done(false, result);
},
cancel() {

View File

@@ -185,7 +185,7 @@ export default defineComponent({
transition: color 0.2s ease;
&:hover {
color: var(--textHighlighted);
color: var(--fgHighlighted);
transition: color 0s;
}

View File

@@ -9,7 +9,6 @@
import { defineComponent } from 'vue';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { twemojiSvgBase } from '../../misc/twemoji-base';
import * as os from '@/os';
export default defineComponent({
props: {

View File

@@ -1,102 +0,0 @@
<template>
<div class="eqryymyo">
<div class="header">
<time ref="time" class="_ghost">
<span class="yyyymmdd">{{ yyyy }}/{{ mm }}/{{ dd }}</span>
<br>
<span class="hhnn">{{ hh }}<span :style="{ visibility: now.getSeconds() % 2 == 0 ? 'visible' : 'hidden' }">:</span>{{ nn }}</span>
</time>
</div>
<div class="content _panel _ghost">
<MkClock/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkClock from './analog-clock.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkClock
},
data() {
return {
now: new Date(),
clock: null
};
},
computed: {
yyyy(): number {
return this.now.getFullYear();
},
mm(): string {
return ('0' + (this.now.getMonth() + 1)).slice(-2);
},
dd(): string {
return ('0' + this.now.getDate()).slice(-2);
},
hh(): string {
return ('0' + this.now.getHours()).slice(-2);
},
nn(): string {
return ('0' + this.now.getMinutes()).slice(-2);
}
},
mounted() {
this.tick();
this.clock = setInterval(this.tick, 1000);
},
beforeUnmount() {
clearInterval(this.clock);
},
methods: {
tick() {
this.now = new Date();
}
}
});
</script>
<style lang="scss" scoped>
.eqryymyo {
display: inline-block;
overflow: visible;
> .header {
padding: 0 12px;
padding-top: 4px;
text-align: center;
font-size: 12px;
font-family: Lucida Console, Courier, monospace;
&:hover + .content {
opacity: 1;
}
> time {
display: table-cell;
vertical-align: middle;
height: 48px;
> .yyyymmdd {
opacity: 0.7;
}
}
}
> .content {
opacity: 0;
display: block;
position: absolute;
top: auto;
right: 0;
margin: 16px 0 0 0;
padding: 16px;
width: 230px;
transition: opacity 0.2s ease;
}
}
</style>

View File

@@ -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>

View File

@@ -1,6 +1,7 @@
import { App } from 'vue';
import mfm from './misskey-flavored-markdown.vue';
import a from './ui/a.vue';
import acct from './acct.vue';
import avatar from './avatar.vue';
import emoji from './emoji.vue';
@@ -10,10 +11,10 @@ import time from './time.vue';
import url from './url.vue';
import loading from './loading.vue';
import error from './error.vue';
import streamIndicator from './stream-indicator.vue';
export default function(app: App) {
app.component('Mfm', mfm);
app.component('MkA', a);
app.component('MkAcct', acct);
app.component('MkAvatar', avatar);
app.component('MkEmoji', emoji);
@@ -23,5 +24,4 @@ export default function(app: App) {
app.component('MkUrl', url);
app.component('MkLoading', loading);
app.component('MkError', error);
app.component('StreamIndicator', streamIndicator);
}

View File

@@ -0,0 +1,62 @@
<template>
<div class="hpaizdrt" :style="bg">
<img v-if="info.faviconUrl" class="icon" :src="info.faviconUrl"/>
<span class="name">{{ info.name }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { instanceName } from '@/config';
export default defineComponent({
props: {
instance: {
type: Object,
required: false
},
},
data() {
return {
info: this.instance || {
faviconUrl: '/favicon.ico',
name: instanceName,
themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content
}
}
},
computed: {
bg(): any {
const themeColor = this.info.themeColor || '#777777';
return {
background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})`
};
}
}
});
</script>
<style lang="scss" scoped>
.hpaizdrt {
$height: 1.1rem;
height: $height;
border-radius: 4px 0 0 4px;
overflow: hidden;
color: #fff;
> .icon {
height: 100%;
}
> .name {
margin-left: 4px;
line-height: $height;
font-size: 0.9em;
vertical-align: top;
font-weight: bold;
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<component :is="self ? 'router-link' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
<component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
:title="url"

View File

@@ -1,11 +1,11 @@
<template>
<router-link class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')">
<MkA class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')">
<span class="me" v-if="isMe">{{ $t('you') }}</span>
<span class="main">
<span class="username">@{{ username }}</span>
<span class="host" v-if="(host != localHost) || $store.state.settings.showFullAcct">@{{ toUnicode(host) }}</span>
</span>
</router-link>
</MkA>
<a class="ldlomzub" :href="url" target="_blank" rel="noopener" v-else>
<span class="main">
<span class="username">@{{ username }}</span>

View File

@@ -9,8 +9,8 @@ import { concat } from '../../prelude/array';
import MkFormula from './formula.vue';
import MkCode from './code.vue';
import MkGoogle from './google.vue';
import MkA from './ui/a.vue';
import { host } from '@/config';
import { RouterLink } from 'vue-router';
export default defineComponent({
props: {
@@ -150,7 +150,7 @@ export default defineComponent({
}
case 'hashtag': {
return [h(RouterLink, {
return [h(MkA, {
key: Math.random(),
to: this.isNote ? `/tags/${encodeURIComponent(token.node.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.node.props.hashtag)}`,
style: 'color:var(--hashtag);'

View File

@@ -38,7 +38,9 @@ export default defineComponent({
}
> ::v-deep(code) {
font-size: 0.8em;
word-break: break-all;
padding: 4px 6px;
}
::v-deep(.title) {

View File

@@ -1,17 +1,17 @@
<template>
<header class="kkwtjztg">
<router-link class="name" :to="userPage(note.user)" v-user-preview="note.user.id">
<MkA class="name" :to="userPage(note.user)" v-user-preview="note.user.id">
<MkUserName :user="note.user"/>
</router-link>
</MkA>
<span class="is-bot" v-if="note.user.isBot">bot</span>
<span class="username"><MkAcct :user="note.user"/></span>
<span class="admin" v-if="note.user.isAdmin"><Fa :icon="faBookmark"/></span>
<span class="moderator" v-if="!note.user.isAdmin && note.user.isModerator"><Fa :icon="farBookmark"/></span>
<div class="info">
<span class="mobile" v-if="note.viaMobile"><Fa :icon="faMobileAlt"/></span>
<router-link class="created-at" :to="notePage(note)">
<MkA class="created-at" :to="notePage(note)">
<MkTime :time="note.createdAt"/>
</router-link>
</MkA>
<span class="visibility" v-if="note.visibility !== 'public'">
<Fa v-if="note.visibility === 'home'" :icon="faHome"/>
<Fa v-if="note.visibility === 'followers'" :icon="faUnlock"/>

View File

@@ -18,9 +18,9 @@
<Fa :icon="faRetweet"/>
<i18n-t keypath="renotedBy" tag="span">
<template #user>
<router-link class="name" :to="userPage(note.user)" v-user-preview="note.userId">
<MkA class="name" :to="userPage(note.user)" v-user-preview="note.userId">
<MkUserName :user="note.user"/>
</router-link>
</MkA>
</template>
</i18n-t>
<div class="info">
@@ -40,6 +40,7 @@
<MkAvatar class="avatar" :user="appearNote.user"/>
<div class="main">
<XNoteHeader class="header" :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
<div class="body" ref="noteBody">
<p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
@@ -48,7 +49,7 @@
<div class="content" v-show="appearNote.cw == null || showContent">
<div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
<router-link class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></router-link>
<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
<a class="rp" v-if="appearNote.renote != null">RN:</a>
</div>
@@ -59,7 +60,7 @@
<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="detail" class="url-preview"/>
<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div>
</div>
<router-link v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</router-link>
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</MkA>
</div>
<footer class="footer">
<XReactionsViewer :note="appearNote" ref="reactionsViewer"/>
@@ -91,9 +92,9 @@
<div v-else class="_panel muted" @click="muted = false">
<i18n-t keypath="userSaysSomething" tag="small">
<template #name>
<router-link class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
<MkUserName :user="appearNote.user"/>
</router-link>
</MkA>
</template>
</i18n-t>
</div>
@@ -101,7 +102,7 @@
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent, markRaw, ref } from 'vue';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
@@ -139,12 +140,13 @@ export default defineComponent({
XCwButton,
XPoll,
MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')),
MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')),
},
inject: {
inChannel: {
default: null
}
},
},
props: {
@@ -258,6 +260,12 @@ export default defineComponent({
} else {
return null;
}
},
showTicker() {
if (this.$store.state.device.instanceTicker === 'always') return true;
if (this.$store.state.device.instanceTicker === 'remote' && this.appearNote.user.instance) return true;
return false;
}
},
@@ -581,11 +589,6 @@ export default defineComponent({
});
menu = [{
type: 'link',
icon: faInfoCircle,
text: this.$t('details'),
to: '/notes/' + this.appearNote.id
}, null, {
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent
@@ -637,6 +640,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 ? {

View File

@@ -18,34 +18,34 @@
</div>
<div class="tail">
<header>
<router-link v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></router-link>
<MkA v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></MkA>
<span v-else>{{ notification.header }}</span>
<MkTime :time="notification.createdAt" v-if="withTime"/>
<MkTime :time="notification.createdAt" v-if="withTime" class="time"/>
</header>
<router-link v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Fa :icon="faQuoteLeft"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Fa :icon="faQuoteRight"/>
</router-link>
<router-link v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
</MkA>
<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
<Fa :icon="faQuoteLeft"/>
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/>
<Fa :icon="faQuoteRight"/>
</router-link>
<router-link v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</router-link>
<router-link v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</router-link>
<router-link v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</router-link>
<router-link v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Fa :icon="faQuoteLeft"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Fa :icon="faQuoteRight"/>
</router-link>
</MkA>
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span>
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span>
@@ -260,7 +260,7 @@ export default defineComponent({
overflow: hidden;
}
> .mk-time {
> .time {
margin-left: auto;
font-size: 0.9em;
}

View File

@@ -1,5 +1,5 @@
<template>
<router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
<article>
<header>
@@ -11,7 +11,7 @@
<p>{{ userName(page.user) }}</p>
</footer>
</article>
</router-link>
</MkA>
</template>
<script lang="ts">

View File

@@ -1,24 +1,33 @@
<template>
<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')">
<XWindow ref="window"
:initial-width="400"
:initial-height="500"
:can-resize="true"
:close-right="true"
:contextmenu="contextmenu"
@closed="$emit('closed')"
>
<template #header>
<XHeader :info="pageInfo" :with-back="false"/>
</template>
<template #buttons>
<button class="_button" @click="expand" v-tooltip="$t('showInPage')"><Fa :icon="faExpandAlt"/></button>
<button class="_button" @click="popout" v-tooltip="$t('popout')"><Fa :icon="faExternalLinkAlt"/></button>
<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
</template>
<div style="min-height: 100%; background: var(--bg);">
<div class="yrolvcoq" style="min-height: 100%; background: var(--bg);">
<component :is="component" v-bind="props" :ref="changePage"/>
</div>
</XWindow>
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import { faExternalLinkAlt, faExpandAlt } from '@fortawesome/free-solid-svg-icons';
import { defineComponent } from 'vue';
import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft } from '@fortawesome/free-solid-svg-icons';
import XWindow from '@/components/ui/window.vue';
import XHeader from '@/ui/_common_/header.vue';
import { popout } from '@/scripts/popout';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { resolve } from '@/router';
export default defineComponent({
components: {
@@ -26,6 +35,14 @@ export default defineComponent({
XHeader,
},
provide() {
return {
navHook: (url) => {
this.navigate(url);
}
};
},
props: {
initialUrl: {
type: String,
@@ -38,7 +55,7 @@ export default defineComponent({
initialProps: {
type: Object,
required: false,
default: {},
default: () => {},
},
},
@@ -50,18 +67,39 @@ export default defineComponent({
url: this.initialUrl,
component: this.initialComponent,
props: this.initialProps,
faExternalLinkAlt, faExpandAlt,
history: [],
faChevronLeft,
};
},
provide() {
return {
navHook: (url, component, props) => {
this.url = url;
this.component = markRaw(component);
this.props = props;
}
};
computed: {
contextmenu() {
return [{
type: 'label',
text: this.url,
}, {
icon: faExpandAlt,
text: this.$t('showInPage'),
action: this.expand
}, {
icon: faExternalLinkAlt,
text: this.$t('popout'),
action: this.popout
}, null, {
icon: faExternalLinkAlt,
text: this.$t('openInNewTab'),
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
}, {
icon: faLink,
text: this.$t('copyLink'),
action: () => {
copyToClipboard(this.url);
}
}];
},
},
methods: {
@@ -72,6 +110,18 @@ export default defineComponent({
}
},
navigate(url, record = true) {
if (record) this.history.push(this.url);
this.url = url;
const { component, props } = resolve(url);
this.component = component;
this.props = props;
},
back() {
this.navigate(this.history.pop(), false);
},
expand() {
this.$router.push(this.url);
this.$refs.window.close();
@@ -84,3 +134,9 @@ export default defineComponent({
},
});
</script>
<style lang="scss" scoped>
.yrolvcoq {
--section-padding: 16px;
}
</style>

View File

@@ -5,9 +5,12 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent, defineAsyncComponent } from 'vue';
export default defineComponent({
components: {
XBlock: defineAsyncComponent(() => import('./page.block.vue'))
},
props: {
value: {
required: true
@@ -22,8 +25,5 @@ export default defineComponent({
required: true
}
},
beforeCreate() {
this.$options.components.XBlock = require('./page.block.vue').default;
},
});
</script>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,27 @@ export default defineComponent({
this.posting = true;
os.api('notes/create', data).then(() => {
this.clear();
this.deleteDraft();
this.$emit('posted');
this.$nextTick(() => {
this.deleteDraft();
this.$emit('posted');
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 => {
}).then(() => {
this.posting = false;
this.$emit('done');
os.dialog({
type: 'error',
text: err.message + '<br>' + (err as any).id,
});
});
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))));
}
},
cancel() {
this.$emit('done');
this.$emit('cancel');
},
insertMention() {

View File

@@ -3,7 +3,7 @@
<div class="bqxuuuey">
<div class="info">
<div>{{ reaction.replace('@.', '') }}</div>
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon"/>
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/>
</div>
<template v-if="users.length <= 10">
<b v-for="u in users" :key="u.id" style="margin-right: 12px;">
@@ -66,7 +66,6 @@ export default defineComponent({
> .icon {
display: block;
width: 60px;
height: 60px;
margin: 0 auto;
}
}

View File

@@ -17,12 +17,12 @@
<button class="item _button index active" @click="top()" v-if="$route.name === 'index'">
<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
</button>
<router-link class="item index" active-class="active" to="/" exact v-else>
<MkA class="item index" active-class="active" to="/" exact v-else>
<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
</router-link>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'router-link' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to">
<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to">
<Fa :icon="menuDef[item].icon" fixed-width/><span class="text">{{ $t(menuDef[item].title) }}</span>
<i v-if="menuDef[item].indicated"><Fa :icon="faCircle"/></i>
</component>
@@ -35,9 +35,9 @@
<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i>
</button>
<router-link class="item" active-class="active" to="/settings">
<MkA class="item" active-class="active" to="/settings">
<Fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span>
</router-link>
</MkA>
</div>
</nav>
</transition>
@@ -46,7 +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'),
@@ -241,7 +246,7 @@ export default defineComponent({
icon: faQuestionCircle,
}, {
type: 'link',
text: this.$t('aboutX', { x: instanceName || host }),
text: this.$t('aboutX', { x: instanceName }),
to: '/about',
icon: faInfoCircle,
}, {

View File

@@ -3,9 +3,9 @@
<div class="body">
<span v-if="note.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span>
<router-link class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></router-link>
<MkA class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/>
<router-link class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</router-link>
<MkA class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<details v-if="note.files.length > 0">
<summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary>

View File

@@ -1,5 +1,5 @@
<template>
<time class="mk-time" :title="absolute">
<time :title="absolute">
<template v-if="mode == 'relative'">{{ relative }}</template>
<template v-else-if="mode == 'absolute'">{{ absolute }}</template>
<template v-else-if="mode == 'detail'">{{ absolute }} ({{ relative }})</template>

View File

@@ -0,0 +1,115 @@
<template>
<a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
<slot></slot>
</a>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faExpandAlt, faColumns, faExternalLinkAlt, faLink, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { router } from '@/router';
import { deckmode } from '@/config';
export default defineComponent({
inject: {
navHook: {
default: null
},
sideViewHook: {
default: null
}
},
props: {
to: {
type: String,
required: true,
},
activeClass: {
type: String,
required: false,
},
behavior: {
type: String,
required: false,
},
},
computed: {
active() {
if (this.activeClass == null) return false;
const resolved = router.resolve(this.to);
if (resolved.path == this.$route.path) return true;
if (resolved.name == null) return false;
if (this.$route.name == null) return false;
return resolved.name == this.$route.name;
}
},
methods: {
onContextmenu(e) {
if (window.getSelection().toString() !== '') return;
os.contextMenu([{
type: 'label',
text: this.to,
}, {
icon: faWindowMaximize,
text: this.$t('openInWindow'),
action: () => {
os.pageWindow(this.to);
}
}, !this.navHook && this.sideViewHook ? {
icon: faColumns,
text: this.$t('openInSideView'),
action: () => {
this.sideViewHook(this.to);
}
} : undefined, {
icon: faExpandAlt,
text: this.$t('showInPage'),
action: () => {
this.$router.push(this.to);
}
}, null, {
icon: faExternalLinkAlt,
text: this.$t('openInNewTab'),
action: () => {
window.open(this.to, '_blank');
}
}, {
icon: faLink,
text: this.$t('copyLink'),
action: () => {
copyToClipboard(this.to);
}
}], e);
},
nav() {
if (this.behavior) {
if (this.behavior === 'window') {
os.pageWindow(this.to);
return;
}
}
if (this.navHook) {
this.navHook(this.to);
} else {
if (this.$store.state.device.defaultSideView && this.sideViewHook && this.to !== '/') {
this.sideViewHook(this.to);
return;
}
if (this.$store.state.device.deckNavWindow && deckmode && this.to !== '/') {
os.pageWindow(this.to);
return;
}
this.$router.push(this.to);
}
}
}
});
</script>

View File

@@ -1,7 +1,9 @@
<template>
<div class="nvlagfpb">
<MkMenu :items="items" @close="$emit('closed')" class="_popup _shadow" :align="'left'"/>
</div>
<transition :name="$store.state.device.animation ? 'fade' : ''" appear>
<div class="nvlagfpb" @contextmenu.prevent.stop="() => {}">
<MkMenu :items="items" @close="$emit('closed')" class="_popup _shadow" :align="'left'"/>
</div>
</transition>
</template>
<script lang="ts">
@@ -35,8 +37,30 @@ export default defineComponent({
},
},
mounted() {
this.$el.style.top = this.ev.pageY + 'px';
this.$el.style.left = this.ev.pageX + 'px';
let left = this.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
let top = this.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
const width = this.$el.offsetWidth;
const height = this.$el.offsetHeight;
if (left + width - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - width + window.pageXOffset;
}
if (top + height - window.pageYOffset > window.innerHeight) {
top = window.innerHeight - height + window.pageYOffset;
}
if (top < 0) {
top = 0;
}
if (left < 0) {
left = 0;
}
this.$el.style.top = top + 'px';
this.$el.style.left = left + 'px';
for (const el of Array.from(document.querySelectorAll('body *'))) {
el.addEventListener('mousedown', this.onMousedown);
@@ -60,4 +84,14 @@ export default defineComponent({
position: absolute;
z-index: 65535;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
transform-origin: left top;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
transform: scale(0.9);
}
</style>

View File

@@ -132,7 +132,7 @@ export default defineComponent({
&.max-width_500px {
> header {
> .title {
padding: 8px 10px;
padding: 8px 10px 8px 0;
}
}
}

View File

@@ -12,12 +12,12 @@
<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item">
<span><MkEllipsis/></span>
</span>
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item">
<MkA v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item">
<Fa v-if="item.icon" :icon="item.icon" fixed-width/>
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span>
<i v-if="item.indicate"><Fa :icon="faCircle"/></i>
</router-link>
</MkA>
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item">
<Fa v-if="item.icon" :icon="item.icon" fixed-width/>
<span>{{ item.text }}</span>

View File

@@ -175,7 +175,7 @@ export default defineComponent({
}
.modal-popup-content-enter-active, .modal-popup-content-leave-active {
transition: opacity 0.3s, transform 0.3s !important;
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) !important;
}
.modal-popup-content-enter-from, .modal-popup-content-leave-to {
pointer-events: none;

View File

@@ -51,7 +51,8 @@ export default defineComponent({
.novjtctn {
position: relative;
display: inline-block;
margin: 0 32px 0 0;
margin: 16px 32px 0 0;
text-align: left;
cursor: pointer;
transition: all 0.3s;

View File

@@ -2,12 +2,13 @@
<div class="adhpbeos" :class="{ focused, filled, tall, pre }">
<div class="input">
<span class="label" ref="label"><slot></slot></span>
<textarea ref="input"
<textarea ref="input" :class="{ code }"
:value="value"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="!code"
@input="onInput"
@focus="focused = true"
@blur="focused = false"
@@ -20,7 +21,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@/os';
export default defineComponent({
props: {
@@ -43,6 +43,10 @@ export default defineComponent({
type: String,
required: false
},
code: {
type: Boolean,
required: false
},
tall: {
type: Boolean,
required: false,
@@ -159,6 +163,11 @@ export default defineComponent({
outline: none;
box-shadow: none;
color: var(--fg);
&.code {
tab-size: 2;
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
}
}

View File

@@ -2,12 +2,16 @@
<transition :name="$store.state.device.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
<div class="ebkgocck" v-if="showing">
<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
<div class="header">
<button class="_button" @click="close()"><Fa :icon="faTimes"/></button>
<div class="header" @contextmenu.prevent.stop="onContextmenu">
<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
<button v-else class="_button" @click="close()"><Fa :icon="faTimes"/></button>
<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
<slot name="header"></slot>
</span>
<slot name="buttons"></slot>
<button v-if="closeRight" class="_button" @click="close()"><Fa :icon="faTimes"/></button>
<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
</div>
<div class="body" v-if="padding">
<div class="_section">
@@ -83,6 +87,15 @@ export default defineComponent({
required: false,
default: false,
},
closeRight: {
type: Boolean,
required: false,
default: false,
},
contextmenu: {
type: Array,
required: false,
}
},
emits: ['closed'],
@@ -127,6 +140,12 @@ export default defineComponent({
}
},
onContextmenu(e) {
if (this.contextmenu) {
os.contextMenu(this.contextmenu, e);
}
},
// 最前面へ移動
top() {
let z = 0;
@@ -313,11 +332,13 @@ export default defineComponent({
// 高さを適用
applyTransformHeight(height) {
if (height > window.innerHeight) height = window.innerHeight;
(this.$el as any).style.height = height + 'px';
},
// 幅を適用
applyTransformWidth(width) {
if (width > window.innerWidth) width = window.innerWidth;
(this.$el as any).style.width = width + 'px';
},
@@ -371,8 +392,6 @@ export default defineComponent({
width: 100%;
height: 100%;
--section-padding: 16px;
> .header {
$height: 50px;
display: flex;
@@ -380,7 +399,6 @@ export default defineComponent({
z-index: 1;
flex-shrink: 0;
box-shadow: 0px 1px var(--divider);
cursor: move;
user-select: none;
height: $height;
@@ -400,6 +418,8 @@ export default defineComponent({
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
cursor: move;
}
}

View File

@@ -8,7 +8,7 @@
</div>
<div v-else class="mk-url-preview" v-size="{ max: [400, 350] }">
<transition name="zoom" mode="out-in">
<component :is="self ? 'router-link' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching">
<component :is="self ? 'MkA' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching">
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`">
<button class="_button" v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enablePlayer')"><Fa :icon="faPlayCircle"/></button>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<component :is="self ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
>

View File

@@ -3,7 +3,7 @@
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<div class="title">
<router-link class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></router-link>
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
<p class="username"><MkAcct :user="user"/></p>
</div>
<div class="description">
@@ -96,7 +96,7 @@ export default defineComponent({
margin: 0;
line-height: 16px;
font-size: 0.8em;
color: var(--text);
color: var(--fg);
opacity: 0.7;
}
}
@@ -125,7 +125,7 @@ export default defineComponent({
> p {
margin: 0;
font-size: 0.7em;
color: var(--text);
color: var(--fg);
}
> span {

View File

@@ -5,7 +5,7 @@
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<div class="title">
<router-link class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></router-link>
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
<p class="username"><MkAcct :user="user"/></p>
</div>
<div class="description">
@@ -151,7 +151,7 @@ export default defineComponent({
margin: 0;
line-height: 16px;
font-size: 0.8em;
color: var(--text);
color: var(--fg);
opacity: 0.7;
}
}
@@ -159,7 +159,7 @@ export default defineComponent({
> .description {
padding: 0 16px;
font-size: 0.8em;
color: var(--text);
color: var(--fg);
}
> .status {
@@ -172,7 +172,7 @@ export default defineComponent({
> p {
margin: 0;
font-size: 0.7em;
color: var(--text);
color: var(--fg);
}
> span {

View File

@@ -6,13 +6,13 @@
</div>
<div class="users">
<router-link v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)">
<MkA v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)">
<MkAvatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
<div class="body">
<MkUserName :user="extract ? extract(item) : item" class="name"/>
<MkAcct :user="extract ? extract(item) : item" class="acct"/>
</div>
</router-link>
</MkA>
</div>
<button class="more _button" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>

View File

@@ -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();

View File

@@ -12,5 +12,5 @@ export const lang = localStorage.getItem('lang');
export const langs = _LANGS_;
export const getLocale = async () => Object.fromEntries((await entries(clientDb.i18n)) as [string, string][]);
export const version = _VERSION_;
export const instanceName = siteName === 'Misskey' ? null : siteName;
export const instanceName = siteName === 'Misskey' ? host : siteName;
export const deckmode = localStorage.getItem('deckmode') === 'true';

View File

@@ -4,14 +4,13 @@
import '@/style.scss';
import { createApp } from 'vue';
import { createApp, defineAsyncComponent } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import Root from './root.vue';
import widgets from './widgets';
import directives from './directives';
import components from '@/components';
import { version, apiUrl } from '@/config';
import { version, apiUrl, deckmode } from '@/config';
import { store } from './store';
import { router } from './router';
import { applyTheme } from '@/scripts/theme';
@@ -51,7 +50,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/
@@ -152,7 +151,12 @@ store.dispatch('instance/fetch').then(() => {
stream.init(store.state.i);
const app = createApp(Root);
const app = createApp(await (
window.location.search === '?zen' ? import('@/ui/zen.vue') :
!store.getters.isSignedIn ? import('@/ui/visitor.vue') :
deckmode ? import('@/ui/deck.vue') :
import('@/ui/default.vue')
).then(x => x.default));
if (_DEV_) {
app.config.performance = true;

View File

@@ -4,11 +4,13 @@ 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';
import { resolve } from '@/router';
const ua = navigator.userAgent.toLowerCase();
export const isMobile = /mobile|iphone|ipad|android/.test(ua);
export const stream = new Stream();
export const stream = markRaw(new Stream());
export const pendingApiRequestsCount = ref(0);
@@ -73,7 +75,7 @@ export function apiWithDialog(
promiseDialog(promise, onSuccess, onFailure ? onFailure : (e) => {
dialog({
type: 'error',
text: e.message + '\n' + (e as any).id,
text: e.message + '<br>' + (e as any).id,
});
});
@@ -111,7 +113,8 @@ export function promiseDialog<T extends Promise<any>>(
}
});
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
// NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない)
popup(MkWaitingDialog, {
success: success,
showing: showing,
text: text,
@@ -160,7 +163,8 @@ export function popup(component: Component | typeof import('*.vue'), props: Reco
};
}
export function pageWindow(url: string, component: Component | typeof import('*.vue'), props: Record<string, any>) {
export function pageWindow(url: string) {
const { component, props } = resolve(url);
popup(defineAsyncComponent(() => import('@/components/page-window.vue')), {
initialUrl: url,
initialComponent: markRaw(component),
@@ -301,6 +305,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();

View File

@@ -0,0 +1,96 @@
<template>
<div>
<section class="_section">
<MkInput v-model:value="endpoint" :datalist="endpoints" @update:value="onEndpointChange()">
<span>Endpoint</span>
</MkInput>
<MkTextarea v-model:value="body" code>
<span>Params (JSON or JSON5)</span>
</MkTextarea>
<MkSwitch v-model:value="withCredential">
With credential
</MkSwitch>
<MkButton primary full @click="send" :disabled="sending">
<template v-if="sending"><MkEllipsis/></template>
<template v-else><Fa :icon="faPaperPlane"/> Send</template>
</MkButton>
</section>
<section class="_section" v-if="res">
<MkTextarea v-model:value="res" code readonly tall>
<span>Response</span>
</MkTextarea>
</section>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faTerminal, faPaperPlane } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/ui/input.vue';
import MkTextarea from '@/components/ui/textarea.vue';
import MkSwitch from '@/components/ui/switch.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton, MkInput, MkTextarea, MkSwitch,
},
data() {
return {
INFO: {
header: [{
title: 'API console',
icon: faTerminal
}]
},
endpoint: '',
body: '{}',
res: null,
sending: false,
endpoints: [],
withCredential: true,
faPaperPlane
};
},
created() {
os.api('endpoints').then(endpoints => {
this.endpoints = endpoints;
});
},
methods: {
send() {
this.sending = true;
os.api(this.endpoint, JSON5.parse(this.body)).then(res => {
this.sending = false;
this.res = JSON5.stringify(res, null, 2);
}, err => {
this.sending = false;
this.res = JSON5.stringify(err, null, 2);
});
},
onEndpointChange() {
os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => {
const body = {};
for (const p of endpoint.params) {
body[p.name] =
p.type === 'String' ? '' :
p.type === 'Number' ? 0 :
p.type === 'Boolean' ? false :
p.type === 'Array' ? [] :
p.type === 'Object' ? {} :
null;
}
this.body = JSON5.stringify(body, null, 2);
});
}
}
});
</script>

View File

@@ -4,7 +4,7 @@
<div class="_content">
<ul>
<li v-for="doc in docs" :key="doc.path">
<router-link :to="`/docs/${doc.path}`">{{ doc.title }}</router-link>
<MkA :to="`/docs/${doc.path}`">{{ doc.title }}</MkA>
</li>
</ul>
</div>

View File

@@ -38,8 +38,8 @@
<template #header><Fa :icon="faHashtag" fixed-width style="margin-right: 0.5em;"/>{{ $t('popularTags') }}</template>
<div class="vxjfqztj">
<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</router-link>
<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</router-link>
<MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA>
<MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA>
</div>
</MkFolder>

View File

@@ -12,7 +12,7 @@
<MkAvatar class="avatar" :user="req.follower"/>
<div class="body">
<div class="name">
<router-link class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></router-link>
<MkA class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></MkA>
<p class="acct">@{{ acct(req.follower) }}</p>
</div>
<div class="description" v-if="req.follower.description" :title="req.follower.description">

View 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>

View File

@@ -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" primary inline @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
<MkButton :disabled="!enableEmail" inline @click="testEmail()">{{ $t('testEmail') }}</MkButton>
<MkButton :disabled="!enableEmail" primary inline @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</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>

View File

@@ -4,30 +4,29 @@
<MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $t('startMessaging') }}</MkButton>
<div class="history" v-if="messages.length > 0">
<router-link v-for="(message, i) in messages"
<MkA v-for="(message, i) in messages"
class="message _panel"
:class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($store.state.i.id) : message.isRead }"
:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
:data-index="i"
:key="message.id"
@click.prevent="go(message)"
>
<div>
<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/>
<header v-if="message.groupId">
<span class="name">{{ message.group.name }}</span>
<MkTime :time="message.createdAt"/>
<MkTime :time="message.createdAt" class="time"/>
</header>
<header v-else>
<span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
<span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
<MkTime :time="message.createdAt"/>
<MkTime :time="message.createdAt" class="time"/>
</header>
<div class="body">
<p class="text"><span class="me" v-if="isMe(message)">{{ $t('you') }}:</span>{{ message.text }}</p>
</div>
</div>
</router-link>
</MkA>
</div>
<div class="_fullinfo" v-if="!fetching && messages.length == 0">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -51,8 +50,6 @@ export default defineComponent({
MkButton
},
inject: ['navHook'],
data() {
return {
INFO: {
@@ -90,18 +87,6 @@ export default defineComponent({
},
methods: {
go(message) {
const url = message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(this.isMe(message) ? message.recipient : message.user)}`;
if (this.navHook) {
this.navHook(url, defineAsyncComponent(() => import('@/pages/messaging/messaging-room.vue')), {
userAcct: message.groupId ? null : getAcct(this.isMe(message) ? message.recipient : message.user),
groupId: message.groupId
});
} else {
this.$router.push(url);
}
},
getAcct,
isMe(message) {
@@ -258,7 +243,7 @@ export default defineComponent({
margin: 0 8px;
}
> .mk-time {
> .time {
margin: 0 0 0 auto;
}
}

View File

@@ -317,7 +317,7 @@ const Component = defineComponent({
text: this.$t('openInWindow'),
icon: faWindowMaximize,
action: () => {
os.pageWindow(url, Component, this.$props);
os.pageWindow(url);
this.$router.back();
},
}, this.inWindow ? undefined : {

View File

@@ -10,7 +10,7 @@
<MkPagination :pagination="ownedPagination" #default="{items}" ref="owned">
<div class="_card" v-for="group in items" :key="group.id">
<div class="_title"><router-link :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</router-link></div>
<div class="_title"><MkA :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</MkA></div>
<div class="_content"><MkAvatars :user-ids="group.userIds"/></div>
</div>
</MkPagination>

View File

@@ -4,7 +4,7 @@
<MkPagination :pagination="pagination" #default="{items}" class="lists _content" ref="list">
<div class="list _panel" v-for="(list, i) in items" :key="list.id">
<router-link :to="`/my/lists/${ list.id }`">{{ list.name }}</router-link>
<MkA :to="`/my/lists/${ list.id }`">{{ list.name }}</MkA>
</div>
</MkPagination>
</div>

View File

@@ -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>
@@ -42,6 +42,12 @@ export default defineComponent({
MkRemoteCaution,
MkButton,
},
props: {
noteId: {
type: String,
required: true
}
},
data() {
return {
INFO: computed(() => this.note ? {
@@ -77,7 +83,7 @@ export default defineComponent({
};
},
watch: {
$route: 'fetch'
noteId: 'fetch'
},
created() {
this.fetch();
@@ -86,7 +92,7 @@ export default defineComponent({
fetch() {
Progress.start();
os.api('notes/show', {
noteId: this.$route.params.note
noteId: this.noteId
}).then(note => {
Promise.all([
os.api('users/notes', {

View File

@@ -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;

View File

@@ -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 = [];

View File

@@ -12,7 +12,7 @@
</header>
<section>
<router-link class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</router-link>
<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA>
<MkInput v-model:value="title">
<span>{{ $t('_pages.title') }}</span>

View File

@@ -20,9 +20,9 @@
</div>
<div class="_section links">
<div class="_content">
<router-link :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</router-link>
<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA>
<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId">
<router-link :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</router-link>
<MkA :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA>
<button v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $t('unpin') }}</button>
<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button>
</template>

View File

@@ -0,0 +1,472 @@
<template>
<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
<header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $t('_reversi.black') }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $t('_reversi.white') }})</header>
<div style="overflow: hidden; line-height: 28px;">
<p class="turn" v-if="!iAmPlayer && !game.isEnded">
<Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
<MkEllipsis/>
</p>
<p class="turn" v-if="logPos != logs.length">
<Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
</p>
<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn()">{{ $t('_reversi.opponentTurn') }}<MkEllipsis/></p>
<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn()" style="animation: anime-tada 1s linear infinite both;">{{ $t('_reversi.myTurn') }}</p>
<p class="result" v-if="game.isEnded && logPos == logs.length">
<template v-if="game.winner">
<Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/>
<span v-if="game.surrendered != null"> ({{ $t('_reversi.surrendered') }})</span>
</template>
<template v-else>{{ $t('_reversi.drawn') }}</template>
</p>
</div>
<div class="board">
<div class="labels-x" v-if="$store.state.settings.gamesReversiShowBoardLabels">
<span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div>
<div class="flex">
<div class="labels-y" v-if="$store.state.settings.gamesReversiShowBoardLabels">
<div v-for="i in game.map.length">{{ i }}</div>
</div>
<div class="cells" :style="cellsStyle">
<div v-for="(stone, i) in o.board"
:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }"
@click="set(i)"
:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`"
>
<template v-if="$store.state.settings.gamesReversiUseAvatarStones || true">
<img v-if="stone === true" :src="blackUser.avatarUrl" alt="black">
<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white">
</template>
<template v-else>
<fa v-if="stone === true" :icon="fasCircle"/>
<fa v-if="stone === false" :icon="farCircle"/>
</template>
</div>
</div>
<div class="labels-y" v-if="$store.state.settings.gamesReversiShowBoardLabels">
<div v-for="i in game.map.length">{{ i }}</div>
</div>
</div>
<div class="labels-x" v-if="$store.state.settings.gamesReversiShowBoardLabels">
<span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div>
</div>
<p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $t('_reversi.black') }}:{{ o.blackCount }} {{ $t('_reversi.white') }}:{{ o.whiteCount }} {{ $t('_reversi.total') }}:{{ o.blackCount + o.whiteCount }}</p>
<div class="actions" v-if="!game.isEnded && iAmPlayer">
<MkButton @click="surrender">{{ $t('_reversi.surrender') }}</MkButton>
</div>
<div class="player" v-if="game.isEnded">
<span>{{ logPos }} / {{ logs.length }}</span>
<!-- TODO <ui-horizon-group> -->
<MkButton @click="logPos = 0" :disabled="logPos == 0"><fa :icon="faAngleDoubleLeft"/></MkButton>
<MkButton @click="logPos--" :disabled="logPos == 0"><fa :icon="faAngleLeft"/></MkButton>
<MkButton @click="logPos++" :disabled="logPos == logs.length"><fa :icon="faAngleRight"/></MkButton>
<MkButton @click="logPos = logs.length" :disabled="logPos == logs.length"><fa :icon="faAngleDoubleRight"/></MkButton>
<!-- TODO </ui-horizon-group> -->
</div>
<div class="info">
<p v-if="game.isLlotheo">{{ $t('_reversi.isLlotheo') }}</p>
<p v-if="game.loopedBoard">{{ $t('_reversi.loopedMap') }}</p>
<p v-if="game.canPutEverywhere">{{ $t('_reversi.canPutEverywhere') }}</p>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faAngleDoubleLeft, faAngleLeft, faAngleRight, faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons';
import { faCircle as fasCircle } from '@fortawesome/free-solid-svg-icons';
import { faCircle as farCircle } from '@fortawesome/free-regular-svg-icons';
import * as CRC32 from 'crc-32';
import Reversi, { Color } from '../../../games/reversi/core';
import { url } from '@/config';
import MkButton from '@/components/ui/button.vue';
import { userPage } from '@/filters/user';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton
},
props: {
initGame: {
type: Object,
require: true
},
connection: {
type: Object,
require: true
},
},
data() {
return {
game: JSON.parse(JSON.stringify(this.initGame)),
o: null as Reversi,
logs: [],
logPos: 0,
pollingClock: null,
faAngleDoubleLeft, faAngleLeft, faAngleRight, faAngleDoubleRight, fasCircle, farCircle
};
},
computed: {
iAmPlayer(): boolean {
if (!this.$store.getters.isSignedIn) return false;
return this.game.user1Id == this.$store.state.i.id || this.game.user2Id == this.$store.state.i.id;
},
myColor(): Color {
if (!this.iAmPlayer) return null;
if (this.game.user1Id == this.$store.state.i.id && this.game.black == 1) return true;
if (this.game.user2Id == this.$store.state.i.id && this.game.black == 2) return true;
return false;
},
opColor(): Color {
if (!this.iAmPlayer) return null;
return this.myColor === true ? false : true;
},
blackUser(): any {
return this.game.black == 1 ? this.game.user1 : this.game.user2;
},
whiteUser(): any {
return this.game.black == 1 ? this.game.user2 : this.game.user1;
},
cellsStyle(): any {
return {
'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`,
'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)`
};
}
},
watch: {
logPos(v) {
if (!this.game.isEnded) return;
const o = new Reversi(this.game.map, {
isLlotheo: this.game.isLlotheo,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
});
for (const log of this.logs.slice(0, v)) {
o.put(log.color, log.pos);
}
this.o = o;
//this.$forceUpdate();
}
},
created() {
this.o = new Reversi(this.game.map, {
isLlotheo: this.game.isLlotheo,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
});
for (const log of this.game.logs) {
this.o.put(log.color, log.pos);
}
this.logs = this.game.logs;
this.logPos = this.logs.length;
// 通信を取りこぼしてもいいように定期的にポーリングさせる
if (this.game.isStarted && !this.game.isEnded) {
this.pollingClock = setInterval(() => {
if (this.game.isEnded) return;
const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
this.connection.send('check', {
crc32: crc32
});
}, 3000);
}
},
mounted() {
this.connection.on('set', this.onSet);
this.connection.on('rescue', this.onRescue);
this.connection.on('ended', this.onEnded);
},
beforeUnmount() {
this.connection.off('set', this.onSet);
this.connection.off('rescue', this.onRescue);
this.connection.off('ended', this.onEnded);
clearInterval(this.pollingClock);
},
methods: {
userPage,
// this.o がリアクティブになった折にはcomputedにできる
turnUser(): any {
if (this.o.turn === true) {
return this.game.black == 1 ? this.game.user1 : this.game.user2;
} else if (this.o.turn === false) {
return this.game.black == 1 ? this.game.user2 : this.game.user1;
} else {
return null;
}
},
// this.o がリアクティブになった折にはcomputedにできる
isMyTurn(): boolean {
if (!this.iAmPlayer) return false;
if (this.turnUser() == null) return false;
return this.turnUser().id == this.$store.state.i.id;
},
set(pos) {
if (this.game.isEnded) return;
if (!this.iAmPlayer) return;
if (!this.isMyTurn) return;
if (!this.o.canPut(this.myColor, pos)) return;
this.o.put(this.myColor, pos);
// サウンドを再生する
if (this.$store.state.device.enableSounds) {
const sound = new Audio(`${url}/assets/reversi-put-me.mp3`);
sound.volume = this.$store.state.device.soundVolume;
sound.play();
}
this.connection.send('set', {
pos: pos
});
this.checkEnd();
this.$forceUpdate();
},
onSet(x) {
this.logs.push(x);
this.logPos++;
this.o.put(x.color, x.pos);
this.checkEnd();
this.$forceUpdate();
// サウンドを再生する
if (this.$store.state.device.enableSounds && x.color != this.myColor) {
const sound = new Audio(`${url}/assets/reversi-put-you.mp3`);
sound.volume = this.$store.state.device.soundVolume;
sound.play();
}
},
onEnded(x) {
this.game = JSON.parse(JSON.stringify(x.game));
},
checkEnd() {
this.game.isEnded = this.o.isEnded;
if (this.game.isEnded) {
if (this.o.winner === true) {
this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id;
this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2;
} else if (this.o.winner === false) {
this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id;
this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1;
} else {
this.game.winnerId = null;
this.game.winner = null;
}
}
},
// 正しいゲーム情報が送られてきたとき
onRescue(game) {
this.game = JSON.parse(JSON.stringify(game));
this.o = new Reversi(this.game.map, {
isLlotheo: this.game.isLlotheo,
canPutEverywhere: this.game.canPutEverywhere,
loopedBoard: this.game.loopedBoard
});
for (const log of this.game.logs) {
this.o.put(log.color, log.pos, true);
}
this.logs = this.game.logs;
this.logPos = this.logs.length;
this.checkEnd();
this.$forceUpdate();
},
surrender() {
os.api('games/reversi/games/surrender', {
gameId: this.game.id
});
},
}
});
</script>
<style lang="scss" scoped>
.xqnhankfuuilcwvhgsopeqncafzsquya {
text-align: center;
> .go-index {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 42px;
height :42px;
}
> header {
padding: 8px;
border-bottom: dashed 1px var(--divider);
}
> .board {
width: calc(100% - 16px);
max-width: 500px;
margin: 0 auto;
$label-size: 16px;
$gap: 4px;
> .labels-x {
height: $label-size;
padding: 0 $label-size;
display: flex;
> * {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8em;
&:first-child {
margin-left: -($gap / 2);
}
&:last-child {
margin-right: -($gap / 2);
}
}
}
> .flex {
display: flex;
> .labels-y {
width: $label-size;
display: flex;
flex-direction: column;
> * {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
&:first-child {
margin-top: -($gap / 2);
}
&:last-child {
margin-bottom: -($gap / 2);
}
}
}
> .cells {
flex: 1;
display: grid;
grid-gap: $gap;
> div {
background: transparent;
border-radius: 6px;
overflow: hidden;
* {
pointer-events: none;
user-select: none;
}
&.empty {
border: solid 2px var(--divider);
}
&.empty.can {
border-color: var(--accent);
}
&.empty.myTurn {
border-color: var(--divider);
&.can {
border-color: var(--accent);
cursor: pointer;
&:hover {
background: var(--accent);
}
}
}
&.prev {
box-shadow: 0 0 0 4px var(--accent);
}
&.isEnded {
border-color: var(--divider);
}
&.none {
border-color: transparent !important;
}
> svg, > img {
display: block;
width: 100%;
height: 100%;
}
}
}
}
}
> .status {
margin: 0;
padding: 16px 0;
}
> .actions {
padding-bottom: 16px;
}
> .player {
padding: 0 16px 32px 16px;
margin: 0 auto;
max-width: 500px;
> span {
display: inline-block;
margin: 0 8px;
min-width: 70px;
}
}
}
</style>

View File

@@ -0,0 +1,393 @@
<template>
<div class="urbixznjwwuukfsckrwzwsqzsxornqij">
<header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header>
<div>
<p>{{ $t('_reversi.gameSettings') }}</p>
<div class="card map _panel">
<header>
<select v-model="mapName" :placeholder="$t('_reversi.chooseBoard')" @change="onMapChange">
<option label="-Custom-" :value="mapName" v-if="mapName == '-Custom-'"/>
<option :label="$t('random')" :value="null"/>
<optgroup v-for="c in mapCategories" :key="c" :label="c">
<option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option>
</optgroup>
</select>
</header>
<div>
<div class="random" v-if="game.map == null"><fa icon="dice"/></div>
<div class="board" v-else :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
<div v-for="(x, i) in game.map.join('')" :class="{ none: x == ' ' }" @click="onPixelClick(i, x)">
<fa v-if="x == 'b'" :icon="fasCircle"/>
<fa v-if="x == 'w'" :icon="farCircle"/>
</div>
</div>
</div>
</div>
<div class="card _panel">
<header>
<span>{{ $t('_reversi.blackOrWhite') }}</span>
</header>
<div>
<MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $t('random') }}</MkRadio>
<MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')">
<i18n-t keypath="_reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user1"/></b>
</template>
</i18n-t>
</MkRadio>
<MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')">
<i18n-t keypath="_reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user2"/></b>
</template>
</i18n-t>
</MkRadio>
</div>
</div>
<div class="card _panel">
<header>
<span>{{ $t('_reversi.rules') }}</span>
</header>
<div>
<MkSwitch v-model:value="game.isLlotheo" @update:value="updateSettings('isLlotheo')">{{ $t('_reversi.isLlotheo') }}</MkSwitch>
<MkSwitch v-model:value="game.loopedBoard" @update:value="updateSettings('loopedBoard')">{{ $t('_reversi.loopedMap') }}</MkSwitch>
<MkSwitch v-model:value="game.canPutEverywhere" @update:value="updateSettings('canPutEverywhere')">{{ $t('_reversi.canPutEverywhere') }}</MkSwitch>
</div>
</div>
<div class="card form _panel" v-if="form">
<header>
<span>{{ $t('_reversi.botSettings') }}</span>
</header>
<div>
<template v-for="item in form">
<MkSwitch v-if="item.type == 'switch'" v-model:value="item.value" :key="item.id" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch>
<div class="card" v-if="item.type == 'radio'" :key="item.id">
<header>
<span>{{ item.label }}</span>
</header>
<div>
<MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio>
</div>
</div>
<div class="card" v-if="item.type == 'slider'" :key="item.id">
<header>
<span>{{ item.label }}</span>
</header>
<div>
<input type="range" :min="item.min" :max="item.max" :step="item.step || 1" v-model="item.value" @change="onChangeForm(item)"/>
</div>
</div>
<div class="card" v-if="item.type == 'textbox'" :key="item.id">
<header>
<span>{{ item.label }}</span>
</header>
<div>
<input v-model="item.value" @change="onChangeForm(item)"/>
</div>
</div>
</template>
</div>
</div>
</div>
<footer class="_acrylic">
<p class="status">
<template v-if="isAccepted && isOpAccepted">{{ $t('_reversi.thisGameIsStartedSoon') }}<MkEllipsis/></template>
<template v-if="isAccepted && !isOpAccepted">{{ $t('_reversi.waitingForOther') }}<MkEllipsis/></template>
<template v-if="!isAccepted && isOpAccepted">{{ $t('_reversi.waitingForMe') }}</template>
<template v-if="!isAccepted && !isOpAccepted">{{ $t('_reversi.waitingBoth') }}<MkEllipsis/></template>
</p>
<div class="actions">
<MkButton inline @click="exit">{{ $t('cancel') }}</MkButton>
<MkButton inline primary @click="accept" v-if="!isAccepted">{{ $t('_reversi.ready') }}</MkButton>
<MkButton inline primary @click="cancel" v-if="isAccepted">{{ $t('_reversi.cancelReady') }}</MkButton>
</div>
</footer>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCircle as fasCircle } from '@fortawesome/free-solid-svg-icons';
import { faCircle as farCircle } from '@fortawesome/free-regular-svg-icons';
import * as maps from '../../../games/reversi/maps';
import MkButton from '@/components/ui/button.vue';
import MkSwitch from '@/components/ui/switch.vue';
import MkRadio from '@/components/ui/radio.vue';
export default defineComponent({
components: {
MkButton,
MkSwitch,
MkRadio,
},
props: {
initGame: {
type: Object,
require: true
},
connection: {
type: Object,
require: true
},
},
data() {
return {
game: this.initGame,
o: null,
isLlotheo: false,
mapName: maps.eighteight.name,
maps: maps,
form: null,
messages: [],
fasCircle, farCircle
};
},
computed: {
mapCategories(): string[] {
const categories = Object.values(maps).map(x => x.category);
return categories.filter((item, pos) => categories.indexOf(item) == pos);
},
isAccepted(): boolean {
if (this.game.user1Id == this.$store.state.i.id && this.game.user1Accepted) return true;
if (this.game.user2Id == this.$store.state.i.id && this.game.user2Accepted) return true;
return false;
},
isOpAccepted(): boolean {
if (this.game.user1Id != this.$store.state.i.id && this.game.user1Accepted) return true;
if (this.game.user2Id != this.$store.state.i.id && this.game.user2Accepted) return true;
return false;
}
},
created() {
this.connection.on('changeAccepts', this.onChangeAccepts);
this.connection.on('updateSettings', this.onUpdateSettings);
this.connection.on('initForm', this.onInitForm);
this.connection.on('message', this.onMessage);
if (this.game.user1Id != this.$store.state.i.id && this.game.form1) this.form = this.game.form1;
if (this.game.user2Id != this.$store.state.i.id && this.game.form2) this.form = this.game.form2;
},
beforeUnmount() {
this.connection.off('changeAccepts', this.onChangeAccepts);
this.connection.off('updateSettings', this.onUpdateSettings);
this.connection.off('initForm', this.onInitForm);
this.connection.off('message', this.onMessage);
},
methods: {
exit() {
},
accept() {
this.connection.send('accept', {});
},
cancel() {
this.connection.send('cancelAccept', {});
},
onChangeAccepts(accepts) {
this.game.user1Accepted = accepts.user1;
this.game.user2Accepted = accepts.user2;
},
updateSettings(key: string) {
this.connection.send('updateSettings', {
key: key,
value: this.game[key]
});
},
onUpdateSettings({ key, value }) {
this.game[key] = value;
if (this.game.map == null) {
this.mapName = null;
} else {
const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join(''));
this.mapName = found ? found.name : '-Custom-';
}
},
onInitForm(x) {
if (x.userId == this.$store.state.i.id) return;
this.form = x.form;
},
onMessage(x) {
if (x.userId == this.$store.state.i.id) return;
this.messages.unshift(x.message);
},
onChangeForm(item) {
this.connection.send('updateForm', {
id: item.id,
value: item.value
});
},
onMapChange() {
if (this.mapName == null) {
this.game.map = null;
} else {
this.game.map = Object.values(maps).find(x => x.name == this.mapName).data;
}
this.updateSettings('map');
},
onPixelClick(pos, pixel) {
const x = pos % this.game.map[0].length;
const y = Math.floor(pos / this.game.map[0].length);
const newPixel =
pixel == ' ' ? '-' :
pixel == '-' ? 'b' :
pixel == 'b' ? 'w' :
' ';
const line = this.game.map[y].split('');
line[x] = newPixel;
this.game.map[y] = line.join('');
this.updateSettings('map');
}
}
});
</script>
<style lang="scss" scoped>
.urbixznjwwuukfsckrwzwsqzsxornqij {
text-align: center;
background: var(--bg);
> header {
padding: 8px;
border-bottom: dashed 1px #c4cdd4;
}
> div {
padding: 0 16px;
> .card {
margin: 0 auto 16px auto;
&.map {
> header {
> select {
width: 100%;
padding: 12px 14px;
background: var(--face);
border: 1px solid var(--inputBorder);
border-radius: 4px;
color: var(--fg);
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&:focus,
&:active {
border-color: var(--accent);
}
}
}
> div {
> .random {
padding: 32px 0;
font-size: 64px;
color: var(--fg);
opacity: 0.7;
}
> .board {
display: grid;
grid-gap: 4px;
width: 300px;
height: 300px;
margin: 0 auto;
color: var(--fg);
> div {
background: transparent;
border: solid 2px var(--divider);
border-radius: 6px;
overflow: hidden;
cursor: pointer;
* {
pointer-events: none;
user-select: none;
width: 100%;
height: 100%;
}
&.none {
border-color: transparent;
}
}
}
}
}
&.form {
> div {
> .card + .card {
margin-top: 16px;
}
input[type='range'] {
width: 100%;
}
}
}
}
.card {
max-width: 400px;
> header {
padding: 18px 20px;
border-bottom: 1px solid var(--divider);
}
> div {
padding: 20px;
color: var(--fg);
}
}
}
> footer {
position: sticky;
bottom: 0;
padding: 16px;
border-top: solid 1px var(--divider);
> .status {
margin: 0 0 16px 0;
}
}
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<div v-if="game == null"><MkLoading/></div>
<GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/>
<GameBoard v-else :init-game="game" :connection="connection"/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import GameSetting from './game.setting.vue';
import GameBoard from './game.board.vue';
import * as os from '@/os';
import { faGamepad } from '@fortawesome/free-solid-svg-icons';
export default defineComponent({
components: {
GameSetting,
GameBoard,
},
props: {
gameId: {
type: String,
required: true
},
},
data() {
return {
INFO: {
header: [{
title: this.$t('_reversi.reversi'),
icon: faGamepad
}]
},
game: null,
connection: null,
};
},
watch: {
gameId() {
this.fetch();
}
},
mounted() {
this.fetch();
},
beforeUnmount() {
if (this.connection) {
this.connection.dispose();
}
},
methods: {
fetch() {
os.api('games/reversi/games/show', {
gameId: this.gameId
}).then(game => {
this.game = game;
if (this.connection) {
this.connection.dispose();
}
this.connection = os.stream.connectToChannel('gamesReversiGame', {
gameId: this.game.id
});
this.connection.on('started', this.onStarted);
});
},
onStarted(game) {
Object.assign(this.game, game);
},
}
});
</script>

View File

@@ -0,0 +1,281 @@
<template>
<div class="bgvwxkhb" v-if="!matching">
<h1>Misskey {{ $t('_reversi.reversi') }}</h1>
<div class="play">
<MkButton primary round @click="match" style="margin: var(--margin) auto 0 auto;">{{ $t('invite') }}</MkButton>
</div>
<div class="_section">
<MkFolder v-if="invitations.length > 0">
<template #header>{{ $t('invitations') }}</template>
<div class="nfcacttm">
<button class="invitation _panel _button" v-for="invitation in invitations" tabindex="-1" @click="accept(invitation)">
<MkAvatar class="avatar" :user="invitation.parent"/>
<span class="name"><b><MkUserName :user="invitation.parent"/></b></span>
<span class="username">@{{ invitation.parent.username }}</span>
<MkTime :time="invitation.createdAt" class="time"/>
</button>
</div>
</MkFolder>
<MkFolder v-if="myGames.length > 0">
<template #header>{{ $t('_reversi.myGames') }}</template>
<div class="knextgwz">
<MkA class="game _panel" v-for="g in myGames" tabindex="-1" :to="`/games/reversi/${g.id}`" :key="g.id">
<div class="players">
<MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/>
</div>
<footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $t('_reversi.ended') : $t('_reversi.playing') }}</span><MkTime class="time" :time="g.createdAt"/></footer>
</MkA>
</div>
</MkFolder>
<MkFolder v-if="games.length > 0">
<template #header>{{ $t('_reversi.allGames') }}</template>
<div class="knextgwz">
<MkA class="game _panel" v-for="g in games" tabindex="-1" :to="`/games/reversi/${g.id}`" :key="g.id">
<div class="players">
<MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/>
</div>
<footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $t('_reversi.ended') : $t('_reversi.playing') }}</span><MkTime class="time" :time="g.createdAt"/></footer>
</MkA>
</div>
</MkFolder>
</div>
</div>
<div class="sazhgisb" v-else>
<h1>
<i18n-t keypath="waitingFor" tag="span">
<template #x>
<b><MkUserName :user="matching"/></b>
</template>
</i18n-t>
<MkEllipsis/>
</h1>
<div class="cancel">
<MkButton inline round @click="cancel">{{ $t('cancel') }}</MkButton>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@/os';
import MkButton from '@/components/ui/button.vue';
import MkFolder from '@/components/ui/folder.vue';
import { faGamepad } from '@fortawesome/free-solid-svg-icons';
export default defineComponent({
components: {
MkButton, MkFolder,
},
inject: ['navHook'],
data() {
return {
INFO: {
header: [{
title: this.$t('_reversi.reversi'),
icon: faGamepad
}]
},
games: [],
gamesFetching: true,
gamesMoreFetching: false,
myGames: [],
matching: null,
invitations: [],
connection: null,
pingClock: null,
};
},
mounted() {
if (this.$store.getters.isSignedIn) {
this.connection = os.stream.useSharedConnection('gamesReversi');
this.connection.on('invited', this.onInvited);
this.connection.on('matched', this.onMatched);
this.pingClock = setInterval(() => {
if (this.matching) {
this.connection.send('ping', {
id: this.matching.id
});
}
}, 3000);
os.api('games/reversi/games', {
my: true
}).then(games => {
this.myGames = games;
});
os.api('games/reversi/invitations').then(invitations => {
this.invitations = this.invitations.concat(invitations);
});
}
os.api('games/reversi/games').then(games => {
this.games = games;
this.gamesFetching = false;
});
},
beforeUnmount() {
if (this.connection) {
this.connection.dispose();
clearInterval(this.pingClock);
}
},
methods: {
go(game) {
const url = '/games/reversi/' + game.id;
if (this.navHook) {
this.navHook(url);
} else {
this.$router.push(url);
}
},
async match() {
const user = await os.selectUser({ local: true });
if (user == null) return;
os.api('games/reversi/match', {
userId: user.id
}).then(res => {
if (res == null) {
this.matching = user;
} else {
this.go(res);
}
});
},
cancel() {
this.matching = null;
os.api('games/reversi/match/cancel');
},
accept(invitation) {
os.api('games/reversi/match', {
userId: invitation.parent.id
}).then(game => {
if (game) {
this.go(game);
}
});
},
onMatched(game) {
this.go(game);
},
onInvited(invite) {
this.invitations.unshift(invite);
}
}
});
</script>
<style lang="scss" scoped>
.bgvwxkhb {
> h1 {
margin: 0;
padding: 24px;
text-align: center;
font-size: 1.5em;
background: linear-gradient(0deg, #43c583, #438881);
color: #fff;
}
> .play {
text-align: center;
}
}
.sazhgisb {
text-align: center;
}
.nfcacttm {
> .invitation {
display: flex;
box-sizing: border-box;
width: 100%;
padding: 16px;
line-height: 32px;
text-align: left;
> .avatar {
width: 32px;
height: 32px;
margin-right: 8px;
}
> .name {
margin-right: 8px;
}
> .username {
margin-right: 8px;
opacity: 0.7;
}
> .time {
margin-left: auto;
opacity: 0.7;
}
}
}
.knextgwz {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
grid-gap: var(--margin);
> .game {
> .players {
text-align: center;
padding: 16px;
line-height: 32px;
> .avatar {
width: 32px;
height: 32px;
&:first-child {
margin-right: 8px;
}
&:last-child {
margin-left: 8px;
}
}
}
> footer {
display: flex;
align-items: baseline;
border-top: solid 1px var(--divider);
padding: 6px 8px;
font-size: 0.9em;
> .state {
&.playing {
color: var(--accent);
}
}
> .time {
margin-left: auto;
opacity: 0.7;
}
}
}
}
</style>

View File

@@ -1,9 +1,14 @@
<template>
<section class="_section">
<div class="_content">
<MkButton @click="generateToken">{{ $t('generateAccessToken') }}</MkButton>
<div>
<div class="_section">
<div class="_content">
<MkButton @click="generateToken">{{ $t('generateAccessToken') }}</MkButton>
</div>
</div>
</section>
<div class="_section">
<MkA to="/api-console" :behavior="isDesktop ? 'window' : null">API console</MkA>
</div>
</div>
</template>
<script lang="ts">
@@ -28,6 +33,7 @@ export default defineComponent({
icon: faKey
}]
},
isDesktop: window.innerWidth >= 1100,
};
},

View File

@@ -2,6 +2,10 @@
<div class="_section">
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('general') }}</div>
<div class="_content">
<div>{{ $t('defaultNavigationBehaviour') }}</div>
<MkSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</MkSwitch>
</div>
<div class="_content">
<div>{{ $t('whenServerDisconnected') }}</div>
<MkRadio v-model="serverDisconnectedBehavior" value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</MkRadio>
@@ -47,10 +51,20 @@
<MkRadio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></MkRadio>
<MkRadio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></MkRadio>
</div>
<div class="_content">
<div>{{ $t('instanceTicker') }}</div>
<MkRadio v-model="instanceTicker" value="none">{{ $t('_instanceTicker.none') }}</MkRadio>
<MkRadio v-model="instanceTicker" value="remote">{{ $t('_instanceTicker.remote') }}</MkRadio>
<MkRadio v-model="instanceTicker" value="always">{{ $t('_instanceTicker.always') }}</MkRadio>
</div>
</section>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faColumns"/> {{ $t('deck') }}</div>
<div class="_content">
<div>{{ $t('defaultNavigationBehaviour') }}</div>
<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch>
</div>
<div class="_content">
<MkSwitch v-model:value="deckAlwaysShowMainColumn">
{{ $t('_deck.alwaysShowMainColumn') }}
@@ -146,11 +160,26 @@ export default defineComponent({
set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
},
defaultSideView: {
get() { return this.$store.state.device.defaultSideView; },
set(value) { this.$store.commit('device/set', { key: 'defaultSideView', value }); }
},
deckNavWindow: {
get() { return this.$store.state.device.deckNavWindow; },
set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); }
},
chatOpenBehavior: {
get() { return this.$store.state.device.chatOpenBehavior; },
set(value) { this.$store.commit('device/set', { key: 'chatOpenBehavior', value }); }
},
instanceTicker: {
get() { return this.$store.state.device.instanceTicker; },
set(value) { this.$store.commit('device/set', { key: 'instanceTicker', value }); }
},
enableInfiniteScroll: {
get() { return this.$store.state.device.enableInfiniteScroll; },
set(value) { this.$store.commit('device/set', { key: 'enableInfiniteScroll', value }); }

View File

@@ -9,10 +9,9 @@
<option value="mute">{{ $t('_exportOrImport.muteList') }}</option>
<option value="blocking">{{ $t('_exportOrImport.blockingList') }}</option>
</MkSelect>
<MkButton inline @click="doExport()"><Fa :icon="faDownload"/> {{ $t('export') }}</MkButton>
<MkButton inline @click="doImport()" :disabled="!['following', 'user-lists'].includes(exportTarget)"><Fa :icon="faUpload"/> {{ $t('import') }}</MkButton>
<MkButton inline primary @click="doExport"><Fa :icon="faDownload"/> {{ $t('export') }}</MkButton>
<MkButton inline primary @click="doImport" :disabled="!['following', 'user-lists'].includes(exportTarget)"><Fa :icon="faUpload"/> {{ $t('import') }}</MkButton>
</div>
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
</section>
</template>
@@ -21,8 +20,8 @@ import { defineComponent } from 'vue';
import { faDownload, faUpload, faBoxes } from '@fortawesome/free-solid-svg-icons';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/ui/select.vue';
import { apiUrl } from '@/config';
import * as os from '@/os';
import { selectFile } from '@/scripts/select-file';
export default defineComponent({
components: {
@@ -59,29 +58,9 @@ export default defineComponent({
});
},
doImport() {
(this.$refs.file as any).click();
},
onChangeFile() {
const [file] = Array.from((this.$refs.file as any).files);
async doImport(e) {
const file = await selectFile(e.currentTarget || e.target);
const data = new FormData();
data.append('file', file);
data.append('i', this.$store.state.i.token);
const promise = fetch(apiUrl + '/drive/files/create', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(f => {
this.reqImport(f);
});
os.promiseDialog(promise);
},
reqImport(file) {
os.api(
this.exportTarget == 'following' ? 'i/import-following' :
this.exportTarget == 'user-lists' ? 'i/import-user-lists' :
@@ -98,7 +77,7 @@ export default defineComponent({
text: e.message
});
});
}
},
}
});
</script>

View File

@@ -1,52 +1,58 @@
<template>
<div class="vvcocwet" :class="{ wide: !narrow }" ref="el">
<div class="nav" v-if="!narrow || $route.name === 'settings'">
<div class="nav" v-if="!narrow || page == null">
<div class="menu">
<div class="label">{{ $t('basicSettings') }}</div>
<router-link class="item" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</router-link>
<router-link class="item" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</router-link>
<router-link class="item" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</router-link>
<router-link class="item" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</router-link>
<router-link class="item" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</router-link>
<router-link class="item" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</router-link>
<MkA class="item" :class="{ active: page === 'profile' }" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</MkA>
<MkA class="item" :class="{ active: page === 'privacy' }" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</MkA>
<MkA class="item" :class="{ active: page === 'reaction' }" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</MkA>
<MkA class="item" :class="{ active: page === 'notifications' }" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</MkA>
<MkA class="item" :class="{ active: page === 'integration' }" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</MkA>
<MkA class="item" :class="{ active: page === 'security' }" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</MkA>
</div>
<div class="menu">
<div class="label">{{ $t('clientSettings') }}</div>
<router-link class="item" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</router-link>
<router-link class="item" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</router-link>
<router-link class="item" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</router-link>
<router-link class="item" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</router-link>
<router-link class="item" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</router-link>
<MkA class="item" :class="{ active: page === 'general' }" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</MkA>
<MkA class="item" :class="{ active: page === 'theme' }" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</MkA>
<MkA class="item" :class="{ active: page === 'sidebar' }" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</MkA>
<MkA class="item" :class="{ active: page === 'sounds' }" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</MkA>
<MkA class="item" :class="{ active: page === 'plugins' }" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</MkA>
</div>
<div class="menu">
<div class="label">{{ $t('otherSettings') }}</div>
<router-link class="item" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</router-link>
<router-link class="item" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</router-link>
<router-link class="item" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</router-link>
<router-link class="item" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</router-link>
<MkA class="item" :class="{ active: page === 'import-export' }" replace to="/settings/import-export"><Fa :icon="faBoxes" fixed-width class="icon"/>{{ $t('importAndExport') }}</MkA>
<MkA class="item" :class="{ active: page === 'mute-block' }" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</MkA>
<MkA class="item" :class="{ active: page === 'word-mute' }" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</MkA>
<MkA class="item" :class="{ active: page === 'api' }" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</MkA>
<MkA class="item" :class="{ active: page === 'other' }" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</MkA>
</div>
<div class="menu">
<button class="_button item" @click="logout">{{ $t('logout') }}</button>
</div>
</div>
<div class="main">
<router-view v-slot="{ Component }">
<transition :name="($store.state.device.animation && !narrow) ? 'view-slide' : ''" appear mode="out-in">
<component :is="Component" @info="onInfo"/>
</transition>
</router-view>
<transition :name="($store.state.device.animation && !narrow) ? 'view-slide' : ''" appear mode="out-in">
<component :is="component" @info="onInfo"/>
</transition>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey } from '@fortawesome/free-solid-svg-icons';
import { computed, defineAsyncComponent, defineComponent, onMounted, ref } from 'vue';
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes } from '@fortawesome/free-solid-svg-icons';
import { faLaugh, faBell } from '@fortawesome/free-regular-svg-icons';
import { store } from '@/store';
import { i18n } from '@/i18n';
export default defineComponent({
props: {
page: {
type: String,
required: false
}
},
setup(props, context) {
const INFO = ref({
header: [{
@@ -60,6 +66,28 @@ export default defineComponent({
const onInfo = (viewInfo) => {
INFO.value = viewInfo;
};
const component = computed(() => {
switch (props.page) {
case 'profile': return defineAsyncComponent(() => import('./profile.vue'));
case 'privacy': return defineAsyncComponent(() => import('./privacy.vue'));
case 'reaction': return defineAsyncComponent(() => import('./reaction.vue'));
case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
case 'api': return defineAsyncComponent(() => import('./api.vue'));
case 'other': return defineAsyncComponent(() => import('./other.vue'));
case 'general': return defineAsyncComponent(() => import('./general.vue'));
case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue'));
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
case 'plugins': return defineAsyncComponent(() => import('./plugins.vue'));
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
case 'regedit': return defineAsyncComponent(() => import('./regedit.vue'));
default: return null;
}
});
onMounted(() => {
narrow.value = el.value.offsetWidth < 650;
@@ -71,11 +99,12 @@ export default defineComponent({
view,
el,
onInfo,
component,
logout: () => {
store.dispatch('logout');
location.href = '/';
},
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey,
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes,
};
},
});
@@ -91,9 +120,6 @@ export default defineComponent({
}
.vvcocwet {
max-width: 1000px;
margin: 0 auto;
> .nav {
> .menu {
margin: 16px 0;
@@ -121,7 +147,7 @@ export default defineComponent({
//border-top: solid 1px var(--divider);
}
&.router-link-active {
&.active {
color: var(--accent);
padding-left: 42px;
}
@@ -143,7 +169,7 @@ export default defineComponent({
> .nav {
width: 30%;
max-width: 260px;
max-width: 300px;
}
> .main {

View File

@@ -1,5 +1,5 @@
<template>
<section class="_section" v-if="enableTwitterIntegration || enableDiscordIntegration || enableGithubIntegration">
<section class="_section">
<div class="_content" v-if="enableTwitterIntegration">
<header><Fa :icon="faTwitter"/> Twitter</header>
<p v-if="integrations.twitter">{{ $t('connectedTo') }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>

View File

@@ -6,9 +6,9 @@
<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template>
<template #default="{items}">
<div class="user" v-for="mute in items" :key="mute.id">
<router-link class="name" :to="userPage(mute.mutee)">
<MkA class="name" :to="userPage(mute.mutee)">
<MkAcct :user="mute.mutee"/>
</router-link>
</MkA>
</div>
</template>
</MkPagination>
@@ -18,9 +18,9 @@
<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template>
<template #default="{items}">
<div class="user" v-for="block in items" :key="block.id">
<router-link class="name" :to="userPage(block.blockee)">
<MkA class="name" :to="userPage(block.blockee)">
<MkAcct :user="block.blockee"/>
</router-link>
</MkA>
</div>
</template>
</MkPagination>

View File

@@ -1,12 +1,17 @@
<template>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $t('showFeaturedNotesInTimeline') }}
</MkSwitch>
<div>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $t('showFeaturedNotesInTimeline') }}
</MkSwitch>
</div>
</div>
</div>
<div class="_section">
<MkA to="/settings/regedit">RegEdit</MkA>
</div>
</div>
</template>

View File

@@ -0,0 +1,77 @@
<template>
<div>
<div class="_section">
<MkInfo warn>{{ $t('editTheseSettingsMayBreakAccount') }}</MkInfo>
</div>
<div class="_section">
<div class="_title">Account</div>
<div class="_content">
<MkTextarea v-model:value="settings" code tall></MkTextarea>
<!--<MkButton @click="saveSettings">Save</MkButton>-->
</div>
</div>
<div class="_section">
<div class="_title">Device</div>
<div class="_content">
<MkTextarea v-model:value="deviceSettings" code tall></MkTextarea>
<MkButton @click="saveDeviceSettings">Save</MkButton>
</div>
</div>
<div class="_section">
<div class="_title">Device (per account)</div>
<div class="_content">
<MkTextarea v-model:value="deviceUserSettings" code tall></MkTextarea>
<MkButton @click="saveDeviceUserSettings">Save</MkButton>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCode } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkInfo from '@/components/ui/info.vue';
import MkButton from '@/components/ui/button.vue';
import MkTextarea from '@/components/ui/textarea.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkInfo, MkButton, MkTextarea
},
emits: ['info'],
data() {
return {
INFO: {
header: [{
title: 'RegEdit',
icon: faCode
}]
},
settings: JSON5.stringify(this.$store.state.settings, null, '\t'),
deviceSettings: JSON5.stringify(this.$store.state.device, null, '\t'),
deviceUserSettings: JSON5.stringify(this.$store.state.deviceUser, null, '\t'),
};
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
saveDeviceSettings() {
const obj = JSON5.parse(this.deviceSettings);
this.$store.commit('device/overwrite', obj);
},
saveDeviceUserSettings() {
const obj = JSON5.parse(this.deviceUserSettings);
this.$store.commit('deviceUser/overwrite', obj);
},
}
});
</script>

View File

@@ -43,7 +43,7 @@
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</MkSelect>
<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a><router-link to="/theme-editor" class="_link">{{ $t('_theme.make') }}</router-link>
<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a><MkA to="/theme-editor" class="_link">{{ $t('_theme.make') }}</MkA>
</div>
<div class="_content">
<MkButton primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</MkButton>

View File

@@ -15,11 +15,18 @@ export default defineComponent({
XNotes
},
props: {
tag: {
type: String,
required: true
}
},
data() {
return {
INFO: {
header: [{
title: this.$route.params.tag,
title: this.tag,
icon: faHashtag
}],
},
@@ -27,7 +34,7 @@ export default defineComponent({
endpoint: 'notes/search-by-tag',
limit: 10,
params: () => ({
tag: this.$route.params.tag,
tag: this.tag,
})
},
faHashtag
@@ -35,7 +42,7 @@ export default defineComponent({
},
watch: {
$route() {
tag() {
(this.$refs.notes as any).reload();
}
},

View File

@@ -229,7 +229,7 @@ export default defineComponent({
},
messagingWindowOpen() {
os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue')));
os.pageWindow('/my/messaging');
},
openWaitingDialog(text?) {

View File

@@ -9,7 +9,7 @@
<div class="_content" v-else-if="tutorial === 1">
<div>{{ $t('_tutorial.step2_1') }}</div>
<div>{{ $t('_tutorial.step2_2') }}</div>
<router-link class="_link" to="/settings/profile">{{ $t('editProfile') }}</router-link>
<MkA class="_link" to="/settings/profile">{{ $t('editProfile') }}</MkA>
</div>
<div class="_content" v-else-if="tutorial === 2">
<div>{{ $t('_tutorial.step3_1') }}</div>
@@ -25,10 +25,10 @@
<div>{{ $t('_tutorial.step5_1') }}</div>
<i18n-t keypath="_tutorial.step5_2" tag="div">
<template #featured>
<router-link class="_link" to="/featured">{{ $t('featured') }}</router-link>
<MkA class="_link" to="/featured">{{ $t('featured') }}</MkA>
</template>
<template #explore>
<router-link class="_link" to="/explore">{{ $t('explore') }}</router-link>
<MkA class="_link" to="/explore">{{ $t('explore') }}</MkA>
</template>
</i18n-t>
<div>{{ $t('_tutorial.step5_3') }}</div>
@@ -43,7 +43,7 @@
<div>{{ $t('_tutorial.step7_1') }}</div>
<i18n-t keypath="_tutorial.step7_2" tag="div">
<template #help>
<router-link class="_link" to="/docs">{{ $t('help') }}</router-link>
<MkA class="_link" to="/docs">{{ $t('help') }}</MkA>
</template>
</i18n-t>
<div>{{ $t('_tutorial.step7_3') }}</div>

View File

@@ -10,7 +10,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import parseAcct from '../../../misc/acct/parse';
import MkUserInfo from '@/components/user-info.vue';
import MkPagination from '@/components/ui/pagination.vue';
import { userPage, acct } from '../../filters/user';
@@ -22,10 +21,14 @@ export default defineComponent({
},
props: {
user: {
type: Object,
required: true
},
type: {
type: String,
required: true
}
},
},
data() {
@@ -34,7 +37,7 @@ export default defineComponent({
endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers',
limit: 20,
params: {
...parseAcct(this.$route.params.user),
userId: this.user.id,
}
},
};
@@ -45,7 +48,7 @@ export default defineComponent({
this.$refs.list.reload();
},
'$route'() {
user() {
this.$refs.list.reload();
}
},

View File

@@ -2,11 +2,11 @@
<div class="ujigsodd">
<MkLoading v-if="fetching"/>
<div class="stream" v-if="!fetching && images.length > 0">
<router-link v-for="(image, i) in images" :key="i"
<MkA v-for="image in images"
class="img"
:style="`background-image: url(${thumbnail(image.file)})`"
:to="notePage(image.note)"
></router-link>
></MkA>
</div>
<p class="empty" v-if="!fetching && images.length == 0">{{ $t('nothing') }}</p>
</div>

View File

@@ -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>
@@ -67,24 +67,23 @@
</dl>
</div>
<div class="status">
<router-link :to="userPage(user)" :class="{ active: $route.name === 'user' }">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }">
<b>{{ number(user.notesCount) }}</b>
<span>{{ $t('notes') }}</span>
</router-link>
<router-link :to="userPage(user, 'following')" :class="{ active: $route.name === 'userFollowing' }">
</MkA>
<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
<b>{{ number(user.followingCount) }}</b>
<span>{{ $t('following') }}</span>
</router-link>
<router-link :to="userPage(user, 'followers')" :class="{ active: $route.name === 'userFollowers' }">
</MkA>
<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
<b>{{ number(user.followersCount) }}</b>
<span>{{ $t('followers') }}</span>
</router-link>
</MkA>
</div>
</div>
</div>
<router-view :user="user"></router-view>
<template v-if="$route.name == 'user'">
<template v-if="page === 'index'">
<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"/>
@@ -106,6 +105,8 @@
<XUserTimeline :user="user" class="_content"/>
</div>
</template>
<XFollowList v-else-if="page === 'following'" type="following" :user="user"/>
<XFollowList v-else-if="page === 'followers'" type="followers" :user="user"/>
</div>
<div v-else-if="error">
<MkError @retry="fetch()"/>
@@ -128,7 +129,7 @@ import parseAcct from '../../../misc/acct/parse';
import { getScrollPosition } from '@/scripts/scroll';
import { getUserMenu } from '@/scripts/get-user-menu';
import number from '../../filters/number';
import { userPage, acct } from '../../filters/user';
import { userPage, acct as getAcct } from '../../filters/user';
import * as os from '@/os';
export default defineComponent({
@@ -139,10 +140,23 @@ export default defineComponent({
MkContainer,
MkRemoteCaution,
MkFolder,
XFollowList: defineAsyncComponent(() => import('./follow-list.vue')),
XPhotos: defineAsyncComponent(() => import('./index.photos.vue')),
XActivity: defineAsyncComponent(() => import('./index.activity.vue')),
},
props: {
acct: {
type: String,
required: true
},
page: {
type: String,
required: false,
default: 'index'
}
},
data() {
return {
INFO: computed(() => this.user ? {
@@ -176,7 +190,7 @@ export default defineComponent({
},
watch: {
$route: 'fetch'
acct: 'fetch'
},
created() {
@@ -192,10 +206,12 @@ export default defineComponent({
},
methods: {
getAcct,
fetch() {
if (this.$route.params.user == null) return;
if (this.acct == null) return;
Progress.start();
os.api('users/show', parseAcct(this.$route.params.user)).then(user => {
os.api('users/show', parseAcct(this.acct)).then(user => {
this.user = user;
}).catch(e => {
this.error = e;

View File

@@ -1,4 +1,4 @@
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, markRaw } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import MkLoading from '@/pages/_loading_.vue';
import MkError from '@/pages/_error_.vue';
@@ -18,30 +18,11 @@ export const router = createRouter({
routes: [
// NOTE: MkTimelineをdynamic importするとAsyncComponentWrapperが間に入るせいでkeep-aliveのコンポーネント指定が効かなくなる
{ path: '/', name: 'index', component: store.getters.isSignedIn ? MkTimeline : page('welcome') },
{ path: '/@:user', name: 'user', component: page('user/index'), children: [
{ path: 'following', name: 'userFollowing', component: page('user/follow-list'), props: { type: 'following' } },
{ path: 'followers', name: 'userFollowers', component: page('user/follow-list'), props: { type: 'followers' } },
]},
{ path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) },
{ path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) },
{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/@:acct/room', props: true, component: page('room/room') },
{ path: '/settings', name: 'settings', component: page('settings/index'), children: [
{ path: 'profile', component: page('settings/profile') },
{ path: 'privacy', component: page('settings/privacy') },
{ path: 'reaction', component: page('settings/reaction') },
{ path: 'notifications', component: page('settings/notifications') },
{ path: 'mute-block', component: page('settings/mute-block') },
{ path: 'word-mute', component: page('settings/word-mute') },
{ path: 'integration', component: page('settings/integration') },
{ path: 'security', component: page('settings/security') },
{ path: 'api', component: page('settings/api') },
{ path: 'other', component: page('settings/other') },
{ path: 'general', component: page('settings/general') },
{ path: 'theme', component: page('settings/theme') },
{ path: 'sidebar', component: page('settings/sidebar') },
{ path: 'sounds', component: page('settings/sounds') },
{ path: 'plugins', component: page('settings/plugins') },
]},
{ path: '/settings/:page?', name: 'settings', component: page('settings/index'), props: route => ({ page: route.params.page || null }) },
{ path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') },
{ path: '/about-misskey', component: page('about-misskey') },
@@ -86,8 +67,12 @@ 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: '/notes/:note', name: 'note', component: page('note') },
{ path: '/tags/:tag', component: page('tag') },
{ path: '/instance/abuses', component: page('instance/abuses') },
{ path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) },
{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) },
{ path: '/games/reversi', component: page('reversi/index') },
{ path: '/games/reversi/:gameId', component: page('reversi/game'), props: route => ({ gameId: route.params.gameId }) },
{ path: '/api-console', component: page('api-console') },
{ path: '/auth/:token', component: page('auth') },
{ path: '/miauth/:session', component: page('miauth') },
{ path: '/authorize-follow', component: page('follow') },
@@ -119,3 +104,13 @@ router.afterEach((to, from) => {
indexScrollPos = window.scrollY;
}
});
export function resolve(path: string) {
const resolved = router.resolve(path);
const route = resolved.matched[0];
return {
component: markRaw(route.components.default),
// TODO: route.propsには関数以外も入る可能性があるのでよしなにハンドリングする
props: route.props?.default ? route.props.default(resolved) : resolved.params
};
}

View File

@@ -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';
@@ -7,7 +7,6 @@ import getAcct from '../../misc/acct/render';
import * as os from '@/os';
import { store, userActions } from '@/store';
import { router } from '@/router';
import { defineAsyncComponent } from 'vue';
import { popout } from './popout';
export function getUserMenu(user) {
@@ -102,6 +101,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',
@@ -131,7 +136,7 @@ export function getUserMenu(user) {
action: () => {
const acct = getAcct(user);
switch (store.state.device.chatOpenBehavior) {
case 'window': { os.pageWindow('/my/messaging/' + acct, defineAsyncComponent(() => import('@/pages/messaging/messaging-room.vue')), { userAcct: acct }); break; }
case 'window': { os.pageWindow('/my/messaging/' + acct); break; }
case 'popout': { popout('/my/messaging'); break; }
default: { router.push('/my/messaging'); break; }
}
@@ -157,6 +162,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,

Some files were not shown because too many files have changed in this diff Show More