Merge branch 'notification-read-api' into swn

This commit is contained in:
tamaina 2021-12-05 12:47:24 +09:00
commit 4a8dfbdf2d
95 changed files with 1935 additions and 1162 deletions

View file

@ -10,19 +10,26 @@
--> -->
## 12.x.x (unreleased) ## 12.98.0 (2021/12/03)
### Improvements ### Improvements
- API: /antennas/notes API で日付による絞り込みができるように - API: /antennas/notes API で日付による絞り込みができるように
- クライアント: アンケートに投票する際に確認ダイアログを出すように - クライアント: アンケートに投票する際に確認ダイアログを出すように
- クライアント: Renoteなート詳細ページから元のートページに遷移できるように - クライアント: Renoteなート詳細ページから元のートページに遷移できるように
- クライアント: 画像ポップアップでクリックで閉じられるように
- クライアント: デザインの調整
- フォロワーを解除できる機能
### Bugfixes ### Bugfixes
- クライアント: LTLやGTLが無効になっている場合でもUI上にタブが表示される問題を修正
- クライアント: ログインにおいてパスワードが誤っている際のエラーメッセージが正しく表示されない問題を修正 - クライアント: ログインにおいてパスワードが誤っている際のエラーメッセージが正しく表示されない問題を修正
- クライアント: リアクションツールチップ、Renoteツールチップのユーザーの並び順を修正 - クライアント: リアクションツールチップ、Renoteツールチップのユーザーの並び順を修正
- クライアント: サウンドのマスターボリュームが正しく保存されない問題を修正 - クライアント: サウンドのマスターボリュームが正しく保存されない問題を修正
- クライアント: 一部環境において通知が表示されると操作不能になる問題を修正 - クライアント: 一部環境において通知が表示されると操作不能になる問題を修正
- クライアント: モバイルでタップしたときにツールチップが表示される問題を修正 - クライアント: モバイルでタップしたときにツールチップが表示される問題を修正
- クライアント: リモートインスタンスのノートに返信するとき、対象のノートにそのリモートインスタンス内のユーザーへのメンションが含まれていると、返信テキスト内にローカルユーザーへのメンションとして引き継がれてしまう場合がある問題を修正
- クライアント: 画像ビューワーで全体表示した時に上側の一部しか表示されない画像がある問題を修正
- API: ユーザーを取得時に条件によっては内部エラーになる問題を修正
### Changes ### Changes
- クライアント: ノートにモデレーターバッジを表示するのを廃止 - クライアント: ノートにモデレーターバッジを表示するのを廃止

View file

@ -734,7 +734,10 @@ translate: "ترجم"
translatedFrom: "تُرجم من {x}" translatedFrom: "تُرجم من {x}"
accountDeletionInProgress: "حذف الحساب جارٍ" accountDeletionInProgress: "حذف الحساب جارٍ"
usernameInfo: "الاسم الذي يميزك عن بافي مستخدمي هذا الخادم، يمكنك استخدام الحروف اللاتينية (a~z, A~Z) والأرقام (0~9) والشرطة السفلية (_). لا يمكنك تغييره بعد تسجيله." usernameInfo: "الاسم الذي يميزك عن بافي مستخدمي هذا الخادم، يمكنك استخدام الحروف اللاتينية (a~z, A~Z) والأرقام (0~9) والشرطة السفلية (_). لا يمكنك تغييره بعد تسجيله."
keepCw: "أبقِ على تحذيرات المحتوى"
lastCommunication: "آخر تواصل" lastCommunication: "آخر تواصل"
resolved: "عولج"
unresolved: "لم يعالج"
itsOn: "مفعّل" itsOn: "مفعّل"
itsOff: "معطّل" itsOff: "معطّل"
emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل" emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل"
@ -747,6 +750,16 @@ makeReactionsPublicDescription: "هذا سيجعل قائمة تفاعلاتك
classic: "تقليدي" classic: "تقليدي"
muteThread: "اكتم النقاش" muteThread: "اكتم النقاش"
unmuteThread: "ارفع الكتم عن النقاش" unmuteThread: "ارفع الكتم عن النقاش"
deleteAccountConfirm: "سيحذف حسابك نهائيًا، أتريد المتابعة؟"
incorrectPassword: "كلمة السر خاطئة."
_emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة"
mx: "خادم البريد الإلكتروني غير صالح"
smtp: "خادم البريد الإلكتروتي لا يستجيب"
_ffVisibility:
public: "علني"
private: "خاص"
_signup: _signup:
almostThere: "كدت تنتهي" almostThere: "كدت تنتهي"
emailAddressInfo: "رجاءً أدخل بريدك الإلكتروني." emailAddressInfo: "رجاءً أدخل بريدك الإلكتروني."
@ -829,6 +842,7 @@ _mfm:
font: "الخط" font: "الخط"
rainbow: "قوس قزح" rainbow: "قوس قزح"
rainbowDescription: "اجعل المحتوى يظهر بألوان الطيف" rainbowDescription: "اجعل المحتوى يظهر بألوان الطيف"
rotate: "تدوير"
_reversi: _reversi:
gameSettings: "إعدادات اللعبة" gameSettings: "إعدادات اللعبة"
chooseBoard: "اختر اللوح" chooseBoard: "اختر اللوح"
@ -980,9 +994,13 @@ _tutorial:
step7_2: "إذا أردت معرفة المزيد عن ميسكي زر {help}." step7_2: "إذا أردت معرفة المزيد عن ميسكي زر {help}."
step7_3: "حظًا سعيدًا واستمتع بوقتك مع ميسكي! 🚀" step7_3: "حظًا سعيدًا واستمتع بوقتك مع ميسكي! 🚀"
_2fa: _2fa:
alreadyRegistered: "سجلت سلفًا جهازًا للاستيثاق بعاملين."
registerDevice: "سجّل جهازًا جديدًا" registerDevice: "سجّل جهازًا جديدًا"
registerKey: "تسجيل مفتاح أمان جديد" registerKey: "تسجيل مفتاح أمان جديد"
step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} و{b})." step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} و{b})."
step2: "امسح رمز الاستجابة السريعة الموجد على الشاشة."
step3: "أدخل الرمز الموجود في تطبيقك لإكمال التثبيت."
step4: "من هذه اللحظة أثناء ولوجك سيُطلب منك الرمز."
_permissions: _permissions:
"read:account": "اعرض معلومات حسابك" "read:account": "اعرض معلومات حسابك"
"write:account": "تعديل معلومات حسابك" "write:account": "تعديل معلومات حسابك"
@ -993,6 +1011,7 @@ _permissions:
"read:favorites": "اعرض المفضلة" "read:favorites": "اعرض المفضلة"
"write:favorites": "عدّل المفضلة" "write:favorites": "عدّل المفضلة"
"read:following": "اعرض معلومات متابَعيك" "read:following": "اعرض معلومات متابَعيك"
"write:following": "تابع أو ألغ متابعة حسابات"
"read:messaging": "اعرض المحادثات" "read:messaging": "اعرض المحادثات"
"write:messaging": "اكتب أو احذف رسائل محادثة" "write:messaging": "اكتب أو احذف رسائل محادثة"
"read:mutes": "اعرض قائمة المستخدمين المكتومين" "read:mutes": "اعرض قائمة المستخدمين المكتومين"
@ -1005,11 +1024,14 @@ _permissions:
"write:votes": "صوّت" "write:votes": "صوّت"
"read:pages": "اعرض صفحاتك" "read:pages": "اعرض صفحاتك"
"write:pages": "عدّل أو احذف صفحاتك" "write:pages": "عدّل أو احذف صفحاتك"
"read:page-likes": "يعرض ما أعجبك من ملاحظات في صفحات"
"read:user-groups": "اعرض فِرق المستخدمين" "read:user-groups": "اعرض فِرق المستخدمين"
"write:user-groups": "عدّل أو احذف فِرق المستخدمين" "write:user-groups": "عدّل أو احذف فِرق المستخدمين"
"read:channels": "طالع قنواتك"
"write:channels": "عدّل القنوات" "write:channels": "عدّل القنوات"
"read:gallery": "اعرض المعرض" "read:gallery": "اعرض المعرض"
"write:gallery": "عدّل المعرض" "write:gallery": "عدّل المعرض"
"read:gallery-likes": "يعرض ما أعجبك من مشاركات المعرض"
_auth: _auth:
shareAccess: "أتريد التفويض لـ \"{name}\" بالوصول لحسابك؟" shareAccess: "أتريد التفويض لـ \"{name}\" بالوصول لحسابك؟"
shareAccessAsk: "هل تخول لهذا التطبيق الوصول لحسابك؟" shareAccessAsk: "هل تخول لهذا التطبيق الوصول لحسابك؟"
@ -1173,6 +1195,7 @@ _rooms:
tv: "تلفاز" tv: "تلفاز"
pinguin: "بطريق" pinguin: "بطريق"
sofa: "أريكة" sofa: "أريكة"
bin: "سلة مهملات"
banknote: "أوراق نقدية" banknote: "أوراق نقدية"
_pages: _pages:
newPage: "أنشئ صفحة جديدة" newPage: "أنشئ صفحة جديدة"
@ -1212,6 +1235,7 @@ _pages:
name: "اسم المتغير" name: "اسم المتغير"
text: "العنوان" text: "العنوان"
default: "القيمة الافتراضية" default: "القيمة الافتراضية"
textareaInput: "مدخل نصي متعدد الأسطر"
_textareaInput: _textareaInput:
name: "اسم المتغير" name: "اسم المتغير"
text: "العنوان" text: "العنوان"
@ -1227,6 +1251,7 @@ _pages:
note: "ملاحظة مضمّنة" note: "ملاحظة مضمّنة"
_note: _note:
id: "معرّف الملاحظة" id: "معرّف الملاحظة"
idDescription: "كبديل يمكنك إدخال رابك الملاحظة هنا"
detailed: "عرض مفصّل" detailed: "عرض مفصّل"
switch: "بدّل" switch: "بدّل"
_switch: _switch:

View file

@ -792,6 +792,7 @@ pubSub: "Pub/Sub Benutzerkonten"
lastCommunication: "Letzte Kommunikation" lastCommunication: "Letzte Kommunikation"
resolved: "Gelöst" resolved: "Gelöst"
unresolved: "Ungelöst" unresolved: "Ungelöst"
breakFollow: "Follower entfernen"
itsOn: "Eingeschaltet" itsOn: "Eingeschaltet"
itsOff: "Ausgeschaltet" itsOff: "Ausgeschaltet"
emailRequiredForSignup: "Angaben einer Email-Adresse als benötigt markieren" emailRequiredForSignup: "Angaben einer Email-Adresse als benötigt markieren"
@ -808,6 +809,8 @@ ffVisibility: "Sichtbarkeit von Gefolgten/Followern"
ffVisibilityDescription: "Konfiguriere wer sehen kann, wem du folgst sowie wer dir folgt." ffVisibilityDescription: "Konfiguriere wer sehen kann, wem du folgst sowie wer dir folgt."
continueThread: "Weiteren Threadverlauf anzeigen" continueThread: "Weiteren Threadverlauf anzeigen"
deleteAccountConfirm: "Dein Benutzerkonto wird unwiderruflich gelöscht. Trotzdem fortfahren?" deleteAccountConfirm: "Dein Benutzerkonto wird unwiderruflich gelöscht. Trotzdem fortfahren?"
incorrectPassword: "Falsches Passwort."
voteConfirm: "Wirklich für \"{choice}\" abstimmen?"
_emailUnavailable: _emailUnavailable:
used: "Diese Email-Adresse wird bereits verwendet" used: "Diese Email-Adresse wird bereits verwendet"
format: "Das Format dieser Email-Adresse ist ungültig" format: "Das Format dieser Email-Adresse ist ungültig"
@ -931,6 +934,8 @@ _mfm:
rainbowDescription: "Lässt den Inhalt in Regenbogenfarben erscheinen." rainbowDescription: "Lässt den Inhalt in Regenbogenfarben erscheinen."
sparkle: "Glitzer" sparkle: "Glitzer"
sparkleDescription: "Verleiht Inhalt einen glitzernden Partikeleffekt." sparkleDescription: "Verleiht Inhalt einen glitzernden Partikeleffekt."
rotate: "Drehen"
rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Spieleinstellungen" gameSettings: "Spieleinstellungen"

View file

@ -808,6 +808,8 @@ ffVisibility: "Follows/Followers Visibility"
ffVisibilityDescription: "Allows you to configure who can see who you follow and who follows you." ffVisibilityDescription: "Allows you to configure who can see who you follow and who follows you."
continueThread: "View thread continuation" continueThread: "View thread continuation"
deleteAccountConfirm: "This will irreversibly delete your account. Proceed?" deleteAccountConfirm: "This will irreversibly delete your account. Proceed?"
incorrectPassword: "Incorrect password."
voteConfirm: "Confirm your vote for \"{choice}\"?"
_emailUnavailable: _emailUnavailable:
used: "This email address is already being used" used: "This email address is already being used"
format: "The format of this email address is invalid" format: "The format of this email address is invalid"
@ -932,7 +934,7 @@ _mfm:
sparkle: "Sparkle" sparkle: "Sparkle"
sparkleDescription: "Gives content a sparkling particle effect." sparkleDescription: "Gives content a sparkling particle effect."
rotate: "Rotate" rotate: "Rotate"
rotateDescription: "Rotates the content by 90 degrees" rotateDescription: "Turns content by a specified angle."
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Game settings" gameSettings: "Game settings"

View file

@ -2,18 +2,18 @@
_lang_: "Esperanto" _lang_: "Esperanto"
headlineMisskey: "Jen la reto konektata de notoj" headlineMisskey: "Jen la reto konektata de notoj"
introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza etbloga servo.\nKreu \"noto\"n por paroli vian penson al iuj ĉirkaŭ vi. 📡\nLa funkcion \"reago\" ebligas esprimi rapide vian senton pri ies noto en Fediverso. 👍\nBonvole esploru novan mondon. 🚀" introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza etbloga servo.\nKreu \"noto\"n por paroli vian penson al iuj ĉirkaŭ vi. 📡\nLa funkcion \"reago\" ebligas esprimi rapide vian senton pri ies noto en Fediverso. 👍\nBonvole esploru novan mondon. 🚀"
monthAndDay: "La {day}a de la {month}a" monthAndDay: "la {day}a de la {month}a"
search: "Serĉi" search: "Serĉi"
notifications: "Sciigoj" notifications: "Sciigoj"
username: "Uzantnomo" username: "Uzantnomo"
password: "Pasvorto" password: "Pasvorto"
forgotPassword: "Ĉu vi forgesis pasvorton?" forgotPassword: "Ĉu vi forgesis pasvorton?"
fetchingAsApObject: "Informpetado de kunfederaĵo…" fetchingAsApObject: "Informpetado de la Fediverso…"
ok: "Akcepteble" ok: "OK"
gotIt: "Mi komprenas" gotIt: "Kompreni"
cancel: "Nuligi" cancel: "Nuligi"
enterUsername: "Entajpu uzantnomon" enterUsername: "Entajpu uzantnomon"
renotedBy: "Noto plusendita de {user}" renotedBy: "Plusendita de {user}"
noNotes: "Neniu noto!" noNotes: "Neniu noto!"
noNotifications: "Vi ne havas sciigojn." noNotifications: "Vi ne havas sciigojn."
instance: "Nodo" instance: "Nodo"
@ -35,22 +35,22 @@ addUser: "Aldoni uzanton"
favorite: "Preferi" favorite: "Preferi"
favorites: "Preferaĵoj" favorites: "Preferaĵoj"
unfavorite: "Malpreferi" unfavorite: "Malpreferi"
favorited: "Aldonita al via listo de preferaĵoj." favorited: "Aldonita al viaj preferaĵoj."
alreadyFavorited: "Jam aldonita al via listo de preferaĵoj." alreadyFavorited: "Jam aldonita al viaj preferaĵoj."
cantFavorite: "Ĝi ne povis esti aldonita al via listo de preferaĵoj." cantFavorite: "Oni ne povis aldoni al viaj preferaĵoj."
pin: "Alpingli" pin: "Alpingli"
unpin: "Depingli" unpin: "Depingli"
copyContent: "Kopii enhavon" copyContent: "Kopii enhavon"
copyLink: "Kopii ligilon" copyLink: "Kopii ligilon"
delete: "Forviŝi" delete: "Forviŝi"
deleteAndEdit: "Forviŝi kaj redakti" deleteAndEdit: "Forviŝi kaj redakti"
deleteAndEditConfirm: "Ĉu vi certas ke vi volas redakti forviŝinte la noton? Tio forviŝos ankaŭ ĉiujn reagojn, plusendojn, kaj respondojn apartenantajn al ĝi." deleteAndEditConfirm: "Ĉu vi certas ke vi volas redakti foriginte la noton? Tio forviŝos reagojn, plusendojn, kaj respondojn ĉiujn apartenantajn al ĝi."
addToList: "Aldoni al listo" addToList: "Aldoni al listo"
sendMessage: "Sendi mesaĝon" sendMessage: "Sendi mesaĝon"
copyUsername: "Kopii uzantnomon" copyUsername: "Kopii uzantnomon"
searchUser: "Serĉi uzanton" searchUser: "Serĉi uzanton"
reply: "Respondi" reply: "Respondi"
loadMore: "Vidu pli" loadMore: "Vidi pli"
showMore: "Vidi pli" showMore: "Vidi pli"
youGotNewFollower: "eksekvis vin" youGotNewFollower: "eksekvis vin"
receiveFollowRequest: "Peto de sekvado estas ricevita" receiveFollowRequest: "Peto de sekvado estas ricevita"
@ -77,10 +77,11 @@ manageLists: "Administri liston"
error: "Eraro" error: "Eraro"
somethingHappened: "Problemo okazis" somethingHappened: "Problemo okazis"
retry: "Provi denove" retry: "Provi denove"
serverIsDead: "La servilo ne respondas. Vole atendu iom kaj penu denove."
enterListName: "Entajpu nomon de la listo" enterListName: "Entajpu nomon de la listo"
privacy: "Privateco" privacy: "Privateco"
makeFollowManuallyApprove: "Eksekvi vin devas peti al vi" makeFollowManuallyApprove: "Eksekvi vin devas peti al vi"
defaultNoteVisibility: "Implicitaĵo de videbleco" defaultNoteVisibility: "Implicita videbleco de la noto"
follow: "Sekvi" follow: "Sekvi"
followRequest: "Peti de sekvado" followRequest: "Peti de sekvado"
followRequests: "Petoj de sekvado" followRequests: "Petoj de sekvado"
@ -88,10 +89,10 @@ unfollow: "Ne plu sekvi"
followRequestPending: "Atendado akcepti vian peton de eksekvado" followRequestPending: "Atendado akcepti vian peton de eksekvado"
enterEmoji: "Entajpu emoĵion" enterEmoji: "Entajpu emoĵion"
renote: "Plusendi la noton" renote: "Plusendi la noton"
unrenote: "Malfari plusendadon" unrenote: "Malfari plusendon"
renoted: "Sukcese plusendita" renoted: "Sukcese plusendita"
cantRenote: "Oni ne povas plusendi la noton." cantRenote: "Oni ne povas plusendi la noton."
cantReRenote: "Plusendo de noto ne estas plusendebla." cantReRenote: "Plusendo ne estas plusendebla."
quote: "Citi" quote: "Citi"
pinnedNote: "Alpinglita noto" pinnedNote: "Alpinglita noto"
pinned: "Alpingli" pinned: "Alpingli"
@ -101,7 +102,7 @@ sensitive: "Enhavo ne estas deca por laborejo (NSFW)"
add: "Aldoni" add: "Aldoni"
reaction: "Reagoj" reaction: "Reagoj"
reactionSettingDescription: "Agordi la reagojn kiujn vi volas prefere montrigi ĉe la elektilo de reagoj" reactionSettingDescription: "Agordi la reagojn kiujn vi volas prefere montrigi ĉe la elektilo de reagoj"
rememberNoteVisibility: "Rememori la agordon de videbleco de la noto laste sendita " rememberNoteVisibility: "Rememori la agordon de videbleco de la laste sendita"
attachCancel: "Deigi aldonaĵon" attachCancel: "Deigi aldonaĵon"
markAsSensitive: "Troviĝi NSFW" markAsSensitive: "Troviĝi NSFW"
unmarkAsSensitive: "Ne troviĝi NSFW" unmarkAsSensitive: "Ne troviĝi NSFW"
@ -121,16 +122,16 @@ selectAntenna: "Elekti antenon"
selectWidget: "Elekti enestraĵon" selectWidget: "Elekti enestraĵon"
editWidgets: "Redakti fenestraĵon" editWidgets: "Redakti fenestraĵon"
editWidgetsExit: "Fini la redaktadon" editWidgetsExit: "Fini la redaktadon"
customEmojis: "Personecigitaj emoĵioj"
emoji: "Emoĵio" emoji: "Emoĵio"
emojis: "Emoĵio" emojis: "Emoĵio"
emojiName: "Nomo de emoĵio" emojiName: "Nomo de la emoĵio"
emojiUrl: "URL de la emoĵio" emojiUrl: "URL de la emoĵio"
addEmoji: "Aldoni emoĵion" addEmoji: "Aldoni emoĵion"
settingGuide: "Agordaj rekomendoj" settingGuide: "Agordaj rekomendoj"
cacheRemoteFiles: "Stapli transajn dosierojn" cacheRemoteFiles: "Stapli forajn dosierojn"
flagAsBot: "Agordo por robota uzanto" flagAsBot: "Fari la flagon por robota uzanto"
flagAsCat: "Agi kat-iĝon" flagAsCat: "Fari la flagon por kat-iĝi"
autoAcceptFollowed: "Aŭtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas"
addAccount: "Aldoni konton" addAccount: "Aldoni konton"
showOnRemote: "Vidi ĉe la surloka nodo" showOnRemote: "Vidi ĉe la surloka nodo"
general: "Ĝenerala" general: "Ĝenerala"
@ -140,7 +141,7 @@ removeWallpaper: "Forviŝi ekranfonon. "
searchWith: "Serĉi: {q}" searchWith: "Serĉi: {q}"
youHaveNoLists: "Vi ne havas listojn." youHaveNoLists: "Vi ne havas listojn."
followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?" followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?"
host: "Gastigo" host: "Nodo"
selectUser: "Elekti uzanton" selectUser: "Elekti uzanton"
recipient: "Ricevonto" recipient: "Ricevonto"
annotation: "Komentarioj" annotation: "Komentarioj"
@ -164,8 +165,9 @@ disk: "Disko"
instanceInfo: "Informoj pri la nodo" instanceInfo: "Informoj pri la nodo"
statistics: "Statistikoj" statistics: "Statistikoj"
clearCachedFiles: "Malplenigi la staplon" clearCachedFiles: "Malplenigi la staplon"
clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn transajn dosierojn en la staplo?" clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn forajn dosierojn en la staplo?"
blockedInstances: "Blokitaj nodoj" blockedInstances: "Blokitaj nodoj"
muteAndBlock: "Silentigi / Bloki"
mutedUsers: "Silentigitaj uzantoj" mutedUsers: "Silentigitaj uzantoj"
blockedUsers: "Blokitaj uzantoj" blockedUsers: "Blokitaj uzantoj"
noUsers: "Neniu uzanto" noUsers: "Neniu uzanto"
@ -175,7 +177,7 @@ pinLimitExceeded: "Vi ne povas alpingli pli"
done: "Fini" done: "Fini"
processing: "Prilaborado…" processing: "Prilaborado…"
preview: "Antaŭmontro" preview: "Antaŭmontro"
default: "Defaŭlta" default: "Implicitaĵo"
noCustomEmojis: "Neniu emoĵio" noCustomEmojis: "Neniu emoĵio"
noJobs: "Neniu laboro" noJobs: "Neniu laboro"
federating: "Federantaj" federating: "Federantaj"
@ -195,7 +197,7 @@ currentPassword: "Aktuala pasvorto"
newPassword: "Nova pasvorto" newPassword: "Nova pasvorto"
newPasswordRetype: "Reentajpu la novan pasvorton" newPasswordRetype: "Reentajpu la novan pasvorton"
attachFile: "Aldoni dosieron" attachFile: "Aldoni dosieron"
more: "Plu!" more: "Pli!"
featured: "Maksimumi" featured: "Maksimumi"
usernameOrUserId: "Uzantnomo aŭ identigilo de uzanto" usernameOrUserId: "Uzantnomo aŭ identigilo de uzanto"
noSuchUser: "Neniuj uzantoj trovitaj" noSuchUser: "Neniuj uzantoj trovitaj"
@ -204,8 +206,8 @@ announcements: "Novaĵoj"
imageUrl: "URL de la bildo" imageUrl: "URL de la bildo"
remove: "Forigi" remove: "Forigi"
removed: "Forigita" removed: "Forigita"
removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"'(o)n?" removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"n?"
deleteAreYouSure: "Ĉu vi certas ke vi volas forviŝi \"{x}\"'(o)n?" deleteAreYouSure: "Ĉu vi certas ke vi volas forviŝi \"{x}\"'?"
resetAreYouSure: "Ĉu vi certas restarigi?" resetAreYouSure: "Ĉu vi certas restarigi?"
saved: "Konservita" saved: "Konservita"
messaging: "Retbabili" messaging: "Retbabili"
@ -225,13 +227,13 @@ agreeTo: "Mi akceptas {0}'(o)n"
tos: "Kondiĉoj de uzado" tos: "Kondiĉoj de uzado"
start: "Komenciĝi" start: "Komenciĝi"
home: "Hejma" home: "Hejma"
remoteUserCaution: "Ĉi tiuj infomoj estas ne tute ekzaktaj pro transa uzanto." remoteUserCaution: "Ĉi tiuj infomoj de la uzanto el fora nodo, ne estas tute ekzaktaj."
activity: "Aktiveco" activity: "Aktiveco"
images: "Bildoj" images: "Bildoj"
birthday: "Naskiĝdato" birthday: "Naskiĝdato"
yearsOld: "{age} jaroj aĝa" yearsOld: "{age} jaroj aĝa"
registeredDate: "Dato de registriĝo" registeredDate: "Dato de registriĝo"
location: "Loko" location: "Kie"
theme: "Koloraro" theme: "Koloraro"
themeForLightMode: "Luma kolararo en la luma modo" themeForLightMode: "Luma kolararo en la luma modo"
themeForDarkMode: "Malluma kolararo en la malluma modo" themeForDarkMode: "Malluma kolararo en la malluma modo"
@ -253,7 +255,7 @@ deleteFolder: "Forviŝi dosierujon"
addFile: "Aldoni dosieron" addFile: "Aldoni dosieron"
emptyDrive: "La disko malplenas" emptyDrive: "La disko malplenas"
emptyFolder: "La dosierujo malplenas" emptyFolder: "La dosierujo malplenas"
unableToDelete: "Ne forigebla" unableToDelete: "Ne forviŝebla"
inputNewFileName: "Entajpu novan nomon de la dosiero" inputNewFileName: "Entajpu novan nomon de la dosiero"
inputNewDescription: "Entajpu novan priskribon" inputNewDescription: "Entajpu novan priskribon"
inputNewFolderName: "Entajpu novan nomon de la dosierujo" inputNewFolderName: "Entajpu novan nomon de la dosierujo"
@ -266,9 +268,11 @@ nsfw: "Enhavo ne estas deca por laborejo (NSFW)"
disconnectedFromServer: "Malkonektita de servilo" disconnectedFromServer: "Malkonektita de servilo"
reload: "Reŝargi" reload: "Reŝargi"
doNothing: "Ignori" doNothing: "Ignori"
reloadConfirm: "Ĉu vi volas reŝargi?"
watch: "Observi" watch: "Observi"
unwatch: "Malobservi" unwatch: "Malobservi"
accept: "Permesi" accept: "Permesi"
reject: "Malakcepti"
normal: "Normala" normal: "Normala"
instanceName: "Nomo de la nodo" instanceName: "Nomo de la nodo"
instanceDescription: "Priskribo de la nodo " instanceDescription: "Priskribo de la nodo "
@ -291,20 +295,22 @@ registration: "Registri"
enableRegistration: "Ebligi novan uzanton registriĝon" enableRegistration: "Ebligi novan uzanton registriĝon"
invite: "Inviti" invite: "Inviti"
driveCapacityPerLocalAccount: "Volumo de disko po unu loka uzanto" driveCapacityPerLocalAccount: "Volumo de disko po unu loka uzanto"
driveCapacityPerRemoteAccount: "Volumo de disko po unu transa uzanto" driveCapacityPerRemoteAccount: "Volumo de disko po unu fora uzanto"
iconUrl: "URL de la ikono (retpaĝsimbolo, ktp)" iconUrl: "URL de la ikono (retpaĝsimbolo, ktp)"
bannerUrl: "URL de standardo" bannerUrl: "URL de standardo"
backgroundImageUrl: "URL de fona bildo" backgroundImageUrl: "URL de fona bildo"
basicInfo: "Baza informo" basicInfo: "Baza informo"
pinnedUsers: "Alpinglita uzanto" pinnedUsers: "Alpinglita uzanto"
pinnedUsersDescription: "Listigu uzantnomojn apartige en ĉiu linio por alpingli al la paĝoj ekz \"Esplori\"."
pinnedPages: "Alpinglitaj paĝoj" pinnedPages: "Alpinglitaj paĝoj"
pinnedPagesDescription: "Listigu dosierindiko apartige en ĉiu linio por alpingli al la ĉefpaĝo de la nodo."
pinnedNotes: "Alpinglita noto" pinnedNotes: "Alpinglita noto"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
enableHcaptcha: "Ebligi hCaptcha" enableHcaptcha: "Ebligi hCaptcha"
hcaptchaSiteKey: "Reteja ŝlosilo" hcaptchaSiteKey: "Reteja ŝlosilo"
hcaptchaSecretKey: "Sekreta ŝlosilo" hcaptchaSecretKey: "Sekreta ŝlosilo"
recaptcha: "reCAPTCHA" recaptcha: "reCAPTCHA"
enableRecaptcha: "Ebligi reCAPTCHA'on" enableRecaptcha: "Ebligi reCAPTCHA"
recaptchaSiteKey: "Reteja ŝlosilo" recaptchaSiteKey: "Reteja ŝlosilo"
recaptchaSecretKey: "Sekreta ŝlosilo" recaptchaSecretKey: "Sekreta ŝlosilo"
antennas: "Antenoj" antennas: "Antenoj"
@ -338,15 +344,17 @@ moderator: "Kontrolisto"
nUsersMentioned: "{n} uzanto(j) menciis" nUsersMentioned: "{n} uzanto(j) menciis"
securityKey: "Sekureca ŝlosilo" securityKey: "Sekureca ŝlosilo"
securityKeyName: "Nomo de la ŝlosilo" securityKeyName: "Nomo de la ŝlosilo"
registerSecurityKey: "Registri ŝlosilon de sekureco"
lastUsed: "Plej malnove uzita" lastUsed: "Plej malnove uzita"
unregister: "Malregistriĝi" unregister: "Malregistriĝi"
passwordLessLogin: "Ensaluti sen pasvorto" passwordLessLogin: "Ensaluti sen pasvorto"
resetPassword: "Restarigi pasvorton" resetPassword: "Restarigi pasvorton"
newPasswordIs: "La nova pasvorto estas {password}." newPasswordIs: "La nova pasvorto estas {password}."
share: "Diskonigi" reduceUiAnimation: "Redukti la animacioj de la fasado"
share: "Kundividi"
notFound: "Ne trovita" notFound: "Ne trovita"
cacheClear: "Malplenigi staplon" cacheClear: "Malplenigi staplon"
markAsReadAllNotifications: "Marki ĉiujn sciigojn kiel legito" markAsReadAllNotifications: "Marki ĉiujn sciigojn kiel legita"
help: "Manlibro de uzado" help: "Manlibro de uzado"
inputMessageHere: "Entajpu masaĝo tie ĉi" inputMessageHere: "Entajpu masaĝo tie ĉi"
close: "Fermi" close: "Fermi"
@ -354,10 +362,11 @@ group: "Grupo"
groups: "Grupoj" groups: "Grupoj"
createGroup: "Krei grupon" createGroup: "Krei grupon"
ownedGroups: "Administrataj grupoj" ownedGroups: "Administrataj grupoj"
joinedGroups: "La grupoj kiujn la uzanto aliĝis" joinedGroups: "Al grupoj kiuj vi aliĝis"
invites: "Inviti" invites: "Inviti"
groupName: "Grupa nomo" groupName: "Grupa nomo"
members: "Membroj" members: "Membroj"
transfer: "Movi"
messagingWithUser: "Babili private" messagingWithUser: "Babili private"
messagingWithGroup: "Babili grupe" messagingWithGroup: "Babili grupe"
title: "Titolo" title: "Titolo"
@ -366,6 +375,7 @@ enable: "Ebligi"
next: "Sekve" next: "Sekve"
retype: "Retajpu" retype: "Retajpu"
noteOf: "Noto de {user}" noteOf: "Noto de {user}"
inviteToGroup: "Inviti al grupo"
quoteAttached: "Kun citaĵo" quoteAttached: "Kun citaĵo"
quoteQuestion: "Ĉu vi aldonas citaĵon?" quoteQuestion: "Ĉu vi aldonas citaĵon?"
noMessagesYet: "Ankoraŭ neniu mesaĝo" noMessagesYet: "Ankoraŭ neniu mesaĝo"
@ -374,27 +384,38 @@ onlyOneFileCanBeAttached: "Oni povas aldoni nur unu dosieron po mesaĝo."
signinRequired: "Bonvolu ensaluti" signinRequired: "Bonvolu ensaluti"
invitations: "Inviti" invitations: "Inviti"
invitationCode: "Invita kodo" invitationCode: "Invita kodo"
available: "Disposabla"
unavailable: "Ne disponebla" unavailable: "Ne disponebla"
usernameInvalidFormat: "La uzantnomo povas enhavi minusklajn kaj majusklajn literojn, numerojn, nur kaj '_'."
tooShort: "Tro mallonga"
tooLong: "Tro longa"
weakPassword: "Malforta pasvorto"
normalPassword: "Normala pasvorto"
strongPassword: "Forta pasvorto"
passwordMatched: "Konforma" passwordMatched: "Konforma"
passwordNotMatched: "Nekonforma" passwordNotMatched: "Nekonforma"
signinWith: "Ensaluti kun {x}"
or: "Aŭ" or: "Aŭ"
language: "Lingvo" language: "Lingvo"
uiLanguage: "Lingvo de fasado" uiLanguage: "Lingvo de fasado"
aboutX: "Pri {x}" aboutX: "Pri {x}"
useOsNativeEmojis: "Oni uzas la emoĵioj de la denaska sistemo" useOsNativeEmojis: "Uzi la emoĵiojn implicitan de la operaciumo"
youHaveNoGroups: "Neniuj grupoj" youHaveNoGroups: "Neniuj grupoj"
noHistory: "Neniom historio"
signinHistory: "Historio de aliroj al la konto"
doing: "Traktado..." doing: "Traktado..."
category: "Kategorio" category: "Kategorio"
tags: "Etikedoj" tags: "Etikedoj"
docSource: "Fonto de la dokumento"
createAccount: "Krei konton" createAccount: "Krei konton"
existingAccount: "Ekzista konto" existingAccount: "Ekzista konto"
regenerate: "Regeneri" regenerate: "Regeneri"
fontSize: "Tipara grando" fontSize: "Tipara grando"
noFollowRequests: "Vi ne havas peto de sekvado" noFollowRequests: "Vi ne havas peto de sekvado"
openImageInNewTab: "Fermi la bildon en nova tablo" openImageInNewTab: "Malfermi la bildojn en nova tablo"
dashboard: "Stirpanelo" dashboard: "Stirpanelo"
local: "Loka" local: "Loka"
remote: "Transa" remote: "Fora"
total: "Entute" total: "Entute"
appearance: "Eksteraĵo" appearance: "Eksteraĵo"
clientSettings: "Agordoj de kliento" clientSettings: "Agordoj de kliento"
@ -402,6 +423,7 @@ accountSettings: "Agordoj de konto"
numberOfDays: "Nombro de tagoj" numberOfDays: "Nombro de tagoj"
hideThisNote: "Kaŝi la noton" hideThisNote: "Kaŝi la noton"
objectStorageBaseUrl: "Baza URL" objectStorageBaseUrl: "Baza URL"
objectStoragePrefix: "Prefix"
objectStorageRegion: "Regiono" objectStorageRegion: "Regiono"
objectStorageUseSSL: "Oni uzas SSL" objectStorageUseSSL: "Oni uzas SSL"
serverLogs: "Servila protokolo" serverLogs: "Servila protokolo"
@ -416,7 +438,7 @@ volume: "Laŭteco"
masterVolume: "Baza laŭteco" masterVolume: "Baza laŭteco"
details: "Detaloj" details: "Detaloj"
chooseEmoji: "Elekti emoĵion" chooseEmoji: "Elekti emoĵion"
recentUsed: "Lastatempaj uzitaj" recentUsed: "Lastatempe uzitaj"
install: "Instali" install: "Instali"
uninstall: "Malinstali" uninstall: "Malinstali"
installedApps: "Instalita programo" installedApps: "Instalita programo"
@ -425,10 +447,12 @@ installedDate: "Dato de instalado"
lastUsedDate: "Lastfoje uzita je" lastUsedDate: "Lastfoje uzita je"
state: "Stato" state: "Stato"
sort: "Ordigado" sort: "Ordigado"
ascendingOrder: "Kreski"
descendingOrder: "Malkreski"
scratchpad: "Malneta redaktilo" scratchpad: "Malneta redaktilo"
output: "Elmeto" output: "Elmeto"
script: "Skripto" script: "Skripto"
disablePagesScript: "Malebligi AiScripto en la paĝoj" disablePagesScript: "Malebligi AiScript en la paĝoj"
deleteAllFiles: "Forviŝi ĉiujn dosierojn" deleteAllFiles: "Forviŝi ĉiujn dosierojn"
deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn dosierojn?" deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn dosierojn?"
removeAllFollowing: "Ĉesi sekvi ĉiujn sekvatojn" removeAllFollowing: "Ĉesi sekvi ĉiujn sekvatojn"
@ -438,7 +462,8 @@ menu: "Menuo"
addItem: "Aldoni novaĵon" addItem: "Aldoni novaĵon"
rooms: "Ĉambro" rooms: "Ĉambro"
deletedNote: "Forviŝita noto" deletedNote: "Forviŝita noto"
invisibleNote: "Malpublika noto" invisibleNote: "Malpublikigita noto"
enableInfiniteScroll: "Ebligi infinitan rulumon"
visibility: "Videbleco" visibility: "Videbleco"
poll: "Balotujo" poll: "Balotujo"
useCw: "Kaŝi enhavo" useCw: "Kaŝi enhavo"
@ -453,16 +478,23 @@ author: "Aŭtoro"
manage: "Administro" manage: "Administro"
plugins: "Kromaĵoj" plugins: "Kromaĵoj"
deck: "Kartaro" deck: "Kartaro"
useFullReactionPicker: "Uzi la tuton de la elektilon de reagoj"
width: "Larĝeco" width: "Larĝeco"
height: "Alteco" height: "Alteco"
large: "Granda"
medium: "Meza" medium: "Meza"
small: "Malgranda" small: "Malgranda"
generateAccessToken: "Generi ĵetonon de aliro"
permission: "Permesoj"
enableAll: "Ebligi ĉiujn"
disableAll: "Malebligi ĉiujn"
notificationType: "Tipo de sciigoj"
edit: "Redakti" edit: "Redakti"
emailServer: "Retpoŝta servilo" emailServer: "Retpoŝta servilo"
email: "Retpoŝto" email: "Retpoŝto"
emailAddress: "Retpoŝta adreso" emailAddress: "Retpoŝta adreso"
smtpConfig: "Agordoj de SMTP servilo" smtpConfig: "Agordoj de SMTP servilo"
smtpHost: "Gastigo" smtpHost: "Transa servilo"
smtpPort: "Pordo" smtpPort: "Pordo"
smtpUser: "Uzantnomo" smtpUser: "Uzantnomo"
smtpPass: "Pasvorto" smtpPass: "Pasvorto"
@ -471,13 +503,20 @@ userSaysSomething: "{name} parolis ion"
makeActive: "Aktivigi" makeActive: "Aktivigi"
display: "Vidi" display: "Vidi"
copy: "Kopii" copy: "Kopii"
metrics: "mezurciferoj"
overview: "Resumo" overview: "Resumo"
logs: "Protokoloj"
delayed: "Prokrasto "
database: "Datumbazo" database: "Datumbazo"
channel: "Kanalo" channel: "Kanalo"
create: "Krei" create: "Krei"
notificationSetting: "Agordoj de sciigoj" notificationSetting: "Agordoj de sciigoj"
useGlobalSetting: "Oni uzas malloka agordo" useGlobalSetting: "Oni uzas malloka agordo"
other: "Aliaj"
regenerateLoginToken: "Regeneri la ĵetonon de aliro"
fileIdOrUrl: "Dosiera identigilo aŭ URL" fileIdOrUrl: "Dosiera identigilo aŭ URL"
chatOpenBehavior: "Konduto por malfermi la fenestron de babilejo"
behavior: "Konduto"
sample: "Ekzemplo" sample: "Ekzemplo"
abuseReports: "Signaloj" abuseReports: "Signaloj"
reportAbuse: "Signalo" reportAbuse: "Signalo"
@ -485,20 +524,21 @@ reportAbuseOf: "Signali kontraŭ {name}'(o)"
send: "Sendi" send: "Sendi"
openInNewTab: "Malfermi en nova langeto" openInNewTab: "Malfermi en nova langeto"
editTheseSettingsMayBreakAccount: "Redakti ĉi tiujn agordojn povas damaĝi vian konton." editTheseSettingsMayBreakAccount: "Redakti ĉi tiujn agordojn povas damaĝi vian konton."
instanceTicker: "Informoj pri la nodo kiu dissendas la noton" instanceTicker: "Nomo de la nodo sendinta notojn"
waitingFor: "Atendado pro {x}"
random: "Hazarde" random: "Hazarde"
system: "Sistemo" system: "Sistemo"
desktop: "Labortablo" desktop: "Labortablo"
createNew: "Krei novan" createNew: "Krei novan"
optional: "Opciaj" optional: "Opciaj"
public: "Publika" public: "Publika"
i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado ĉe {link}." i18nInfo: "Misskey estas tradukata en diversaj lingvoj de volontuloj. Oni povas kontribui ĉe {link}."
accountInfo: "Kontaj Informoj" accountInfo: "Kontaj Informoj"
notesCount: "La nombro de notoj" notesCount: "La nombro de notoj"
repliesCount: "La nombro de respondoj senditaj" repliesCount: "La nombro de respondoj senditaj"
renotesCount: "La nombro de notoj kiujn la uzanto plusendis" renotesCount: "La nombro de notoj plusenditaj de la uzanto"
repliedCount: "La nombro de respondoj ricevitaj" repliedCount: "La nombro de respondoj ricevitaj"
renotedCount: "La nombro de uzantulaj notoj plusenditaj" renotedCount: "La nombro de plusendoj de la notoj skribitaj de la uzanto"
followingCount: "La nombro de sekvatoj" followingCount: "La nombro de sekvatoj"
followersCount: "La nombro de sekvantoj" followersCount: "La nombro de sekvantoj"
sentReactionsCount: "La nombro de la reagoj senditaj" sentReactionsCount: "La nombro de la reagoj senditaj"
@ -512,10 +552,15 @@ noteFavoritesCount: "La nombro de notoj preferataj"
pageLikesCount: "La nombro de paĝoj kiun la uzanto preferas" pageLikesCount: "La nombro de paĝoj kiun la uzanto preferas"
pageLikedCount: "La nombro de uzantoj, kiuj preferas paĝon de ĉi tiu uzanto" pageLikedCount: "La nombro de uzantoj, kiuj preferas paĝon de ĉi tiu uzanto"
contact: "Kontakto" contact: "Kontakto"
useSystemFont: "Uzi la tiparon implicitan de la sistemo"
developer: "Evoluiganto"
makeExplorable: "Videbligi konton sur la paĝo \"Esplori\"" makeExplorable: "Videbligi konton sur la paĝo \"Esplori\""
makeExplorableDescription: "Se vi elŝaltas tiun, via konto ne montros en la paĝo \"Esplori\"."
duplicate: "Duobligi" duplicate: "Duobligi"
left: "Maldekstra" left: "Maldekstra"
center: "Centra" center: "Centra"
wide: "Vasta"
narrow: "Malvasta"
showTitlebar: "Videbligi titolan stangon" showTitlebar: "Videbligi titolan stangon"
clearCache: "Malplenigi staplon" clearCache: "Malplenigi staplon"
onlineUsersCount: "{n} uzanto(j) estas surlinea" onlineUsersCount: "{n} uzanto(j) estas surlinea"
@ -525,9 +570,11 @@ myTheme: "Miaj koloraroj"
backgroundColor: "Fona koloro" backgroundColor: "Fona koloro"
textColor: "Teksto" textColor: "Teksto"
saveAs: "Konservi kiel…" saveAs: "Konservi kiel…"
advanced: "Altnivela"
value: "Valoro" value: "Valoro"
createdAt: "Kreita je" createdAt: "Kreita je"
updatedAt: "Laste ĝisdatigita" updatedAt: "Laste ĝisdatigita"
saveConfirm: "Ĉu vi konservas la ŝanĝon?"
deleteConfirm: "Ĉu certas forviŝi?" deleteConfirm: "Ĉu certas forviŝi?"
closeAccount: "Forigi konton" closeAccount: "Forigi konton"
currentVersion: "Nuna versio" currentVersion: "Nuna versio"
@ -538,9 +585,10 @@ inUse: "Uzata"
editCode: "Redakti kodon" editCode: "Redakti kodon"
emailNotification: "Sciigoj per retpoŝto" emailNotification: "Sciigoj per retpoŝto"
inChannelSearch: "Serĉi en kanalo" inChannelSearch: "Serĉi en kanalo"
useReactionPickerForContextMenu: "Malfermi reago-elektilon per dekstro-klaki" useReactionPickerForContextMenu: "Dekstre-klaki por malfermi la elektilon de reagoj"
typingUsers: "{users} nun skribas…" typingUsers: "{users} nun skribas…"
clear: "Vakigi" clear: "Vakigi"
markAllAsRead: "Marki ĉiujn kiel legito"
goBack: "Reiri antaŭ" goBack: "Reiri antaŭ"
addDescription: "Priskribi" addDescription: "Priskribi"
info: "Informoj" info: "Informoj"
@ -559,7 +607,7 @@ memo: "Memorigilo"
high: "Alta" high: "Alta"
middle: "Meza" middle: "Meza"
low: "Malalta" low: "Malalta"
customCss: "Uzantula CSS" customCss: "Personecigita CSS"
global: "Malloka" global: "Malloka"
sent: "Sendi" sent: "Sendi"
received: "Ricevita" received: "Ricevita"
@ -569,10 +617,27 @@ troubleshooting: "Problemsolvi"
learnMore: "Lernu pli" learnMore: "Lernu pli"
translate: "Traduki" translate: "Traduki"
translatedFrom: "Tradukita el {x}" translatedFrom: "Tradukita el {x}"
itsOn: "Ŝaltita"
unread: "Nelegita"
controlPanel: "Ŝaltpodio" controlPanel: "Ŝaltpodio"
classic: "Klasika" classic: "Klasika"
ffVisibility: "Videbleco pri viaj sekvataro/sekvantaro\n"
ffVisibilityDescription: "Agordi la videblecon kiu povas vidi tiujn kiujn vi sekvas kaj tiujn kiuj sekvas vin."
continueThread: "Vidi pli mesaĝarojn"
incorrectPassword: "Nevalida pasvorto"
_emailUnavailable:
used: "La retpoŝto jam estas uzita."
format: "Nevalida formato."
disposable: "Dumtempa retpoŝto ne estas uzebla."
smtp: "Tiu retpoŝta servilo ne respondas"
_ffVisibility:
public: "Publika"
followers: "Afiŝi nur al sekvantoj"
private: "Malpublikigita"
_signup: _signup:
emailAddressInfo: "Entajpu vian retpoŝton" emailAddressInfo: "Entajpu vian retpoŝton"
_accountDelete:
accountDelete: "Forigi konton"
_ad: _ad:
back: "Nuligi" back: "Nuligi"
_forgotPassword: _forgotPassword:
@ -598,7 +663,7 @@ _aboutMisskey:
contributors: "Precipaj kontribuantoj" contributors: "Precipaj kontribuantoj"
allContributors: "Ĉiuj kontribuantoj" allContributors: "Ĉiuj kontribuantoj"
source: "Fontkodo" source: "Fontkodo"
translation: "Traduki Misskey'on" translation: "Traduki Misskey"
patrons: "Mecenatoj" patrons: "Mecenatoj"
_mfm: _mfm:
dummy: "Misskey evoluigas la mondon de Fediverso" dummy: "Misskey evoluigas la mondon de Fediverso"
@ -614,19 +679,21 @@ _mfm:
inlineMath: "Formulo (en linio)" inlineMath: "Formulo (en linio)"
blockMath: "Formulo (bloko)" blockMath: "Formulo (bloko)"
quote: "Citi" quote: "Citi"
emoji: "Personecigitaj emoĵioj"
search: "Serĉi" search: "Serĉi"
flip: "Inversa" flip: "Inversa"
x2: "Granda" x2: "Granda"
x3: "Grandega" x3: "Grandega"
x4: "Pli grandega" x4: "Pli grandega"
font: "Presliteraro" font: "Presliteraro"
rotate: "Orientiĝo"
_reversi: _reversi:
total: "Entute" total: "Entute"
_instanceTicker: _instanceTicker:
none: "Ne montri" none: "Ne montri"
remote: "Montri al transaj uzantoj" remote: "Montri al foraj uzantoj"
always: "Ĉiam montri" always: "Ĉiam montri"
_serverDisconnectedBehavior:
reload: "Aŭtomate reŝargi"
_channel: _channel:
create: "Krei kanalon" create: "Krei kanalon"
edit: "Redakti kanalon" edit: "Redakti kanalon"
@ -640,13 +707,14 @@ _menuDisplay:
hide: "Kaŝi" hide: "Kaŝi"
_wordMute: _wordMute:
muteWords: "Silentigitaj vortoj" muteWords: "Silentigitaj vortoj"
soft: "En kliento" soft: "Per la kliento"
hard: "En servilo" hard: "Per la servilo"
mutedNotes: "Silentigitaj notoj" mutedNotes: "Silentigitaj notoj"
_theme: _theme:
manage: "Administri kolorarojn" manage: "Administri kolorarojn"
code: "Kolorara kodo" code: "Kolorara kodo"
description: "Priskribo" description: "Priskribo"
defaultValue: "Implicitaĵa valoro"
color: "Koloro" color: "Koloro"
darken: "Malbrileco" darken: "Malbrileco"
lighten: "Brileco" lighten: "Brileco"
@ -657,7 +725,7 @@ _theme:
hashtag: "Kradvorto" hashtag: "Kradvorto"
mention: "Mencioj" mention: "Mencioj"
mentionMe: "Mencio al vi" mentionMe: "Mencio al vi"
renote: "Noto plusendita" renote: "Plusendita"
buttonBg: "Fono de butono" buttonBg: "Fono de butono"
driveFolderBg: "Fono de dosierujo de la disko" driveFolderBg: "Fono de dosierujo de la disko"
messageBg: "Fono de retbabilejo" messageBg: "Fono de retbabilejo"
@ -688,7 +756,7 @@ _tutorial:
title: "Uzado de Misskey" title: "Uzado de Misskey"
step1_1: "Bonvenon." step1_1: "Bonvenon."
step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}." step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}."
step7_3: "Do, bonvolu amuziĝi Misskey'on🚀" step7_3: "Do, bonvolu amuziĝi sur Misskey🚀"
_2fa: _2fa:
registerKey: "Nove registri ŝlosilon" registerKey: "Nove registri ŝlosilon"
_permissions: _permissions:
@ -732,10 +800,10 @@ _widgets:
federation: "Federaĵo" federation: "Federaĵo"
slideshow: "Bildoprezento" slideshow: "Bildoprezento"
button: "Butono" button: "Butono"
onlineUsers: "Surkonektita uzanto" onlineUsers: "Surkonektitaj uzantoj"
aichan: "Ai" aichan: "Ai"
_cw: _cw:
show: "Vidu pli" show: "Vidi pli"
files: "{count} dosiero(j)" files: "{count} dosiero(j)"
_poll: _poll:
choiceN: "Balotilo {n}" choiceN: "Balotilo {n}"
@ -747,15 +815,15 @@ _poll:
closed: "Oni jam balotis ĝin" closed: "Oni jam balotis ĝin"
_visibility: _visibility:
public: "Publika" public: "Publika"
publicDescription: "Via noto estos videbla de ĉiuj uzantoj" publicDescription: "Afiŝi al ĉiuj en la Fediverso"
home: "Hejma" home: "Hejma"
homeDescription: "Dissendi nur sur hejma templinio" homeDescription: "Dissendi nur sur hejma templinio"
followers: "Nur al sekvantoj" followers: "Nur al sekvantoj"
followersDescription: "Publiki nur al viaj sekvantoj" followersDescription: "Afiŝi nur al sekvantoj"
specified: "Rekte" specified: "Rekte"
specifiedDescription: "Montri nur al specifaj uzantoj" specifiedDescription: "Afiŝi nur al specifaj uzantoj"
localOnly: "Nur loka" localOnly: "Nur loka"
localOnlyDescription: "Ne montri al transaj uzantoj" localOnlyDescription: "Ne afiŝi al foraj uzantoj"
_postForm: _postForm:
replyPlaceholder: "Respondi la noton…" replyPlaceholder: "Respondi la noton…"
quotePlaceholder: "Citi la noton…" quotePlaceholder: "Citi la noton…"
@ -789,7 +857,7 @@ _rooms:
translate: "Movi" translate: "Movi"
chooseImage: "Elekti bildon" chooseImage: "Elekti bildon"
_roomType: _roomType:
default: "Defaŭlta" default: "Implicitaĵo"
_furnitures: _furnitures:
bed: "Lito" bed: "Lito"
low-table: "Malaltotablo" low-table: "Malaltotablo"
@ -835,18 +903,22 @@ _pages:
textInput: "Enmeto el teksto" textInput: "Enmeto el teksto"
_textInput: _textInput:
text: "Titolo" text: "Titolo"
default: "Implicitaĵa valoro"
textareaInput: "Enmeto el teksto en multaj linioj" textareaInput: "Enmeto el teksto en multaj linioj"
_textareaInput: _textareaInput:
text: "Titolo" text: "Titolo"
default: "Implicitaĵa valoro"
numberInput: "Nombra enmeto" numberInput: "Nombra enmeto"
_numberInput: _numberInput:
text: "Titolo" text: "Titolo"
default: "Implicitaĵa valoro"
_canvas: _canvas:
id: "Kanvasa identigilo" id: "Kanvasa identigilo"
_note: _note:
id: "Identigilo de noto" id: "Identigilo de noto"
_switch: _switch:
text: "Titolo" text: "Titolo"
default: "Implicitaĵa valoro"
_counter: _counter:
text: "Titolo" text: "Titolo"
_button: _button:
@ -856,6 +928,7 @@ _pages:
event: "Nomo de la evento" event: "Nomo de la evento"
_radioButton: _radioButton:
title: "Titolo" title: "Titolo"
default: "Implicitaĵa valoro"
script: script:
categories: categories:
text: "Manipulo de teksto" text: "Manipulo de teksto"
@ -874,6 +947,7 @@ _pages:
arg1: "Teksto" arg1: "Teksto"
_join: _join:
arg1: "Listoj" arg1: "Listoj"
arg2: "apartigilo"
_randomPick: _randomPick:
arg1: "Listoj" arg1: "Listoj"
_dailyRandomPick: _dailyRandomPick:
@ -904,6 +978,7 @@ _pages:
_relayStatus: _relayStatus:
requesting: "Atendado de aprobon" requesting: "Atendado de aprobon"
accepted: "Konfirmita" accepted: "Konfirmita"
rejected: "Malakceptita"
_notification: _notification:
fileUploaded: "La dosiero sukcese alŝutiĝis." fileUploaded: "La dosiero sukcese alŝutiĝis."
youGotMention: "{name} mencis" youGotMention: "{name} mencis"
@ -918,13 +993,13 @@ _notification:
yourFollowRequestAccepted: "Via peto de sekvado estis akceptita." yourFollowRequestAccepted: "Via peto de sekvado estis akceptita."
_types: _types:
all: "Ĉio" all: "Ĉio"
follow: "Nova sekvatoj" follow: "Novaj sekvatoj"
mention: "Mencioj" mention: "Mencioj"
reply: "Respondoj" reply: "Respondoj"
renote: "Notoj plusenditaj" renote: "Plusendoj"
quote: "Citi" quote: "Citi"
reaction: "Reagoj" reaction: "Reagoj"
receiveFollowRequest: "Ricevita peton de sekvado" receiveFollowRequest: "Ricevi peton de sekvado"
followRequestAccepted: "Akceptita peto por sekvado" followRequestAccepted: "Akceptita peto por sekvado"
_deck: _deck:
profile: "Agordaro" profile: "Agordaro"

View file

@ -737,6 +737,7 @@ pubSub: "Cuentas Pub/Sub"
lastCommunication: "Última comunicación" lastCommunication: "Última comunicación"
resolved: "Resuelto" resolved: "Resuelto"
unresolved: "Sin resolver" unresolved: "Sin resolver"
controlPanel: "Panel de control"
_accountDelete: _accountDelete:
accountDelete: "Eliminar Cuenta" accountDelete: "Eliminar Cuenta"
_ad: _ad:
@ -767,6 +768,7 @@ _mfm:
flip: "Echar de un capirotazo" flip: "Echar de un capirotazo"
flipDescription: "Voltea el contenido hacia arriba / abajo o hacia la izquierda / derecha." flipDescription: "Voltea el contenido hacia arriba / abajo o hacia la izquierda / derecha."
font: "Fuente" font: "Fuente"
rotate: "Rotar"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Configuración del juego" gameSettings: "Configuración del juego"

View file

@ -919,6 +919,7 @@ _mfm:
rainbowDescription: "Permet d'afficher le contenu en couleurs arc-en-ciel." rainbowDescription: "Permet d'afficher le contenu en couleurs arc-en-ciel."
sparkle: "Paillettes" sparkle: "Paillettes"
sparkleDescription: "Ajoute un effet scintillant au contenu." sparkleDescription: "Ajoute un effet scintillant au contenu."
rotate: "Pivoter"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Réglages de la partie" gameSettings: "Réglages de la partie"

View file

@ -806,6 +806,10 @@ muteThread: "Bisukan thread"
unmuteThread: "Suarakan thread" unmuteThread: "Suarakan thread"
ffVisibility: "Visibilitas Mengikuti/Pengikut" ffVisibility: "Visibilitas Mengikuti/Pengikut"
ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu ikuti." ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu ikuti."
continueThread: "Lihat lanjutan thread"
deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?"
incorrectPassword: "Kata sandi salah."
voteConfirm: "Konfirmasi suara kamu untuk ({choice})"
_emailUnavailable: _emailUnavailable:
used: "Alamat surel ini telah digunakan" used: "Alamat surel ini telah digunakan"
format: "Format tidak valid." format: "Format tidak valid."
@ -929,6 +933,8 @@ _mfm:
rainbowDescription: "Membuat konten muncul dalam warna pelangi." rainbowDescription: "Membuat konten muncul dalam warna pelangi."
sparkle: "Kelap-kelip" sparkle: "Kelap-kelip"
sparkleDescription: "Memberikan konten efek partikel kelap-kelip." sparkleDescription: "Memberikan konten efek partikel kelap-kelip."
rotate: "Putar"
rotateDescription: "Putar konten sesuai sudut yang ditentukan."
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Pengaturan permainan" gameSettings: "Pengaturan permainan"

View file

@ -806,6 +806,7 @@ _mfm:
font: "Tipo di carattere" font: "Tipo di carattere"
fontDescription: "Puoi scegliere il tipo di carattere per il contenuto." fontDescription: "Puoi scegliere il tipo di carattere per il contenuto."
rainbow: "Arcobaleno" rainbow: "Arcobaleno"
rotate: "Ruota"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Impostazioni di gioco" gameSettings: "Impostazioni di gioco"

View file

@ -792,6 +792,7 @@ pubSub: "Pub/Subのアカウント"
lastCommunication: "直近の通信" lastCommunication: "直近の通信"
resolved: "解決済み" resolved: "解決済み"
unresolved: "未解決" unresolved: "未解決"
breakFollow: "フォロワーを解除"
itsOn: "オンになっています" itsOn: "オンになっています"
itsOff: "オフになっています" itsOff: "オフになっています"
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする" emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"

View file

@ -700,6 +700,7 @@ _mfm:
spin: "アニメーション(回転)" spin: "アニメーション(回転)"
blur: "ぼかし" blur: "ぼかし"
font: "フォント" font: "フォント"
rotate: "回転"
_reversi: _reversi:
reversi: "リバーシ" reversi: "リバーシ"
gameSettings: "対局の設定" gameSettings: "対局の設定"

View file

@ -899,6 +899,7 @@ _mfm:
rainbowDescription: "내용을 무지개로 표시합니다." rainbowDescription: "내용을 무지개로 표시합니다."
sparkle: "반짝반짝" sparkle: "반짝반짝"
sparkleDescription: "반짝이는 파티클 효과를 추가합니다." sparkleDescription: "반짝이는 파티클 효과를 추가합니다."
rotate: "회전"
_reversi: _reversi:
reversi: "리버시" reversi: "리버시"
gameSettings: "대국 설정" gameSettings: "대국 설정"

View file

@ -1,5 +1,193 @@
--- ---
_lang_: "Nederlands" _lang_: "Nederlands"
headlineMisskey: "Netwerk verbonden door notities" headlineMisskey: "Netwerk verbonden door notities"
introMisskey: "Welkom! Misskey is een open source, gedecentraliseerde microblogdienst.\nMaak \"notities\" om je gedachten te delen met iedereen om je heen. 📡\nMet \"reacties\" kun je ook snel je mening geven over berichten van anderen. 👍\nLaten we een nieuwe wereld verkennen! 🚀"
monthAndDay: "{day} {month}"
search: "Zoeken"
notifications: "Meldingen"
username: "Gebruikersnaam"
password: "Wachtwoord"
forgotPassword: "Wachtwoord vergeten"
fetchingAsApObject: "Ophalen vanuit de Fediverse"
ok: "Ok"
gotIt: "Begrepen"
cancel: "Annuleren"
enterUsername: "Voer een gebruikersnaam in"
renotedBy: "Hergedeeld door {user}"
noNotes: "Geen notities"
noNotifications: "Geen meldingen"
instance: "Server"
settings: "Instellingen"
basicSettings: "Basisinstellingen"
otherSettings: "Overige instellingen"
openInWindow: "In een venster openen"
profile: "Profiel"
timeline: "Tijdlijn"
noAccountDescription: "Deze gebruiker heeft nog geen bio geschreven"
login: "Inloggen"
loggingIn: "Aan het inloggen"
logout: "Afmelden"
signup: "Registreren"
uploading: "Bezig met uploaden"
save: "Opslaan"
users: "Gebruikers"
addUser: "Toevoegen gebruiker"
favorite: "Favorieten"
favorites: "Toevoegen aan favorieten"
unfavorite: "Verwijderen uit favorieten"
favorited: "Toegevoegd aan favorieten."
alreadyFavorited: "Al toegevoegd aan favorieten"
cantFavorite: "Kon niet toevoegen aan favorieten"
pin: "Vastmaken aan profielpagina"
unpin: "Losmaken van profielpagina"
copyContent: "Kopiëren inhoud"
copyLink: "Kopiëren link"
delete: "Verwijderen"
deleteAndEdit: "Verwijderen en bewerken"
deleteAndEditConfirm: "Weet je zeker dat je deze notitie wilt verwijderen en dan bewerken? Je verliest alle reacties, herdelingen en antwoorden erop."
addToList: "Aan lijst toevoegen"
sendMessage: "Verstuur bericht"
copyUsername: "Kopiëren gebruikersnaam "
searchUser: "Zoeken een gebruiker"
reply: "Antwoord"
loadMore: "Laad meer"
showMore: "Toon meer"
youGotNewFollower: "volgde jou"
receiveFollowRequest: "Volgverzoek ontvangen"
followRequestAccepted: "Volgverzoek geaccepteerd"
mention: "Vermelding"
mentions: "Vermeldingen"
directNotes: "Directe notities"
importAndExport: "Import / export"
import: "Import"
export: "Export"
files: "Bestanden"
download: "Downloaden"
driveFileDeleteConfirm: "Weet je zeker dat je het bestand \"{name}\" wilt verwijderen? Notities met dit bestand als bijlage worden ook verwijderd."
unfollowConfirm: "Weet je zeker dat je {name} wilt ontvolgen?"
exportRequested: "Je hebt een export aangevraagd. Dit kan een tijdje duren. Het wordt toegevoegd aan je Drive zodra het is voltooid."
importRequested: "Je hebt een import aangevraagd. Dit kan even duren."
lists: "Lijsten"
noLists: "Je hebt geen lijsten"
note: "Notitie"
notes: "Notities"
following: "Volgend"
followers: "Volgers"
followsYou: "Volgt jou"
createList: "Creëer lijst"
manageLists: "Beheren lijsten"
error: "Fout"
somethingHappened: "Er is iets misgegaan."
retry: "Probeer opnieuw"
pageLoadError: "Pagina laden mislukt"
pageLoadErrorDescription: "Dit wordt normaal gesproken veroorzaakt door netwerkfouten of door de cache van de browser. Probeer de cache te wissen en probeer het na een tijdje wachten opnieuw."
serverIsDead: "De server reageert niet. Wacht even en probeer het opnieuw."
youShouldUpgradeClient: "Werk je client bij om deze pagina te zien."
enterListName: "Voer de naam van de lijst in"
privacy: "Privacy"
makeFollowManuallyApprove: "Volgverzoeken vergen een goedkeuring"
defaultNoteVisibility: "Standaard zichtbaarheid"
follow: "Volgen"
followRequest: "Verzoek om te mogen volgen"
followRequests: "Volgverzoeken"
unfollow: "Ontvolgen"
followRequestPending: "Wachten op goedkeuring volgverzoek"
enterEmoji: "Voer een emoji in"
renote: "Herdelen"
unrenote: "Stop herdelen"
renoted: "Herdeeld"
cantRenote: "Dit bericht kan niet worden herdeeld"
cantReRenote: "Een herdeling kan niet worden herdeeld"
quote: "Quote"
pinnedNote: "Vastgemaakte notitie"
pinned: "Vastmaken aan profielpagina"
you: "Jij"
clickToShow: "Klik om te bekijken"
sensitive: "NSFW"
add: "Toevoegen"
reaction: "Reacties"
reactionSettingDescription: "Configureer welke reacties je wilt weergeven in de reactiekiezer."
reactionSettingDescription2: "Sleep om opnieuw te ordenen, Klik om te verwijderen, Druk op \"+\" om toe te voegen"
rememberNoteVisibility: "Vergeet niet de notitie zichtbaarheidsinstellingen"
attachCancel: "Verwijder bijlage"
markAsSensitive: "Markeren als NSFW"
unmarkAsSensitive: "Geen NSFW"
enterFileName: "Invoeren bestandsnaam"
mute: "Dempen"
unmute: "Stop dempen"
block: "Blokkeren"
unblock: "Deblokkeren"
suspend: "Opschorten"
unsuspend: "Heractiveren"
blockConfirm: "Weet je zeker dat je dit account wil blokkeren?"
instances: "Server"
remove: "Verwijderen"
nsfw: "NSFW"
pinnedNotes: "Vastgemaakte notitie"
userList: "Lijsten"
smtpUser: "Gebruikersnaam"
smtpPass: "Wachtwoord"
user: "Gebruikers"
muteThread: "Discussies dempen " muteThread: "Discussies dempen "
unmuteThread: "Dempen van discussie ongedaan maken" unmuteThread: "Dempen van discussie ongedaan maken"
_email:
_follow:
title: "volgde jou"
_mfm:
mention: "Vermelding"
quote: "Quote"
search: "Zoeken"
_theme:
keys:
mention: "Vermelding"
renote: "Herdelen"
_sfx:
note: "Notities"
notification: "Meldingen"
_widgets:
notifications: "Meldingen"
timeline: "Tijdlijn"
_cw:
show: "Laad meer"
_visibility:
followers: "Volgers"
_profile:
username: "Gebruikersnaam"
_exportOrImport:
followingList: "Volgend"
muteList: "Dempen"
blockingList: "Blokkeren"
userLists: "Lijsten"
_pages:
script:
categories:
list: "Lijsten"
blocks:
_join:
arg1: "Lijsten"
_randomPick:
arg1: "Lijsten"
_dailyRandomPick:
arg1: "Lijsten"
_seedRandomPick:
arg2: "Lijsten"
_pick:
arg1: "Lijsten"
_listLen:
arg1: "Lijsten"
types:
array: "Lijsten"
_notification:
youWereFollowed: "volgde jou"
_types:
follow: "Volgend"
mention: "Vermelding"
renote: "Herdelen"
quote: "Quote"
reaction: "Reacties"
_deck:
_columns:
notifications: "Meldingen"
tl: "Tijdlijn"
list: "Lijsten"
mentions: "Vermeldingen"

View file

@ -815,6 +815,7 @@ _mfm:
blur: "Rozmycie" blur: "Rozmycie"
font: "Czcionka" font: "Czcionka"
fontDescription: "Wybiera czcionkę do wyświetlania treści." fontDescription: "Wybiera czcionkę do wyświetlania treści."
rotate: "Obróć"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Ustawienia gry" gameSettings: "Ustawienia gry"

View file

@ -1,22 +1,33 @@
--- ---
_lang_: "Português" _lang_: "Português"
headlineMisskey: "Rede conectada por notas"
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Pesquisar" search: "Pesquisar"
notifications: "Notificações" notifications: "Notificações"
username: "Nome de usuário" username: "Nome de usuário"
password: "Senha" password: "Senha"
forgotPassword: "Esqueci a senha"
fetchingAsApObject: "Buscando no Fediverso"
ok: "OK" ok: "OK"
gotIt: "Entendi" gotIt: "Entendi"
cancel: "Cancelar" cancel: "Cancelar"
enterUsername: "Digite o nome de usuário" enterUsername: "Digite o nome de usuário"
renotedBy: "Repostado por {user}" renotedBy: "Repostado por {user}"
noNotes: "Sem posts" noNotes: "Sem posts"
noNotifications: "Sem notificações"
instance: "Instância"
settings: "Configurações" settings: "Configurações"
basicSettings: "Configurações básicas" basicSettings: "Configurações básicas"
otherSettings: "Outras configurações" otherSettings: "Outras configurações"
openInWindow: "Abrir numa janela"
profile: "Perfil" profile: "Perfil"
timeline: "Timeline" timeline: "Timeline"
login: "Iniciar sessão"
loggingIn: "Iniciando sessão…"
logout: "Sair" logout: "Sair"
signup: "Registrar-se"
uploading: "Enviando…"
save: "Guardar"
users: "Usuários" users: "Usuários"
favorite: "Favoritar" favorite: "Favoritar"
favorites: "Favoritar" favorites: "Favoritar"

View file

@ -922,6 +922,7 @@ _mfm:
rainbowDescription: "Заставлять содержимое отображаться в цветах радуги." rainbowDescription: "Заставлять содержимое отображаться в цветах радуги."
sparkle: "Блеск" sparkle: "Блеск"
sparkleDescription: "Добавьте эффект искрящихся частиц." sparkleDescription: "Добавьте эффект искрящихся частиц."
rotate: "Повернуть"
_reversi: _reversi:
reversi: "Реверси" reversi: "Реверси"
gameSettings: "Настройки игры" gameSettings: "Настройки игры"

View file

@ -771,6 +771,7 @@ _mfm:
blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким, якщо навести на нього вказівник миші." blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким, якщо навести на нього вказівник миші."
font: "Шрифт" font: "Шрифт"
fontDescription: "Встановлює шрифт для контенту." fontDescription: "Встановлює шрифт для контенту."
rotate: "Обертати"
_reversi: _reversi:
reversi: "Реверсі" reversi: "Реверсі"
gameSettings: "Налаштування гри" gameSettings: "Налаштування гри"

View file

@ -792,6 +792,7 @@ pubSub: "Pub/Sub账户"
lastCommunication: "最近通信" lastCommunication: "最近通信"
resolved: "已解决" resolved: "已解决"
unresolved: "未解决" unresolved: "未解决"
breakFollow: "移除关注者"
itsOn: "已开启" itsOn: "已开启"
itsOff: "已关闭" itsOff: "已关闭"
emailRequiredForSignup: "注册账户需要电子邮件地址" emailRequiredForSignup: "注册账户需要电子邮件地址"
@ -808,6 +809,8 @@ ffVisibility: "连接的可见范围"
ffVisibilityDescription: "您可以设置您的关注/关注者信息的公开范围" ffVisibilityDescription: "您可以设置您的关注/关注者信息的公开范围"
continueThread: "查看更多帖子" continueThread: "查看更多帖子"
deleteAccountConfirm: "将要删除账户。是否确认?" deleteAccountConfirm: "将要删除账户。是否确认?"
incorrectPassword: "密码错误"
voteConfirm: "确定投给“{choice}” "
_emailUnavailable: _emailUnavailable:
used: "已经被使用过" used: "已经被使用过"
format: "无效的格式" format: "无效的格式"
@ -931,6 +934,8 @@ _mfm:
rainbowDescription: "用彩虹色来显示内容。" rainbowDescription: "用彩虹色来显示内容。"
sparkle: "闪光" sparkle: "闪光"
sparkleDescription: "添加发光粒子效果。" sparkleDescription: "添加发光粒子效果。"
rotate: "旋转"
rotateDescription: "旋转指定的角度。"
_reversi: _reversi:
reversi: "黑白棋" reversi: "黑白棋"
gameSettings: "对局设置" gameSettings: "对局设置"

View file

@ -840,6 +840,7 @@ _mfm:
blur: "模糊" blur: "模糊"
font: "字型" font: "字型"
fontDescription: "您可以設定顯示內容的字型" fontDescription: "您可以設定顯示內容的字型"
rotate: "旋轉"
_reversi: _reversi:
reversi: "黑白棋" reversi: "黑白棋"
gameSettings: "對弈設定" gameSettings: "對弈設定"

View file

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "12.97.1", "version": "12.98.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -46,7 +46,7 @@
"@types/fluent-ffmpeg": "2.1.17", "@types/fluent-ffmpeg": "2.1.17",
"@typescript-eslint/parser": "5.4.0", "@typescript-eslint/parser": "5.4.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "9.0.0", "cypress": "9.1.0",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"typescript": "4.5.2" "typescript": "4.5.2"
} }

View file

@ -19,6 +19,7 @@ export type FileInfo = {
}; };
width?: number; width?: number;
height?: number; height?: number;
orientation?: number;
blurhash?: string; blurhash?: string;
warnings: string[]; warnings: string[];
}; };
@ -47,6 +48,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
// image dimensions // image dimensions
let width: number | undefined; let width: number | undefined;
let height: number | undefined; let height: number | undefined;
let orientation: number | undefined;
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) { if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
const imageSize = await detectImageSize(path).catch(e => { const imageSize = await detectImageSize(path).catch(e => {
@ -61,6 +63,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
} else if (imageSize.wUnits === 'px') { } else if (imageSize.wUnits === 'px') {
width = imageSize.width; width = imageSize.width;
height = imageSize.height; height = imageSize.height;
orientation = imageSize.orientation;
// 制限を超えている画像は octet-stream にする // 制限を超えている画像は octet-stream にする
if (imageSize.width > 16383 || imageSize.height > 16383) { if (imageSize.width > 16383 || imageSize.height > 16383) {
@ -87,6 +90,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
type, type,
width, width,
height, height,
orientation,
blurhash, blurhash,
warnings, warnings,
}; };
@ -163,6 +167,7 @@ async function detectImageSize(path: string): Promise<{
height: number; height: number;
wUnits: string; wUnits: string;
hUnits: string; hUnits: string;
orientation?: number;
}> { }> {
const readable = fs.createReadStream(path); const readable = fs.createReadStream(path);
const imageSize = await probeImageSize(readable); const imageSize = await probeImageSize(readable);

View file

@ -77,7 +77,7 @@ export class DriveFile {
default: {}, default: {},
comment: 'The any properties of the DriveFile. For example, it includes image width/height.' comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
}) })
public properties: { width?: number; height?: number; avgColor?: string }; public properties: { width?: number; height?: number; orientation?: number; avgColor?: string };
@Index() @Index()
@Column('boolean') @Column('boolean')

View file

@ -28,6 +28,19 @@ export class DriveFileRepository extends Repository<DriveFile> {
); );
} }
public getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
const properties = JSON.parse(JSON.stringify(file.properties));
if (file.properties.orientation >= 5) {
[properties.width, properties.height] = [properties.height, properties.width];
}
properties.orientation = undefined;
return properties;
}
return file.properties;
}
public getPublicUrl(file: DriveFile, thumbnail = false, meta?: Meta): string | null { public getPublicUrl(file: DriveFile, thumbnail = false, meta?: Meta): string | null {
// リモートかつメディアプロキシ // リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && config.mediaProxy != null) { if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
@ -122,7 +135,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
size: file.size, size: file.size,
isSensitive: file.isSensitive, isSensitive: file.isSensitive,
blurhash: file.blurhash, blurhash: file.blurhash,
properties: file.properties, properties: opts.self ? file.properties : this.getPublicProperties(file),
url: opts.self ? file.url : this.getPublicUrl(file, false, meta), url: opts.self ? file.url : this.getPublicUrl(file, false, meta),
thumbnailUrl: this.getPublicUrl(file, true, meta), thumbnailUrl: this.getPublicUrl(file, true, meta),
comment: file.comment, comment: file.comment,
@ -202,6 +215,11 @@ export const packedDriveFileSchema = {
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
example: 720 example: 720
}, },
orientation: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 8
},
avgColor: { avgColor: {
type: 'string' as const, type: 'string' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,

View file

@ -189,12 +189,12 @@ export class UserRepository extends Repository<User> {
const followingCount = profile == null ? null : const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount : (profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount :
(profile.ffVisibility === 'followers') && (relation!.isFollowing) ? user.followingCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null; null;
const followersCount = profile == null ? null : const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount : (profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount :
(profile.ffVisibility === 'followers') && (relation!.isFollowing) ? user.followersCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null; null;
const falsy = opts.detail ? false : undefined; const falsy = opts.detail ? false : undefined;

View file

@ -1,8 +1,9 @@
import { IRemoteUser } from '@/models/entities/user'; import { IRemoteUser } from '@/models/entities/user';
import reject from '@/services/following/requests/reject'; import { remoteReject } from '@/services/following/reject';
import { IFollow } from '../../type'; import { IFollow } from '../../type';
import DbResolver from '../../db-resolver'; import DbResolver from '../../db-resolver';
import { relayRejected } from '@/services/relay'; import { relayRejected } from '@/services/relay';
import { Users } from '@/models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => { export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
@ -14,7 +15,7 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<string> =>
return `skip: follower not found`; return `skip: follower not found`;
} }
if (follower.host != null) { if (!Users.isLocalUser(follower)) {
return `skip: follower is not a local user`; return `skip: follower is not a local user`;
} }
@ -24,6 +25,6 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<string> =>
return await relayRejected(match[1]); return await relayRejected(match[1]);
} }
await reject(actor, follower); await remoteReject(actor, follower);
return `ok`; return `ok`;
}; };

View file

@ -0,0 +1,27 @@
import unfollow from '@/services/following/delete';
import cancelRequest from '@/services/following/requests/cancel';
import {IAccept} from '../../type';
import { IRemoteUser } from '@/models/entities/user';
import { Followings } from '@/models/index';
import DbResolver from '../../db-resolver';
export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
const dbResolver = new DbResolver();
const follower = await dbResolver.getUserFromApId(activity.object);
if (follower == null) {
return `skip: follower not found`;
}
const following = await Followings.findOne({
followerId: follower.id,
followeeId: actor.id
});
if (following) {
await unfollow(follower, actor);
return `ok: unfollowed`;
}
return `skip: フォローされていない`;
};

View file

@ -1,8 +1,9 @@
import { IRemoteUser } from '@/models/entities/user'; import { IRemoteUser } from '@/models/entities/user';
import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType } from '../../type'; import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type';
import unfollow from './follow'; import unfollow from './follow';
import unblock from './block'; import unblock from './block';
import undoLike from './like'; import undoLike from './like';
import undoAccept from './accept';
import { undoAnnounce } from './announce'; import { undoAnnounce } from './announce';
import Resolver from '../../resolver'; import Resolver from '../../resolver';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
@ -29,6 +30,7 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => {
if (isBlock(object)) return await unblock(actor, object); if (isBlock(object)) return await unblock(actor, object);
if (isLike(object)) return await undoLike(actor, object); if (isLike(object)) return await undoLike(actor, object);
if (isAnnounce(object)) return await undoAnnounce(actor, object); if (isAnnounce(object)) return await undoAnnounce(actor, object);
if (isAccept(object)) return await undoAccept(actor, object);
return `skip: unknown object type ${getApType(object)}`; return `skip: unknown object type ${getApType(object)}`;
}; };

View file

@ -0,0 +1,82 @@
import $ from 'cafy';
import { ID } from '@/misc/cafy-id';
import * as ms from 'ms';
import deleteFollowing from '@/services/following/delete';
import define from '../../define';
import { ApiError } from '../../error';
import { getUser } from '../../common/getters';
import { Followings, Users } from '@/models/index';
export const meta = {
tags: ['following', 'users'],
limit: {
duration: ms('1hour'),
max: 100
},
requireCredential: true as const,
kind: 'write:following',
params: {
userId: {
validator: $.type(ID),
}
},
errors: {
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8'
},
followerIsYourself: {
message: 'Follower is yourself.',
code: 'FOLLOWER_IS_YOURSELF',
id: '07dc03b9-03da-422d-885b-438313707662'
},
notFollowing: {
message: 'The other use is not following you.',
code: 'NOT_FOLLOWING',
id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09'
},
},
res: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User'
}
};
export default define(meta, async (ps, user) => {
const followee = user;
// Check if the follower is yourself
if (user.id === ps.userId) {
throw new ApiError(meta.errors.followerIsYourself);
}
// Get follower
const follower = await getUser(ps.userId).catch(e => {
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw e;
});
// Check not following
const exist = await Followings.findOne({
followerId: follower.id,
followeeId: followee.id
});
if (exist == null) {
throw new ApiError(meta.errors.notFollowing);
}
await deleteFollowing(follower, followee);
return await Users.pack(followee.id, user);
});

View file

@ -1,6 +1,6 @@
import $ from 'cafy'; import $ from 'cafy';
import { ID } from '@/misc/cafy-id'; import { ID } from '@/misc/cafy-id';
import rejectFollowRequest from '@/services/following/requests/reject'; import { rejectFollowRequest } from '@/services/following/reject';
import define from '../../../define'; import define from '../../../define';
import { ApiError } from '../../../error'; import { ApiError } from '../../../error';
import { getUser } from '../../../common/getters'; import { getUser } from '../../../common/getters';

View file

@ -372,12 +372,16 @@ export default async function(
const properties: { const properties: {
width?: number; width?: number;
height?: number; height?: number;
orientation?: number;
} = {}; } = {};
if (info.width) { if (info.width) {
properties['width'] = info.width; properties['width'] = info.width;
properties['height'] = info.height; properties['height'] = info.height;
} }
if (info.orientation != null) {
properties['orientation'] = info.orientation;
}
const profile = user ? await UserProfiles.findOne(user.id) : null; const profile = user ? await UserProfiles.findOne(user.id) : null;

View file

@ -2,6 +2,7 @@ import { publishMainStream, publishUserEvent } from '@/services/stream';
import { renderActivity } from '@/remote/activitypub/renderer/index'; import { renderActivity } from '@/remote/activitypub/renderer/index';
import renderFollow from '@/remote/activitypub/renderer/follow'; import renderFollow from '@/remote/activitypub/renderer/follow';
import renderUndo from '@/remote/activitypub/renderer/undo'; import renderUndo from '@/remote/activitypub/renderer/undo';
import renderReject from '@/remote/activitypub/renderer/reject';
import { deliver } from '@/queue/index'; import { deliver } from '@/queue/index';
import Logger from '../logger'; import Logger from '../logger';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
@ -40,6 +41,12 @@ export default async function(follower: { id: User['id']; host: User['host']; ur
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
deliver(follower, content, followee.inbox); deliver(follower, content, followee.inbox);
} }
if (Users.isLocalUser(followee) && Users.isRemoteUser(follower)) {
// local user has null host
const content = renderActivity(renderReject(renderFollow(follower, followee), followee));
deliver(followee, content, follower.inbox);
}
} }
export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) { export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) {

View file

@ -0,0 +1,105 @@
import { renderActivity } from '@/remote/activitypub/renderer/index';
import renderFollow from '@/remote/activitypub/renderer/follow';
import renderReject from '@/remote/activitypub/renderer/reject';
import { deliver } from '@/queue/index';
import { publishMainStream, publishUserEvent } from '@/services/stream';
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user';
import { Users, FollowRequests, Followings } from '@/models/index';
import { decrementFollowing } from './delete';
type Local = ILocalUser | { id: User['id']; host: User['host']; uri: User['host'] };
type Remote = IRemoteUser;
type Both = Local | Remote;
/**
* API following/request/reject
*/
export async function rejectFollowRequest(user: Local, follower: Both) {
if (Users.isRemoteUser(follower)) {
deliverReject(user, follower);
}
await removeFollowRequest(user, follower);
if (Users.isLocalUser(follower)) {
publishUnfollow(user, follower);
}
}
/**
* API following/reject
*/
export async function rejectFollow(user: Local, follower: Both) {
if (Users.isRemoteUser(follower)) {
deliverReject(user, follower);
}
await removeFollow(user, follower);
if (Users.isLocalUser(follower)) {
publishUnfollow(user, follower);
}
}
/**
* AP Reject/Follow
*/
export async function remoteReject(actor: Remote, follower: Local) {
await removeFollowRequest(actor, follower);
await removeFollow(actor, follower);
publishUnfollow(actor, follower);
}
/**
* Remove follow request record
*/
async function removeFollowRequest(followee: Both, follower: Both) {
const request = await FollowRequests.findOne({
followeeId: followee.id,
followerId: follower.id
});
if (!request) return;
await FollowRequests.delete(request.id);
}
/**
* Remove follow record
*/
async function removeFollow(followee: Both, follower: Both) {
const following = await Followings.findOne({
followeeId: followee.id,
followerId: follower.id
});
if (!following) return;
await Followings.delete(following.id);
decrementFollowing(follower, followee);
}
/**
* Deliver Reject to remote
*/
async function deliverReject(followee: Local, follower: Remote) {
const request = await FollowRequests.findOne({
followeeId: followee.id,
followerId: follower.id
});
const content = renderActivity(renderReject(renderFollow(follower, followee, request?.requestId || undefined), followee));
deliver(followee, content, follower.inbox);
}
/**
* Publish unfollow to local
*/
async function publishUnfollow(followee: Both, follower: Local) {
const packedFollowee = await Users.pack(followee.id, follower, {
detail: true
});
publishUserEvent(follower.id, 'unfollow', packedFollowee);
publishMainStream(follower.id, 'unfollow', packedFollowee);
}

View file

@ -1,46 +0,0 @@
import { renderActivity } from '@/remote/activitypub/renderer/index';
import renderFollow from '@/remote/activitypub/renderer/follow';
import renderReject from '@/remote/activitypub/renderer/reject';
import { deliver } from '@/queue/index';
import { publishMainStream, publishUserEvent } from '@/services/stream';
import { User, ILocalUser } from '@/models/entities/user';
import { Users, FollowRequests, Followings } from '@/models/index';
import { decrementFollowing } from '../delete';
export default async function(followee: { id: User['id']; host: User['host']; uri: User['host'] }, follower: User) {
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) {
const request = await FollowRequests.findOne({
followeeId: followee.id,
followerId: follower.id
});
const content = renderActivity(renderReject(renderFollow(follower, followee, request!.requestId!), followee));
deliver(followee, content, follower.inbox);
}
const request = await FollowRequests.findOne({
followeeId: followee.id,
followerId: follower.id
});
if (request) {
await FollowRequests.delete(request.id);
} else {
const following = await Followings.findOne({
followeeId: followee.id,
followerId: follower.id
});
if (following) {
await Followings.delete(following.id);
decrementFollowing(follower, followee);
}
}
Users.pack(followee.id, follower, {
detail: true
}).then(packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
});
}

View file

@ -17,6 +17,7 @@ describe('Get file info', () => {
}, },
width: undefined, width: undefined,
height: undefined, height: undefined,
orientation: undefined,
}); });
})); }));
@ -34,6 +35,7 @@ describe('Get file info', () => {
}, },
width: 512, width: 512,
height: 512, height: 512,
orientation: undefined,
}); });
})); }));
@ -51,6 +53,7 @@ describe('Get file info', () => {
}, },
width: 256, width: 256,
height: 256, height: 256,
orientation: undefined,
}); });
})); }));
@ -68,6 +71,7 @@ describe('Get file info', () => {
}, },
width: 256, width: 256,
height: 256, height: 256,
orientation: undefined,
}); });
})); }));
@ -85,6 +89,7 @@ describe('Get file info', () => {
}, },
width: 256, width: 256,
height: 256, height: 256,
orientation: undefined,
}); });
})); }));
@ -102,6 +107,7 @@ describe('Get file info', () => {
}, },
width: 256, width: 256,
height: 256, height: 256,
orientation: undefined,
}); });
})); }));
@ -120,6 +126,7 @@ describe('Get file info', () => {
}, },
width: 256, width: 256,
height: 256, height: 256,
orientation: undefined,
}); });
})); }));
@ -137,6 +144,25 @@ describe('Get file info', () => {
}, },
width: 25000, width: 25000,
height: 25000, height: 25000,
orientation: undefined,
});
}));
it('Rotate JPEG', async (async () => {
const path = `${__dirname}/resources/rotate.jpg`;
const info = await getFileInfo(path) as any;
delete info.warnings;
delete info.blurhash;
assert.deepStrictEqual(info, {
size: 12624,
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
type: {
mime: 'image/jpeg',
ext: 'jpg'
},
width: 512,
height: 256,
orientation: 8,
}); });
})); }));
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -12,66 +12,67 @@
<template #header> <template #header>
{{ title }} {{ title }}
</template> </template>
<FormBase class="xkpnjxcv">
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)"> <MkSpacer :margin-min="20" :margin-max="32">
<FormInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1"> <div class="xkpnjxcv _formRoot">
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span> <template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
<template v-if="form[item].description" #desc>{{ form[item].description }}</template> <FormInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1" class="_formBlock">
</FormInput> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text"> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span> </FormInput>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template> <FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text" class="_formBlock">
</FormInput> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]"> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span> </FormInput>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template> <FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]" class="_formBlock">
</FormTextarea> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<FormSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]"> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
<span v-text="form[item].label || item"></span> </FormTextarea>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template> <FormSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]" class="_formBlock">
</FormSwitch> <span v-text="form[item].label || item"></span>
<FormSelect v-else-if="form[item].type === 'enum'" v-model="values[item]"> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> </FormSwitch>
<option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option> <FormSelect v-else-if="form[item].type === 'enum'" v-model="values[item]" class="_formBlock">
</FormSelect> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<FormRadios v-else-if="form[item].type === 'radio'" v-model="values[item]"> <option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option>
<template #desc><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> </FormSelect>
<option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option> <FormRadios v-else-if="form[item].type === 'radio'" v-model="values[item]" class="_formBlock">
</FormRadios> <template #caption><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step"> <option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option>
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> </FormRadios>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template> <FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter" class="_formBlock">
</FormRange> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<FormButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)"> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
<span v-text="form[item].content || item"></span> </FormRange>
</FormButton> <MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)" class="_formBlock">
</template> <span v-text="form[item].content || item"></span>
</FormBase> </MkButton>
</template>
</div>
</MkSpacer>
</XModalWindow> </XModalWindow>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import XModalWindow from '@/components/ui/modal-window.vue'; import XModalWindow from '@/components/ui/modal-window.vue';
import FormBase from './debobigego/base.vue'; import FormInput from './form/input.vue';
import FormInput from './debobigego/input.vue'; import FormTextarea from './form/textarea.vue';
import FormTextarea from './debobigego/textarea.vue'; import FormSwitch from './form/switch.vue';
import FormSwitch from './debobigego/switch.vue'; import FormSelect from './form/select.vue';
import FormSelect from './debobigego/select.vue'; import FormRange from './form/range.vue';
import FormRange from './debobigego/range.vue'; import MkButton from './ui/button.vue';
import FormButton from './debobigego/button.vue'; import FormRadios from './form/radios.vue';
import FormRadios from './debobigego/radios.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
XModalWindow, XModalWindow,
FormBase,
FormInput, FormInput,
FormTextarea, FormTextarea,
FormSwitch, FormSwitch,
FormSelect, FormSelect,
FormRange, FormRange,
FormButton, MkButton,
FormRadios, FormRadios,
}, },

View file

@ -16,7 +16,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue'; import { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
import * as os from '@/os'; import * as os from '@/os';
export default defineComponent({ export default defineComponent({
@ -58,6 +58,9 @@ export default defineComponent({
}, },
setup(props, context) { setup(props, context) {
const containerEl = ref<HTMLElement>();
const thumbEl = ref<HTMLElement>();
const rawValue = ref((props.modelValue - props.min) / (props.max - props.min)); const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
const steppedValue = computed(() => { const steppedValue = computed(() => {
if (props.step) { if (props.step) {
@ -78,10 +81,25 @@ export default defineComponent({
if (thumbEl.value == null) return 0; if (thumbEl.value == null) return 0;
return thumbEl.value!.offsetWidth; return thumbEl.value!.offsetWidth;
}); });
const thumbPosition = computed(() => { const thumbPosition = ref(0);
if (containerEl.value == null) return 0; const calcThumbPosition = () => {
return (containerEl.value.offsetWidth - thumbWidth.value) * steppedValue.value; if (containerEl.value == null) {
thumbPosition.value = 0;
} else {
thumbPosition.value = (containerEl.value.offsetWidth - thumbWidth.value) * steppedValue.value;
}
};
watch([steppedValue, containerEl], calcThumbPosition);
onMounted(() => {
const ro = new ResizeObserver((entries, observer) => {
calcThumbPosition();
});
ro.observe(containerEl.value);
onUnmounted(() => {
ro.disconnect();
});
}); });
const steps = computed(() => { const steps = computed(() => {
if (props.step) { if (props.step) {
return (props.max - props.min) / props.step; return (props.max - props.min) / props.step;
@ -89,8 +107,6 @@ export default defineComponent({
return 0; return 0;
} }
}); });
const containerEl = ref<HTMLElement>();
const thumbEl = ref<HTMLElement>();
const onMousedown = (ev: MouseEvent | TouchEvent) => { const onMousedown = (ev: MouseEvent | TouchEvent) => {
ev.preventDefault(); ev.preventDefault();

View file

@ -7,7 +7,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue'; import { defineComponent, inject, onMounted, onUnmounted, ref } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -24,7 +24,7 @@ export default defineComponent({
marginMax: { marginMax: {
type: Number, type: Number,
required: false, required: false,
default: 32, default: 24,
}, },
}, },
@ -33,8 +33,14 @@ export default defineComponent({
const root = ref<HTMLElement>(); const root = ref<HTMLElement>();
const content = ref<HTMLElement>(); const content = ref<HTMLElement>();
const margin = ref(0); const margin = ref(0);
const shouldSpacerMin = inject('shouldSpacerMin', false);
const adjust = (rect: { width: number; height: number; }) => { const adjust = (rect: { width: number; height: number; }) => {
if (rect.width > (props.contentMax || 500)) { if (shouldSpacerMin) {
margin.value = props.marginMin;
return;
}
if (rect.width > props.contentMax || rect.width > 500) {
margin.value = props.marginMax; margin.value = props.marginMax;
} else { } else {
margin.value = props.marginMin; margin.value = props.marginMin;

View file

@ -44,16 +44,36 @@ export default defineComponent({
onMounted(() => { onMounted(() => {
const lightbox = new PhotoSwipeLightbox({ const lightbox = new PhotoSwipeLightbox({
dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => ({ dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => {
src: media.url, const item = {
w: media.properties.width, src: media.url,
h: media.properties.height, w: media.properties.width,
alt: media.name, h: media.properties.height,
})), alt: media.name,
};
if (media.properties.orientation != null && media.properties.orientation >= 5) {
[item.w, item.h] = [item.h, item.w];
}
return item;
}),
gallery: gallery.value, gallery: gallery.value,
children: '.image', children: '.image',
thumbSelector: '.image', thumbSelector: '.image',
pswpModule: PhotoSwipe loop: false,
padding: window.innerWidth > 500 ? {
top: 32,
bottom: 32,
left: 32,
right: 32,
} : {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
imageClickAction: 'close',
tapAction: 'toggle-controls',
pswpModule: PhotoSwipe,
}); });
lightbox.on('itemData', (e) => { lightbox.on('itemData', (e) => {
@ -68,6 +88,9 @@ export default defineComponent({
itemData.src = file.url; itemData.src = file.url;
itemData.w = Number(file.properties.width); itemData.w = Number(file.properties.width);
itemData.h = Number(file.properties.height); itemData.h = Number(file.properties.height);
if (file.properties.orientation != null && file.properties.orientation >= 5) {
[itemData.w, itemData.h] = [itemData.h, itemData.w];
}
itemData.msrc = file.thumbnailUrl; itemData.msrc = file.thumbnailUrl;
itemData.thumbCropped = true; itemData.thumbCropped = true;
}); });

View file

@ -649,7 +649,7 @@ export default defineComponent({
text: this.$ts.pin, text: this.$ts.pin,
action: () => this.togglePin(true) action: () => this.togglePin(true)
} : undefined, } : undefined,
...(this.$i.isModerator || this.$i.isAdmin ? [ /*...(this.$i.isModerator || this.$i.isAdmin ? [
null, null,
{ {
icon: 'fas fa-bullhorn', icon: 'fas fa-bullhorn',
@ -657,7 +657,7 @@ export default defineComponent({
action: this.promote action: this.promote
}] }]
: [] : []
), ),*/
...(this.appearNote.userId != this.$i.id ? [ ...(this.appearNote.userId != this.$i.id ? [
null, null,
{ {

View file

@ -623,6 +623,7 @@ export default defineComponent({
text: this.$ts.pin, text: this.$ts.pin,
action: () => this.togglePin(true) action: () => this.togglePin(true)
} : undefined, } : undefined,
/*
...(this.$i.isModerator || this.$i.isAdmin ? [ ...(this.$i.isModerator || this.$i.isAdmin ? [
null, null,
{ {
@ -631,7 +632,7 @@ export default defineComponent({
action: this.promote action: this.promote
}] }]
: [] : []
), ),*/
...(this.appearNote.userId != this.$i.id ? [ ...(this.appearNote.userId != this.$i.id ? [
null, null,
{ {

View file

@ -206,8 +206,6 @@ export default defineComponent({
> .input { > .input {
flex: 1; flex: 1;
margin-top: 16px;
margin-bottom: 0;
} }
> button { > button {
@ -223,7 +221,7 @@ export default defineComponent({
} }
> section { > section {
margin: 16px 0 -16px 0; margin: 16px 0 0 0;
> div { > div {
margin: 0 8px; margin: 0 8px;

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="tivcixzd" :class="{ done: closed || isVoted }"> <div class="tivcixzd" :class="{ done: closed || isVoted }">
<ul> <ul>
<li v-for="(choice, i) in poll.choices" :key="i" :class="{ voted: choice.voted }" @click="vote(i)"> <li v-for="(choice, i) in note.poll.choices" :key="i" :class="{ voted: choice.voted }" @click="vote(i)">
<div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div> <div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
<span> <span>
<template v-if="choice.isVoted"><i class="fas fa-check"></i></template> <template v-if="choice.isVoted"><i class="fas fa-check"></i></template>
@ -13,7 +13,7 @@
<p v-if="!readOnly"> <p v-if="!readOnly">
<span>{{ $t('_poll.totalVotes', { n: total }) }}</span> <span>{{ $t('_poll.totalVotes', { n: total }) }}</span>
<span> · </span> <span> · </span>
<a v-if="!closed && !isVoted" @click="toggleShowResult">{{ showResult ? $ts._poll.vote : $ts._poll.showResult }}</a> <a v-if="!closed && !isVoted" @click="showResult = !showResult">{{ showResult ? $ts._poll.vote : $ts._poll.showResult }}</a>
<span v-if="isVoted">{{ $ts._poll.voted }}</span> <span v-if="isVoted">{{ $ts._poll.voted }}</span>
<span v-else-if="closed">{{ $ts._poll.closed }}</span> <span v-else-if="closed">{{ $ts._poll.closed }}</span>
<span v-if="remaining > 0"> · {{ timer }}</span> <span v-if="remaining > 0"> · {{ timer }}</span>
@ -22,9 +22,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, onUnmounted, ref, toRef } from 'vue';
import { sum } from '@/scripts/array'; import { sum } from '@/scripts/array';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -38,71 +39,67 @@ export default defineComponent({
default: false, default: false,
} }
}, },
data() {
return {
remaining: -1,
showResult: false,
};
},
computed: {
poll(): any {
return this.note.poll;
},
total(): number {
return sum(this.poll.choices.map(x => x.votes));
},
closed(): boolean {
return !this.remaining;
},
timer(): string {
return this.$t(
this.remaining >= 86400 ? '_poll.remainingDays' :
this.remaining >= 3600 ? '_poll.remainingHours' :
this.remaining >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
s: Math.floor(this.remaining % 60),
m: Math.floor(this.remaining / 60) % 60,
h: Math.floor(this.remaining / 3600) % 24,
d: Math.floor(this.remaining / 86400)
});
},
isVoted(): boolean {
return !this.poll.multiple && this.poll.choices.some(c => c.isVoted);
}
},
created() {
this.showResult = this.readOnly || this.isVoted;
if (this.note.poll.expiresAt) { setup(props) {
const update = () => { const remaining = ref(-1);
if (this.remaining = Math.floor(Math.max(new Date(this.note.poll.expiresAt).getTime() - Date.now(), 0) / 1000))
requestAnimationFrame(update); const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
else const closed = computed(() => remaining.value === 0);
this.showResult = true; const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
const timer = computed(() => i18n.t(
remaining.value >= 86400 ? '_poll.remainingDays' :
remaining.value >= 3600 ? '_poll.remainingHours' :
remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
s: Math.floor(remaining.value % 60),
m: Math.floor(remaining.value / 60) % 60,
h: Math.floor(remaining.value / 3600) % 24,
d: Math.floor(remaining.value / 86400)
}));
const showResult = ref(props.readOnly || isVoted.value);
//
if (props.note.poll.expiresAt) {
const tick = () => {
remaining.value = Math.floor(Math.max(new Date(props.note.poll.expiresAt).getTime() - Date.now(), 0) / 1000);
if (remaining.value === 0) {
showResult.value = true;
}
}; };
update(); tick();
const intevalId = window.setInterval(tick, 3000);
onUnmounted(() => {
window.clearInterval(intevalId);
});
} }
},
methods: { const vote = async (id) => {
toggleShowResult() { if (props.readOnly || closed.value || isVoted.value) return;
this.showResult = !this.showResult;
},
async vote(id) {
if (this.readOnly || this.closed || !this.poll.multiple && this.poll.choices.some(c => c.isVoted)) return;
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'question', type: 'question',
text: this.$t('voteConfirm', { choice: this.poll.choices[id].text }), text: i18n.t('voteConfirm', { choice: props.note.poll.choices[id].text }),
}); });
if (canceled) return; if (canceled) return;
await os.api('notes/polls/vote', { await os.api('notes/polls/vote', {
noteId: this.note.id, noteId: props.note.id,
choice: id choice: id,
}); });
if (!this.showResult) this.showResult = !this.poll.multiple; if (!showResult.value) showResult.value = !props.note.poll.multiple;
} };
}
return {
remaining,
showResult,
total,
isVoted,
closed,
timer,
vote,
};
},
}); });
</script> </script>
@ -118,38 +115,38 @@ export default defineComponent({
display: block; display: block;
position: relative; position: relative;
margin: 4px 0; margin: 4px 0;
padding: 4px 8px; padding: 4px;
border: solid 0.5px var(--divider); //border: solid 0.5px var(--divider);
background: var(--accentedBg);
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
&:hover {
background: rgba(#000, 0.05);
}
&:active {
background: rgba(#000, 0.1);
}
> .backdrop { > .backdrop {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
height: 100%; height: 100%;
background: var(--accent); background: var(--accent);
background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
transition: width 1s ease; transition: width 1s ease;
} }
> span { > span {
position: relative; position: relative;
display: inline-block;
padding: 3px 5px;
background: var(--panel);
border-radius: 3px;
> i { > i {
margin-right: 4px; margin-right: 4px;
color: var(--accent);
} }
> .votes { > .votes {
margin-left: 4px; margin-left: 4px;
opacity: 0.7;
} }
} }
} }
@ -166,14 +163,6 @@ export default defineComponent({
&.done { &.done {
> ul > li { > ul > li {
cursor: default; cursor: default;
&:hover {
background: transparent;
}
&:active {
background: transparent;
}
} }
} }
} }

View file

@ -289,9 +289,14 @@ export default defineComponent({
if (this.reply && this.reply.text != null) { if (this.reply && this.reply.text != null) {
const ast = mfm.parse(this.reply.text); const ast = mfm.parse(this.reply.text);
const otherHost = this.reply.user.host;
for (const x of extractMentions(ast)) { for (const x of extractMentions(ast)) {
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; const mention = x.host ?
`@${x.username}@${toASCII(x.host)}` :
(otherHost == null || otherHost == host) ?
`@${x.username}` :
`@${x.username}@${toASCII(otherHost)}`;
// //
if (this.$i.username == x.username && x.host == null) continue; if (this.$i.username == x.username && x.host == null) continue;

View file

@ -41,6 +41,7 @@ export default defineComponent({
> .icon { > .icon {
display: block; display: block;
width: 60px; width: 60px;
font-size: 60px; // unicodewidth
margin: 0 auto; margin: 0 auto;
} }

View file

@ -62,6 +62,7 @@ export default defineComponent({
> .icon { > .icon {
display: block; display: block;
width: 60px; width: 60px;
font-size: 60px; // unicodewidth
margin: 0 auto; margin: 0 auto;
} }

View file

@ -153,6 +153,7 @@ export default defineComponent({
box-sizing: border-box; box-sizing: border-box;
min-width: 200px; min-width: 200px;
overflow: auto; overflow: auto;
overscroll-behavior: contain;
&.center { &.center {
> .item { > .item {

View file

@ -52,7 +52,7 @@ export default defineComponent({
> .title { > .title {
opacity: 0.7; opacity: 0.7;
margin: 0 0 8px 12px; margin: 0 0 8px 0;
} }
> .items { > .items {

View file

@ -1,3 +1,6 @@
// TODO: useTooltip関数使うようにしたい
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
import { Directive, ref } from 'vue'; import { Directive, ref } from 'vue';
import { isDeviceTouch } from '@/scripts/is-device-touch'; import { isDeviceTouch } from '@/scripts/is-device-touch';
import { popup, alert } from '@/os'; import { popup, alert } from '@/os';

View file

@ -1,4 +1,4 @@
import { computed, ref } from 'vue'; import { computed, ref, reactive } from 'vue';
import { search } from '@/scripts/search'; import { search } from '@/scripts/search';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -7,7 +7,7 @@ import { $i } from './account';
import { unisonReload } from '@/scripts/unison-reload'; import { unisonReload } from '@/scripts/unison-reload';
import { router } from './router'; import { router } from './router';
export const menuDef = { export const menuDef = reactive({
notifications: { notifications: {
title: 'notifications', title: 'notifications',
icon: 'fas fa-bell', icon: 'fas fa-bell',
@ -221,4 +221,4 @@ export const menuDef = {
}*/], ev.currentTarget || ev.target); }*/], ev.currentTarget || ev.target);
}, },
}, },
}; });

View file

@ -556,7 +556,7 @@ export function contextMenu(items: any[], ev: MouseEvent) {
}); });
} }
export function post(props: Record<string, any>) { export function post(props: Record<string, any> = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない // NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
// NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、 // NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、

View file

@ -24,7 +24,7 @@
</FormSection> </FormSection>
<FormSection> <FormSection>
<div class="_inputSplit"> <div class="_inputSplit _formBlock">
<MkKeyValue class="_formBlock"> <MkKeyValue class="_formBlock">
<template #key>{{ $ts.administrator }}</template> <template #key>{{ $ts.administrator }}</template>
<template #value>{{ $instance.maintainerName }}</template> <template #value>{{ $instance.maintainerName }}</template>
@ -34,10 +34,9 @@
<template #value>{{ $instance.maintainerEmail }}</template> <template #value>{{ $instance.maintainerEmail }}</template>
</MkKeyValue> </MkKeyValue>
</div> </div>
<FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" class="_formBlock" external>{{ $ts.tos }}</FormLink>
</FormSection> </FormSection>
<FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" external>{{ $ts.tos }}</FormLink>
<FormSuspense :p="initStats"> <FormSuspense :p="initStats">
<FormSection> <FormSection>
<template #label>{{ $ts.statistics }}</template> <template #label>{{ $ts.statistics }}</template>

View file

@ -33,7 +33,7 @@
</div> </div>
--> -->
<MkPagination #default="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);"> <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
<div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap"> <div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap">
<div class="_content target"> <div class="_content target">
<MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/>

View file

@ -7,7 +7,7 @@
</MkInput> </MkInput>
<MkPagination ref="emojis" :pagination="pagination"> <MkPagination ref="emojis" :pagination="pagination">
<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
<template #default="{items}"> <template v-slot="{items}">
<div class="ldhfsamy"> <div class="ldhfsamy">
<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)"> <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)">
<img :src="emoji.url" class="img" :alt="emoji.name"/> <img :src="emoji.url" class="img" :alt="emoji.name"/>
@ -31,7 +31,7 @@
</MkInput> </MkInput>
<MkPagination ref="remoteEmojis" :pagination="remotePagination"> <MkPagination ref="remoteEmojis" :pagination="remotePagination">
<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
<template #default="{items}"> <template v-slot="{items}">
<div class="ldhfsamy"> <div class="ldhfsamy">
<div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)"> <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)">
<img :src="emoji.url" class="img" :alt="emoji.name"/> <img :src="emoji.url" class="img" :alt="emoji.name"/>

View file

@ -28,7 +28,7 @@
<template #label>MIME type</template> <template #label>MIME type</template>
</MkInput> </MkInput>
</div> </div>
<MkPagination #default="{items}" ref="files" :pagination="pagination" class="urempief"> <MkPagination v-slot="{items}" ref="files" :pagination="pagination" class="urempief">
<button v-for="file in items" :key="file.id" class="file _panel _button _gap" @click="show(file, $event)"> <button v-for="file in items" :key="file.id" class="file _panel _button _gap" @click="show(file, $event)">
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
<div class="body"> <div class="body">

View file

@ -36,7 +36,7 @@
</MkInput> </MkInput>
</div> </div>
<MkPagination #default="{items}" ref="users" :pagination="pagination" class="users"> <MkPagination v-slot="{items}" ref="users" :pagination="pagination" class="users">
<button v-for="user in items" :key="user.id" class="user _panel _button _gap" @click="show(user)"> <button v-for="user in items" :key="user.id" class="user _panel _button _gap" @click="show(user)">
<MkAvatar class="avatar" :user="user" :disable-link="true" :show-indicator="true"/> <MkAvatar class="avatar" :user="user" :disable-link="true" :show-indicator="true"/>
<div class="body"> <div class="body">

View file

@ -1,6 +1,6 @@
<template> <template>
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<MkPagination #default="{items}" :pagination="pagination" class="ruryvtyk _content"> <MkPagination v-slot="{items}" :pagination="pagination" class="ruryvtyk _content">
<section v-for="(announcement, i) in items" :key="announcement.id" class="_card announcement"> <section v-for="(announcement, i) in items" :key="announcement.id" class="_card announcement">
<div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div> <div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
<div class="_content"> <div class="_content">

View file

@ -1,26 +1,28 @@
<template> <template>
<div class="_root"> <MkSpacer :content-max="700">
<div class="_block" style="padding: 24px;"> <div class="_formRoot">
<MkInput v-model="endpoint" :datalist="endpoints" class="" @update:modelValue="onEndpointChange()"> <div class="_formBlock">
<template #label>Endpoint</template> <MkInput v-model="endpoint" :datalist="endpoints" class="_formBlock" @update:modelValue="onEndpointChange()">
</MkInput> <template #label>Endpoint</template>
<MkTextarea v-model="body" code> </MkInput>
<template #label>Params (JSON or JSON5)</template> <MkTextarea v-model="body" class="_formBlock" code>
</MkTextarea> <template #label>Params (JSON or JSON5)</template>
<MkSwitch v-model="withCredential"> </MkTextarea>
With credential <MkSwitch v-model="withCredential" class="_formBlock">
</MkSwitch> With credential
<MkButton primary full :disabled="sending" @click="send"> </MkSwitch>
<template v-if="sending"><MkEllipsis/></template> <MkButton class="_formBlock" primary :disabled="sending" @click="send">
<template v-else><i class="fas fa-paper-plane"></i> Send</template> <template v-if="sending"><MkEllipsis/></template>
</MkButton> <template v-else><i class="fas fa-paper-plane"></i> Send</template>
</MkButton>
</div>
<div v-if="res" class="_formBlock">
<MkTextarea v-model="res" code readonly tall>
<template #label>Response</template>
</MkTextarea>
</div>
</div> </div>
<div v-if="res" class="_block" style="padding: 24px;"> </MkSpacer>
<MkTextarea v-model="res" code readonly tall>
<template #label>Response</template>
</MkTextarea>
</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -64,7 +66,8 @@ export default defineComponent({
methods: { methods: {
send() { send() {
this.sending = true; this.sending = true;
os.api(this.endpoint, JSON5.parse(this.body)).then(res => { const body = JSON5.parse(this.body);
os.api(this.endpoint, body, body.i || this.withCredential ? undefined : null).then(res => {
this.sending = false; this.sending = false;
this.res = JSON5.stringify(res, null, 2); this.res = JSON5.stringify(res, null, 2);
}, err => { }, err => {

View file

@ -10,20 +10,20 @@
<div class="_section"> <div class="_section">
<div v-if="tab === 'featured'" class="_content grwlizim featured"> <div v-if="tab === 'featured'" class="_content grwlizim featured">
<MkPagination #default="{items}" :pagination="featuredPagination"> <MkPagination v-slot="{items}" :pagination="featuredPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination> </MkPagination>
</div> </div>
<div v-if="tab === 'following'" class="_content grwlizim following"> <div v-if="tab === 'following'" class="_content grwlizim following">
<MkPagination #default="{items}" :pagination="followingPagination"> <MkPagination v-slot="{items}" :pagination="followingPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination> </MkPagination>
</div> </div>
<div v-if="tab === 'owned'" class="_content grwlizim owned"> <div v-if="tab === 'owned'" class="_content grwlizim owned">
<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton> <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
<MkPagination #default="{items}" :pagination="ownedPagination"> <MkPagination v-slot="{items}" :pagination="ownedPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination> </MkPagination>
</div> </div>

View file

@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<MkPagination #default="{items}" ref="instances" :key="host + state" :pagination="pagination"> <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
<div class="dqokceoi"> <div class="dqokceoi">
<MkA v-for="instance in items" :key="instance.id" class="instance" :to="`/instance-info/${instance.host}`"> <MkA v-for="instance in items" :key="instance.id" class="instance" :to="`/instance-info/${instance.host}`">
<div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div> <div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div>

View file

@ -7,7 +7,7 @@
<div>{{ $ts.noFollowRequests }}</div> <div>{{ $ts.noFollowRequests }}</div>
</div> </div>
</template> </template>
<template #default="{items}"> <template v-slot="{items}">
<div v-for="req in items" :key="req.id" class="user _panel"> <div v-for="req in items" :key="req.id" class="user _panel">
<MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
<div class="body"> <div class="body">

View file

@ -9,7 +9,7 @@
<div v-if="tab === 'explore'"> <div v-if="tab === 'explore'">
<MkFolder class="_gap"> <MkFolder class="_gap">
<template #header><i class="fas fa-clock"></i>{{ $ts.recentPosts }}</template> <template #header><i class="fas fa-clock"></i>{{ $ts.recentPosts }}</template>
<MkPagination #default="{items}" :pagination="recentPostsPagination" :disable-auto-load="true"> <MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disable-auto-load="true">
<div class="vfpdbgtk"> <div class="vfpdbgtk">
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
</div> </div>
@ -17,7 +17,7 @@
</MkFolder> </MkFolder>
<MkFolder class="_gap"> <MkFolder class="_gap">
<template #header><i class="fas fa-fire-alt"></i>{{ $ts.popularPosts }}</template> <template #header><i class="fas fa-fire-alt"></i>{{ $ts.popularPosts }}</template>
<MkPagination #default="{items}" :pagination="popularPostsPagination" :disable-auto-load="true"> <MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disable-auto-load="true">
<div class="vfpdbgtk"> <div class="vfpdbgtk">
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
</div> </div>
@ -25,7 +25,7 @@
</MkFolder> </MkFolder>
</div> </div>
<div v-else-if="tab === 'liked'"> <div v-else-if="tab === 'liked'">
<MkPagination #default="{items}" :pagination="likedPostsPagination"> <MkPagination v-slot="{items}" :pagination="likedPostsPagination">
<div class="vfpdbgtk"> <div class="vfpdbgtk">
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/> <MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
</div> </div>
@ -33,7 +33,7 @@
</div> </div>
<div v-else-if="tab === 'my'"> <div v-else-if="tab === 'my'">
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="fas fa-plus"></i> {{ $ts.postToGallery }}</MkA> <MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="fas fa-plus"></i> {{ $ts.postToGallery }}</MkA>
<MkPagination #default="{items}" :pagination="myPostsPagination"> <MkPagination v-slot="{items}" :pagination="myPostsPagination">
<div class="vfpdbgtk"> <div class="vfpdbgtk">
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
</div> </div>

View file

@ -36,7 +36,7 @@
<MkAd :prefer="['horizontal', 'horizontal-big']"/> <MkAd :prefer="['horizontal', 'horizontal-big']"/>
<MkContainer :max-height="300" :foldable="true" class="other"> <MkContainer :max-height="300" :foldable="true" class="other">
<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template> <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
<MkPagination #default="{items}" :pagination="otherPostsPagination"> <MkPagination v-slot="{items}" :pagination="otherPostsPagination">
<div class="sdrarzaf"> <div class="sdrarzaf">
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
</div> </div>

View file

@ -1,15 +1,17 @@
<template> <template>
<div class="ieepwinx _section"> <MkSpacer :content-max="700">
<MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> <div class="ieepwinx">
<MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
<div class="_content"> <div class="">
<MkPagination #default="{items}" ref="list" :pagination="pagination"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination">
<MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`"> <MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`">
<div class="name">{{ antenna.name }}</div> <div class="name">{{ antenna.name }}</div>
</MkA> </MkA>
</MkPagination> </MkPagination>
</div>
</div> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -29,6 +31,7 @@ export default defineComponent({
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
title: this.$ts.manageAntennas, title: this.$ts.manageAntennas,
icon: 'fas fa-satellite', icon: 'fas fa-satellite',
bg: 'var(--bg)',
action: { action: {
icon: 'fas fa-plus', icon: 'fas fa-plus',
handler: this.create handler: this.create
@ -45,7 +48,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.ieepwinx { .ieepwinx {
padding: 16px;
> .add { > .add {
margin: 0 auto 16px auto; margin: 0 auto 16px auto;

View file

@ -1,16 +1,16 @@
<template> <template>
<div class="_section qtcaoidl"> <MkSpacer :content-max="700">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> <div class="qtcaoidl">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
<div class="_content"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list">
<MkPagination #default="{items}" ref="list" :pagination="pagination" class="list">
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap"> <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b> <b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div> <div v-if="item.description" class="description">{{ item.description }}</div>
</MkA> </MkA>
</MkPagination> </MkPagination>
</div> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -31,6 +31,7 @@ export default defineComponent({
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
title: this.$ts.clip, title: this.$ts.clip,
icon: 'fas fa-paperclip', icon: 'fas fa-paperclip',
bg: 'var(--bg)',
action: { action: {
icon: 'fas fa-plus', icon: 'fas fa-plus',
handler: this.create handler: this.create
@ -86,17 +87,15 @@ export default defineComponent({
margin: 0 auto 16px auto; margin: 0 auto 16px auto;
} }
> ._content { > .list {
> .list { > .item {
> .item { display: block;
display: block; padding: 16px;
padding: 16px;
> .description { > .description {
margin-top: 8px; margin-top: 8px;
padding-top: 8px; padding-top: 8px;
border-top: solid 0.5px var(--divider); border-top: solid 0.5px var(--divider);
}
} }
} }
} }

View file

@ -12,7 +12,7 @@
<div v-if="tab === 'owned'" class="_content"> <div v-if="tab === 'owned'" class="_content">
<MkButton primary style="margin: 0 auto var(--margin) auto;" @click="create"><i class="fas fa-plus"></i> {{ $ts.createGroup }}</MkButton> <MkButton primary style="margin: 0 auto var(--margin) auto;" @click="create"><i class="fas fa-plus"></i> {{ $ts.createGroup }}</MkButton>
<MkPagination #default="{items}" ref="owned" :pagination="ownedPagination"> <MkPagination v-slot="{items}" ref="owned" :pagination="ownedPagination">
<div v-for="group in items" :key="group.id" class="_card"> <div v-for="group in items" :key="group.id" class="_card">
<div class="_title"><MkA :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</MkA></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 class="_content"><MkAvatars :user-ids="group.userIds"/></div>
@ -21,7 +21,7 @@
</div> </div>
<div v-else-if="tab === 'joined'" class="_content"> <div v-else-if="tab === 'joined'" class="_content">
<MkPagination #default="{items}" ref="joined" :pagination="joinedPagination"> <MkPagination v-slot="{items}" ref="joined" :pagination="joinedPagination">
<div v-for="group in items" :key="group.id" class="_card"> <div v-for="group in items" :key="group.id" class="_card">
<div class="_title">{{ group.name }}</div> <div class="_title">{{ group.name }}</div>
<div class="_content"><MkAvatars :user-ids="group.userIds"/></div> <div class="_content"><MkAvatars :user-ids="group.userIds"/></div>
@ -30,7 +30,7 @@
</div> </div>
<div v-else-if="tab === 'invites'" class="_content"> <div v-else-if="tab === 'invites'" class="_content">
<MkPagination #default="{items}" ref="invitations" :pagination="invitationPagination"> <MkPagination v-slot="{items}" ref="invitations" :pagination="invitationPagination">
<div v-for="invitation in items" :key="invitation.id" class="_card"> <div v-for="invitation in items" :key="invitation.id" class="_card">
<div class="_title">{{ invitation.group.name }}</div> <div class="_title">{{ invitation.group.name }}</div>
<div class="_content"><MkAvatars :user-ids="invitation.group.userIds"/></div> <div class="_content"><MkAvatars :user-ids="invitation.group.userIds"/></div>

View file

@ -1,14 +1,16 @@
<template> <template>
<div class="qkcjvfiv"> <MkSpacer :content-max="700">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton> <div class="qkcjvfiv">
<MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton>
<MkPagination #default="{items}" ref="list" :pagination="pagination" class="lists _content"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="lists _content">
<MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`"> <MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`">
<div class="name">{{ list.name }}</div> <div class="name">{{ list.name }}</div>
<MkAvatars :user-ids="list.userIds"/> <MkAvatars :user-ids="list.userIds"/>
</MkA> </MkA>
</MkPagination> </MkPagination>
</div> </div>
</MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -60,8 +62,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.qkcjvfiv { .qkcjvfiv {
padding: 16px;
> .add { > .add {
margin: 0 auto var(--margin) auto; margin: 0 auto var(--margin) auto;
} }

View file

@ -1,35 +1,37 @@
<template> <template>
<div class="mk-list-page"> <MkSpacer :content-max="700">
<transition name="zoom" mode="out-in"> <div class="mk-list-page">
<div v-if="list" class="_section"> <transition name="zoom" mode="out-in">
<div class="_content"> <div v-if="list" class="_section">
<MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton> <div class="_content">
<MkButton inline @click="renameList()">{{ $ts.rename }}</MkButton> <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton>
<MkButton inline @click="deleteList()">{{ $ts.delete }}</MkButton> <MkButton inline @click="renameList()">{{ $ts.rename }}</MkButton>
<MkButton inline @click="deleteList()">{{ $ts.delete }}</MkButton>
</div>
</div> </div>
</div> </transition>
</transition>
<transition name="zoom" mode="out-in"> <transition name="zoom" mode="out-in">
<div v-if="list" class="_section members _gap"> <div v-if="list" class="_section members _gap">
<div class="_title">{{ $ts.members }}</div> <div class="_title">{{ $ts.members }}</div>
<div class="_content"> <div class="_content">
<div class="users"> <div class="users">
<div v-for="user in users" :key="user.id" class="user _panel"> <div v-for="user in users" :key="user.id" class="user _panel">
<MkAvatar :user="user" class="avatar" :show-indicator="true"/> <MkAvatar :user="user" class="avatar" :show-indicator="true"/>
<div class="body"> <div class="body">
<MkUserName :user="user" class="name"/> <MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/> <MkAcct :user="user" class="acct"/>
</div> </div>
<div class="action"> <div class="action">
<button class="_button" @click="removeUser(user)"><i class="fas fa-times"></i></button> <button class="_button" @click="removeUser(user)"><i class="fas fa-times"></i></button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </transition>
</transition> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -49,6 +51,7 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => this.list ? { [symbols.PAGE_INFO]: computed(() => this.list ? {
title: this.list.name, title: this.list.name,
icon: 'fas fa-list-ul', icon: 'fas fa-list-ul',
bg: 'var(--bg)',
} : null), } : null),
list: null, list: null,
users: [], users: [],

View file

@ -45,10 +45,10 @@
<template #label>{{ $ts._pages.script.blocks._fn.slots }}</template> <template #label>{{ $ts._pages.script.blocks._fn.slots }}</template>
<template #caption>{{ $t('_pages.script.blocks._fn.slots-info') }}</template> <template #caption>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
</MkTextarea> </MkTextarea>
<XV v-if="modelValue.value.expression" v-model="modelValue.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="value.value.slots" :name="name"/> <XV v-if="modelValue.value.expression" v-model="modelValue.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="modelValue.value.slots" :name="name"/>
</section> </section>
<section v-else-if="modelValue.type.startsWith('fn:')" class="" style="padding:16px;"> <section v-else-if="modelValue.type.startsWith('fn:')" class="" style="padding:16px;">
<XV v-for="(x, i) in modelValue.args" :key="i" v-model="value.args[i]" :title="hpml.getVarByName(modelValue.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name"/> <XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="hpml.getVarByName(modelValue.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name"/>
</section> </section>
<section v-else class="" style="padding:16px;"> <section v-else class="" style="padding:16px;">
<XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="$t(`_pages.script.blocks._${modelValue.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots"/> <XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="$t(`_pages.script.blocks._${modelValue.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots"/>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <MkSpacer :content-max="700">
<div class="jqqmcavi" style="margin: 16px;"> <div class="jqqmcavi">
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton> <MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton>
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> <MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton> <MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton>
@ -8,7 +8,7 @@
</div> </div>
<div v-if="tab === 'settings'"> <div v-if="tab === 'settings'">
<div style="padding: 16px;" class="_formRoot"> <div class="_formRoot">
<MkInput v-model="title" class="_formBlock"> <MkInput v-model="title" class="_formBlock">
<template #label>{{ $ts._pages.title }}</template> <template #label>{{ $ts._pages.title }}</template>
</MkInput> </MkInput>
@ -43,7 +43,7 @@
</div> </div>
<div v-else-if="tab === 'contents'"> <div v-else-if="tab === 'contents'">
<div style="padding: 16px;"> <div>
<XBlocks v-model="content" class="content" :hpml="hpml"/> <XBlocks v-model="content" class="content" :hpml="hpml"/>
<MkButton v-if="!readonly" @click="add()"><i class="fas fa-plus"></i></MkButton> <MkButton v-if="!readonly" @click="add()"><i class="fas fa-plus"></i></MkButton>
@ -75,7 +75,7 @@
<MkTextarea v-model="script" class="_code"/> <MkTextarea v-model="script" class="_code"/>
</div> </div>
</div> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -1,5 +1,5 @@
<template> <template>
<div> <MkSpacer :content-max="700">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh"> <div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh">
<div class="_block main"> <div class="_block main">
@ -48,7 +48,7 @@
<MkAd :prefer="['horizontal', 'horizontal-big']"/> <MkAd :prefer="['horizontal', 'horizontal-big']"/>
<MkContainer :max-height="300" :foldable="true" class="other"> <MkContainer :max-height="300" :foldable="true" class="other">
<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template> <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
<MkPagination #default="{items}" :pagination="otherPostsPagination"> <MkPagination v-slot="{items}" :pagination="otherPostsPagination">
<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_gap"/> <MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_gap"/>
</MkPagination> </MkPagination>
</MkContainer> </MkContainer>
@ -56,7 +56,7 @@
<MkError v-else-if="error" @retry="fetch()"/> <MkError v-else-if="error" @retry="fetch()"/>
<MkLoading v-else/> <MkLoading v-else/>
</transition> </transition>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -201,14 +201,7 @@ export default defineComponent({
} }
.xcukqgmh { .xcukqgmh {
--padding: 32px;
&.max-width_450px {
--padding: 16px;
}
> .main { > .main {
padding: var(--padding);
> .header { > .header {
padding: 16px; padding: 16px;
@ -302,7 +295,7 @@ export default defineComponent({
} }
> .footer { > .footer {
margin: var(--padding); margin: var(--margin) 0 var(--margin) 0;
font-size: 85%; font-size: 85%;
opacity: 0.75; opacity: 0.75;
} }

View file

@ -1,50 +1,40 @@
<template> <template>
<MkSpacer> <MkSpacer :content-max="700">
<!-- TODO: MkHeaderに統合 --> <div v-if="tab === 'featured'" class="rknalgpo">
<MkTab v-if="$i" v-model="tab"> <MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
<option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._pages.featured }}</option> <MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/>
<option value="my"><i class="fas fa-edit"></i> {{ $ts._pages.my }}</option> </MkPagination>
<option value="liked"><i class="fas fa-heart"></i> {{ $ts._pages.liked }}</option> </div>
</MkTab>
<div class="_section"> <div v-else-if="tab === 'my'" class="rknalgpo my">
<div v-if="tab === 'featured'" class="rknalgpo _content"> <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
<MkPagination #default="{items}" :pagination="featuredPagesPagination"> <MkPagination v-slot="{items}" :pagination="myPagesPagination">
<MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/> <MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/>
</MkPagination> </MkPagination>
</div> </div>
<div v-if="tab === 'my'" class="rknalgpo _content my"> <div v-else-if="tab === 'liked'" class="rknalgpo">
<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton> <MkPagination v-slot="{items}" :pagination="likedPagesPagination">
<MkPagination #default="{items}" :pagination="myPagesPagination"> <MkPagePreview v-for="like in items" :key="like.page.id" class="ckltabjg" :page="like.page"/>
<MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/> </MkPagination>
</MkPagination>
</div>
<div v-if="tab === 'liked'" class="rknalgpo _content">
<MkPagination #default="{items}" :pagination="likedPagesPagination">
<MkPagePreview v-for="like in items" :key="like.page.id" class="ckltabjg" :page="like.page"/>
</MkPagination>
</div>
</div> </div>
</MkSpacer> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import MkPagePreview from '@/components/page-preview.vue'; import MkPagePreview from '@/components/page-preview.vue';
import MkPagination from '@/components/ui/pagination.vue'; import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue'; import MkButton from '@/components/ui/button.vue';
import MkTab from '@/components/tab.vue';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
export default defineComponent({ export default defineComponent({
components: { components: {
MkPagePreview, MkPagination, MkButton, MkTab MkPagePreview, MkPagination, MkButton
}, },
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.pages, title: this.$ts.pages,
icon: 'fas fa-sticky-note', icon: 'fas fa-sticky-note',
bg: 'var(--bg)', bg: 'var(--bg)',
@ -53,7 +43,23 @@ export default defineComponent({
text: this.$ts.create, text: this.$ts.create,
handler: this.create, handler: this.create,
}], }],
}, tabs: [{
active: this.tab === 'featured',
title: this.$ts._pages.featured,
icon: 'fas fa-fire-alt',
onClick: () => { this.tab = 'featured'; },
}, {
active: this.tab === 'my',
title: this.$ts._pages.my,
icon: 'fas fa-edit',
onClick: () => { this.tab = 'my'; },
}, {
active: this.tab === 'liked',
title: this.$ts._pages.liked,
icon: 'fas fa-heart',
onClick: () => { this.tab = 'liked'; },
},]
})),
tab: 'featured', tab: 'featured',
featuredPagesPagination: { featuredPagesPagination: {
endpoint: 'pages/featured', endpoint: 'pages/featured',

View file

@ -7,7 +7,7 @@
<div>{{ $ts.nothing }}</div> <div>{{ $ts.nothing }}</div>
</div> </div>
</template> </template>
<template #default="{items}"> <template v-slot="{items}">
<div v-for="token in items" :key="token.id" class="_debobigegoPanel bfomjevm"> <div v-for="token in items" :key="token.id" class="_debobigegoPanel bfomjevm">
<img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/> <img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
<div class="body"> <div class="body">

View file

@ -1,7 +1,7 @@
<template> <template>
<div ref="el" class="vvcocwet" :class="{ wide: !narrow }"> <div ref="el" class="vvcocwet" :class="{ wide: !narrow }">
<div v-if="!narrow || page == null" class="nav"> <div v-if="!narrow || page == null" class="nav">
<MkSpacer :content-max="700"> <MkSpacer :content-max="700" :margin-min="20">
<div class="baaadecd"> <div class="baaadecd">
<div class="title">{{ $ts.settings }}</div> <div class="title">{{ $ts.settings }}</div>
<MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo>

View file

@ -7,7 +7,7 @@
<div v-if="tab === 'mute'"> <div v-if="tab === 'mute'">
<MkPagination :pagination="mutingPagination" class="muting"> <MkPagination :pagination="mutingPagination" class="muting">
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template> <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
<template #default="{items}"> <template v-slot="{items}">
<FormGroup> <FormGroup>
<FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)"> <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)">
<MkAcct :user="mute.mutee"/> <MkAcct :user="mute.mutee"/>
@ -19,7 +19,7 @@
<div v-if="tab === 'block'"> <div v-if="tab === 'block'">
<MkPagination :pagination="blockingPagination" class="blocking"> <MkPagination :pagination="blockingPagination" class="blocking">
<template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template> <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template>
<template #default="{items}"> <template v-slot="{items}">
<FormGroup> <FormGroup>
<FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)"> <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)">
<MkAcct :user="block.blockee"/> <MkAcct :user="block.blockee"/>

View file

@ -13,7 +13,7 @@
<FormSection> <FormSection>
<template #label>{{ $ts.signinHistory }}</template> <template #label>{{ $ts.signinHistory }}</template>
<FormPagination :pagination="pagination"> <FormPagination :pagination="pagination">
<template #default="{items}"> <template v-slot="{items}">
<div> <div>
<div v-for="item in items" :key="item.id" v-panel class="timnmucd"> <div v-for="item in items" :key="item.id" v-panel class="timnmucd">
<header> <header>

View file

@ -119,6 +119,7 @@ export default defineComponent({
mim: 0, mim: 0,
max: 1, max: 1,
step: 0.05, step: 0.05,
textConverter: (v) => `${Math.floor(v * 100)}%`,
label: this.$ts.volume, label: this.$ts.volume,
default: this.sounds[type].volume default: this.sounds[type].volume
}, },

View file

@ -1,6 +1,6 @@
<template> <template>
<FormBase> <div class="_formRoot">
<FormSelect v-model="selectedThemeId"> <FormSelect v-model="selectedThemeId" class="_formBlock">
<template #label>{{ $ts.theme }}</template> <template #label>{{ $ts.theme }}</template>
<optgroup :label="$ts._theme.installedThemes"> <optgroup :label="$ts._theme.installedThemes">
<option v-for="x in installedThemes" :key="x.id" :value="x.id">{{ x.name }}</option> <option v-for="x in installedThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
@ -10,31 +10,31 @@
</optgroup> </optgroup>
</FormSelect> </FormSelect>
<template v-if="selectedTheme"> <template v-if="selectedTheme">
<FormInput readonly :modelValue="selectedTheme.author"> <FormInput readonly :modelValue="selectedTheme.author" class="_formBlock">
<span>{{ $ts.author }}</span> <template #label>{{ $ts.author }}</template>
</FormInput> </FormInput>
<FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc"> <FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc" class="_formBlock">
<span>{{ $ts._theme.description }}</span> <template #label>{{ $ts._theme.description }}</template>
</FormTextarea> </FormTextarea>
<FormTextarea readonly tall :modelValue="selectedThemeCode"> <FormTextarea readonly tall :modelValue="selectedThemeCode" class="_formBlock">
<span>{{ $ts._theme.code }}</span> <template #label>{{ $ts._theme.code }}</template>
<template #desc><button class="_textButton" @click="copyThemeCode()">{{ $ts.copy }}</button></template> <template #caption><button class="_textButton" @click="copyThemeCode()">{{ $ts.copy }}</button></template>
</FormTextarea> </FormTextarea>
<FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton> <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton>
</template> </template>
</FormBase> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import * as JSON5 from 'json5'; import * as JSON5 from 'json5';
import FormTextarea from '@/components/debobigego/textarea.vue'; import FormTextarea from '@/components/form/textarea.vue';
import FormSelect from '@/components/debobigego/select.vue'; import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/debobigego/radios.vue'; import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/debobigego/base.vue'; import FormBase from '@/components/debobigego/base.vue';
import FormGroup from '@/components/debobigego/group.vue'; import FormGroup from '@/components/debobigego/group.vue';
import FormInput from '@/components/debobigego/input.vue'; import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/debobigego/button.vue'; import FormButton from '@/components/ui/button.vue';
import { Theme, builtinThemes } from '@/scripts/theme'; import { Theme, builtinThemes } from '@/scripts/theme';
import copyToClipboard from '@/scripts/copy-to-clipboard'; import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os'; import * as os from '@/os';

View file

@ -1,20 +1,22 @@
<template> <template>
<div v-size="{ min: [800] }" v-hotkey.global="keymap" class="cmuxhskf"> <MkSpacer :content-max="800">
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/> <div v-hotkey.global="keymap" class="cmuxhskf">
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/> <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> <div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
<div class="tl _block"> <div class="tl _block">
<XTimeline ref="tl" :key="src" <XTimeline ref="tl" :key="src"
class="tl" class="tl"
:src="src" :src="src"
:sound="true" :sound="true"
@before="before()" @before="before()"
@after="after()" @after="after()"
@queue="queueUpdated" @queue="queueUpdated"
/> />
</div>
</div> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -66,7 +68,7 @@ export default defineComponent({
icon: 'fas fa-home', icon: 'fas fa-home',
iconOnly: true, iconOnly: true,
onClick: () => { this.src = 'home'; this.saveSrc(); }, onClick: () => { this.src = 'home'; this.saveSrc(); },
}, { }, ...(this.isLocalTimelineAvailable ? [{
active: this.src === 'local', active: this.src === 'local',
title: this.$ts._timelines.local, title: this.$ts._timelines.local,
icon: 'fas fa-comments', icon: 'fas fa-comments',
@ -78,13 +80,13 @@ export default defineComponent({
icon: 'fas fa-share-alt', icon: 'fas fa-share-alt',
iconOnly: true, iconOnly: true,
onClick: () => { this.src = 'social'; this.saveSrc(); }, onClick: () => { this.src = 'social'; this.saveSrc(); },
}, { }] : []), ...(this.isGlobalTimelineAvailable ? [{
active: this.src === 'global', active: this.src === 'global',
title: this.$ts._timelines.global, title: this.$ts._timelines.global,
icon: 'fas fa-globe', icon: 'fas fa-globe',
iconOnly: true, iconOnly: true,
onClick: () => { this.src = 'global'; this.saveSrc(); }, onClick: () => { this.src = 'global'; this.saveSrc(); },
}], }] : [])],
})), })),
}; };
}, },
@ -188,8 +190,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.cmuxhskf { .cmuxhskf {
padding: var(--margin);
> .new { > .new {
position: sticky; position: sticky;
top: calc(var(--stickyTop, 0px) + 16px); top: calc(var(--stickyTop, 0px) + 16px);
@ -213,10 +213,5 @@ export default defineComponent({
border-radius: var(--radius); border-radius: var(--radius);
overflow: clip; overflow: clip;
} }
&.min-width_800px {
max-width: 800px;
margin: 0 auto;
}
} }
</style> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<MkPagination #default="{items}" ref="list" :pagination="pagination"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination">
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap"> <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b> <b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div> <div v-if="item.description" class="description">{{ item.description }}</div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<MkPagination #default="{items}" ref="list" :pagination="pagination" class="mk-following-or-followers"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="mk-following-or-followers">
<div class="users _isolated"> <div class="users _isolated">
<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/> <MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/>
</div> </div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<MkPagination #default="{items}" :pagination="pagination"> <MkPagination v-slot="{items}" :pagination="pagination">
<div class="jrnovfpt"> <div class="jrnovfpt">
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/> <MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
</div> </div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<MkPagination #default="{items}" ref="list" :pagination="pagination"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination">
<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_gap"/> <MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_gap"/>
</MkPagination> </MkPagination>
</div> </div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<MkPagination #default="{items}" ref="list" :pagination="pagination"> <MkPagination v-slot="{items}" ref="list" :pagination="pagination">
<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb"> <div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb">
<div class="header"> <div class="header">
<MkAvatar class="avatar" :user="user"/> <MkAvatar class="avatar" :user="user"/>

View file

@ -109,6 +109,14 @@ export function getUserMenu(user) {
return !confirm.canceled; return !confirm.canceled;
} }
async function invalidateFollow() {
os.apiWithDialog('following/invalidate', {
userId: user.id
}).then(() => {
user.isFollowed = !user.isFollowed;
})
}
let menu = [{ let menu = [{
icon: 'fas fa-at', icon: 'fas fa-at',
text: i18n.locale.copyUsername, text: i18n.locale.copyUsername,
@ -153,6 +161,14 @@ export function getUserMenu(user) {
action: toggleBlock action: toggleBlock
}]); }]);
if (user.isFollowed) {
menu = menu.concat([{
icon: 'fas fa-unlink',
text: i18n.locale.breakFollow,
action: invalidateFollow
}]);
}
menu = menu.concat([null, { menu = menu.concat([null, {
icon: 'fas fa-exclamation-circle', icon: 'fas fa-exclamation-circle',
text: i18n.locale.reportAbuse, text: i18n.locale.reportAbuse,

View file

@ -1,5 +1,6 @@
import { isScreenTouching } from '@/os'; import { isScreenTouching } from '@/os';
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import { isDeviceTouch } from './is-device-touch';
export function useTooltip(onShow: (showing: Ref<boolean>) => void) { export function useTooltip(onShow: (showing: Ref<boolean>) => void) {
let isHovering = false; let isHovering = false;
@ -13,7 +14,7 @@ export function useTooltip(onShow: (showing: Ref<boolean>) => void) {
// iOS(Androidも)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、その対策 // iOS(Androidも)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、その対策
// これが無いと、画面に触れてないのにツールチップが出たりしてしまう // これが無いと、画面に触れてないのにツールチップが出たりしてしまう
if (!isScreenTouching) return; if (isDeviceTouch && !isScreenTouching) return;
const showing = ref(true); const showing = ref(true);
onShow(showing); onShow(showing);

View file

@ -0,0 +1,205 @@
<template>
<div class="kmwsukvl">
<div>
<button v-click-anime class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button>
<MkA v-click-anime class="item index" active-class="active" to="/" exact>
<i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}">
<i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span>
<span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span>
</component>
</template>
<div class="divider"></div>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin">
<i class="fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span>
</MkA>
<button v-click-anime class="item _button" @click="more">
<i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<span v-if="otherMenuItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
</button>
<MkA v-click-anime class="item" active-class="active" to="/settings">
<i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
</MkA>
<button class="item _button post" data-cy-open-post-form @click="post">
<i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</button>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, toRef, watch } from 'vue';
import { host } from '@/config';
import { search } from '@/scripts/search';
import * as os from '@/os';
import { menuDef } from '@/menu';
import { openAccountMenu } from '@/account';
import { defaultStore } from '@/store';
export default defineComponent({
setup(props, context) {
const menu = toRef(defaultStore.state, 'menu');
const otherMenuItemIndicated = computed(() => {
for (const def in menuDef) {
if (menu.value.includes(def)) continue;
if (menuDef[def].indicated) return true;
}
return false;
});
return {
host: host,
accounts: [],
connection: null,
menu,
menuDef: menuDef,
otherMenuItemIndicated,
post: os.post,
search,
openAccountMenu,
more: () => {
os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed');
},
};
},
});
</script>
<style lang="scss" scoped>
.kmwsukvl {
$ui-font-size: 1em; // TODO:
$avatar-size: 32px;
$avatar-margin: 8px;
> div {
> .divider {
margin: 16px 16px;
border-top: solid 0.5px var(--divider);
}
> .item {
position: relative;
display: block;
padding-left: 24px;
font-size: $ui-font-size;
line-height: 2.85rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> i {
position: relative;
width: 32px;
}
> i,
> .avatar {
margin-right: $avatar-margin;
}
> .avatar {
width: $avatar-size;
height: $avatar-size;
vertical-align: middle;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative;
font-size: 0.9em;
}
&:hover {
text-decoration: none;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
&:hover, &.active {
&:before {
content: "";
display: block;
width: calc(100% - 24px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
}
&:first-child, &:last-child {
position: sticky;
z-index: 1;
padding-top: 8px;
padding-bottom: 8px;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
}
&:first-child {
top: 0;
&:hover, &.active {
&:before {
content: none;
}
}
}
&:last-child {
bottom: 0;
color: var(--fgOnAccent);
&:before {
content: "";
display: block;
width: calc(100% - 20px);
height: calc(100% - 20px);
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
}
}
}
}
</style>

View file

@ -1,386 +1,305 @@
<template> <template>
<div class="mvcprjjd"> <div class="mvcprjjd" :class="{ iconOnly }">
<transition name="nav-back"> <div>
<div v-if="showing" <button v-click-anime class="item _button account" @click="openAccountMenu">
class="nav-back _modalBg" <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
@click="showing = false" </button>
@touchstart.passive="showing = false" <MkA v-click-anime class="item index" active-class="active" to="/" exact>
></div> <i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span>
</transition> </MkA>
<template v-for="item in menu">
<transition name="nav"> <div v-if="item === '-'" class="divider"></div>
<nav v-show="showing" class="nav" :class="{ iconOnly, hidden }"> <component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}">
<div> <i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span>
<button v-click-anime class="item _button account" @click="openAccountMenu"> <span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span>
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> </component>
</button> </template>
<MkA v-click-anime class="item index" active-class="active" to="/" exact> <div class="divider"></div>
<i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span> <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin">
</MkA> <i class="fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span>
<template v-for="item in menu"> </MkA>
<div v-if="item === '-'" class="divider"></div> <button v-click-anime class="item _button" @click="more">
<component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}"> <i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span> <span v-if="otherMenuItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
<span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> </button>
</component> <MkA v-click-anime class="item" active-class="active" to="/settings">
</template> <i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
<div class="divider"></div> </MkA>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin"> <button class="item _button post" data-cy-open-post-form @click="post">
<i class="fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span> <i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</MkA> </button>
<button v-click-anime class="item _button" @click="more"> </div>
<i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<span v-if="otherNavItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
</button>
<MkA v-click-anime class="item" active-class="active" to="/settings">
<i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
</MkA>
<button class="item _button post" data-cy-open-post-form @click="post">
<i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</button>
</div>
</nav>
</transition>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, ref, watch } from 'vue';
import { host } from '@/config'; import { host } from '@/config';
import { search } from '@/scripts/search'; import { search } from '@/scripts/search';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu'; import { menuDef } from '@/menu';
import { openAccountMenu } from '@/account'; import { openAccountMenu } from '@/account';
import { defaultStore } from '@/store';
export default defineComponent({ export default defineComponent({
props: { setup(props, context) {
defaultHidden: { const iconOnly = ref(false);
type: Boolean,
required: false,
default: false,
}
},
data() { const menu = computed(() => defaultStore.state.menu);
return { const otherMenuItemIndicated = computed(() => {
host: host, for (const def in menuDef) {
showing: false, if (menu.value.includes(def)) continue;
accounts: [], if (menuDef[def].indicated) return true;
connection: null,
menuDef: menuDef,
iconOnly: false,
hidden: this.defaultHidden,
};
},
computed: {
menu(): string[] {
return this.$store.state.menu;
},
otherNavItemIndicated(): boolean {
for (const def in this.menuDef) {
if (this.menu.includes(def)) continue;
if (this.menuDef[def].indicated) return true;
} }
return false; return false;
}, });
const calcViewState = () => {
iconOnly.value = (window.innerWidth <= 1279) || (defaultStore.state.menuDisplay === 'sideIcon');
};
calcViewState();
window.addEventListener('resize', calcViewState);
watch(defaultStore.reactiveState.menuDisplay, () => {
calcViewState();
});
return {
host: host,
accounts: [],
connection: null,
menu,
menuDef: menuDef,
otherMenuItemIndicated,
iconOnly,
post: os.post,
search,
openAccountMenu,
more: () => {
os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed');
},
};
}, },
watch: {
$route(to, from) {
this.showing = false;
},
'$store.reactiveState.menuDisplay.value'() {
this.calcViewState();
},
iconOnly() {
this.$nextTick(() => {
this.$emit('change-view-mode');
});
},
hidden() {
this.$nextTick(() => {
this.$emit('change-view-mode');
});
}
},
created() {
window.addEventListener('resize', this.calcViewState);
this.calcViewState();
},
methods: {
calcViewState() {
this.iconOnly = (window.innerWidth <= 1279) || (this.$store.state.menuDisplay === 'sideIcon');
if (!this.defaultHidden) {
this.hidden = (window.innerWidth <= 650);
}
},
show() {
this.showing = true;
},
post() {
os.post();
},
search() {
search();
},
more(ev) {
os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed');
},
openAccountMenu,
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.nav-enter-active,
.nav-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-enter-from,
.nav-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.nav-back-enter-active,
.nav-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-back-enter-from,
.nav-back-leave-active {
opacity: 0;
}
.mvcprjjd { .mvcprjjd {
$ui-font-size: 1em; // TODO: $ui-font-size: 1em; // TODO:
$nav-width: 250px; $nav-width: 250px;
$nav-icon-only-width: 86px; $nav-icon-only-width: 86px;
$avatar-size: 32px;
$avatar-margin: 8px;
> .nav-back { flex: 0 0 $nav-width;
width: $nav-width;
box-sizing: border-box;
> div {
position: fixed;
top: 0;
left: 0;
z-index: 1001; z-index: 1001;
}
> .nav {
$avatar-size: 32px;
$avatar-margin: 8px;
flex: 0 0 $nav-width;
width: $nav-width; width: $nav-width;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box; box-sizing: border-box;
overflow: auto;
overflow-x: clip;
background: var(--navBg);
&.iconOnly { > .divider {
flex: 0 0 $nav-icon-only-width; margin: 16px 16px;
width: $nav-icon-only-width; border-top: solid 0.5px var(--divider);
&:not(.hidden) {
> div {
width: $nav-icon-only-width;
> .divider {
margin: 8px auto;
width: calc(100% - 32px);
}
> .item {
padding-left: 0;
padding: 18px 0;
width: 100%;
text-align: center;
font-size: $ui-font-size * 1.1;
line-height: initial;
> i,
> .avatar {
display: block;
margin: 0 auto;
}
> i {
opacity: 0.7;
}
> .text {
display: none;
}
&:hover, &.active {
> i, > .text {
opacity: 1;
}
}
&:first-child {
margin-bottom: 8px;
}
&:last-child {
margin-top: 8px;
}
}
}
}
} }
&.hidden { > .item {
position: fixed; position: relative;
top: 0; display: block;
left: 0; padding-left: 24px;
z-index: 1001; font-size: $ui-font-size;
} line-height: 2.85rem;
text-overflow: ellipsis;
&:not(.hidden) { overflow: hidden;
display: block !important; white-space: nowrap;
} width: 100%;
text-align: left;
> div {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: $nav-width;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box; box-sizing: border-box;
overflow: auto; color: var(--navFg);
overflow-x: clip;
background: var(--navBg);
> .divider { > i {
margin: 16px 16px; position: relative;
border-top: solid 0.5px var(--divider); width: 32px;
} }
> .item { > i,
> .avatar {
margin-right: $avatar-margin;
}
> .avatar {
width: $avatar-size;
height: $avatar-size;
vertical-align: middle;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative; position: relative;
display: block; font-size: 0.9em;
padding-left: 24px; }
font-size: $ui-font-size;
line-height: 2.85rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> i { &:hover {
position: relative; text-decoration: none;
width: 32px; color: var(--navHoverFg);
} }
> i, &.active {
> .avatar { color: var(--navActive);
margin-right: $avatar-margin; }
}
> .avatar { &:hover, &.active {
width: $avatar-size; &:before {
height: $avatar-size; content: "";
vertical-align: middle; display: block;
} width: calc(100% - 24px);
height: 100%;
> .indicator { margin: auto;
position: absolute; position: absolute;
top: 0; top: 0;
left: 20px; left: 0;
color: var(--navIndicator); right: 0;
font-size: 8px; bottom: 0;
animation: blink 1s infinite; border-radius: 999px;
background: var(--accentedBg);
} }
}
> .text { &:first-child, &:last-child {
position: relative; position: sticky;
font-size: 0.9em; z-index: 1;
} padding-top: 8px;
padding-bottom: 8px;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
}
&:hover { &:first-child {
text-decoration: none; top: 0;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
&:hover, &.active { &:hover, &.active {
&:before { &:before {
content: ""; content: none;
display: block;
width: calc(100% - 24px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
} }
} }
}
&:first-child, &:last-child { &:last-child {
position: sticky; bottom: 0;
z-index: 1; color: var(--fgOnAccent);
padding-top: 8px;
padding-bottom: 8px; &:before {
background: var(--X14); content: "";
-webkit-backdrop-filter: var(--blur, blur(8px)); display: block;
backdrop-filter: var(--blur, blur(8px)); width: calc(100% - 20px);
height: calc(100% - 20px);
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
}
}
}
&.iconOnly {
flex: 0 0 $nav-icon-only-width;
width: $nav-icon-only-width;
> div {
width: $nav-icon-only-width;
> .divider {
margin: 8px auto;
width: calc(100% - 32px);
}
> .item {
padding-left: 0;
padding: 18px 0;
width: 100%;
text-align: center;
font-size: $ui-font-size * 1.1;
line-height: initial;
> i,
> .avatar {
display: block;
margin: 0 auto;
}
> i {
opacity: 0.7;
}
> .text {
display: none;
}
&:hover, &.active {
> i, > .text {
opacity: 1;
}
} }
&:first-child { &:first-child {
top: 0; margin-bottom: 8px;
&:hover, &.active {
&:before {
content: none;
}
}
} }
&:last-child { &:last-child {
bottom: 0; margin-top: 8px;
color: var(--fgOnAccent); }
&:before { &:before {
content: ""; width: 100%;
display: block; border-radius: 0;
width: calc(100% - 20px); }
height: calc(100% - 20px);
margin: auto; &.post {
position: absolute; height: $nav-icon-only-width;
top: 0;
left: 0; > i {
right: 0; opacity: 1;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
} }
} }
&.post:before {
width: calc(100% - 32px);
height: calc(100% - 32px);
border-radius: 100%;
}
} }
} }
} }

View file

@ -632,6 +632,7 @@ export default defineComponent({
text: this.$ts.pin, text: this.$ts.pin,
action: () => this.togglePin(true) action: () => this.togglePin(true)
} : undefined, } : undefined,
/*
...(this.$i.isModerator || this.$i.isAdmin ? [ ...(this.$i.isModerator || this.$i.isAdmin ? [
null, null,
{ {
@ -640,7 +641,7 @@ export default defineComponent({
action: this.promote action: this.promote
}] }]
: [] : []
), ),*/
...(this.appearNote.userId != this.$i.id ? [ ...(this.appearNote.userId != this.$i.id ? [
null, null,
{ {

View file

@ -1,16 +1,14 @@
<template> <template>
<div class="mk-app" :class="{ wallpaper, isMobile }" :style="`--globalHeaderHeight:${globalHeaderHeight}px`"> <div class="gbhvwtnk" :class="{ wallpaper }" :style="`--globalHeaderHeight:${globalHeaderHeight}px`">
<XHeaderMenu v-if="showMenuOnTop" v-get-size="(w, h) => globalHeaderHeight = h"/> <XHeaderMenu v-if="showMenuOnTop" v-get-size="(w, h) => globalHeaderHeight = h"/>
<div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }"> <div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }">
<template v-if="!isMobile"> <div v-if="!showMenuOnTop" class="sidebar">
<div v-if="!showMenuOnTop" class="sidebar"> <XSidebar/>
<XSidebar/> </div>
</div> <div v-else ref="widgetsLeft" class="widgets left">
<div v-else ref="widgetsLeft" class="widgets left"> <XWidgets :place="'left'" @mounted="attachSticky('widgetsLeft')"/>
<XWidgets :place="'left'" @mounted="attachSticky('widgetsLeft')"/> </div>
</div>
</template>
<main class="main" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu"> <main class="main" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu">
<div class="content"> <div class="content">
@ -32,16 +30,6 @@
</div> </div>
</div> </div>
<div v-if="isMobile" class="buttons">
<button ref="navButton" class="button nav _button" @click="showDrawerNav"><i class="fas fa-bars"></i><span v-if="navIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
<button class="button post _button" @click="post"><i class="fas fa-pencil-alt"></i></button>
</div>
<XDrawerSidebar v-if="isMobile" ref="drawerNav" class="sidebar"/>
<transition name="tray-back"> <transition name="tray-back">
<div v-if="widgetsShowing" <div v-if="widgetsShowing"
class="tray-back _modalBg" class="tray-back _modalBg"
@ -65,20 +53,17 @@ import { defineComponent, defineAsyncComponent, markRaw } from 'vue';
import { instanceName } from '@/config'; import { instanceName } from '@/config';
import { StickySidebar } from '@/scripts/sticky-sidebar'; import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from './classic.sidebar.vue'; import XSidebar from './classic.sidebar.vue';
import XDrawerSidebar from '@/ui/_common_/sidebar.vue';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu'; import { menuDef } from '@/menu';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
const DESKTOP_THRESHOLD = 1100; const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 600;
export default defineComponent({ export default defineComponent({
components: { components: {
XCommon, XCommon,
XSidebar, XSidebar,
XDrawerSidebar,
XHeaderMenu: defineAsyncComponent(() => import('./classic.header.vue')), XHeaderMenu: defineAsyncComponent(() => import('./classic.header.vue')),
XWidgets: defineAsyncComponent(() => import('./classic.widgets.vue')), XWidgets: defineAsyncComponent(() => import('./classic.widgets.vue')),
}, },
@ -86,6 +71,7 @@ export default defineComponent({
provide() { provide() {
return { return {
shouldHeaderThin: this.showMenuOnTop, shouldHeaderThin: this.showMenuOnTop,
shouldSpacerMin: true,
}; };
}, },
@ -94,7 +80,6 @@ export default defineComponent({
pageInfo: null, pageInfo: null,
menuDef: menuDef, menuDef: menuDef,
globalHeaderHeight: 0, globalHeaderHeight: 0,
isMobile: window.innerWidth <= MOBILE_THRESHOLD,
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
widgetsShowing: false, widgetsShowing: false,
fullView: false, fullView: false,
@ -103,20 +88,17 @@ export default defineComponent({
}, },
computed: { computed: {
navIndicated(): boolean {
for (const def in this.menuDef) {
if (def === 'notifications') continue; //
if (this.menuDef[def].indicated) return true;
}
return false;
},
showMenuOnTop(): boolean { showMenuOnTop(): boolean {
return !this.isMobile && this.$store.state.menuDisplay === 'top'; return this.$store.state.menuDisplay === 'top';
} }
}, },
created() { created() {
if (window.innerWidth < 1024) {
localStorage.setItem('ui', 'default');
location.reload();
}
document.documentElement.style.overflowY = 'scroll'; document.documentElement.style.overflowY = 'scroll';
if (this.$store.state.widgets.length === 0) { if (this.$store.state.widgets.length === 0) {
@ -135,7 +117,6 @@ export default defineComponent({
mounted() { mounted() {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
this.isMobile = (window.innerWidth <= MOBILE_THRESHOLD);
this.isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD); this.isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD);
}, { passive: true }); }, { passive: true });
@ -178,22 +159,10 @@ export default defineComponent({
}, { passive: true }); }, { passive: true });
}, },
post() {
os.post();
},
top() { top() {
window.scroll({ top: 0, behavior: 'smooth' }); window.scroll({ top: 0, behavior: 'smooth' });
}, },
back() {
history.back();
},
showDrawerNav() {
this.$refs.drawerNav.show();
},
onTransition() { onTransition() {
if (window._scroll) window._scroll(); if (window._scroll) window._scroll();
}, },
@ -257,10 +226,9 @@ export default defineComponent({
opacity: 0; opacity: 0;
} }
.mk-app { .gbhvwtnk {
$ui-font-size: 1em; $ui-font-size: 1em;
$widgets-hide-threshold: 1200px; $widgets-hide-threshold: 1200px;
$nav-icon-only-width: 78px; // TODO:
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ // 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
min-height: calc(var(--vh, 1vh) * 100); min-height: calc(var(--vh, 1vh) * 100);
@ -271,21 +239,6 @@ export default defineComponent({
//backdrop-filter: var(--blur, blur(4px)); //backdrop-filter: var(--blur, blur(4px));
} }
&.isMobile {
> .columns {
display: block;
margin: 0;
> .main {
margin: 0;
padding-bottom: 92px;
border: none;
width: 100%;
border-radius: 0;
}
}
}
> .columns { > .columns {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -371,76 +324,6 @@ export default defineComponent({
} }
} }
> .buttons {
position: fixed;
z-index: 1000;
bottom: 0;
padding: 16px;
display: flex;
width: 100%;
box-sizing: border-box;
-webkit-backdrop-filter: var(--blur, blur(32px));
backdrop-filter: var(--blur, blur(32px));
background-color: var(--header);
border-top: solid 0.5px var(--divider);
> .button {
position: relative;
flex: 1;
padding: 0;
margin: auto;
height: 64px;
border-radius: 8px;
background: var(--panel);
color: var(--fg);
&:not(:last-child) {
margin-right: 12px;
}
@media (max-width: 400px) {
height: 60px;
&:not(:last-child) {
margin-right: 8px;
}
}
&:hover {
background: var(--X2);
}
> .indicator {
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
> * {
font-size: 22px;
}
&:disabled {
cursor: default;
> * {
opacity: 0.5;
}
}
}
}
> .tray-back { > .tray-back {
z-index: 1001; z-index: 1001;
} }

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="mk-deck" :class="`${deckStore.reactiveState.columnAlign.value}`" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }" <div class="mk-deck" :class="[{ isMobile }, `${deckStore.reactiveState.columnAlign.value}`]" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }"
@contextmenu.self.prevent="onContextmenu" @contextmenu.self.prevent="onContextmenu"
> >
<XSidebar ref="nav"/> <XSidebar v-if="!isMobile"/>
<template v-for="ids in layout"> <template v-for="ids in layout">
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため --> <!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
@ -22,91 +22,76 @@
/> />
</template> </template>
<button v-if="$i" class="nav _button" @click="showNav()"><i class="fas fa-bars"></i><span v-if="navIndicated" class="indicator"><i class="fas fa-circle"></i></span></button> <div v-if="isMobile" class="buttons">
<button v-if="$i" class="post _buttonPrimary" @click="post()"><i class="fas fa-pencil-alt"></i></button> <button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="$router.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button>
</div>
<transition name="menu-back">
<div v-if="drawerMenuShowing"
class="menu-back _modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</transition>
<transition name="menu">
<XDrawerMenu v-if="drawerMenuShowing" class="menu"/>
</transition>
<XCommon/> <XCommon/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, provide, ref, watch } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { host } from '@/config';
import DeckColumnCore from '@/ui/deck/column-core.vue'; import DeckColumnCore from '@/ui/deck/column-core.vue';
import XSidebar from '@/ui/_common_/sidebar.vue'; import XSidebar from '@/ui/_common_/sidebar.vue';
import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
import { getScrollContainer } from '@/scripts/scroll'; import { getScrollContainer } from '@/scripts/scroll';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu'; import { menuDef } from '@/menu';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import { deckStore, addColumn, loadDeck } from './deck/deck-store'; import { deckStore, addColumn as addColumnToStore, loadDeck } from './deck/deck-store';
import { useRoute } from 'vue-router';
import { $i } from '@/account';
import { i18n } from '@/i18n';
export default defineComponent({ export default defineComponent({
components: { components: {
XCommon, XCommon,
XSidebar, XSidebar,
XDrawerMenu,
DeckColumnCore, DeckColumnCore,
}, },
provide() { setup() {
return deckStore.state.navWindow ? { const isMobile = ref(window.innerWidth <= 500);
navHook: (url) => { window.addEventListener('resize', () => {
os.pageWindow(url); isMobile.value = window.innerWidth <= 500;
} });
} : {};
},
data() { const drawerMenuShowing = ref(false);
return {
deckStore,
host: host,
menuDef: menuDef,
wallpaper: localStorage.getItem('wallpaper') != null,
};
},
computed: { const route = useRoute();
columns() { watch(route, () => {
return deckStore.reactiveState.columns.value; drawerMenuShowing.value = false;
}, });
layout() {
return deckStore.reactiveState.layout.value; const columns = deckStore.reactiveState.columns;
}, const layout = deckStore.reactiveState.layout.value;
navIndicated(): boolean { const menuIndicated = computed(() => {
if (!this.$i) return false; if ($i == null) return false;
for (const def in this.menuDef) { for (const def in menuDef) {
if (this.menuDef[def].indicated) return true; if (menuDef[def].indicated) return true;
} }
return false; return false;
}, });
},
created() { const addColumn = async (ev) => {
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', this.onWheel);
loadDeck();
},
mounted() {
},
methods: {
onWheel(e) {
if (getScrollContainer(e.target) == null) {
document.documentElement.scrollLeft += e.deltaY > 0 ? 96 : -96;
}
},
showNav() {
this.$refs.nav.show();
},
post() {
os.post();
},
async addColumn(ev) {
const columns = [ const columns = [
'main', 'main',
'widgets', 'widgets',
@ -119,33 +104,83 @@ export default defineComponent({
]; ];
const { canceled, result: column } = await os.select({ const { canceled, result: column } = await os.select({
title: this.$ts._deck.addColumn, title: i18n.locale._deck.addColumn,
items: columns.map(column => ({ items: columns.map(column => ({
value: column, text: this.$t('_deck._columns.' + column) value: column, text: i18n.t('_deck._columns.' + column)
})) }))
}); });
if (canceled) return; if (canceled) return;
addColumn({ addColumnToStore({
type: column, type: column,
id: uuid(), id: uuid(),
name: this.$t('_deck._columns.' + column), name: i18n.t('_deck._columns.' + column),
width: 330, width: 330,
}); });
}, };
onContextmenu(e) { const onContextmenu = (ev) => {
os.contextMenu([{ os.contextMenu([{
text: this.$ts._deck.addColumn, text: i18n.locale._deck.addColumn,
icon: null, icon: null,
action: this.addColumn action: addColumn
}], e); }], ev);
}, };
}
provide('shouldSpacerMin', true);
if (deckStore.state.navWindow) {
provide('navHook', (url) => {
os.pageWindow(url);
});
}
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', (ev) => {
if (getScrollContainer(ev.target) == null) {
document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96;
}
});
loadDeck();
return {
isMobile,
deckStore,
drawerMenuShowing,
columns,
layout,
menuIndicated,
onContextmenu,
wallpaper: localStorage.getItem('wallpaper') != null,
post: os.post,
};
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.menu-enter-active,
.menu-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menu-enter-from,
.menu-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.menu-back-enter-active,
.menu-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menu-back-enter-from,
.menu-back-leave-active {
opacity: 0;
}
.mk-deck { .mk-deck {
$nav-hide-threshold: 650px; // TODO: $nav-hide-threshold: 650px; // TODO:
@ -169,6 +204,10 @@ export default defineComponent({
} }
} }
&.isMobile {
padding-bottom: 100px;
}
> .column { > .column {
flex-shrink: 0; flex-shrink: 0;
margin-right: var(--deckMargin); margin-right: var(--deckMargin);
@ -183,43 +222,89 @@ export default defineComponent({
} }
} }
> .post, > .buttons {
> .nav {
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
bottom: 32px; bottom: 0;
width: 64px; left: 0;
height: 64px; padding: 16px;
border-radius: 100%; display: flex;
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); width: 100%;
font-size: 22px; box-sizing: border-box;
@media (min-width: ($nav-hide-threshold + 1px)) { > .button {
display: none; position: relative;
flex: 1;
padding: 0;
margin: auto;
height: 64px;
border-radius: 8px;
background: var(--panel);
color: var(--fg);
&:not(:last-child) {
margin-right: 12px;
}
@media (max-width: 400px) {
height: 60px;
&:not(:last-child) {
margin-right: 8px;
}
}
&:hover {
background: var(--X2);
}
> .indicator {
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
> * {
font-size: 22px;
}
&:disabled {
cursor: default;
> * {
opacity: 0.5;
}
}
} }
} }
> .post { > .menu-back {
right: 32px; z-index: 1001;
} }
> .nav { > .menu {
left: 32px; position: fixed;
background: var(--panel); top: 0;
color: var(--fg); left: 0;
z-index: 1001;
&:hover { // 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
background: var(--X2); height: calc(var(--vh, 1vh) * 100);
} width: 240px;
box-sizing: border-box;
> .indicator { overflow: auto;
position: absolute; overscroll-behavior: contain;
top: 0; background: var(--bg);
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
} }
} }
</style> </style>

View file

@ -401,6 +401,7 @@ export default defineComponent({
height: calc(100% - var(--deckColumnHeaderHeight)); height: calc(100% - var(--deckColumnHeaderHeight));
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
box-sizing: border-box; box-sizing: border-box;
} }

View file

@ -1,9 +1,9 @@
<template> <template>
<div class="mk-app" :class="{ wallpaper }"> <div class="dkgtipfy" :class="{ wallpaper }">
<XSidebar ref="nav" class="sidebar"/> <XSidebar v-if="!isMobile" class="sidebar"/>
<div ref="contents" class="contents" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu"> <div class="contents" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu">
<main ref="main"> <main>
<div class="content"> <div class="content">
<MkStickyContainer> <MkStickyContainer>
<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
@ -20,32 +20,44 @@
</main> </main>
</div> </div>
<XSide v-if="isDesktop" ref="side" class="side"/> <XSideView v-if="isDesktop" ref="side" class="side"/>
<div v-if="isDesktop" ref="widgets" class="widgets"> <div v-if="isDesktop" ref="widgetsEl" class="widgets">
<XWidgets @mounted="attachSticky"/> <XWidgets @mounted="attachSticky"/>
</div> </div>
<div class="buttons" :class="{ navHidden }"> <button class="widgetButton _button" :class="{ show: true }" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
<button ref="navButton" class="button nav _button" @click="showNav"><i class="fas fa-bars"></i><span v-if="navIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<div v-if="isMobile" class="buttons">
<button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button> <button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> <button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button> <button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
<button class="button post _button" @click="post"><i class="fas fa-pencil-alt"></i></button> <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button>
</div> </div>
<button class="widgetButton _button" :class="{ navHidden }" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button> <transition name="menuDrawer-back">
<div v-if="drawerMenuShowing"
class="menuDrawer-back _modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</transition>
<transition name="tray-back"> <transition name="menuDrawer">
<XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/>
</transition>
<transition name="widgetsDrawer-back">
<div v-if="widgetsShowing" <div v-if="widgetsShowing"
class="tray-back _modalBg" class="widgetsDrawer-back _modalBg"
@click="widgetsShowing = false" @click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false" @touchstart.passive="widgetsShowing = false"
></div> ></div>
</transition> </transition>
<transition name="tray"> <transition name="widgetsDrawer">
<XWidgets v-if="widgetsShowing" class="tray"/> <XWidgets v-if="widgetsShowing" class="widgetsDrawer"/>
</transition> </transition>
<XCommon/> <XCommon/>
@ -53,60 +65,69 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue'; import { defineComponent, defineAsyncComponent, provide, onMounted, computed, ref, watch } from 'vue';
import { instanceName } from '@/config'; import { instanceName } from '@/config';
import { StickySidebar } from '@/scripts/sticky-sidebar'; import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from '@/ui/_common_/sidebar.vue'; import XSidebar from '@/ui/_common_/sidebar.vue';
import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import XSide from './classic.side.vue'; import XSideView from './classic.side.vue';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
import { defaultStore } from '@/store';
import * as EventEmitter from 'eventemitter3';
import { menuDef } from '@/menu';
import { useRoute } from 'vue-router';
import { i18n } from '@/i18n';
const DESKTOP_THRESHOLD = 1100; const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 500;
export default defineComponent({ export default defineComponent({
components: { components: {
XCommon, XCommon,
XSidebar, XSidebar,
XDrawerMenu,
XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')), XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')),
XSide, // NOTE: dynamic importAsyncComponentWrapperref XSideView, // NOTE: dynamic importAsyncComponentWrapperref
}, },
provide() { setup() {
return { const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
sideViewHook: this.isDesktop ? (url) => { const isMobile = ref(window.innerWidth <= MOBILE_THRESHOLD);
this.$refs.side.navigate(url); window.addEventListener('resize', () => {
} : null isMobile.value = window.innerWidth <= MOBILE_THRESHOLD;
}; });
},
data() { const pageInfo = ref();
return { const widgetsEl = ref<HTMLElement>();
pageInfo: null, const widgetsShowing = ref(false);
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
menuDef: menuDef,
navHidden: false,
widgetsShowing: false,
wallpaper: localStorage.getItem('wallpaper') != null,
};
},
computed: { const sideViewController = new EventEmitter();
navIndicated(): boolean {
for (const def in this.menuDef) { provide('sideViewHook', isDesktop.value ? (url) => {
sideViewController.emit('navigate', url);
} : null);
const menuIndicated = computed(() => {
for (const def in menuDef) {
if (def === 'notifications') continue; // if (def === 'notifications') continue; //
if (this.menuDef[def].indicated) return true; if (menuDef[def].indicated) return true;
} }
return false; return false;
} });
},
const drawerMenuShowing = ref(false);
const route = useRoute();
watch(route, () => {
drawerMenuShowing.value = false;
});
created() {
document.documentElement.style.overflowY = 'scroll'; document.documentElement.style.overflowY = 'scroll';
if (this.$store.state.widgets.length === 0) { if (defaultStore.state.widgets.length === 0) {
this.$store.set('widgets', [{ defaultStore.set('widgets', [{
name: 'calendar', name: 'calendar',
id: 'a', place: 'right', data: {} id: 'a', place: 'right', data: {}
}, { }, {
@ -117,123 +138,129 @@ export default defineComponent({
id: 'c', place: 'right', data: {} id: 'c', place: 'right', data: {}
}]); }]);
} }
},
mounted() { onMounted(() => {
this.adjustUI(); if (!isDesktop.value) {
window.addEventListener('resize', () => {
const ro = new ResizeObserver((entries, observer) => { if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
this.adjustUI(); }, { passive: true });
}
}); });
ro.observe(this.$refs.contents); const changePage = (page) => {
window.addEventListener('resize', this.adjustUI, { passive: true });
if (!this.isDesktop) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
}, { passive: true });
}
},
methods: {
changePage(page) {
if (page == null) return; if (page == null) return;
if (page[symbols.PAGE_INFO]) { if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO]; pageInfo.value = page[symbols.PAGE_INFO];
document.title = `${this.pageInfo.title} | ${instanceName}`; document.title = `${pageInfo.value.title} | ${instanceName}`;
} }
}, };
adjustUI() { const onContextmenu = (ev) => {
const navWidth = this.$refs.nav.$el.offsetWidth;
this.navHidden = navWidth === 0;
},
showNav() {
this.$refs.nav.show();
},
attachSticky(el) {
const sticky = new StickySidebar(this.$refs.widgets);
window.addEventListener('scroll', () => {
sticky.calc(window.scrollY);
}, { passive: true });
},
post() {
os.post();
},
top() {
window.scroll({ top: 0, behavior: 'smooth' });
},
back() {
history.back();
},
onTransition() {
if (window._scroll) window._scroll();
},
onContextmenu(e) {
const isLink = (el: HTMLElement) => { const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true; if (el.tagName === 'A') return true;
if (el.parentElement) { if (el.parentElement) {
return isLink(el.parentElement); return isLink(el.parentElement);
} }
}; };
if (isLink(e.target)) return; if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
if (window.getSelection().toString() !== '') return; if (window.getSelection().toString() !== '') return;
const path = this.$route.path; const path = route.path;
os.contextMenu([{ os.contextMenu([{
type: 'label', type: 'label',
text: path, text: path,
}, { }, {
icon: 'fas fa-columns', icon: 'fas fa-columns',
text: this.$ts.openInSideView, text: i18n.locale.openInSideView,
action: () => { action: () => {
this.$refs.side.navigate(path); this.$refs.side.navigate(path);
} }
}, { }, {
icon: 'fas fa-window-maximize', icon: 'fas fa-window-maximize',
text: this.$ts.openInWindow, text: i18n.locale.openInWindow,
action: () => { action: () => {
os.pageWindow(path); os.pageWindow(path);
} }
}], e); }], ev);
}, };
}
const attachSticky = (el) => {
const sticky = new StickySidebar(widgetsEl.value);
window.addEventListener('scroll', () => {
sticky.calc(window.scrollY);
}, { passive: true });
};
return {
pageInfo,
isDesktop,
isMobile,
widgetsEl,
widgetsShowing,
drawerMenuShowing,
menuIndicated,
wallpaper: localStorage.getItem('wallpaper') != null,
changePage,
top: () => {
window.scroll({ top: 0, behavior: 'smooth' });
},
onTransition: () => {
if (window._scroll) window._scroll();
},
post: os.post,
onContextmenu,
attachSticky,
};
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.tray-enter-active, .widgetsDrawer-enter-active,
.tray-leave-active { .widgetsDrawer-leave-active {
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
} }
.tray-enter-from, .widgetsDrawer-enter-from,
.tray-leave-active { .widgetsDrawer-leave-active {
opacity: 0; opacity: 0;
transform: translateX(240px); transform: translateX(240px);
} }
.tray-back-enter-active, .widgetsDrawer-back-enter-active,
.tray-back-leave-active { .widgetsDrawer-back-leave-active {
opacity: 1; opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
} }
.tray-back-enter-from, .widgetsDrawer-back-enter-from,
.tray-back-leave-active { .widgetsDrawer-back-leave-active {
opacity: 0; opacity: 0;
} }
.mk-app { .menuDrawer-enter-active,
.menuDrawer-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menuDrawer-enter-from,
.menuDrawer-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.menuDrawer-back-enter-active,
.menuDrawer-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menuDrawer-back-enter-from,
.menuDrawer-back-leave-active {
opacity: 0;
}
.dkgtipfy {
$ui-font-size: 1em; // TODO: $ui-font-size: 1em; // TODO:
$widgets-hide-threshold: 1090px; $widgets-hide-threshold: 1090px;
@ -248,6 +275,7 @@ export default defineComponent({
} }
> .sidebar { > .sidebar {
border-right: solid 0.5px var(--divider);
} }
> .contents { > .contents {
@ -284,6 +312,7 @@ export default defineComponent({
} }
} }
/*
> .widgetButton { > .widgetButton {
display: block; display: block;
position: fixed; position: fixed;
@ -304,12 +333,35 @@ export default defineComponent({
@media (min-width: ($widgets-hide-threshold + 1px)) { @media (min-width: ($widgets-hide-threshold + 1px)) {
display: none; display: none;
} }
}*/
> .widgetButton {
display: none;
}
> .widgetsDrawer-back {
z-index: 1001;
}
> .widgetsDrawer {
position: fixed;
top: 0;
right: 0;
z-index: 1001;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
padding: var(--margin);
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--bg);
} }
> .buttons { > .buttons {
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
bottom: 0; bottom: 0;
left: 0;
padding: 16px; padding: 16px;
display: flex; display: flex;
width: 100%; width: 100%;
@ -318,10 +370,6 @@ export default defineComponent({
backdrop-filter: var(--blur, blur(32px)); backdrop-filter: var(--blur, blur(32px));
background-color: var(--header); background-color: var(--header);
&:not(.navHidden) {
display: none;
}
> .button { > .button {
position: relative; position: relative;
flex: 1; flex: 1;
@ -379,22 +427,24 @@ export default defineComponent({
} }
} }
> .tray-back { > .menuDrawer-back {
z-index: 1001; z-index: 1001;
} }
> .tray { > .menuDrawer {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; left: 0;
z-index: 1001; z-index: 1001;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ // 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100); height: calc(var(--vh, 1vh) * 100);
padding: var(--margin); width: 240px;
box-sizing: border-box; box-sizing: border-box;
overflow: auto; overflow: auto;
overscroll-behavior: contain;
background: var(--bg); background: var(--bg);
} }
} }
</style> </style>

View file

@ -16,7 +16,7 @@
</div> </div>
<div class="announcements panel"> <div class="announcements panel">
<header>{{ $ts.announcements }}</header> <header>{{ $ts.announcements }}</header>
<MkPagination #default="{items}" :pagination="announcements" class="list"> <MkPagination v-slot="{items}" :pagination="announcements" class="list">
<section v-for="announcement in items" :key="announcement.id" class="item"> <section v-for="announcement in items" :key="announcement.id" class="item">
<div class="title">{{ announcement.title }}</div> <div class="title">{{ announcement.title }}</div>
<div class="content"> <div class="content">

View file

@ -631,7 +631,7 @@ blob-util@^2.0.2:
resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==
bluebird@3.7.2, bluebird@^3.7.2: bluebird@3.7.2:
version "3.7.2" version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@ -1115,10 +1115,10 @@ csso@~2.3.1:
clap "^1.0.9" clap "^1.0.9"
source-map "^0.5.3" source-map "^0.5.3"
cypress@9.0.0: cypress@9.1.0:
version "9.0.0" version "9.1.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.0.0.tgz#8c496f7f350e611604cc2f77b663fb81d0c235d2" resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.1.0.tgz#5d23c1b363b7d4853009c74a422a083a8ad2601c"
integrity sha512-/93SWBZTw7BjFZ+I9S8SqkFYZx7VhedDjTtRBmXO0VzTeDbmxgK/snMJm/VFjrqk/caWbI+XY4Qr80myDMQvYg== integrity sha512-fyXcCN51vixkPrz/vO/Qy6WL3hKYJzCQFeWofOpGOFewVVXrGfmfSOGFntXpzWBXsIwPn3wzW0HOFw51jZajNQ==
dependencies: dependencies:
"@cypress/request" "^2.88.7" "@cypress/request" "^2.88.7"
"@cypress/xvfb" "^1.2.4" "@cypress/xvfb" "^1.2.4"
@ -1127,7 +1127,7 @@ cypress@9.0.0:
"@types/sizzle" "^2.3.2" "@types/sizzle" "^2.3.2"
arch "^2.2.0" arch "^2.2.0"
blob-util "^2.0.2" blob-util "^2.0.2"
bluebird "^3.7.2" bluebird "3.7.2"
cachedir "^2.3.0" cachedir "^2.3.0"
chalk "^4.1.0" chalk "^4.1.0"
check-more-types "^2.24.0" check-more-types "^2.24.0"