Compare commits
72 Commits
13.0.0-bet
...
13.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90e2186872 | ||
|
|
3043b2f619 | ||
|
|
d2fc5a248b | ||
|
|
e6d666e1ee | ||
|
|
c5cfbd99d0 | ||
|
|
33b22a323c | ||
|
|
f032fb628a | ||
|
|
7761eb8897 | ||
|
|
58fa8c4a01 | ||
|
|
789d61d175 | ||
|
|
b52fd72727 | ||
|
|
d79905e141 | ||
|
|
cd6b1290cb | ||
|
|
c382497167 | ||
|
|
a8fb578854 | ||
|
|
ff00c90a88 | ||
|
|
d0755b5ce8 | ||
|
|
17fa5667b8 | ||
|
|
01d5e385ec | ||
|
|
af80fee899 | ||
|
|
6b37c09274 | ||
|
|
1453a0f5cf | ||
|
|
1688083e9a | ||
|
|
616594d3cd | ||
|
|
6783178dc3 | ||
|
|
3f033d6ab7 | ||
|
|
d10e000883 | ||
|
|
ce528ff22e | ||
|
|
5e4e02235a | ||
|
|
e4179336e4 | ||
|
|
7823ba494f | ||
|
|
7bdff90415 | ||
|
|
f3c0af7e23 | ||
|
|
72dfbfcf35 | ||
|
|
9cbe878d0b | ||
|
|
618405c4d3 | ||
|
|
0b08fcac4a | ||
|
|
eac6ebb239 | ||
|
|
194fb14e07 | ||
|
|
c2d05b507a | ||
|
|
4df43a9107 | ||
|
|
0da7fcdbed | ||
|
|
1e50b2688a | ||
|
|
c1cd018626 | ||
|
|
b588e8b60b | ||
|
|
06f55ffb37 | ||
|
|
02df6a28cd | ||
|
|
d64abedf9f | ||
|
|
4d39d1caf6 | ||
|
|
d06f61f23f | ||
|
|
c179d6f735 | ||
|
|
3bc0cdbfb7 | ||
|
|
b04155e7ba | ||
|
|
014c97fa85 | ||
|
|
96ccf550b1 | ||
|
|
8f28ff63f1 | ||
|
|
b7dec6e87d | ||
|
|
1bb2c22493 | ||
|
|
39c3995c74 | ||
|
|
8cc80faf20 | ||
|
|
4d66077f85 | ||
|
|
3ece2dc990 | ||
|
|
6071e962f4 | ||
|
|
ed43369797 | ||
|
|
c65957853b | ||
|
|
6a18360269 | ||
|
|
c438bd2e27 | ||
|
|
462acc9eee | ||
|
|
e4144a17a4 | ||
|
|
3cfd017538 | ||
|
|
403849805a | ||
|
|
402b234d15 |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -40,6 +40,8 @@ You should also include the user name that made the change.
|
||||
- Firefox109以下はサポートされなくなりました
|
||||
|
||||
#### For app developers
|
||||
- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました
|
||||
- カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします
|
||||
- API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
|
||||
- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
|
||||
- e.g. `https://p1.a9z.dev/emoji/misskey.webp`
|
||||
@@ -59,12 +61,14 @@ You should also include the user name that made the change.
|
||||
- Server: signToActivityPubGet is set to true by default @syuilo
|
||||
- Server: improve syslog performance @syuilo
|
||||
- Server: improve note scoring for featured notes @CyberRex0
|
||||
- Server: アンケート選択肢の文字数制限を緩和 @syuilo
|
||||
- Server: improve stats api performance @syuilo
|
||||
- Server: improve nodeinfo performance @syuilo
|
||||
- Server: delete outdated notifications regularly to improve db performance @syuilo
|
||||
- Server: delete outdated hard-mutes regularly to improve db performance @syuilo
|
||||
- Server: delete outdated notes of antenna regularly to improve db performance @syuilo
|
||||
- Server: improve activitypub deliver performance @syuilo
|
||||
- Client: use tabler-icons instead of fontawesome to better design @syuilo
|
||||
- Client: Add AiScript App widget
|
||||
- Client: Add new gabber kick sounds (thanks for noizenecio)
|
||||
- Client: Add link to user RSS feed in profile menu @ssmucny
|
||||
- Client: Compress non-animated PNG files @saschanaz
|
||||
@@ -72,15 +76,18 @@ You should also include the user name that made the change.
|
||||
- Client: enhance dashboard of control panel @syuilo
|
||||
- Client: Vite is upgraded to v4 @syuilo, @tamaina
|
||||
- Client: HMR is available while yarn dev @tamaina
|
||||
- Client: Make widgets of universal/classic sync between devices @tamaina
|
||||
- Client: Implement the button to subscribe push notification @tamaina
|
||||
- Client: Implement the toggle to or not to close push notifications when notifications or messages are read @tamaina
|
||||
- Client: Improve RSS widget @tamaina
|
||||
- Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
|
||||
- Client: OpenSearch support @SoniEx2 @chaoticryptidz
|
||||
- Client: Support remote objects in search @SoniEx2
|
||||
- Client: user activity page @syuilo
|
||||
- Client: Make widgets of universal/classic sync between devices @tamaina
|
||||
- Client: add user list widget @syuilo
|
||||
- Client: Add AiScript App widget
|
||||
- Client: add profile widget @syuilo
|
||||
- Client: add instance info widget @syuilo
|
||||
- Client: Improve RSS widget @tamaina
|
||||
- Client: add heatmap of daily active users to about page @syuilo
|
||||
- Client: introduce fluent emoji @syuilo
|
||||
- Client: add new theme @syuilo
|
||||
@@ -100,8 +107,12 @@ You should also include the user name that made the change.
|
||||
- Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
|
||||
- Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
|
||||
- Server: Escape SQL LIKE @mei23
|
||||
- Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu
|
||||
- Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
|
||||
- Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo
|
||||
- Client: 日付形式の文字列などがカスタム絵文字として表示されるのを修正 @syuilo
|
||||
- Client: case insensitive emoji search @saschanaz
|
||||
- Client: 画面の幅が狭いとウィジェットドロワーを閉じる手段がなくなるのを修正 @syuilo
|
||||
- Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
|
||||
- Client: use proxied image for instance icon @syuilo
|
||||
- Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:18.12.1-bullseye AS builder
|
||||
FROM node:18.13.0-bullseye AS builder
|
||||
|
||||
ARG NODE_ENV=production
|
||||
|
||||
@@ -22,7 +22,7 @@ COPY . ./
|
||||
RUN git submodule update --init
|
||||
RUN yarn build
|
||||
|
||||
FROM node:18.12.1-bullseye-slim AS runner
|
||||
FROM node:18.13.0-bullseye-slim AS runner
|
||||
|
||||
WORKDIR /misskey
|
||||
|
||||
|
||||
@@ -1117,6 +1117,8 @@ _weekday:
|
||||
friday: "الجمعة"
|
||||
saturday: "السبت"
|
||||
_widgets:
|
||||
profile: "الملف التعريفي"
|
||||
instanceInfo: "معلومات مثيل الخادم"
|
||||
memo: "ملاحظة لاصقة"
|
||||
notifications: "الإشعارات"
|
||||
timeline: "الخيط الزمني"
|
||||
@@ -1294,7 +1296,6 @@ _notification:
|
||||
youGotReply: "ردّ عليك {name}"
|
||||
youGotQuote: "اقتبس منك {name}"
|
||||
youRenoted: "إعادت نشر من {name}"
|
||||
youGotPoll: "شارك {name} في استطلاع الرأي"
|
||||
youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
|
||||
youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
|
||||
youWereFollowed: "يتابعك"
|
||||
@@ -1311,7 +1312,6 @@ _notification:
|
||||
renote: "أعد النشر"
|
||||
quote: "الاقتباسات"
|
||||
reaction: "التفاعلات"
|
||||
pollVote: "مصوِت شارك في الاستطلاع"
|
||||
receiveFollowRequest: "طلبات المتابعة المتلقاة"
|
||||
followRequestAccepted: "طلبات المتابعة المقبولة"
|
||||
groupInvited: "دعوات الفريق"
|
||||
|
||||
@@ -1200,6 +1200,8 @@ _weekday:
|
||||
friday: "শুক্রবার"
|
||||
saturday: "শনিবার"
|
||||
_widgets:
|
||||
profile: "প্রোফাইল"
|
||||
instanceInfo: "ইন্সট্যান্সের তথ্য"
|
||||
memo: "স্টিকি নোট"
|
||||
notifications: "বিজ্ঞপ্তি"
|
||||
timeline: "টাইমলাইন"
|
||||
@@ -1386,7 +1388,6 @@ _notification:
|
||||
youGotReply: "{name} আপনাকে জবাব দিয়েছে"
|
||||
youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে"
|
||||
youRenoted: "{name} এর Renote"
|
||||
youGotPoll: "{name} আপনার পোলে ভোট দিয়েছে"
|
||||
youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে"
|
||||
youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে"
|
||||
youWereFollowed: "আপনাকে অনুসরণ করছে"
|
||||
@@ -1403,7 +1404,6 @@ _notification:
|
||||
renote: "রিনোট"
|
||||
quote: "উদ্ধৃতি"
|
||||
reaction: "প্রতিক্রিয়া"
|
||||
pollVote: "পোলে ভোট আছে"
|
||||
pollEnded: "পোল শেষ"
|
||||
receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
|
||||
followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
|
||||
|
||||
@@ -399,6 +399,8 @@ _antennaSources:
|
||||
userList: "Publicacions d'una llista d'usuaris"
|
||||
userGroup: "Publicacions d'usuaris d'un grup"
|
||||
_widgets:
|
||||
profile: "Perfil"
|
||||
instanceInfo: "Informació del fitxer d'instal·lació"
|
||||
notifications: "Notificacions"
|
||||
timeline: "Línia de temps"
|
||||
activity: "Activitat"
|
||||
|
||||
@@ -694,6 +694,8 @@ _weekday:
|
||||
friday: "Pátek"
|
||||
saturday: "Sobota"
|
||||
_widgets:
|
||||
profile: "Váš profil"
|
||||
instanceInfo: "Informace o instanci"
|
||||
notifications: "Oznámení"
|
||||
timeline: "Časová osa"
|
||||
calendar: "Kalendář"
|
||||
|
||||
@@ -1302,6 +1302,8 @@ _weekday:
|
||||
friday: "Freitag"
|
||||
saturday: "Samstag"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Instanzinformationen"
|
||||
memo: "Merkzettel"
|
||||
notifications: "Benachrichtigungen"
|
||||
timeline: "Chronik"
|
||||
@@ -1328,6 +1330,7 @@ _widgets:
|
||||
userList: "Benutzerliste"
|
||||
_userList:
|
||||
chooseList: "Liste auswählen"
|
||||
clicker: "Klickzähler"
|
||||
_cw:
|
||||
hide: "Inhalt verbergen"
|
||||
show: "Inhalt anzeigen"
|
||||
@@ -1503,7 +1506,6 @@ _notification:
|
||||
youGotReply: "{name} hat dir geantwortet"
|
||||
youGotQuote: "{name} hat dich zitiert"
|
||||
youRenoted: "Renote deiner Notiz von {name}"
|
||||
youGotPoll: "{name} hat in deiner Umfrage abgestimmt"
|
||||
youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
|
||||
youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
|
||||
youWereFollowed: "ist dir gefolgt"
|
||||
@@ -1521,7 +1523,6 @@ _notification:
|
||||
renote: "Renotes"
|
||||
quote: "Zitationen"
|
||||
reaction: "Reaktionen"
|
||||
pollVote: "Antworten auf Umfragen"
|
||||
pollEnded: "Ende von Umfragen"
|
||||
receiveFollowRequest: "Erhaltene Follow-Anfragen"
|
||||
followRequestAccepted: "Akzeptierte Follow-Anfragen"
|
||||
|
||||
@@ -343,6 +343,8 @@ _antennaSources:
|
||||
userList: "Σημειώματα από καθορισμένη λίστα μελών"
|
||||
userGroup: "Σημειώματα από μέλη καθορισμένης ομάδας"
|
||||
_widgets:
|
||||
profile: "Προφίλ"
|
||||
instanceInfo: "Πληροφορίες του instance"
|
||||
notifications: "Ειδοποιήσεις"
|
||||
timeline: "Χρονολόγιο"
|
||||
calendar: "Ημερολόγιο"
|
||||
|
||||
@@ -1302,6 +1302,8 @@ _weekday:
|
||||
friday: "Friday"
|
||||
saturday: "Saturday"
|
||||
_widgets:
|
||||
profile: "Profile"
|
||||
instanceInfo: "Instance Information"
|
||||
memo: "Sticky notes"
|
||||
notifications: "Notifications"
|
||||
timeline: "Timeline"
|
||||
@@ -1328,6 +1330,7 @@ _widgets:
|
||||
userList: "User list"
|
||||
_userList:
|
||||
chooseList: "Select a list"
|
||||
clicker: "Clicker"
|
||||
_cw:
|
||||
hide: "Hide"
|
||||
show: "Show content"
|
||||
@@ -1503,7 +1506,6 @@ _notification:
|
||||
youGotReply: "{name} replied to you"
|
||||
youGotQuote: "{name} quoted you"
|
||||
youRenoted: "Renote from {name}"
|
||||
youGotPoll: "{name} voted on your poll"
|
||||
youGotMessagingMessageFromUser: "{name} sent you a chat message"
|
||||
youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group"
|
||||
youWereFollowed: "followed you"
|
||||
@@ -1521,7 +1523,6 @@ _notification:
|
||||
renote: "Renotes"
|
||||
quote: "Quotes"
|
||||
reaction: "Reactions"
|
||||
pollVote: "Votes on polls"
|
||||
pollEnded: "Polls ending"
|
||||
receiveFollowRequest: "Received follow requests"
|
||||
followRequestAccepted: "Accepted follow requests"
|
||||
|
||||
@@ -1296,6 +1296,8 @@ _weekday:
|
||||
friday: "Viernes"
|
||||
saturday: "Sábado"
|
||||
_widgets:
|
||||
profile: "Perfil"
|
||||
instanceInfo: "información de la instancia"
|
||||
memo: "Nota adhesiva"
|
||||
notifications: "Notificaciones"
|
||||
timeline: "Linea de tiempo"
|
||||
@@ -1487,7 +1489,6 @@ _notification:
|
||||
youGotReply: "Respuesta de {name}"
|
||||
youGotQuote: "Citado por {name}"
|
||||
youRenoted: "Renotado por {name}"
|
||||
youGotPoll: "Encuestado por {name}"
|
||||
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
|
||||
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
|
||||
youWereFollowed: "te ha seguido"
|
||||
@@ -1505,7 +1506,6 @@ _notification:
|
||||
renote: "Renotar"
|
||||
quote: "Citar"
|
||||
reaction: "Reacción"
|
||||
pollVote: "Votado en la encuesta"
|
||||
pollEnded: "La encuesta terminó"
|
||||
receiveFollowRequest: "Recibió una solicitud de seguimiento"
|
||||
followRequestAccepted: "El seguimiento fue aceptado"
|
||||
|
||||
@@ -1289,6 +1289,8 @@ _weekday:
|
||||
friday: "Vendredi"
|
||||
saturday: "Samedi"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Informations sur l’instance"
|
||||
memo: "Note collante"
|
||||
notifications: "Notifications"
|
||||
timeline: "Fil"
|
||||
@@ -1478,7 +1480,6 @@ _notification:
|
||||
youGotReply: "Réponse de {name}"
|
||||
youGotQuote: "Cité·e par {name}"
|
||||
youRenoted: "{name} vous a Renoté"
|
||||
youGotPoll: "{name} a participé à votre sondage"
|
||||
youGotMessagingMessageFromUser: "{name} vous envoyé un message"
|
||||
youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
|
||||
youWereFollowed: "Vous suit"
|
||||
@@ -1496,7 +1497,6 @@ _notification:
|
||||
renote: "Renotes"
|
||||
quote: "Citations"
|
||||
reaction: "Réactions"
|
||||
pollVote: "Votes dans des sondages"
|
||||
pollEnded: "Sondages se cloturant"
|
||||
receiveFollowRequest: "Demande d'abonnement reçue"
|
||||
followRequestAccepted: "Demande d'abonnement acceptée"
|
||||
|
||||
@@ -1206,6 +1206,8 @@ _weekday:
|
||||
friday: "Jumat"
|
||||
saturday: "Sabtu"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Informasi Instansi"
|
||||
memo: "Catatan memo"
|
||||
notifications: "Pemberitahuan"
|
||||
timeline: "Linimasa"
|
||||
@@ -1402,7 +1404,6 @@ _notification:
|
||||
youGotReply: "{name} membalas kamu"
|
||||
youGotQuote: "{name} mengutip kamu"
|
||||
youRenoted: "{name} me-renote kamu"
|
||||
youGotPoll: "{name} memilih di angket kamu"
|
||||
youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan"
|
||||
youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}"
|
||||
youWereFollowed: "Mengikuti kamu"
|
||||
@@ -1419,7 +1420,6 @@ _notification:
|
||||
renote: "Renote"
|
||||
quote: "Kutip"
|
||||
reaction: "Reaksi"
|
||||
pollVote: "Memilih di angket"
|
||||
pollEnded: "Jajak pendapat berakhir"
|
||||
receiveFollowRequest: "Permintaan mengikuti diterima"
|
||||
followRequestAccepted: "Permintaan mengikuti disetujui"
|
||||
|
||||
@@ -1296,6 +1296,8 @@ _weekday:
|
||||
friday: "Venerdì"
|
||||
saturday: "Sabato"
|
||||
_widgets:
|
||||
profile: "Profilo"
|
||||
instanceInfo: "Informazioni sull'istanza"
|
||||
memo: "Promemoria"
|
||||
notifications: "Notifiche"
|
||||
timeline: "Timeline"
|
||||
@@ -1487,7 +1489,6 @@ _notification:
|
||||
youGotReply: "{name} ti ha risposto"
|
||||
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
|
||||
youRenoted: "{name} ha rinotato"
|
||||
youGotPoll: "{name} ha votato"
|
||||
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
|
||||
youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
|
||||
youWereFollowed: "Ha iniziato a seguirti"
|
||||
@@ -1505,7 +1506,6 @@ _notification:
|
||||
renote: "Rinota"
|
||||
quote: "Cita"
|
||||
reaction: "Reazioni"
|
||||
pollVote: "Voti ricevuti"
|
||||
pollEnded: "Sondaggio chiuso."
|
||||
receiveFollowRequest: "Richiesta di follow ricevuta"
|
||||
followRequestAccepted: "Richiesta di follow accettata"
|
||||
|
||||
@@ -1335,6 +1335,8 @@ _weekday:
|
||||
saturday: "土曜日"
|
||||
|
||||
_widgets:
|
||||
profile: "プロフィール"
|
||||
instanceInfo: "インスタンス情報"
|
||||
memo: "付箋"
|
||||
notifications: "通知"
|
||||
timeline: "タイムライン"
|
||||
|
||||
@@ -1295,6 +1295,8 @@ _weekday:
|
||||
friday: "金曜日"
|
||||
saturday: "土曜日"
|
||||
_widgets:
|
||||
profile: "プロフィール"
|
||||
instanceInfo: "インスタンス情報"
|
||||
memo: "付箋"
|
||||
notifications: "通知"
|
||||
timeline: "タイムライン"
|
||||
@@ -1485,7 +1487,6 @@ _notification:
|
||||
youGotReply: "{name}からのリプライ"
|
||||
youGotQuote: "{name}による引用"
|
||||
youRenoted: "{name}がRenoteしたみたいやで"
|
||||
youGotPoll: "{name}が投票したみたいやで"
|
||||
youGotMessagingMessageFromUser: "{name}からのチャットがあるで"
|
||||
youGotMessagingMessageFromGroup: "{name}のチャットがあるで"
|
||||
youWereFollowed: "フォローされたで"
|
||||
@@ -1503,7 +1504,6 @@ _notification:
|
||||
renote: "Renote"
|
||||
quote: "引用"
|
||||
reaction: "リアクション"
|
||||
pollVote: "アンケートに投票されたで"
|
||||
pollEnded: "アンケートが終了したで"
|
||||
receiveFollowRequest: "フォロー許可してほしいみたいやで"
|
||||
followRequestAccepted: "フォローが受理されたで"
|
||||
|
||||
@@ -73,6 +73,7 @@ _sfx:
|
||||
_permissions:
|
||||
"write:account": "Ẓreg talɣut n umiḍan-ik·im"
|
||||
_widgets:
|
||||
profile: "Amaɣnu"
|
||||
notifications: "Ilɣuyen"
|
||||
_userList:
|
||||
chooseList: "Fren tabdart"
|
||||
|
||||
@@ -69,6 +69,7 @@ _mfm:
|
||||
_sfx:
|
||||
notification: "ಅಧಿಸೂಚನೆಗಳು"
|
||||
_widgets:
|
||||
profile: "ಪ್ರೊಫೈಲು"
|
||||
notifications: "ಅಧಿಸೂಚನೆಗಳು"
|
||||
timeline: "ಸಮಯಸಾಲು"
|
||||
_cw:
|
||||
|
||||
@@ -1302,6 +1302,8 @@ _weekday:
|
||||
friday: "금요일"
|
||||
saturday: "토요일"
|
||||
_widgets:
|
||||
profile: "프로필"
|
||||
instanceInfo: "인스턴스 정보"
|
||||
memo: "스티커 메모"
|
||||
notifications: "알림"
|
||||
timeline: "타임라인"
|
||||
@@ -1328,6 +1330,7 @@ _widgets:
|
||||
userList: "사용자 목록"
|
||||
_userList:
|
||||
chooseList: "리스트 선택"
|
||||
clicker: "클리커"
|
||||
_cw:
|
||||
hide: "숨기기"
|
||||
show: "더 보기"
|
||||
@@ -1503,7 +1506,6 @@ _notification:
|
||||
youGotReply: "{name}님이 답글함"
|
||||
youGotQuote: "{name}님이 인용함"
|
||||
youRenoted: "{name}님이 Renote"
|
||||
youGotPoll: "{name}님이 투표함"
|
||||
youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
|
||||
youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
|
||||
youWereFollowed: "새로운 팔로워가 있습니다"
|
||||
@@ -1521,7 +1523,6 @@ _notification:
|
||||
renote: "리노트"
|
||||
quote: "인용"
|
||||
reaction: "리액션"
|
||||
pollVote: "투표 참여"
|
||||
pollEnded: "투표가 종료됨"
|
||||
receiveFollowRequest: "팔로우 요청을 받았을 때"
|
||||
followRequestAccepted: "팔로우 요청이 승인되었을 때"
|
||||
|
||||
@@ -440,6 +440,8 @@ _sfx:
|
||||
notification: "Meldingen"
|
||||
chat: "Chat"
|
||||
_widgets:
|
||||
profile: "Profiel"
|
||||
instanceInfo: "Serverinformatie"
|
||||
notifications: "Meldingen"
|
||||
timeline: "Tijdlijn"
|
||||
activity: "Activiteit"
|
||||
|
||||
@@ -1213,6 +1213,8 @@ _weekday:
|
||||
friday: "Piątek"
|
||||
saturday: "Sobota"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Informacje o instancji"
|
||||
memo: "Przypięte notatki"
|
||||
notifications: "Powiadomienia"
|
||||
timeline: "Oś czasu"
|
||||
@@ -1380,7 +1382,6 @@ _notification:
|
||||
youGotReply: "{name} odpowiedział(a) Tobie"
|
||||
youGotQuote: "{name} zacytował(a) Ciebie"
|
||||
youRenoted: "{name} udostępnił(a) Twój wpis"
|
||||
youGotPoll: "{name} zagłosował(a) w Twojej ankiecie"
|
||||
youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość"
|
||||
youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
|
||||
youWereFollowed: "Zaobserwował(a) Cię"
|
||||
@@ -1398,7 +1399,6 @@ _notification:
|
||||
renote: "Udostępnij"
|
||||
quote: "Cytuj"
|
||||
reaction: "Reakcja"
|
||||
pollVote: "Głosy w ankietach"
|
||||
receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
|
||||
followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
|
||||
groupInvited: "Zaproszono do grup"
|
||||
|
||||
@@ -488,6 +488,8 @@ _sfx:
|
||||
notification: "Notificações"
|
||||
chat: "Chat"
|
||||
_widgets:
|
||||
profile: "Perfil"
|
||||
instanceInfo: "Informações da instância"
|
||||
notifications: "Notificações"
|
||||
timeline: "Timeline"
|
||||
activity: "atividade"
|
||||
@@ -524,7 +526,6 @@ _notification:
|
||||
youGotMention: "{name} te mencionou"
|
||||
youGotReply: "{name} te respondeu"
|
||||
youGotQuote: "{name} te citou"
|
||||
youGotPoll: "{name} votou em sua enquete"
|
||||
youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
|
||||
youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
|
||||
youWereFollowed: "Você tem um novo seguidor"
|
||||
@@ -541,7 +542,6 @@ _notification:
|
||||
renote: "Repostar"
|
||||
quote: "Citar"
|
||||
reaction: "Reações"
|
||||
pollVote: "Votações em enquetes"
|
||||
pollEnded: "Enquetes terminando"
|
||||
receiveFollowRequest: "Recebeu pedidos de seguimento"
|
||||
followRequestAccepted: "Aceitou pedidos de seguimento"
|
||||
|
||||
@@ -667,6 +667,8 @@ _sfx:
|
||||
notification: "Notificări"
|
||||
chat: "Chat"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Informații despre instanță"
|
||||
notifications: "Notificări"
|
||||
timeline: "Cronologie"
|
||||
activity: "Activitate"
|
||||
|
||||
@@ -1213,6 +1213,8 @@ _weekday:
|
||||
friday: "Пятница"
|
||||
saturday: "Суббота"
|
||||
_widgets:
|
||||
profile: "Профиль"
|
||||
instanceInfo: "Информация об инстансе"
|
||||
memo: "Напоминания"
|
||||
notifications: "Уведомления"
|
||||
timeline: "Лента"
|
||||
@@ -1399,7 +1401,6 @@ _notification:
|
||||
youGotReply: "{name} отвечает вам."
|
||||
youGotQuote: "{name} цитирует вас."
|
||||
youRenoted: "{name} передаёт вашу заметку."
|
||||
youGotPoll: "{name} участвует в вашем опросе."
|
||||
youGotMessagingMessageFromUser: "{name} пишет вам."
|
||||
youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
|
||||
youWereFollowed: "У вас новый подписчик."
|
||||
@@ -1414,7 +1415,6 @@ _notification:
|
||||
renote: "Репосты"
|
||||
quote: "Цитаты"
|
||||
reaction: "Реакции"
|
||||
pollVote: "Голосования"
|
||||
receiveFollowRequest: "Получен запрос на подписку"
|
||||
followRequestAccepted: "Запрос на подписку одобрен"
|
||||
groupInvited: "Приглашение в группы"
|
||||
|
||||
@@ -1295,6 +1295,8 @@ _weekday:
|
||||
friday: "Piatok"
|
||||
saturday: "Sobota"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Informácie o serveri"
|
||||
memo: "Prilepené poznámky"
|
||||
notifications: "Oznámenia"
|
||||
timeline: "Časová os"
|
||||
@@ -1484,7 +1486,6 @@ _notification:
|
||||
youGotReply: "{name} vám odpovedal/a"
|
||||
youGotQuote: "{name} vás citoval/a"
|
||||
youRenoted: "{name} preposlal/a vašu poznámku"
|
||||
youGotPoll: "{name} hlasoval/a"
|
||||
youGotMessagingMessageFromUser: "{name} vám poslal/a správu"
|
||||
youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}"
|
||||
youWereFollowed: "Máte nového sledujúceho"
|
||||
@@ -1502,7 +1503,6 @@ _notification:
|
||||
renote: "Preposlať"
|
||||
quote: "Citovať"
|
||||
reaction: "Reakcie"
|
||||
pollVote: "Hlasy v hlasovaniach"
|
||||
pollEnded: "Hlasovanie skončilo"
|
||||
receiveFollowRequest: "Doručené žiadosti o sledovanie"
|
||||
followRequestAccepted: "Schválené žiadosti o sledovanie"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
_lang_: "Svenska"
|
||||
headlineMisskey: "Ett nätverk kopplat av noter"
|
||||
introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀"
|
||||
introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter. 👍\nLåt oss utforska en ny värld! 🚀"
|
||||
poweredByMisskeyDescription: "{name} är en tjänst driven av den öppna källkodsplatformen <b>Misskey</b> (benämns \"Misskey instans\")."
|
||||
monthAndDay: "{day}/{month}"
|
||||
search: "Sök"
|
||||
@@ -17,7 +17,7 @@ noThankYou: "Nej tack"
|
||||
enterUsername: "Ange användarnamn"
|
||||
renotedBy: "Omnoterad av {user}"
|
||||
noNotes: "Inga noteringar"
|
||||
noNotifications: "Inga aviseringar"
|
||||
noNotifications: "Inga notifikationer"
|
||||
instance: "Instanser"
|
||||
settings: "Inställningar"
|
||||
basicSettings: "Basinställningar"
|
||||
@@ -30,13 +30,13 @@ login: "Logga in"
|
||||
loggingIn: "Loggar in"
|
||||
logout: "Logga ut"
|
||||
signup: "Registrera"
|
||||
uploading: "Uppladdning sker..."
|
||||
uploading: "Laddar upp..."
|
||||
save: "Spara"
|
||||
users: "Användare"
|
||||
addUser: "Lägg till användare"
|
||||
favorite: "Lägg till i favoriter"
|
||||
favorites: "Favoriter"
|
||||
unfavorite: "Avfavorisera"
|
||||
unfavorite: "Ta bort från favoriter"
|
||||
favorited: "Tillagd i favoriter."
|
||||
alreadyFavorited: "Redan tillagd i favoriter."
|
||||
cantFavorite: "Gick inte att lägga till i favoriter."
|
||||
@@ -146,7 +146,7 @@ flagAsBotDescription: "Aktivera det här alternativet om kontot är kontrollerat
|
||||
flagAsCat: "Markera konto som katt"
|
||||
flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
|
||||
flagShowTimelineReplies: "Visa svar i tidslinje"
|
||||
flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om påslagen."
|
||||
flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om aktiverad."
|
||||
autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt"
|
||||
addAccount: "Lägg till konto"
|
||||
loginFailed: "Inloggningen misslyckades"
|
||||
@@ -253,16 +253,120 @@ explore: "Utforska"
|
||||
messageRead: "Läs"
|
||||
noMoreHistory: "Det finns ingen mer historik"
|
||||
startMessaging: "Starta en chatt"
|
||||
nUsersRead: "läst av {n}"
|
||||
agreeTo: "Jag accepterar {0}"
|
||||
tos: "Användarvillkor"
|
||||
home: "Hem"
|
||||
remoteUserCaution: "Då denna användaren kommer från en fjärrinstans, kan informationen visad vara ofullständig."
|
||||
activity: "Aktivitet"
|
||||
images: "Bilder"
|
||||
birthday: "Födelsedag"
|
||||
yearsOld: "{age} år gammal"
|
||||
registeredDate: "Gick med"
|
||||
location: "Plats"
|
||||
theme: "Teman"
|
||||
themeForLightMode: "Tema att använda i Ljust Läge"
|
||||
themeForDarkMode: "Tema att använda i Mörkt Läge"
|
||||
light: "Ljust"
|
||||
dark: "Mörk"
|
||||
lightThemes: "Ljusa teman"
|
||||
darkThemes: "Mörka teman"
|
||||
syncDeviceDarkMode: "Synka Mörkt Läge med din enhets inställningar"
|
||||
drive: "Drive"
|
||||
fileName: "Filnamn"
|
||||
selectFile: "Välj en fil"
|
||||
selectFiles: "Välj filer"
|
||||
selectFolder: "Välj en mapp"
|
||||
selectFolders: "Välj mappar"
|
||||
renameFile: "Byt namn på filen"
|
||||
folderName: "Mappnamn"
|
||||
createFolder: "Skapa en mapp"
|
||||
renameFolder: "Byt namn på mappen"
|
||||
deleteFolder: "Ta bort mappen"
|
||||
addFile: "Lägg till fil"
|
||||
emptyDrive: "Din Drive är tom"
|
||||
emptyFolder: "Denna mappen är tom"
|
||||
unableToDelete: "Kunde inte ta bort"
|
||||
inputNewFileName: "Ange nytt filnamn"
|
||||
inputNewDescription: "Ange ny bildtext"
|
||||
inputNewFolderName: "Ange nytt mappnamn"
|
||||
circularReferenceFolder: "Destinationsmappen är en undermapp av mappen du vill flytta."
|
||||
hasChildFilesOrFolders: "Då denna mappen inte är tom, kan den inte tas bort."
|
||||
copyUrl: "Kopiera URL"
|
||||
rename: "Byt namn"
|
||||
avatar: "Profilbild"
|
||||
banner: "Banner"
|
||||
nsfw: "Känsligt innehåll"
|
||||
reload: "Ladda om"
|
||||
doNothing: "Ignorera"
|
||||
reloadConfirm: "Vill du ladda om tidslinjen?"
|
||||
accept: "Tillåt"
|
||||
reject: "Neka"
|
||||
normal: "Normal"
|
||||
instanceName: "Instansnamn"
|
||||
instanceDescription: "Instansbeskrivning"
|
||||
maintainerEmail: "Administratörens epost"
|
||||
tosUrl: "URL till användarvillkår"
|
||||
thisYear: "Detta året"
|
||||
thisMonth: "Denna månaden"
|
||||
today: "Idag"
|
||||
dayX: "{day}"
|
||||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Sidor"
|
||||
integration: "Integrationer"
|
||||
connectService: "Anslut"
|
||||
disconnectService: "Koppla från"
|
||||
enableLocalTimeline: "Aktivera lokal tidslinje"
|
||||
enableGlobalTimeline: "Aktivera global tidslinje"
|
||||
enableRegistration: "Aktivera registrering av nya användare"
|
||||
inMb: "I megabyte"
|
||||
iconUrl: "URL till profilbilden"
|
||||
bannerUrl: "URL till banner-bilden"
|
||||
pinnedNotes: "Fästad not"
|
||||
enableHcaptcha: "Aktivera hCaptcha"
|
||||
enableRecaptcha: "Aktivera reCAPTCHA"
|
||||
enableTurnstile: "Aktivera Turnstile"
|
||||
antennas: "Antenner"
|
||||
manageAntennas: "Hantera Antenner"
|
||||
antennaSource: "Antennkälla"
|
||||
antennaKeywords: "Nyckelord att lyssna efter"
|
||||
antennaExcludeKeywords: "Nyckelord att exkludera"
|
||||
antennaKeywordsDescription: "Separera med mellanslag för en AND kondition, eller med nya linjer för en OR kondition"
|
||||
notifyAntenna: "Notifiera om nya noter"
|
||||
withFileAntenna: "Endast noter med filer"
|
||||
enableServiceworker: "Aktivera pushnotiser i denna webbläsaren"
|
||||
antennaUsersDescription: "Ange ett användarnamn per linje"
|
||||
recentlyUpdatedUsers: "Nyligen aktiva användare"
|
||||
recentlyRegisteredUsers: "Nyligen registrerade användare"
|
||||
userList: "Listor"
|
||||
aboutMisskey: "Om Misskey"
|
||||
administrator: "Administratör"
|
||||
newPasswordIs: "Det nya lösenordet är \"{password}\""
|
||||
share: "Dela"
|
||||
enable: "Aktivera"
|
||||
serviceworkerInfo: "Måste vara aktiverad för pushnotiser."
|
||||
enableInfiniteScroll: "Ladda mer automatiskt"
|
||||
enablePlayer: "Öppna videospelare"
|
||||
enableAll: "Aktivera alla"
|
||||
enableEmail: "Aktivera epost-utskick"
|
||||
smtpHost: "Värd"
|
||||
smtpUser: "Användarnamn"
|
||||
smtpPass: "Lösenord"
|
||||
clearCache: "Rensa cache"
|
||||
enabled: "Aktiverad"
|
||||
user: "Användare"
|
||||
global: "Global"
|
||||
squareAvatars: "Visa fyrkantiga profilbilder"
|
||||
searchByGoogle: "Sök"
|
||||
file: "Filer"
|
||||
enableAutoSensitive: "Automatisk NSFW markering"
|
||||
enableAutoSensitiveDescription: "Tillåter automatiskt detektering och marketing av NSFW media genom Maskininlärning när möjligt. Även om denna inställningen är avaktiverad, kan det vara aktiverat på hela instansen."
|
||||
pushNotification: "Pushnotiser"
|
||||
subscribePushNotification: "Aktivera pushnotiser"
|
||||
unsubscribePushNotification: "Avaktivera pushnotiser"
|
||||
pushNotificationAlreadySubscribed: "Pushnotiser är redan aktiverade"
|
||||
pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för pushnotiser"
|
||||
_email:
|
||||
_follow:
|
||||
title: "följde dig"
|
||||
@@ -271,6 +375,9 @@ _mfm:
|
||||
quote: "Citat"
|
||||
emoji: "Anpassa emoji"
|
||||
search: "Sök"
|
||||
_channel:
|
||||
setBanner: "Välj banner"
|
||||
removeBanner: "Ta bort banner"
|
||||
_theme:
|
||||
keys:
|
||||
mention: "Nämn"
|
||||
@@ -279,9 +386,19 @@ _sfx:
|
||||
note: "Noter"
|
||||
notification: "Notifikationer"
|
||||
chat: "Chatt"
|
||||
antenna: "Antenner"
|
||||
_antennaSources:
|
||||
all: "Alla noter"
|
||||
homeTimeline: "Noter från följda användare"
|
||||
users: "Noter från specifika användare"
|
||||
userList: "Noter från en specificerad lista av användare"
|
||||
userGroup: "Noter från användare i en specificerad grupp"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
instanceInfo: "Instansinformation"
|
||||
notifications: "Notifikationer"
|
||||
timeline: "Tidslinje"
|
||||
activity: "Aktivitet"
|
||||
federation: "Federation"
|
||||
jobQueue: "Jobbkö"
|
||||
_userList:
|
||||
@@ -289,18 +406,29 @@ _widgets:
|
||||
_cw:
|
||||
show: "Ladda mer"
|
||||
_visibility:
|
||||
home: "Hem"
|
||||
followers: "Följare"
|
||||
_profile:
|
||||
username: "Användarnamn"
|
||||
changeAvatar: "Ändra profilbild"
|
||||
changeBanner: "Ändra banner"
|
||||
_exportOrImport:
|
||||
allNotes: "Alla noter"
|
||||
followingList: "Följer"
|
||||
muteList: "Tysta"
|
||||
blockingList: "Blockera"
|
||||
userLists: "Listor"
|
||||
_charts:
|
||||
federation: "Federation"
|
||||
_timelines:
|
||||
home: "Hem"
|
||||
global: "Global"
|
||||
_pages:
|
||||
blocks:
|
||||
image: "Bilder"
|
||||
_notification:
|
||||
youWereFollowed: "följde dig"
|
||||
unreadAntennaNote: "Antenn {name}"
|
||||
_types:
|
||||
follow: "Följer"
|
||||
mention: "Nämn"
|
||||
@@ -314,5 +442,6 @@ _deck:
|
||||
_columns:
|
||||
notifications: "Notifikationer"
|
||||
tl: "Tidslinje"
|
||||
antenna: "Antenner"
|
||||
list: "Listor"
|
||||
mentions: "Omnämningar"
|
||||
|
||||
@@ -8,7 +8,7 @@ search: "ค้นหา"
|
||||
notifications: "การเเจ้งเตือน"
|
||||
username: "ชื่อผู้ใช้"
|
||||
password: "รหัสผ่าน"
|
||||
forgotPassword: "ลืมรหัสผ่าน?"
|
||||
forgotPassword: "ลืมรหัสผ่านใช่ไหม"
|
||||
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
|
||||
ok: "โอเค"
|
||||
gotIt: "เข้าใจแล้ว !"
|
||||
@@ -920,6 +920,10 @@ like: "ชื่นชอบ"
|
||||
unlike: "ไม่ชอบ"
|
||||
numberOfLikes: "จำนวนไลค์"
|
||||
show: "แสดงผล"
|
||||
neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
|
||||
remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
|
||||
didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
|
||||
pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
|
||||
_sensitiveMediaDetection:
|
||||
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
|
||||
sensitivity: "การตรวจจับความไว"
|
||||
@@ -1298,6 +1302,8 @@ _weekday:
|
||||
friday: "วันศุกร์"
|
||||
saturday: "วันเสาร์"
|
||||
_widgets:
|
||||
profile: "โปรไฟล์"
|
||||
instanceInfo: "ข้อมูล อินสแตนซ์"
|
||||
memo: "โน้ตแปะ"
|
||||
notifications: "การเเจ้งเตือน"
|
||||
timeline: "ไทม์ไลน์"
|
||||
@@ -1324,6 +1330,7 @@ _widgets:
|
||||
userList: "รายชื่อผู้ใช้"
|
||||
_userList:
|
||||
chooseList: "เลือกรายการ"
|
||||
clicker: "คลิกเกอร์"
|
||||
_cw:
|
||||
hide: "ซ่อน"
|
||||
show: "โหลดเพิ่มเติม"
|
||||
@@ -1499,7 +1506,6 @@ _notification:
|
||||
youGotReply: "{name} ตอบกลับถึงคุณ"
|
||||
youGotQuote: "{name} อ้างถึงคุณ"
|
||||
youRenoted: "รีโน้ตจาก {name}"
|
||||
youGotPoll: "{name} โหวตบนแบบสำรวจความคิดเห็นของคุณ"
|
||||
youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ"
|
||||
youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม"
|
||||
youWereFollowed: "ได้ติดตามคุณ"
|
||||
@@ -1517,7 +1523,6 @@ _notification:
|
||||
renote: "รีโน้ต"
|
||||
quote: "อ้างคำพูด"
|
||||
reaction: "รีแอคชั่น"
|
||||
pollVote: "จำนวนโหวตที่ได้รับ"
|
||||
pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
|
||||
receiveFollowRequest: "ได้รับคำขอติดตาม\n"
|
||||
followRequestAccepted: "ยอมรับคำขอติดตาม"
|
||||
|
||||
@@ -53,6 +53,7 @@ _mfm:
|
||||
_sfx:
|
||||
notification: "Bildirim"
|
||||
_widgets:
|
||||
profile: "Profil"
|
||||
notifications: "Bildirim"
|
||||
timeline: "Zaman çizelgesi"
|
||||
_profile:
|
||||
|
||||
@@ -1229,6 +1229,8 @@ _weekday:
|
||||
friday: "П'ятниця"
|
||||
saturday: "Субота"
|
||||
_widgets:
|
||||
profile: "Профіль"
|
||||
instanceInfo: "Про цей інстанс"
|
||||
memo: "Нагадування"
|
||||
notifications: "Сповіщення"
|
||||
timeline: "Стрічка"
|
||||
@@ -1415,7 +1417,6 @@ _notification:
|
||||
youGotReply: "{name} відповідає"
|
||||
youGotQuote: "{name} цитує вас"
|
||||
youRenoted: "{name} поширює"
|
||||
youGotPoll: "{name} бере участь в опитуванні"
|
||||
youGotMessagingMessageFromUser: "Повідомлення від {name}"
|
||||
youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}"
|
||||
youWereFollowed: "Новий підписник"
|
||||
@@ -1430,7 +1431,6 @@ _notification:
|
||||
renote: "Поширення"
|
||||
quote: "Цитування"
|
||||
reaction: "Реакції"
|
||||
pollVote: "Опитування"
|
||||
receiveFollowRequest: "Запити на підписку"
|
||||
followRequestAccepted: "Прийняті підписки"
|
||||
groupInvited: "Запрошення до груп"
|
||||
|
||||
@@ -1271,6 +1271,8 @@ _weekday:
|
||||
friday: "Thứ Sáu"
|
||||
saturday: "Thứ Bảy"
|
||||
_widgets:
|
||||
profile: "Trang cá nhân"
|
||||
instanceInfo: "Thông tin máy chủ"
|
||||
memo: "Tút đã ghim"
|
||||
notifications: "Thông báo"
|
||||
timeline: "Bảng tin"
|
||||
@@ -1460,7 +1462,6 @@ _notification:
|
||||
youGotReply: "{name} trả lời bạn"
|
||||
youGotQuote: "{name} trích dẫn tút của bạn"
|
||||
youRenoted: "{name} đăng lại tút của bạn"
|
||||
youGotPoll: "{name} bình chọn tút của bạn"
|
||||
youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn"
|
||||
youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
|
||||
youWereFollowed: "đã theo dõi bạn"
|
||||
@@ -1477,7 +1478,6 @@ _notification:
|
||||
renote: "Đăng lại"
|
||||
quote: "Trích dẫn"
|
||||
reaction: "Biểu cảm"
|
||||
pollVote: "Lượt bình chọn"
|
||||
pollEnded: "Bình chọn kết thúc"
|
||||
receiveFollowRequest: "Yêu cầu theo dõi"
|
||||
followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
|
||||
|
||||
@@ -920,6 +920,10 @@ like: "点赞!"
|
||||
unlike: "取消赞"
|
||||
numberOfLikes: "点赞数"
|
||||
show: "显示"
|
||||
neverShow: "不再显示"
|
||||
remindMeLater: "稍后提醒我"
|
||||
didYouLikeMisskey: "您喜欢Misskey吗?"
|
||||
pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发,请在有余力的情况下进行捐助!"
|
||||
_sensitiveMediaDetection:
|
||||
description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
|
||||
sensitivity: "检测敏感度"
|
||||
@@ -1298,6 +1302,8 @@ _weekday:
|
||||
friday: "星期五"
|
||||
saturday: "星期六"
|
||||
_widgets:
|
||||
profile: "个人资料"
|
||||
instanceInfo: "实例信息"
|
||||
memo: "便签"
|
||||
notifications: "通知"
|
||||
timeline: "时间线"
|
||||
@@ -1324,6 +1330,7 @@ _widgets:
|
||||
userList: "用户列表"
|
||||
_userList:
|
||||
chooseList: "选择列表"
|
||||
clicker: "点击器"
|
||||
_cw:
|
||||
hide: "隐藏"
|
||||
show: "查看更多"
|
||||
@@ -1499,7 +1506,6 @@ _notification:
|
||||
youGotReply: "来自{name}的回复"
|
||||
youGotQuote: "来自{name}的引用"
|
||||
youRenoted: "来自{name}的转发"
|
||||
youGotPoll: "来自{name}的投票"
|
||||
youGotMessagingMessageFromUser: "来自{name}的聊天"
|
||||
youGotMessagingMessageFromGroup: "来自{name}的群聊"
|
||||
youWereFollowed: "关注了你。"
|
||||
@@ -1517,7 +1523,6 @@ _notification:
|
||||
renote: "转发"
|
||||
quote: "引用"
|
||||
reaction: "回应"
|
||||
pollVote: "问卷调查被投票"
|
||||
pollEnded: "问卷调查结束"
|
||||
receiveFollowRequest: "收到关注请求"
|
||||
followRequestAccepted: "关注请求已通过"
|
||||
|
||||
@@ -252,7 +252,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
|
||||
explore: "探索"
|
||||
messageRead: "已讀"
|
||||
noMoreHistory: "沒有更多歷史紀錄"
|
||||
startMessaging: "開始傳送訊息"
|
||||
startMessaging: "開始聊天"
|
||||
nUsersRead: "{n}人已讀"
|
||||
agreeTo: "我同意{0}"
|
||||
tos: "使用條款"
|
||||
@@ -797,7 +797,7 @@ squareAvatars: "頭像以方形顯示"
|
||||
sent: "發送"
|
||||
received: "收取"
|
||||
searchResult: "搜尋結果"
|
||||
hashtags: "#tag"
|
||||
hashtags: "標籤"
|
||||
troubleshooting: "故障排除"
|
||||
useBlurEffect: "在 UI 上使用模糊效果"
|
||||
learnMore: "更多資訊"
|
||||
@@ -923,6 +923,7 @@ show: "檢視"
|
||||
neverShow: "不再顯示"
|
||||
remindMeLater: "以後再說"
|
||||
didYouLikeMisskey: "您是否喜愛Misskey呢?"
|
||||
pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!"
|
||||
_sensitiveMediaDetection:
|
||||
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
||||
sensitivity: "檢測敏感度"
|
||||
@@ -1158,7 +1159,7 @@ _theme:
|
||||
navActive: "側邊欄文本 (活動)"
|
||||
navIndicator: "側邊欄指示符"
|
||||
link: "鏈接"
|
||||
hashtag: "#tag"
|
||||
hashtag: "標籤"
|
||||
mention: "提到"
|
||||
mentionMe: "提到了我"
|
||||
renote: "轉發貼文"
|
||||
@@ -1191,7 +1192,7 @@ _sfx:
|
||||
note: "貼文"
|
||||
noteMy: "我的貼文"
|
||||
notification: "通知"
|
||||
chat: "傳送訊息"
|
||||
chat: "聊天"
|
||||
chatBg: "聊天背景"
|
||||
antenna: "天線接收"
|
||||
channel: "頻道通知"
|
||||
@@ -1301,6 +1302,8 @@ _weekday:
|
||||
friday: "週五"
|
||||
saturday: "週六"
|
||||
_widgets:
|
||||
profile: "個人檔案"
|
||||
instanceInfo: "實例資訊"
|
||||
memo: "備忘錄"
|
||||
notifications: "通知"
|
||||
timeline: "時間軸"
|
||||
@@ -1327,6 +1330,7 @@ _widgets:
|
||||
userList: "使用者列表"
|
||||
_userList:
|
||||
chooseList: "選擇清單"
|
||||
clicker: "點擊器"
|
||||
_cw:
|
||||
hide: "隱藏"
|
||||
show: "瀏覽更多"
|
||||
@@ -1502,7 +1506,6 @@ _notification:
|
||||
youGotReply: "{name}回覆了您"
|
||||
youGotQuote: "{name}引用了您"
|
||||
youRenoted: "{name} 轉發了你的貼文"
|
||||
youGotPoll: "{name}已投票"
|
||||
youGotMessagingMessageFromUser: "{name}發送給您的訊息"
|
||||
youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
|
||||
youWereFollowed: "您有新的追隨者"
|
||||
@@ -1520,7 +1523,6 @@ _notification:
|
||||
renote: "轉發貼文"
|
||||
quote: "引用"
|
||||
reaction: "反應"
|
||||
pollVote: "統計已投票數"
|
||||
pollEnded: "問卷調查結束"
|
||||
receiveFollowRequest: "已收到追隨請求"
|
||||
followRequestAccepted: "追隨請求已接受"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "13.0.0-beta.32",
|
||||
"version": "13.0.0-beta.39",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
11
packages/backend/migration/1673336077243-PollChoiceLength.js
Normal file
11
packages/backend/migration/1673336077243-PollChoiceLength.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export class PollChoiceLength1673336077243 {
|
||||
name = 'PollChoiceLength1673336077243'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(256) array`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "poll" ALTER COLUMN "choices" TYPE character varying(128) array`);
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
"json5-loader": "4.0.1",
|
||||
"jsonld": "8.1.0",
|
||||
"jsrsasign": "10.6.1",
|
||||
"mfm-js": "0.23.0",
|
||||
"mfm-js": "0.23.1",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "0.0.14",
|
||||
"ms": "3.0.0-canary.1",
|
||||
|
||||
@@ -15,8 +15,8 @@ import type { Packed } from '@/misc/schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AntennaService implements OnApplicationShutdown {
|
||||
@@ -135,7 +135,7 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
|
||||
this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
|
||||
antenna: { id: antenna.id, name: antenna.name },
|
||||
note: await this.noteEntityService.pack(note)
|
||||
note: await this.noteEntityService.pack(note),
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
@@ -144,27 +144,19 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
|
||||
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
||||
|
||||
/**
|
||||
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
|
||||
*/
|
||||
@bindThis
|
||||
public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
|
||||
public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise<boolean> {
|
||||
if (note.visibility === 'specified') return false;
|
||||
|
||||
if (note.visibility === 'followers') return false;
|
||||
|
||||
// アンテナ作成者がノート作成者にブロックされていたらスキップ
|
||||
const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
|
||||
if (blockings.some(blocking => blocking === antenna.userId)) return false;
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
|
||||
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
|
||||
}
|
||||
|
||||
if (!antenna.withReplies && note.replyId != null) return false;
|
||||
|
||||
if (antenna.src === 'home') {
|
||||
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
|
||||
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
|
||||
// TODO
|
||||
} else if (antenna.src === 'list') {
|
||||
const listUsers = (await this.userListJoiningsRepository.findBy({
|
||||
userListId: antenna.userListId!,
|
||||
|
||||
@@ -398,13 +398,13 @@ export class FileInfoService {
|
||||
.raw()
|
||||
.ensureAlpha()
|
||||
.resize(64, 64, { fit: 'inside' })
|
||||
.toBuffer((err, buffer, { width, height }) => {
|
||||
.toBuffer((err, buffer, info) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
let hash;
|
||||
|
||||
try {
|
||||
hash = encode(new Uint8ClampedArray(buffer), width, height, 5, 5);
|
||||
hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
@@ -22,23 +22,25 @@ export class EmojiEntityService {
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: Emoji['id'] | Emoji,
|
||||
opts: { omitHost?: boolean; omitId?: boolean; } = {},
|
||||
): Promise<Packed<'Emoji'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: emoji.id,
|
||||
id: opts.omitId ? undefined : emoji.id,
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
host: emoji.host,
|
||||
host: opts.omitHost ? undefined : emoji.host,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packMany(
|
||||
emojis: any[],
|
||||
opts: { omitHost?: boolean; omitId?: boolean; } = {},
|
||||
) {
|
||||
return Promise.all(emojis.map(x => this.pack(x)));
|
||||
return Promise.all(emojis.map(x => this.pack(x, opts)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export class Poll {
|
||||
public multiple: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}',
|
||||
length: 256, array: true, default: '{}',
|
||||
})
|
||||
public choices: string[];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export const packedEmojiSchema = {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
@@ -26,12 +26,8 @@ export const packedEmojiSchema = {
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -8,6 +8,8 @@ import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import NotesChart from '@/core/chart/charts/notes.js';
|
||||
import UsersChart from '@/core/chart/charts/users.js';
|
||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
const nodeinfo2_1path = '/nodeinfo/2.1';
|
||||
@@ -27,6 +29,8 @@ export class NodeinfoServerService {
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private metaService: MetaService,
|
||||
private notesChart: NotesChart,
|
||||
private usersChart: UsersChart,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
@@ -46,20 +50,27 @@ export class NodeinfoServerService {
|
||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
const nodeinfo2 = async () => {
|
||||
const now = Date.now();
|
||||
|
||||
const notesChart = await this.notesChart.getChart('hour', 1, null);
|
||||
const localPosts = notesChart.local.total[0];
|
||||
|
||||
const usersChart = await this.usersChart.getChart('hour', 1, null);
|
||||
const total = usersChart.local.total[0];
|
||||
|
||||
const [
|
||||
meta,
|
||||
total,
|
||||
activeHalfyear,
|
||||
activeMonth,
|
||||
localPosts,
|
||||
//activeHalfyear,
|
||||
//activeMonth,
|
||||
] = await Promise.all([
|
||||
this.metaService.fetch(true),
|
||||
this.usersRepository.count({ where: { host: IsNull() } }),
|
||||
this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
|
||||
this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
|
||||
this.notesRepository.count({ where: { userHost: IsNull() } }),
|
||||
// 重い
|
||||
//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
|
||||
//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
|
||||
]);
|
||||
|
||||
const activeHalfyear = null;
|
||||
const activeMonth = null;
|
||||
|
||||
const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;
|
||||
|
||||
return {
|
||||
|
||||
@@ -220,6 +220,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/
|
||||
import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js';
|
||||
import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||
@@ -550,6 +551,7 @@ const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/c
|
||||
const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default };
|
||||
const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default };
|
||||
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
|
||||
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
|
||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
|
||||
@@ -884,6 +886,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$messaging_messages_delete,
|
||||
$messaging_messages_read,
|
||||
$meta,
|
||||
$emojis,
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
$mute_delete,
|
||||
@@ -1212,6 +1215,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$messaging_messages_delete,
|
||||
$messaging_messages_read,
|
||||
$meta,
|
||||
$emojis,
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
$mute_delete,
|
||||
|
||||
@@ -219,6 +219,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/
|
||||
import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js';
|
||||
import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
import * as ep___mute_delete from './endpoints/mute/delete.js';
|
||||
@@ -547,6 +548,7 @@ const eps = [
|
||||
['messaging/messages/delete', ep___messaging_messages_delete],
|
||||
['messaging/messages/read', ep___messaging_messages_read],
|
||||
['meta', ep___meta],
|
||||
['emojis', ep___emojis],
|
||||
['miauth/gen-token', ep___miauth_genToken],
|
||||
['mute/create', ep___mute_create],
|
||||
['mute/delete', ep___mute_delete],
|
||||
|
||||
90
packages/backend/src/server/api/endpoints/emojis.ts
Normal file
90
packages/backend/src/server/api/endpoints/emojis.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { EmojisRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
emojis: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const emojis = await this.emojisRepository.find({
|
||||
where: {
|
||||
host: IsNull(),
|
||||
},
|
||||
order: {
|
||||
category: 'ASC',
|
||||
name: 'ASC',
|
||||
},
|
||||
cache: {
|
||||
id: 'meta_emojis',
|
||||
milliseconds: 3600000, // 1 hour
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
emojis: await this.emojiEntityService.packMany(emojis, {
|
||||
omitId: true,
|
||||
omitHost: true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/
|
||||
import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -152,43 +151,6 @@ export const meta = {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
emojis: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ads: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
@@ -326,30 +288,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.adsRepository)
|
||||
private adsRepository: AdsRepository,
|
||||
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
const emojis = await this.emojisRepository.find({
|
||||
where: {
|
||||
host: IsNull(),
|
||||
},
|
||||
order: {
|
||||
category: 'ASC',
|
||||
name: 'ASC',
|
||||
},
|
||||
cache: {
|
||||
id: 'meta_emojis',
|
||||
milliseconds: 3600000, // 1 hour
|
||||
},
|
||||
});
|
||||
|
||||
const ads = await this.adsRepository.find({
|
||||
where: {
|
||||
expiresAt: MoreThan(new Date()),
|
||||
@@ -390,7 +334,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
backgroundImageUrl: instance.backgroundImageUrl,
|
||||
logoImageUrl: instance.logoImageUrl,
|
||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
|
||||
emojis: await this.emojiEntityService.packMany(emojis),
|
||||
defaultLightTheme: instance.defaultLightTheme,
|
||||
defaultDarkTheme: instance.defaultDarkTheme,
|
||||
ads: ads.map(ad => ({
|
||||
|
||||
@@ -3,6 +3,8 @@ import { IsNull } from 'typeorm';
|
||||
import type { InstancesRepository, NoteReactionsRepository, NotesRepository, UsersRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import NotesChart from '@/core/chart/charts/notes.js';
|
||||
import UsersChart from '@/core/chart/charts/users.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false,
|
||||
@@ -66,21 +68,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
@Inject(DI.noteReactionsRepository)
|
||||
private noteReactionsRepository: NoteReactionsRepository,
|
||||
|
||||
private notesChart: NotesChart,
|
||||
private usersChart: UsersChart,
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
const notesChart = await this.notesChart.getChart('hour', 1, null);
|
||||
const notesCount = notesChart.local.total[0] + notesChart.remote.total[0];
|
||||
const originalNotesCount = notesChart.local.total[0];
|
||||
|
||||
const usersChart = await this.usersChart.getChart('hour', 1, null);
|
||||
const usersCount = usersChart.local.total[0] + usersChart.remote.total[0];
|
||||
const originalUsersCount = usersChart.local.total[0];
|
||||
|
||||
const [
|
||||
notesCount,
|
||||
originalNotesCount,
|
||||
usersCount,
|
||||
originalUsersCount,
|
||||
reactionsCount,
|
||||
//originalReactionsCount,
|
||||
instances,
|
||||
] = await Promise.all([
|
||||
this.notesRepository.count({ cache: 3600000 }), // 1 hour
|
||||
this.notesRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }),
|
||||
this.usersRepository.count({ cache: 3600000 }),
|
||||
this.usersRepository.count({ where: { host: IsNull() }, cache: 3600000 }),
|
||||
this.noteReactionsRepository.count({ cache: 3600000 }), // 1 hour
|
||||
//this.noteReactionsRepository.count({ where: { userHost: IsNull() }, cache: 3600000 }),
|
||||
this.instancesRepository.count({ cache: 3600000 }),
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"json5": "2.2.3",
|
||||
"katex": "0.16.4",
|
||||
"matter-js": "0.18.0",
|
||||
"mfm-js": "0.23.0",
|
||||
"mfm-js": "0.23.1",
|
||||
"misskey-js": "0.0.14",
|
||||
"photoswipe": "5.3.4",
|
||||
"prismjs": "1.29.0",
|
||||
|
||||
@@ -47,6 +47,7 @@ import { emojilist } from '@/scripts/emojilist';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
import { customEmojis } from '@/custom-emojis';
|
||||
|
||||
type EmojiDef = {
|
||||
emoji: string;
|
||||
@@ -86,7 +87,6 @@ for (const x of lib) {
|
||||
emjdb.sort((a, b) => a.name.length - b.name.length);
|
||||
|
||||
//#region Construct Emoji DB
|
||||
const customEmojis = instance.emojis;
|
||||
const emojiDefinitions: EmojiDef[] = [];
|
||||
|
||||
for (const x of customEmojis) {
|
||||
@@ -117,7 +117,6 @@ export default {
|
||||
emojiDb,
|
||||
emojiDefinitions,
|
||||
emojilist,
|
||||
customEmojis,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ export default defineComponent({
|
||||
} else {
|
||||
if (props.ad && item._shouldInsertAd_) {
|
||||
return [h(MkAd, {
|
||||
class: 'a', // advertiseの意(ブロッカー対策)
|
||||
key: item.id + ':ad',
|
||||
prefer: ['horizontal', 'horizontal-big'],
|
||||
}), el];
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')">
|
||||
<div class="mk-dialog">
|
||||
<div v-if="icon" class="icon">
|
||||
<div :class="$style.root">
|
||||
<div v-if="icon" :class="$style.icon">
|
||||
<i :class="icon"></i>
|
||||
</div>
|
||||
<div v-else-if="!input && !select" class="icon" :class="type">
|
||||
<i v-if="type === 'success'" class="ti ti-check"></i>
|
||||
<i v-else-if="type === 'error'" class="ti ti-circle-x"></i>
|
||||
<i v-else-if="type === 'warning'" class="ti ti-alert-triangle"></i>
|
||||
<i v-else-if="type === 'info'" class="ti ti-info-circle"></i>
|
||||
<i v-else-if="type === 'question'" class="ti ti-question-circle"></i>
|
||||
<MkLoading v-else-if="type === 'waiting'" :em="true"/>
|
||||
<div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]">
|
||||
<i v-if="type === 'success'" :class="$style.iconInner" class="ti ti-check"></i>
|
||||
<i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i>
|
||||
<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i>
|
||||
<i v-else-if="type === 'info'" :class="$style.iconInner" class="ti ti-info-circle"></i>
|
||||
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-question-circle"></i>
|
||||
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
||||
</div>
|
||||
<header v-if="title"><Mfm :text="title"/></header>
|
||||
<div v-if="text" class="body"><Mfm :text="text"/></div>
|
||||
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
||||
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div>
|
||||
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown">
|
||||
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||
</MkInput>
|
||||
@@ -27,11 +27,11 @@
|
||||
</optgroup>
|
||||
</template>
|
||||
</MkSelect>
|
||||
<div v-if="(showOkButton || showCancelButton) && !actions" class="buttons">
|
||||
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
|
||||
<MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt }}</MkButton>
|
||||
<MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||
</div>
|
||||
<div v-if="actions" class="buttons">
|
||||
<div v-if="actions" :class="$style.buttons">
|
||||
<MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,8 +143,8 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-dialog {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
padding: 32px;
|
||||
min-width: 320px;
|
||||
@@ -153,56 +153,56 @@ onBeforeUnmount(() => {
|
||||
text-align: center;
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: 24px;
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
|
||||
&.info {
|
||||
color: #55c4dd;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
& + header {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
margin: 0 0 8px 0;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
|
||||
& + .body {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .body {
|
||||
margin: 16px 0 0 0;
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
margin-top: 16px;
|
||||
|
||||
> * {
|
||||
margin: 0 8px;
|
||||
}
|
||||
& + .title {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.iconInner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.type_info {
|
||||
color: #55c4dd;
|
||||
}
|
||||
|
||||
.type_success {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.type_error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.type_warning {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 8px 0;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
|
||||
& + .text {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 16px 0 0 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
|
||||
<input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
|
||||
<div ref="emojis" class="emojis">
|
||||
<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
|
||||
<div ref="emojisEl" class="emojis">
|
||||
<section class="result">
|
||||
<div v-if="searchResultCustom.length > 0" class="body">
|
||||
<button
|
||||
v-for="emoji in searchResultCustom"
|
||||
:key="emoji.id"
|
||||
:key="emoji.name"
|
||||
class="_button item"
|
||||
:title="emoji.name"
|
||||
tabindex="0"
|
||||
@@ -85,9 +85,10 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import * as os from '@/os';
|
||||
import { isTouchUsing } from '@/scripts/touch';
|
||||
import { deviceKind } from '@/scripts/device-kind';
|
||||
import { emojiCategories, instance } from '@/instance';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
import { getCustomEmojiCategories, customEmojis } from '@/custom-emojis';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
showPinned?: boolean;
|
||||
@@ -103,8 +104,9 @@ const emit = defineEmits<{
|
||||
(ev: 'chosen', v: string): void;
|
||||
}>();
|
||||
|
||||
const search = shallowRef<HTMLInputElement>();
|
||||
const emojis = shallowRef<HTMLDivElement>();
|
||||
const customEmojiCategories = getCustomEmojiCategories();
|
||||
const searchEl = shallowRef<HTMLInputElement>();
|
||||
const emojisEl = shallowRef<HTMLDivElement>();
|
||||
|
||||
const {
|
||||
reactions: pinned,
|
||||
@@ -118,15 +120,13 @@ const {
|
||||
const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
|
||||
const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
|
||||
const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
|
||||
const customEmojiCategories = emojiCategories;
|
||||
const customEmojis = instance.emojis;
|
||||
const q = ref<string>('');
|
||||
const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
|
||||
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
|
||||
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
|
||||
|
||||
watch(q, () => {
|
||||
if (emojis.value) emojis.value.scrollTop = 0;
|
||||
if (emojisEl.value) emojisEl.value.scrollTop = 0;
|
||||
|
||||
if (q.value === '') {
|
||||
searchResultCustom.value = [];
|
||||
@@ -268,14 +268,14 @@ watch(q, () => {
|
||||
|
||||
function focus() {
|
||||
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
|
||||
search.value?.focus({
|
||||
searchEl.value?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (emojis.value) emojis.value.scrollTop = 0;
|
||||
if (emojisEl.value) emojisEl.value.scrollTop = 0;
|
||||
q.value = '';
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ function input(): void {
|
||||
// Using custom input event instead of v-model to respond immediately on
|
||||
// Android, where composition happens on all languages
|
||||
// (v-model does not update during composition)
|
||||
q.value = search.value?.value.trim() ?? '';
|
||||
q.value = searchEl.value?.value.trim() ?? '';
|
||||
}
|
||||
|
||||
function paste(event: ClipboardEvent): void {
|
||||
|
||||
@@ -59,6 +59,11 @@ function chosen(emoji: any) {
|
||||
function opening() {
|
||||
picker.value?.reset();
|
||||
picker.value?.focus();
|
||||
|
||||
// 何故かちょっと待たないとフォーカスされない
|
||||
setTimeout(() => {
|
||||
picker.value?.focus();
|
||||
}, 10);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]">
|
||||
<img class="icon" :src="getInstanceIcon(instance)" alt=""/>
|
||||
<img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/>
|
||||
<div class="body">
|
||||
<span class="host">{{ instance.name ?? instance.host }}</span>
|
||||
<span class="sub _monospace"><b>{{ instance.host }}</b> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span>
|
||||
|
||||
@@ -75,7 +75,7 @@ function close() {
|
||||
|
||||
&.asDrawer {
|
||||
width: 100%;
|
||||
padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px) 16px;
|
||||
padding: 16px 16px max(env(safe-area-inset-bottom, 0px), 16px) 16px;
|
||||
border-radius: 24px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<component
|
||||
:is="self ? 'MkA' : 'a'" ref="el" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
|
||||
:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
|
||||
:title="url"
|
||||
>
|
||||
<slot></slot>
|
||||
<i v-if="target === '_blank'" class="ti ti-external-link icon"></i>
|
||||
<i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -35,13 +35,9 @@ useTooltip($$(el), (showing) => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xlcxczvw {
|
||||
word-break: break-all;
|
||||
|
||||
> .icon {
|
||||
padding-left: 2px;
|
||||
font-size: .9em;
|
||||
}
|
||||
<style lang="scss" module>
|
||||
.icon {
|
||||
padding-left: 2px;
|
||||
font-size: .9em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,54 +2,54 @@
|
||||
<div>
|
||||
<div
|
||||
ref="itemsEl" v-hotkey="keymap"
|
||||
class="rrevdjwt _popup _shadow"
|
||||
:class="{ center: align === 'center', asDrawer }"
|
||||
class="_popup _shadow"
|
||||
:class="[$style.root, { [$style.center]: align === 'center', [$style.asDrawer]: asDrawer }]"
|
||||
:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
|
||||
@contextmenu.self="e => e.preventDefault()"
|
||||
>
|
||||
<template v-for="(item, i) in items2">
|
||||
<div v-if="item === null" class="divider"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item">
|
||||
<div v-if="item === null" :class="$style.divider"></div>
|
||||
<span v-else-if="item.type === 'label'" :class="[$style.label, $style.item]">
|
||||
<span>{{ item.text }}</span>
|
||||
</span>
|
||||
<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item">
|
||||
<span v-else-if="item.type === 'pending'" :tabindex="i" :class="[$style.pending, $style.item]">
|
||||
<span><MkEllipsis/></span>
|
||||
</span>
|
||||
<MkA v-else-if="item.type === 'link'" :to="item.to" :tabindex="i" class="_button item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
|
||||
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
|
||||
<MkA v-else-if="item.type === 'link'" :to="item.to" :tabindex="i" class="_button" :class="$style.item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
|
||||
<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
|
||||
<span>{{ item.text }}</span>
|
||||
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
|
||||
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
|
||||
</MkA>
|
||||
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
|
||||
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button" :class="$style.item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
|
||||
<span>{{ item.text }}</span>
|
||||
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
|
||||
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
|
||||
</a>
|
||||
<button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
|
||||
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
|
||||
<button v-else-if="item.type === 'user'" :tabindex="i" class="_button" :class="[$style.item, { [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/>
|
||||
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
|
||||
</button>
|
||||
<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<span v-else-if="item.type === 'switch'" :tabindex="i" :class="$style.item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<MkSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</MkSwitch>
|
||||
</span>
|
||||
<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
|
||||
<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="showChildren(item, $event)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
|
||||
<span>{{ item.text }}</span>
|
||||
<span class="caret"><i class="ti ti-caret-right ti-fw"></i></span>
|
||||
<span :class="$style.caret"><i class="ti ti-caret-right ti-fw"></i></span>
|
||||
</button>
|
||||
<button v-else :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
|
||||
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
|
||||
<button v-else :tabindex="i" class="_button" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
|
||||
<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
|
||||
<span>{{ item.text }}</span>
|
||||
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
|
||||
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
|
||||
</button>
|
||||
</template>
|
||||
<span v-if="items2.length === 0" class="none item">
|
||||
<span v-if="items2.length === 0" :class="[$style.none, $style.item]">
|
||||
<span>{{ i18n.ts.none }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="childMenu" class="child">
|
||||
<div v-if="childMenu" :class="$style.child">
|
||||
<XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,8 +186,8 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rrevdjwt {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
padding: 8px 0;
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
@@ -200,143 +200,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 5px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
font-size: 0.9em;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: calc(100% - 16px);
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
|
||||
&:before {
|
||||
background: var(--accentedBg);
|
||||
}
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #ff2a2a;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
|
||||
&:before {
|
||||
background: #ff4242;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #fff;
|
||||
|
||||
&:before {
|
||||
background: #d42e2e !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.active {
|
||||
color: var(--fgOnAccent) !important;
|
||||
opacity: 1;
|
||||
|
||||
&:before {
|
||||
background: var(--accent) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:active):focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--focus) inset;
|
||||
}
|
||||
|
||||
&.label {
|
||||
pointer-events: none;
|
||||
font-size: 0.7em;
|
||||
padding-bottom: 4px;
|
||||
|
||||
> span {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
&.pending {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.none {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.parent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
|
||||
> .caret {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&.childShowing {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
|
||||
&:before {
|
||||
background: var(--accentedBg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> i {
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 13px;
|
||||
color: var(--indicator);
|
||||
font-size: 12px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
> .divider {
|
||||
margin: 8px 0;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
&.asDrawer {
|
||||
padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
|
||||
padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
|
||||
width: 100%;
|
||||
border-radius: 24px;
|
||||
border-bottom-right-radius: 0;
|
||||
@@ -351,7 +216,7 @@ onBeforeUnmount(() => {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
> i {
|
||||
> .icon {
|
||||
margin-right: 14px;
|
||||
width: 24px;
|
||||
}
|
||||
@@ -362,4 +227,139 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 5px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
font-size: 0.9em;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: calc(100% - 16px);
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
|
||||
&:before {
|
||||
background: var(--accentedBg);
|
||||
}
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #ff2a2a;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
|
||||
&:before {
|
||||
background: #ff4242;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #fff;
|
||||
|
||||
&:before {
|
||||
background: #d42e2e !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.active {
|
||||
color: var(--fgOnAccent) !important;
|
||||
opacity: 1;
|
||||
|
||||
&:before {
|
||||
background: var(--accent) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:active):focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--focus) inset;
|
||||
}
|
||||
|
||||
&.label {
|
||||
pointer-events: none;
|
||||
font-size: 0.7em;
|
||||
padding-bottom: 4px;
|
||||
|
||||
> span {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
&.pending {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.none {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.parent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
|
||||
&.childShowing {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
|
||||
&:before {
|
||||
background: var(--accentedBg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.caret {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 13px;
|
||||
color: var(--indicator);
|
||||
font-size: 12px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 8px 0;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,27 +4,26 @@
|
||||
v-show="!isDeleted"
|
||||
ref="el"
|
||||
v-hotkey="keymap"
|
||||
class="tkcbzcuz"
|
||||
:class="$style.root"
|
||||
:tabindex="!isDeleted ? '-1' : null"
|
||||
:class="{ renote: isRenote }"
|
||||
>
|
||||
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
|
||||
<div v-if="pinned" class="info"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
||||
<div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>
|
||||
<div v-if="appearNote._featuredId_" class="info"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>
|
||||
<div v-if="isRenote" class="renote">
|
||||
<MkAvatar v-once class="avatar" :user="note.user"/>
|
||||
<i class="ti ti-repeat"></i>
|
||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
|
||||
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
||||
<!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
|
||||
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
|
||||
<div v-if="isRenote" :class="$style.renote">
|
||||
<MkAvatar v-once :class="$style.renoteAvatar" :user="note.user"/>
|
||||
<i class="ti ti-repeat" style="margin-right: 4px;"></i>
|
||||
<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
|
||||
<template #user>
|
||||
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
|
||||
<MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.user)">
|
||||
<MkUserName :user="note.user"/>
|
||||
</MkA>
|
||||
</template>
|
||||
</I18n>
|
||||
<div class="info">
|
||||
<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
|
||||
<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
|
||||
<div :class="$style.renoteInfo">
|
||||
<button ref="renoteTime" :class="$style.renoteTime" class="_button" @click="showRenoteMenu()">
|
||||
<i v-if="isMyRenote" class="ti ti-dots" :class="$style.renoteMenu"></i>
|
||||
<MkTime :time="note.createdAt"/>
|
||||
</button>
|
||||
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
|
||||
@@ -35,80 +34,80 @@
|
||||
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<article class="article" @contextmenu.stop="onContextmenu">
|
||||
<MkAvatar v-once class="avatar" :user="appearNote.user"/>
|
||||
<div class="main">
|
||||
<MkNoteHeader class="header" :note="appearNote" :mini="true"/>
|
||||
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
|
||||
<div class="body">
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
|
||||
<article :class="$style.article" @contextmenu.stop="onContextmenu">
|
||||
<MkAvatar v-once :class="$style.avatar" :user="appearNote.user"/>
|
||||
<div :class="$style.main">
|
||||
<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
|
||||
<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>
|
||||
<div style="container-type: inline-size;">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
|
||||
<MkCwButton v-model="showContent" :note="appearNote"/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
|
||||
<div class="text">
|
||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||
<div :class="$style.text">
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||
<Mfm v-if="appearNote.text" v-once :text="appearNote.text" :author="appearNote.user" :i="$i"/>
|
||||
<a v-if="appearNote.renote != null" class="rp">RN:</a>
|
||||
<div v-if="translating || translation" class="translation">
|
||||
<div v-if="translating || translation" :class="$style.translation">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else class="translated">
|
||||
<div v-else :class="$style.translated">
|
||||
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="appearNote.files.length > 0" class="files">
|
||||
<div v-if="appearNote.files.length > 0" :class="$style.files">
|
||||
<MkMediaList :media-list="appearNote.files"/>
|
||||
</div>
|
||||
<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
|
||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
|
||||
<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div>
|
||||
<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
|
||||
<span>{{ i18n.ts.showMore }}</span>
|
||||
<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/>
|
||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
|
||||
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
|
||||
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
|
||||
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
<button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true">
|
||||
<span>{{ i18n.ts.showLess }}</span>
|
||||
<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
|
||||
<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<footer :class="$style.footer">
|
||||
<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
|
||||
<button class="button _button" @click="reply()">
|
||||
<button :class="$style.footerButton" class="_button" @click="reply()">
|
||||
<i class="ti ti-arrow-back-up"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
ref="renoteButton"
|
||||
class="button _button"
|
||||
:class="$style.footerButton"
|
||||
class="_button"
|
||||
@mousedown="renote()"
|
||||
>
|
||||
<i class="ti ti-repeat"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button v-else class="button _button" disabled>
|
||||
<button v-else :class="$style.footerButton" class="_button" disabled>
|
||||
<i class="ti ti-ban"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
|
||||
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
|
||||
<i class="ti ti-plus"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
|
||||
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
|
||||
<i class="ti ti-minus"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="button _button" @mousedown="menu()">
|
||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else class="muted" @click="muted = false">
|
||||
<div v-else :class="$style.muted" @click="muted = false">
|
||||
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
||||
<template #name>
|
||||
<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
|
||||
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||
<MkUserName :user="appearNote.user"/>
|
||||
</MkA>
|
||||
</template>
|
||||
@@ -349,8 +348,8 @@ function readPromo() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tkcbzcuz {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
transition: box-shadow 0.1s ease;
|
||||
font-size: 1.05em;
|
||||
@@ -387,322 +386,259 @@ function readPromo() {
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > .article > .main > .footer > .button {
|
||||
&:hover > .article > .main > .footer > .footerButton {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> .info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 32px 8px 32px;
|
||||
line-height: 24px;
|
||||
font-size: 90%;
|
||||
white-space: pre;
|
||||
color: #d28a3f;
|
||||
|
||||
> i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
> .hide {
|
||||
margin-left: auto;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
> .info + .article {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
> .reply-to {
|
||||
opacity: 0.7;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
> .renote {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 32px 8px 32px;
|
||||
line-height: 28px;
|
||||
white-space: pre;
|
||||
color: var(--renote);
|
||||
|
||||
> .avatar {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 8px 0 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
> i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
flex-shrink: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
> .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
> .info {
|
||||
margin-left: auto;
|
||||
font-size: 0.9em;
|
||||
|
||||
> .time {
|
||||
flex-shrink: 0;
|
||||
color: inherit;
|
||||
|
||||
> .dropdownIcon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .renote + .article {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
> .article {
|
||||
display: flex;
|
||||
padding: 28px 32px 18px;
|
||||
|
||||
> .avatar {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin: 0 14px 8px 0;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
position: sticky;
|
||||
top: calc(22px + var(--stickyTop, 0px));
|
||||
left: 0;
|
||||
}
|
||||
|
||||
> .main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
> .body {
|
||||
container-type: inline-size;
|
||||
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
&.isLong {
|
||||
> .showLess {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
position: sticky;
|
||||
bottom: 1em;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--popup);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
position: relative;
|
||||
max-height: 9em;
|
||||
overflow: clip;
|
||||
|
||||
> .fade {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
background: linear-gradient(0deg, var(--panel), var(--X15));
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> span {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .text {
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .reply {
|
||||
color: var(--accent);
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
> .rp {
|
||||
margin-left: 4px;
|
||||
font-style: oblique;
|
||||
color: var(--renote);
|
||||
}
|
||||
|
||||
> .translation {
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .url-preview {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
> .poll {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
> .renote {
|
||||
padding: 8px 0;
|
||||
|
||||
> .note {
|
||||
padding: 16px;
|
||||
border: dashed 1px var(--renote);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .channel {
|
||||
opacity: 0.7;
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
> .footer {
|
||||
> .button {
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
opacity: 0.7;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
> .count {
|
||||
display: inline;
|
||||
margin: 0 0 0 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.reacted {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .reply {
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 500px) {
|
||||
.tkcbzcuz {
|
||||
font-size: 0.9em;
|
||||
.tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 32px 8px 32px;
|
||||
line-height: 24px;
|
||||
font-size: 90%;
|
||||
white-space: pre;
|
||||
color: #d28a3f;
|
||||
}
|
||||
|
||||
> .article {
|
||||
> .avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
.tip + .article {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.replyTo {
|
||||
opacity: 0.7;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.renote {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 32px 8px 32px;
|
||||
line-height: 28px;
|
||||
white-space: pre;
|
||||
color: var(--renote);
|
||||
|
||||
& + .article {
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.renoteAvatar {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 8px 0 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.renoteText {
|
||||
overflow: hidden;
|
||||
flex-shrink: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.renoteUserName {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.renoteInfo {
|
||||
margin-left: auto;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.renoteTime {
|
||||
flex-shrink: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.renoteMenu {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.article {
|
||||
display: flex;
|
||||
padding: 28px 32px 18px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
display: block !important;
|
||||
margin: 0 14px 8px 0;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
position: sticky !important;
|
||||
top: calc(22px + var(--stickyTop, 0px));
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.showLess {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
position: sticky;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.howLessLabel {
|
||||
display: inline-block;
|
||||
background: var(--popup);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.contentCollapsed {
|
||||
position: relative;
|
||||
max-height: 9em;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
background: linear-gradient(0deg, var(--panel), var(--X15));
|
||||
|
||||
&:hover > .collapsedLabel {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
|
||||
.collapsedLabel {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.text {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.replyIcon {
|
||||
color: var(--accent);
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.translation {
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.urlPreview {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.poll {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.quote {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.quoteNote {
|
||||
padding: 16px;
|
||||
border: dashed 1px var(--renote);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.channel {
|
||||
opacity: 0.7;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.footerButton {
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
opacity: 0.7;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
}
|
||||
|
||||
.footerButtonCount {
|
||||
display: inline;
|
||||
margin: 0 0 0 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@container (max-width: 500px) {
|
||||
.root {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 450px) {
|
||||
.tkcbzcuz {
|
||||
> .renote {
|
||||
padding: 8px 16px 0 16px;
|
||||
}
|
||||
.renote {
|
||||
padding: 8px 16px 0 16px;
|
||||
}
|
||||
|
||||
> .info {
|
||||
padding: 8px 16px 0 16px;
|
||||
}
|
||||
.tip {
|
||||
padding: 8px 16px 0 16px;
|
||||
}
|
||||
|
||||
> .article {
|
||||
padding: 14px 16px 9px;
|
||||
.article {
|
||||
padding: 14px 16px 9px;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
margin: 0 10px 8px 0;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
top: calc(14px + var(--stickyTop, 0px));
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
margin: 0 10px 8px 0;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
top: calc(14px + var(--stickyTop, 0px));
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 350px) {
|
||||
.tkcbzcuz {
|
||||
> .article {
|
||||
> .main {
|
||||
> .footer {
|
||||
> .button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.footerButton {
|
||||
&:not(:last-child) {
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 300px) {
|
||||
.tkcbzcuz {
|
||||
> .article {
|
||||
> .avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
> .main {
|
||||
> .footer {
|
||||
> .button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.footerButton {
|
||||
&:not(:last-child) {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<header class="kkwtjztg">
|
||||
<MkA v-once v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
|
||||
<header :class="$style.root">
|
||||
<MkA v-once v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
|
||||
<MkUserName :user="note.user"/>
|
||||
</MkA>
|
||||
<div v-if="note.user.isBot" class="is-bot">bot</div>
|
||||
<div class="username"><MkAcct :user="note.user"/></div>
|
||||
<div class="info">
|
||||
<MkA class="created-at" :to="notePage(note)">
|
||||
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
|
||||
<div :class="$style.username"><MkAcct :user="note.user"/></div>
|
||||
<div :class="$style.info">
|
||||
<MkA :to="notePage(note)">
|
||||
<MkTime :time="note.createdAt"/>
|
||||
</MkA>
|
||||
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
|
||||
@@ -32,49 +32,49 @@ defineProps<{
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kkwtjztg {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
> .name {
|
||||
flex-shrink: 1;
|
||||
display: block;
|
||||
margin: 0 .5em 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
.name {
|
||||
flex-shrink: 1;
|
||||
display: block;
|
||||
margin: 0 .5em 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
> .is-bot {
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
margin: 0 .5em 0 0;
|
||||
padding: 1px 6px;
|
||||
font-size: 80%;
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
> .username {
|
||||
flex-shrink: 9999999;
|
||||
margin: 0 .5em 0 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> .info {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
font-size: 0.9em;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.isBot {
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
margin: 0 .5em 0 0;
|
||||
padding: 1px 6px;
|
||||
font-size: 80%;
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.username {
|
||||
flex-shrink: 9999999;
|
||||
margin: 0 .5em 0 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="yohlumlk">
|
||||
<MkAvatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<MkNoteHeader class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
|
||||
<div :class="$style.root">
|
||||
<MkAvatar :class="$style.avatar" :user="note.user"/>
|
||||
<div :class="$style.main">
|
||||
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
||||
<div>
|
||||
<p v-if="note.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/>
|
||||
<MkCwButton v-model="showContent" :note="note"/>
|
||||
</p>
|
||||
<div v-show="note.cw == null || showContent" class="content">
|
||||
<MkSubNoteContent class="text" :note="note"/>
|
||||
<div v-show="note.cw == null || showContent">
|
||||
<MkSubNoteContent :class="$style.text" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,73 +31,60 @@ const props = defineProps<{
|
||||
const showContent = $ref(false);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.yohlumlk {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: clip;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin: 0 10px 0 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin: 0 10px 0 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
> .main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
> .header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
.cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .text {
|
||||
cursor: default;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.text {
|
||||
cursor: default;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@container (min-width: 350px) {
|
||||
.yohlumlk {
|
||||
> .avatar {
|
||||
margin: 0 10px 0 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.avatar {
|
||||
margin: 0 10px 0 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 500px) {
|
||||
.yohlumlk {
|
||||
> .avatar {
|
||||
margin: 0 12px 0 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
.avatar {
|
||||
margin: 0 12px 0 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div class="wrpstxzv" :class="{ children: depth > 1 }">
|
||||
<div class="main">
|
||||
<MkAvatar class="avatar" :user="note.user"/>
|
||||
<div class="body">
|
||||
<MkNoteHeader class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i"/>
|
||||
<div :class="[$style.root, { [$style.children]: depth > 1 }]">
|
||||
<div :class="$style.main">
|
||||
<MkAvatar :class="$style.avatar" :user="note.user"/>
|
||||
<div :class="$style.body">
|
||||
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
||||
<div>
|
||||
<p v-if="note.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/>
|
||||
<MkCwButton v-model="showContent" :note="note"/>
|
||||
</p>
|
||||
<div v-show="note.cw == null || showContent" class="content">
|
||||
<MkSubNoteContent class="text" :note="note"/>
|
||||
<div v-show="note.cw == null || showContent">
|
||||
<MkSubNoteContent :class="$style.text" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="depth < 5">
|
||||
<MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/>
|
||||
<MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1"/>
|
||||
</template>
|
||||
<div v-else class="more">
|
||||
<MkA class="text _link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
|
||||
<div v-else :class="$style.more">
|
||||
<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -57,8 +57,8 @@ if (props.detail) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrpstxzv {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
padding: 16px 32px;
|
||||
font-size: 0.9em;
|
||||
|
||||
@@ -66,62 +66,54 @@ if (props.detail) {
|
||||
padding: 10px 0 0 16px;
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
> .main {
|
||||
display: flex;
|
||||
.main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin: 0 8px 0 0;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin: 0 8px 0 0;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
.body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
> .header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
.cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.reply, .more {
|
||||
border-left: solid 0.5px var(--divider);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
> .reply, > .more {
|
||||
border-left: solid 0.5px var(--divider);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
> .more {
|
||||
padding: 10px 0 0 16px;
|
||||
}
|
||||
.more {
|
||||
padding: 10px 0 0 16px;
|
||||
}
|
||||
|
||||
@container (max-width: 450px) {
|
||||
.wrpstxzv {
|
||||
.root {
|
||||
padding: 14px 16px;
|
||||
|
||||
&.children {
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
</template>
|
||||
|
||||
<template #default="{ items: notes }">
|
||||
<div class="giivymft" :class="{ noGap }">
|
||||
<XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes">
|
||||
<XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/>
|
||||
</XList>
|
||||
<div :class="[$style.root, { [$style.noGap]: noGap }]">
|
||||
<MkDateSeparatedList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" :class="$style.notes">
|
||||
<XNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
|
||||
</MkDateSeparatedList>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
@@ -20,7 +20,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef } from 'vue';
|
||||
import XNote from '@/components/MkNote.vue';
|
||||
import XList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
@@ -36,8 +36,8 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.giivymft {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
&.noGap {
|
||||
> .notes {
|
||||
background: var(--panel);
|
||||
@@ -48,7 +48,7 @@ defineExpose({
|
||||
> .notes {
|
||||
background: var(--bg);
|
||||
|
||||
.qtqtichx {
|
||||
.note {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div ref="elRef" class="qglefbjs" :class="notification.type">
|
||||
<div v-once class="head">
|
||||
<MkAvatar v-if="notification.type === 'pollEnded'" class="icon" :user="notification.note.user"/>
|
||||
<MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/>
|
||||
<img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/>
|
||||
<div class="sub-icon" :class="notification.type">
|
||||
<div ref="elRef" :class="$style.root">
|
||||
<div v-once :class="$style.head">
|
||||
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user"/>
|
||||
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user"/>
|
||||
<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
|
||||
<div :class="[$style.subIcon, $style['t_' + notification.type]]">
|
||||
<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
|
||||
<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
|
||||
<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
|
||||
@@ -21,46 +21,47 @@
|
||||
:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
:no-style="true"
|
||||
style="width: 100%; height: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tail">
|
||||
<header>
|
||||
<div :class="$style.tail">
|
||||
<header :class="$style.header">
|
||||
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
|
||||
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" class="name" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
||||
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
||||
<span v-else>{{ notification.header }}</span>
|
||||
<MkTime v-if="withTime" :time="notification.createdAt" class="time"/>
|
||||
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
|
||||
</header>
|
||||
<div v-once class="content">
|
||||
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<i class="ti ti-quote"></i>
|
||||
<div v-once :class="$style.content">
|
||||
<MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||
<i class="ti ti-quote"></i>
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
</MkA>
|
||||
<MkA v-else-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
|
||||
<i class="ti ti-quote"></i>
|
||||
<MkA v-else-if="notification.type === 'renote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
|
||||
<i class="ti ti-quote"></i>
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
</MkA>
|
||||
<MkA v-else-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<MkA v-else-if="notification.type === 'reply'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||
</MkA>
|
||||
<MkA v-else-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<MkA v-else-if="notification.type === 'mention'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||
</MkA>
|
||||
<MkA v-else-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<MkA v-else-if="notification.type === 'quote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||
</MkA>
|
||||
<MkA v-else-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<i class="ti ti-quote"></i>
|
||||
<MkA v-else-if="notification.type === 'pollEnded'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||
<i class="ti ti-quote"></i>
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
</MkA>
|
||||
<span v-else-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
|
||||
<span v-else-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
|
||||
<span v-else-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
|
||||
<span v-else-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
|
||||
<span v-else-if="notification.type === 'app'" class="text">
|
||||
<span v-else-if="notification.type === 'follow'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
|
||||
<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
|
||||
<span v-else-if="notification.type === 'receiveFollowRequest'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
|
||||
<span v-else-if="notification.type === 'groupInvited'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
|
||||
<span v-else-if="notification.type === 'app'" :class="$style.text">
|
||||
<Mfm :text="notification.body" :nowrap="!full"/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -156,8 +157,8 @@ useTooltip(reactionRef, (showing) => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.qglefbjs {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 24px 32px;
|
||||
@@ -165,139 +166,135 @@ useTooltip(reactionRef, (showing) => {
|
||||
overflow-wrap: break-word;
|
||||
display: flex;
|
||||
contain: content;
|
||||
}
|
||||
|
||||
> .head {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex-shrink: 0;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
margin-right: 8px;
|
||||
.head {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex-shrink: 0;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
> .sub-icon {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100%;
|
||||
background: var(--panel);
|
||||
box-shadow: 0 0 0 3px var(--panel);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
.subIcon {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100%;
|
||||
background: var(--panel);
|
||||
box-shadow: 0 0 0 3px var(--panel);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> * {
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.follow, &.followRequestAccepted, &.receiveFollowRequest, &.groupInvited {
|
||||
padding: 3px;
|
||||
background: #36aed2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.renote {
|
||||
padding: 3px;
|
||||
background: #36d298;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.quote {
|
||||
padding: 3px;
|
||||
background: #36d298;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.reply {
|
||||
padding: 3px;
|
||||
background: #007aff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.mention {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.pollEnded {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .tail {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
|
||||
> .name {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .time {
|
||||
margin-left: auto;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
> i {
|
||||
vertical-align: super;
|
||||
font-size: 50%;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
> i:first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
> i:last-child {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest, .t_groupInvited {
|
||||
padding: 3px;
|
||||
background: #36aed2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_renote {
|
||||
padding: 3px;
|
||||
background: #36d298;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_quote {
|
||||
padding: 3px;
|
||||
background: #36d298;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_reply {
|
||||
padding: 3px;
|
||||
background: #007aff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_mention {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_pollEnded {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tail {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.headerName {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.headerTime {
|
||||
margin-left: auto;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.content {
|
||||
}
|
||||
|
||||
.text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quote {
|
||||
vertical-align: super;
|
||||
font-size: 50%;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.quote:first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.quote:last-child {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
@container (max-width: 600px) {
|
||||
.qglefbjs {
|
||||
.root {
|
||||
padding: 16px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 500px) {
|
||||
.qglefbjs {
|
||||
.root {
|
||||
padding: 12px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
</template>
|
||||
|
||||
<template #default="{ items: notifications }">
|
||||
<XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true">
|
||||
<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
|
||||
<XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/>
|
||||
</XList>
|
||||
</MkDateSeparatedList>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</template>
|
||||
@@ -21,7 +21,7 @@ import { defineComponent, markRaw, onUnmounted, onMounted, computed, shallowRef
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import XNotification from '@/components/MkNotification.vue';
|
||||
import XList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import XNote from '@/components/MkNote.vue';
|
||||
import * as os from '@/os';
|
||||
import { stream } from '@/stream';
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<Transition :name="$store.state.animation ? 'y' : ''">
|
||||
<TransitionGroup v-if="Object.keys(note.reactions).length > 0" :name="$store.state.animation ? 'x' : ''" tag="div" class="tdflqwzn" :class="{ isMe }">
|
||||
<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
|
||||
</TransitionGroup>
|
||||
</Transition>
|
||||
<TransitionGroup :name="$store.state.animation ? 'x' : ''" tag="div" class="tdflqwzn" :class="{ isMe }">
|
||||
<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -22,16 +20,6 @@ const isMe = computed(() => $i && $i.id === props.note.userId);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.y-enter-active, .y-leave-active {
|
||||
overflow: clip;
|
||||
max-height: 36px;
|
||||
transition: opacity 0.2s cubic-bezier(0,.5,.5,1), max-height 0.2s cubic-bezier(0,.5,.5,1) !important;
|
||||
}
|
||||
.y-enter-from, .y-leave-to {
|
||||
max-height: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.x-move, .x-enter-active, .x-leave-active {
|
||||
transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<Transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="emit('closed')">
|
||||
<div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_tooltip_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_tooltip_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_tooltip_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_tooltip_leaveTo : ''"
|
||||
appear @after-leave="emit('closed')"
|
||||
>
|
||||
<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
|
||||
<slot>
|
||||
<Mfm v-if="asMfm" :text="text"/>
|
||||
<span v-else>{{ text }}</span>
|
||||
@@ -74,20 +80,20 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tooltip-enter-active,
|
||||
.tooltip-leave-active {
|
||||
<style lang="scss" module>
|
||||
.transition_tooltip_enterActive,
|
||||
.transition_tooltip_leaveActive {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.tooltip-enter-from,
|
||||
.tooltip-leave-active {
|
||||
.transition_tooltip_enterFrom,
|
||||
.transition_tooltip_leaveTo {
|
||||
opacity: 0;
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
.buebdbiu {
|
||||
.root {
|
||||
position: absolute;
|
||||
font-size: 0.8em;
|
||||
padding: 8px 12px;
|
||||
|
||||
@@ -15,21 +15,21 @@
|
||||
handle=".handle"
|
||||
:animation="150"
|
||||
:group="{ name: 'SortableMkWidgets' }"
|
||||
@update:model-value="v => emit('updateWidgets', v)"
|
||||
:class="$style['edit-editing']"
|
||||
@update:model-value="v => emit('updateWidgets', v)"
|
||||
>
|
||||
<template #item="{element}">
|
||||
<div :class="[$style.widget, $style['customize-container']]">
|
||||
<button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
|
||||
<button :class="$style['customize-container-remove']" class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
|
||||
<div class="handle">
|
||||
<component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/>
|
||||
<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Sortable>
|
||||
</template>
|
||||
<component :is="`mkw-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
|
||||
<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<span class="mk-acct">
|
||||
<span class="name">@{{ user.username }}</span>
|
||||
<span v-if="user.host || detail || $store.state.showFullAcct" class="host">@{{ user.host || host }}</span>
|
||||
<span>
|
||||
<span>@{{ user.username }}</span>
|
||||
<span v-if="user.host || detail || $store.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -18,10 +18,3 @@ defineProps<{
|
||||
const host = toUnicode(hostRaw);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-acct {
|
||||
> .host {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick">
|
||||
<img class="inner" :src="url" decoding="async"/>
|
||||
<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
|
||||
<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" class="_noSelect" :style="{ color }" :title="acct(user)" @click="onClick">
|
||||
<img :class="$style.inner" :src="url" decoding="async"/>
|
||||
<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
|
||||
</span>
|
||||
<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
|
||||
<img class="inner" :src="url" decoding="async"/>
|
||||
<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
|
||||
<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
|
||||
<img :class="$style.inner" :src="url" decoding="async"/>
|
||||
<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
|
||||
</MkA>
|
||||
</template>
|
||||
|
||||
@@ -52,7 +52,7 @@ watch(() => props.user.avatarBlurhash, () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss" module>
|
||||
@keyframes earwiggleleft {
|
||||
from { transform: rotate(37.6deg) skew(30deg); }
|
||||
25% { transform: rotate(10deg) skew(30deg); }
|
||||
@@ -69,74 +69,74 @@ watch(() => props.user.avatarBlurhash, () => {
|
||||
to { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
}
|
||||
|
||||
.eiwwqkts {
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
flex-shrink: 0;
|
||||
border-radius: 100%;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.inner {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 100%;
|
||||
z-index: 1;
|
||||
overflow: clip;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
.square {
|
||||
border-radius: 20%;
|
||||
|
||||
> .inner {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 100%;
|
||||
z-index: 1;
|
||||
overflow: clip;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
> .inner {
|
||||
border-radius: 20%;
|
||||
}
|
||||
.cat {
|
||||
&:before, &:after {
|
||||
background: #df548f;
|
||||
border: solid 4px currentColor;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&.cat {
|
||||
&:before, &:after {
|
||||
background: #df548f;
|
||||
border: solid 4px currentColor;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
&:before {
|
||||
border-radius: 0 75% 75%;
|
||||
transform: rotate(37.5deg) skew(30deg);
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-radius: 75% 0 75% 75%;
|
||||
transform: rotate(-37.5deg) skew(-30deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:before {
|
||||
border-radius: 0 75% 75%;
|
||||
transform: rotate(37.5deg) skew(30deg);
|
||||
animation: earwiggleleft 1s infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-radius: 75% 0 75% 75%;
|
||||
transform: rotate(-37.5deg) skew(-30deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:before {
|
||||
animation: earwiggleleft 1s infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
animation: earwiggleright 1s infinite;
|
||||
}
|
||||
animation: earwiggleright 1s infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<img v-if="isCustom" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
|
||||
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
|
||||
<img v-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async"/>
|
||||
<img v-else-if="char && !useOsNativeEmojis" :class="$style.root" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
|
||||
<span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span>
|
||||
<span v-else>{{ emoji }}</span>
|
||||
</template>
|
||||
@@ -47,32 +47,32 @@ function computeTitle(event: PointerEvent): void {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-emoji {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
height: 1.25em;
|
||||
vertical-align: -0.25em;
|
||||
}
|
||||
|
||||
.custom {
|
||||
height: 2.5em;
|
||||
vertical-align: middle;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.normal {
|
||||
height: 1.25em;
|
||||
vertical-align: -0.25em;
|
||||
|
||||
&.custom {
|
||||
height: 2.5em;
|
||||
vertical-align: middle;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
&.normal {
|
||||
height: 1.25em;
|
||||
vertical-align: -0.25em;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.noStyle {
|
||||
height: auto !important;
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.noStyle {
|
||||
height: auto !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<component
|
||||
:is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel" :target="target"
|
||||
:is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel" :target="target"
|
||||
@contextmenu.stop="() => {}"
|
||||
>
|
||||
<template v-if="!self">
|
||||
<span class="schema">{{ schema }}//</span>
|
||||
<span class="hostname">{{ hostname }}</span>
|
||||
<span v-if="port != ''" class="port">:{{ port }}</span>
|
||||
<span :class="$style.schema">{{ schema }}//</span>
|
||||
<span :class="$style.hostname">{{ hostname }}</span>
|
||||
<span v-if="port != ''" :class="$style.port">:{{ port }}</span>
|
||||
</template>
|
||||
<template v-if="pathname === '/' && self">
|
||||
<span class="self">{{ hostname }}</span>
|
||||
<span :class="$style.self">{{ hostname }}</span>
|
||||
</template>
|
||||
<span v-if="pathname != ''" class="pathname">{{ self ? pathname.substring(1) : pathname }}</span>
|
||||
<span class="query">{{ query }}</span>
|
||||
<span class="hash">{{ hash }}</span>
|
||||
<span v-if="pathname != ''" :class="$style.pathname">{{ self ? pathname.substring(1) : pathname }}</span>
|
||||
<span :class="$style.query">{{ query }}</span>
|
||||
<span :class="$style.hash">{{ hash }}</span>
|
||||
<i v-if="target === '_blank'" class="ti ti-external-link icon"></i>
|
||||
</component>
|
||||
</template>
|
||||
@@ -53,37 +53,37 @@ const attr = self ? 'to' : 'href';
|
||||
const target = self ? null : '_blank';
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ieqqeuvs {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
padding-left: 2px;
|
||||
font-size: .9em;
|
||||
}
|
||||
.icon {
|
||||
padding-left: 2px;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
> .self {
|
||||
font-weight: bold;
|
||||
}
|
||||
.self {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> .schema {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.schema {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
> .hostname {
|
||||
font-weight: bold;
|
||||
}
|
||||
.hostname {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> .pathname {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.pathname {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
> .query {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.query {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
> .hash {
|
||||
font-style: italic;
|
||||
}
|
||||
.hash {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
47
packages/frontend/src/custom-emojis.ts
Normal file
47
packages/frontend/src/custom-emojis.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { api } from './os';
|
||||
import { miLocalStorage } from './local-storage';
|
||||
|
||||
const storageCache = miLocalStorage.getItem('emojis');
|
||||
export let customEmojis = storageCache ? JSON.parse(storageCache) : [];
|
||||
|
||||
fetchCustomEmojis();
|
||||
|
||||
export async function fetchCustomEmojis() {
|
||||
const now = Date.now();
|
||||
const lastFetchedAt = miLocalStorage.getItem('lastEmojisFetchedAt');
|
||||
if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return;
|
||||
|
||||
const res = await api('emojis', {});
|
||||
|
||||
customEmojis = res.emojis;
|
||||
miLocalStorage.setItem('emojis', JSON.stringify(customEmojis));
|
||||
miLocalStorage.setItem('lastEmojisFetchedAt', now.toString());
|
||||
}
|
||||
|
||||
let cachedCategories;
|
||||
export function getCustomEmojiCategories() {
|
||||
if (cachedCategories) return cachedCategories;
|
||||
|
||||
const categories = new Set();
|
||||
for (const emoji of customEmojis) {
|
||||
categories.add(emoji.category);
|
||||
}
|
||||
const res = Array.from(categories);
|
||||
cachedCategories = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
let cachedTags;
|
||||
export function getCustomEmojiTags() {
|
||||
if (cachedTags) return cachedTags;
|
||||
|
||||
const tags = new Set();
|
||||
for (const emoji of customEmojis) {
|
||||
for (const tag of emoji.aliases) {
|
||||
tags.add(tag);
|
||||
}
|
||||
}
|
||||
const res = Array.from(tags);
|
||||
cachedTags = res;
|
||||
return res;
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import { miLocalStorage } from './local-storage';
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
||||
const instanceData = miLocalStorage.getItem('instance');
|
||||
const cached = miLocalStorage.getItem('instance');
|
||||
|
||||
// TODO: instanceをリアクティブにするかは再考の余地あり
|
||||
|
||||
export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : {
|
||||
export const instance: Misskey.entities.InstanceMetadata = reactive(cached ? JSON.parse(cached) : {
|
||||
// TODO: set default values
|
||||
});
|
||||
|
||||
@@ -24,23 +24,3 @@ export async function fetchInstance() {
|
||||
|
||||
miLocalStorage.setItem('instance', JSON.stringify(instance));
|
||||
}
|
||||
|
||||
export const emojiCategories = computed(() => {
|
||||
if (instance.emojis == null) return [];
|
||||
const categories = new Set();
|
||||
for (const emoji of instance.emojis) {
|
||||
categories.add(emoji.category);
|
||||
}
|
||||
return Array.from(categories);
|
||||
});
|
||||
|
||||
export const emojiTags = computed(() => {
|
||||
if (instance.emojis == null) return [];
|
||||
const tags = new Set();
|
||||
for (const emoji of instance.emojis) {
|
||||
for (const tag of emoji.aliases) {
|
||||
tags.add(tag);
|
||||
}
|
||||
}
|
||||
return Array.from(tags);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ type Keys =
|
||||
'v' |
|
||||
'lastVersion' |
|
||||
'instance' |
|
||||
'emojis' | // TODO: indexed db
|
||||
'lastEmojisFetchedAt' |
|
||||
'account' |
|
||||
'accounts' |
|
||||
'latestDonationInfoShownAt' |
|
||||
|
||||
@@ -4,89 +4,11 @@ import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { apiUrl, url } from '@/config';
|
||||
import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
|
||||
import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
|
||||
import { MenuItem } from '@/types/menu';
|
||||
import { $i } from '@/account';
|
||||
|
||||
export const pendingApiRequestsCount = ref(0);
|
||||
|
||||
const apiClient = new Misskey.api.APIClient({
|
||||
origin: url,
|
||||
});
|
||||
|
||||
export const api = ((endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) => {
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
const onFinally = () => {
|
||||
pendingApiRequestsCount.value--;
|
||||
};
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
// Append a credential
|
||||
if ($i) (data as any).i = $i.token;
|
||||
if (token !== undefined) (data as any).i = token;
|
||||
|
||||
// Send request
|
||||
window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'omit',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(body.error);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
promise.then(onFinally, onFinally);
|
||||
|
||||
return promise;
|
||||
}) as typeof apiClient.request;
|
||||
|
||||
export const apiGet = ((endpoint: string, data: Record<string, any> = {}) => {
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
const onFinally = () => {
|
||||
pendingApiRequestsCount.value--;
|
||||
};
|
||||
|
||||
const query = new URLSearchParams(data);
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
// Send request
|
||||
window.fetch(`${apiUrl}/${endpoint}?${query}`, {
|
||||
method: 'GET',
|
||||
credentials: 'omit',
|
||||
cache: 'default',
|
||||
}).then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(body.error);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
promise.then(onFinally, onFinally);
|
||||
|
||||
return promise;
|
||||
}) as typeof apiClient.request;
|
||||
import { pendingApiRequestsCount, api, apiGet } from '@/scripts/api';
|
||||
export { pendingApiRequestsCount, api, apiGet };
|
||||
|
||||
export const apiWithDialog = ((
|
||||
endpoint: string,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<!-- たくさんあると邪魔
|
||||
<div class="tags">
|
||||
<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
|
||||
<span class="tag _button" v-for="tag in customEmojiTags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
@@ -28,8 +28,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, computed, watch } from 'vue';
|
||||
import XEmoji from './emojis.emoji.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
@@ -37,62 +37,42 @@ import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import * as os from '@/os';
|
||||
import { emojiCategories, emojiTags } from '@/instance';
|
||||
import { customEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSelect,
|
||||
MkFoldableSection,
|
||||
MkTab,
|
||||
XEmoji,
|
||||
},
|
||||
const customEmojiCategories = getCustomEmojiCategories();
|
||||
const customEmojiTags = getCustomEmojiTags();
|
||||
let q = $ref('');
|
||||
let searchEmojis = $ref(null);
|
||||
let selectedTags = $ref(new Set());
|
||||
|
||||
data() {
|
||||
return {
|
||||
q: '',
|
||||
customEmojiCategories: emojiCategories,
|
||||
customEmojis: this.$instance.emojis,
|
||||
tags: emojiTags,
|
||||
selectedTags: new Set(),
|
||||
searchEmojis: null,
|
||||
};
|
||||
},
|
||||
function search() {
|
||||
if ((q === '' || q == null) && selectedTags.size === 0) {
|
||||
searchEmojis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
watch: {
|
||||
q() { this.search(); },
|
||||
selectedTags: {
|
||||
handler() {
|
||||
this.search();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
if (selectedTags.size === 0) {
|
||||
searchEmojis = customEmojis.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
|
||||
} else {
|
||||
searchEmojis = customEmojis.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t)));
|
||||
}
|
||||
}
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
|
||||
this.searchEmojis = null;
|
||||
return;
|
||||
}
|
||||
function toggleTag(tag) {
|
||||
if (selectedTags.has(tag)) {
|
||||
selectedTags.delete(tag);
|
||||
} else {
|
||||
selectedTags.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedTags.size === 0) {
|
||||
this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q));
|
||||
} else {
|
||||
this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t)));
|
||||
}
|
||||
},
|
||||
|
||||
toggleTag(tag) {
|
||||
if (this.selectedTags.has(tag)) {
|
||||
this.selectedTags.delete(tag);
|
||||
} else {
|
||||
this.selectedTags.add(tag);
|
||||
}
|
||||
},
|
||||
},
|
||||
watch($$(q), () => {
|
||||
search();
|
||||
});
|
||||
|
||||
watch($$(selectedTags), () => {
|
||||
search();
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -36,7 +36,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||
import * as os from '@/os';
|
||||
import { unique } from '@/scripts/array';
|
||||
import { i18n } from '@/i18n';
|
||||
import { emojiCategories } from '@/instance';
|
||||
import { getCustomEmojiCategories } from '@/custom-emojis';
|
||||
|
||||
const props = defineProps<{
|
||||
emoji: any,
|
||||
@@ -46,7 +46,7 @@ let dialog = $ref(null);
|
||||
let name: string = $ref(props.emoji.name);
|
||||
let category: string = $ref(props.emoji.category);
|
||||
let aliases: string = $ref(props.emoji.aliases.join(' '));
|
||||
let categories: string[] = $ref(emojiCategories);
|
||||
const categories = getCustomEmojiCategories();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="icon"><i class="ti ti-icons"></i></div>
|
||||
<div class="body">
|
||||
<div class="value">
|
||||
<MkNumber :value="$instance.emojis.length" style="margin-right: 0.5em;"/>
|
||||
<MkNumber :value="customEmojis.length" style="margin-right: 0.5em;"/>
|
||||
</div>
|
||||
<div class="label">Custom emojis</div>
|
||||
</div>
|
||||
@@ -63,6 +63,7 @@ import number from '@/filters/number';
|
||||
import MkNumberDiff from '@/components/MkNumberDiff.vue';
|
||||
import MkNumber from '@/components/MkNumber.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { customEmojis } from '@/custom-emojis';
|
||||
|
||||
let stats: any = $ref(null);
|
||||
let usersComparedToThePrevDay = $ref<number>();
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="800">
|
||||
<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
|
||||
<div>{{ relay.inbox }}</div>
|
||||
<div class="status">
|
||||
<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i>
|
||||
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i>
|
||||
<i v-else class="ti ti-clock icon requesting"></i>
|
||||
<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
|
||||
<div class="_gaps">
|
||||
<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
|
||||
<div>{{ relay.inbox }}</div>
|
||||
<div class="status">
|
||||
<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i>
|
||||
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i>
|
||||
<i v-else class="ti ti-clock icon requesting"></i>
|
||||
<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
|
||||
</div>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<button class="zuvgdzyu _button" @click="menu">
|
||||
<img :src="`/emoji/${emoji.name}.webp`" class="img" :alt="emoji.name"/>
|
||||
<img :src="`/emoji/${emoji.name}.webp`" class="img" loading="lazy"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
@@ -49,6 +49,7 @@ function menu(ev) {
|
||||
> .img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
> .body {
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
</template>
|
||||
|
||||
<template #default="{ items }">
|
||||
<XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
|
||||
<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
|
||||
<XNote :key="item.id" :note="item.note" :class="$style.note"/>
|
||||
</XList>
|
||||
</MkDateSeparatedList>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkSpacer>
|
||||
@@ -24,7 +24,7 @@
|
||||
import { ref } from 'vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import XNote from '@/components/MkNote.vue';
|
||||
import XList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<template #default="{ items: messages, fetching: pFetching }">
|
||||
<XList
|
||||
<MkDateSeparatedList
|
||||
v-if="messages.length > 0"
|
||||
v-slot="{ item: message }"
|
||||
:class="{ messages: true, 'deny-move-transition': pFetching }"
|
||||
@@ -25,7 +25,7 @@
|
||||
reversed
|
||||
>
|
||||
<XMessage :key="message.id" :message="message" :is-group="group != null"/>
|
||||
</XList>
|
||||
</MkDateSeparatedList>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
@@ -55,7 +55,7 @@ import * as Misskey from 'misskey-js';
|
||||
import * as Acct from 'misskey-js/built/acct';
|
||||
import XMessage from './messaging-room.message.vue';
|
||||
import XForm from './messaging-room.form.vue';
|
||||
import XList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import { isBottomVisible, onScrollBottom, scrollToBottom } from '@/scripts/scroll';
|
||||
import * as os from '@/os';
|
||||
@@ -351,7 +351,6 @@ definePageMetadata(computed(() => !fetching ? user ? {
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
padding-top: 8px;
|
||||
bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
|
||||
|
||||
> .new-message {
|
||||
width: 100%;
|
||||
|
||||
@@ -317,12 +317,13 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
import { instance } from '@/instance';
|
||||
import { customEmojis } from '@/custom-emojis';
|
||||
|
||||
let preview_mention = $ref('@example');
|
||||
let preview_hashtag = $ref('#test');
|
||||
let preview_url = $ref('https://example.com');
|
||||
let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`);
|
||||
let preview_emoji = $ref(instance.emojis.length ? `:${instance.emojis[0].name}:` : ':emojiname:');
|
||||
let preview_emoji = $ref(customEmojis.length ? `:${customEmojis[0].name}:` : ':emojiname:');
|
||||
let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`);
|
||||
let preview_small = $ref(`<small>${i18n.ts._mfm.dummy}</small>`);
|
||||
let preview_center = $ref(`<center>${i18n.ts._mfm.dummy}</center>`);
|
||||
|
||||
@@ -183,6 +183,8 @@ const menuDef = computed(() => [{
|
||||
action: () => {
|
||||
miLocalStorage.removeItem('locale');
|
||||
miLocalStorage.removeItem('theme');
|
||||
miLocalStorage.removeItem('emojis');
|
||||
miLocalStorage.removeItem('lastEmojisFetchedAt');
|
||||
unisonReload();
|
||||
},
|
||||
}, {
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="headerTabs" :display-my-avatar="true"/></template>
|
||||
<MkSpacer :content-max="800">
|
||||
<div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf">
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _panel" style="margin-bottom: var(--margin);"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||
<div ref="rootEl" v-hotkey.global="keymap">
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||
|
||||
<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div class="tl">
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div :class="$style.tl">
|
||||
<XTimeline
|
||||
ref="tl" :key="src"
|
||||
class="tl"
|
||||
:src="src"
|
||||
:sound="true"
|
||||
@queue="queueUpdated"
|
||||
@@ -154,30 +153,28 @@ definePageMetadata(computed(() => ({
|
||||
})));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cmuxhskf {
|
||||
> .new {
|
||||
position: sticky;
|
||||
top: calc(var(--stickyTop, 0px) + 16px);
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
<style lang="scss" module>
|
||||
.new {
|
||||
position: sticky;
|
||||
top: calc(var(--stickyTop, 0px) + 16px);
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
|
||||
> button {
|
||||
display: block;
|
||||
margin: var(--margin) auto 0 auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
> .post-form {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
> .tl {
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
> button {
|
||||
display: block;
|
||||
margin: var(--margin) auto 0 auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.postForm {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.tl {
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
}
|
||||
</style>
|
||||
|
||||
79
packages/frontend/src/scripts/api.ts
Normal file
79
packages/frontend/src/scripts/api.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Endpoints } from 'misskey-js/built/api.types';
|
||||
import { ref } from 'vue';
|
||||
import { apiUrl } from '@/config';
|
||||
import { $i } from '@/account';
|
||||
export const pendingApiRequestsCount = ref(0);
|
||||
|
||||
// Implements Misskey.api.ApiClient.request
|
||||
export function api<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, data: P = {} as any, token?: string | null | undefined): Promise<Endpoints[E]['res']> {
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
const onFinally = () => {
|
||||
pendingApiRequestsCount.value--;
|
||||
};
|
||||
|
||||
const promise = new Promise<Endpoints[E]['res'] | void>((resolve, reject) => {
|
||||
// Append a credential
|
||||
if ($i) (data as any).i = $i.token;
|
||||
if (token !== undefined) (data as any).i = token;
|
||||
|
||||
// Send request
|
||||
window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'omit',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(body.error);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
promise.then(onFinally, onFinally);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Implements Misskey.api.ApiClient.request
|
||||
export function apiGet<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, data: P = {} as any): Promise<Endpoints[E]['res']> {
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
const onFinally = () => {
|
||||
pendingApiRequestsCount.value--;
|
||||
};
|
||||
|
||||
const query = new URLSearchParams(data as any);
|
||||
|
||||
const promise = new Promise<Endpoints[E]['res'] | void>((resolve, reject) => {
|
||||
// Send request
|
||||
window.fetch(`${apiUrl}/${endpoint}?${query}`, {
|
||||
method: 'GET',
|
||||
credentials: 'omit',
|
||||
cache: 'default',
|
||||
}).then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(body.error);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
promise.then(onFinally, onFinally);
|
||||
|
||||
return promise;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export class StickySidebar {
|
||||
private isTop = false;
|
||||
private isBottom = false;
|
||||
private offsetTop: number;
|
||||
private globalHeaderHeight: number = 59;
|
||||
private globalHeaderHeight = 59;
|
||||
|
||||
constructor(container: StickySidebar['container'], marginTop = 0, globalHeaderHeight = 0) {
|
||||
this.container = container;
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
--marginHalf: 10px;
|
||||
|
||||
--margin: var(--marginFull);
|
||||
|
||||
--minBottomSpacing: 0px;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
--margin: var(--marginHalf);
|
||||
--minBottomSpacing: calc(72px + max(12px, env(safe-area-inset-bottom, 0px)));
|
||||
}
|
||||
|
||||
//--ad: rgb(255 169 0 / 10%);
|
||||
|
||||
@@ -9,8 +9,15 @@
|
||||
|
||||
<XUpload v-if="uploads.length > 0"/>
|
||||
|
||||
<TransitionGroup :name="$store.state.animation ? 'notification' : ''" tag="div" class="notifications">
|
||||
<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" class="notification"/>
|
||||
<TransitionGroup
|
||||
tag="div" :class="$style.notifications"
|
||||
:move-class="$store.state.animation ? $style.transition_notification_move : ''"
|
||||
:enter-active-class="$store.state.animation ? $style.transition_notification_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_notification_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_notification_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_notification_leaveTo : ''"
|
||||
>
|
||||
<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" :class="$style.notification"/>
|
||||
</TransitionGroup>
|
||||
|
||||
<XStreamIndicator/>
|
||||
@@ -73,11 +80,14 @@ if ($i) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notification-move, .notification-enter-active, .notification-leave-active {
|
||||
<style lang="scss" module>
|
||||
.transition_notification_move,
|
||||
.transition_notification_enterActive,
|
||||
.transition_notification_leaveActive {
|
||||
transition: opacity 0.3s, transform 0.3s !important;
|
||||
}
|
||||
.notification-enter-from, .notification-leave-to {
|
||||
.transition_notification_enterFrom,
|
||||
.transition_notification_leaveTo {
|
||||
opacity: 0;
|
||||
transform: translateX(-250px);
|
||||
}
|
||||
@@ -91,31 +101,28 @@ if ($i) {
|
||||
padding: 0 32px;
|
||||
pointer-events: none;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
> .notification {
|
||||
& + .notification {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.notification {
|
||||
& + .notification {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
@media (max-width: 500px) {
|
||||
.notifications {
|
||||
top: initial;
|
||||
bottom: 112px;
|
||||
padding: 0 16px;
|
||||
bottom: calc(var(--minBottomSpacing) + var(--margin));
|
||||
padding: 0 var(--margin);
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
|
||||
> .notification {
|
||||
& + .notification {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
bottom: calc(env(safe-area-inset-bottom, 0px) + 92px);
|
||||
padding: 0 8px;
|
||||
.notification {
|
||||
& + .notification {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,8 +38,8 @@ onUnmounted(() => {
|
||||
.nsbbhtug {
|
||||
position: fixed;
|
||||
z-index: 16385;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
bottom: calc(var(--minBottomSpacing) + var(--margin));
|
||||
right: var(--margin);
|
||||
margin: 0;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9em;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<XSidebar/>
|
||||
</div>
|
||||
<div v-else ref="widgetsLeft" class="widgets left">
|
||||
<XWidgets place="left" :classic="true" @mounted="attachSticky(widgetsLeft)"/>
|
||||
<XWidgets place="left" :margin-top="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/>
|
||||
</div>
|
||||
|
||||
<main class="main" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
|
||||
@@ -17,7 +17,7 @@
|
||||
</main>
|
||||
|
||||
<div v-if="isDesktop" ref="widgetsRight" class="widgets right">
|
||||
<XWidgets :place="showMenuOnTop ? 'right' : null" :classic="true" @mounted="attachSticky(widgetsRight)"/>
|
||||
<XWidgets :place="showMenuOnTop ? 'right' : null" :margin-top="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,7 @@ provide('shouldHeaderThin', showMenuOnTop);
|
||||
provide('forceSpacerMin', true);
|
||||
|
||||
function attachSticky(el) {
|
||||
const sticky = new StickySidebar(el, defaultStore.state.menuDisplay === 'top' ? 0 : 16, defaultStore.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
|
||||
const sticky = new StickySidebar(el, 0, defaultStore.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
|
||||
window.addEventListener('scroll', () => {
|
||||
sticky.calc(window.scrollY);
|
||||
}, { passive: true });
|
||||
@@ -248,7 +248,6 @@ onMounted(() => {
|
||||
> .widgets {
|
||||
//--panelBorder: none;
|
||||
width: 300px;
|
||||
margin-top: 16px;
|
||||
|
||||
@media (max-width: $widgets-hide-threshold) {
|
||||
display: none;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<template>
|
||||
<div
|
||||
class="mk-deck" :class="[{ isMobile }]"
|
||||
>
|
||||
<div :class="[$style.root, { [$style.rootIsMobile]: isMobile }]">
|
||||
<XSidebar v-if="!isMobile"/>
|
||||
|
||||
<div class="main">
|
||||
<XStatusBars class="statusbars"/>
|
||||
<div ref="columnsEl" class="columns" :class="deckStore.reactiveState.columnAlign.value" @contextmenu.self.prevent="onContextmenu">
|
||||
<div :class="$style.main">
|
||||
<XStatusBars/>
|
||||
<div ref="columnsEl" :class="[$style.columns, deckStore.reactiveState.columnAlign.value]" @contextmenu.self.prevent="onContextmenu">
|
||||
<template v-for="ids in layout">
|
||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
||||
<section
|
||||
v-if="ids.length > 1"
|
||||
class="folder column"
|
||||
:class="$style.folder"
|
||||
:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
|
||||
>
|
||||
<DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/>
|
||||
@@ -20,51 +18,64 @@
|
||||
v-else
|
||||
:ref="ids[0]"
|
||||
:key="ids[0]"
|
||||
class="column"
|
||||
:class="$style.column"
|
||||
:column="columns.find(c => c.id === ids[0])"
|
||||
:is-stacked="false"
|
||||
:style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }"
|
||||
@parent-focus="moveFocus(ids[0], $event)"
|
||||
/>
|
||||
</template>
|
||||
<div v-if="layout.length === 0" class="intro _panel">
|
||||
<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
|
||||
<div>{{ i18n.ts._deck.introduction }}</div>
|
||||
<MkButton primary class="add" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton>
|
||||
<MkButton primary style="margin: 1em auto;" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton>
|
||||
<div>{{ i18n.ts._deck.introduction2 }}</div>
|
||||
</div>
|
||||
<div class="sideMenu">
|
||||
<div class="top">
|
||||
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${deckStore.state.profile}`" class="_button button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
|
||||
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" class="_button button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
|
||||
<div :class="$style.sideMenu">
|
||||
<div :class="$style.sideMenuTop">
|
||||
<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${deckStore.state.profile}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
|
||||
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
|
||||
</div>
|
||||
<div class="middle">
|
||||
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" class="_button button" @click="addColumn"><i class="ti ti-plus"></i></button>
|
||||
<div :class="$style.sideMenuMiddle">
|
||||
<button v-tooltip.noDelay.left="i18n.ts._deck.addColumn" :class="$style.sideMenuButton" class="_button" @click="addColumn"><i class="ti ti-plus"></i></button>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<button v-tooltip.noDelay.left="i18n.ts.settings" class="_button button settings" @click="showSettings"><i class="ti ti-settings"></i></button>
|
||||
<div :class="$style.sideMenuBottom">
|
||||
<button v-tooltip.noDelay.left="i18n.ts.settings" :class="$style.sideMenuButton" class="_button" @click="showSettings"><i class="ti ti-settings"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isMobile" class="buttons">
|
||||
<button class="button nav _button" @click="drawerMenuShowing = true"><i class="ti ti-menu-2"></i><span v-if="menuIndicated" class="indicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button class="button home _button" @click="mainRouter.push('/')"><i class="ti ti-home"></i></button>
|
||||
<button class="button notifications _button" @click="mainRouter.push('/my/notifications')"><i class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button class="button post _button" @click="os.post()"><i class="ti ti-pencil"></i></button>
|
||||
<div v-if="isMobile" :class="$style.nav">
|
||||
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i :class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
|
||||
</div>
|
||||
|
||||
<Transition :name="$store.state.animation ? 'menu-back' : ''">
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
|
||||
>
|
||||
<div
|
||||
v-if="drawerMenuShowing"
|
||||
class="menu-back _modalBg"
|
||||
:class="$style.menuBg"
|
||||
class="_modalBg"
|
||||
@click="drawerMenuShowing = false"
|
||||
@touchstart.passive="drawerMenuShowing = false"
|
||||
></div>
|
||||
</Transition>
|
||||
|
||||
<Transition :name="$store.state.animation ? 'menu' : ''">
|
||||
<XDrawerMenu v-if="drawerMenuShowing" class="menu"/>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_menuDrawer_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
|
||||
>
|
||||
<div v-if="drawerMenuShowing" :class="$style.menu">
|
||||
<XDrawerMenu/>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<XCommon/>
|
||||
@@ -223,30 +234,30 @@ async function deleteProfile() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-enter-active,
|
||||
.menu-leave-active {
|
||||
<style lang="scss" module>
|
||||
.transition_menuDrawerBg_enterActive,
|
||||
.transition_menuDrawerBg_leaveActive {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.transition_menuDrawerBg_enterFrom,
|
||||
.transition_menuDrawerBg_leaveTo {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.transition_menuDrawer_enterActive,
|
||||
.transition_menuDrawer_leaveActive {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.menu-enter-from,
|
||||
.menu-leave-active {
|
||||
.transition_menuDrawer_enterFrom,
|
||||
.transition_menuDrawer_leaveTo {
|
||||
opacity: 0;
|
||||
transform: translateX(-240px);
|
||||
}
|
||||
|
||||
.menu-back-enter-active,
|
||||
.menu-back-leave-active {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.menu-back-enter-from,
|
||||
.menu-back-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mk-deck {
|
||||
.root {
|
||||
$nav-hide-threshold: 650px; // TODO: どこかに集約したい
|
||||
|
||||
--margin: var(--marginHalf);
|
||||
@@ -257,178 +268,170 @@ async function deleteProfile() {
|
||||
height: 100dvh;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.isMobile {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
.rootIsMobile {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
> .main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
> .columns {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: clip;
|
||||
.columns {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: clip;
|
||||
|
||||
&.center {
|
||||
> .column:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
> .column:last-of-type {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .column {
|
||||
flex-shrink: 0;
|
||||
border-right: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
|
||||
&:first-of-type {
|
||||
border-left: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
}
|
||||
|
||||
&.folder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> *:not(:last-of-type) {
|
||||
border-bottom: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .intro {
|
||||
padding: 32px;
|
||||
height: min-content;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
|
||||
> .add {
|
||||
margin: 1em auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .sideMenu {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
|
||||
> .top, > .middle, > .bottom {
|
||||
> .button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
}
|
||||
|
||||
> .top {
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
> .middle {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
> .bottom {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
&.center {
|
||||
> .column:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
> .button {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
height: 64px;
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
height: 60px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--X2);
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--indicator);
|
||||
font-size: 16px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
> * {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
|
||||
> * {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
> .column:last-of-type {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .menu-back {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
> .menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
width: 240px;
|
||||
box-sizing: border-box;
|
||||
contain: strict;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--navBg);
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
flex-shrink: 0;
|
||||
border-right: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
|
||||
&:first-of-type {
|
||||
border-left: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
}
|
||||
}
|
||||
|
||||
.folder {
|
||||
composes: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> *:not(:last-of-type) {
|
||||
border-bottom: solid var(--deckDividerThickness) var(--deckDivider);
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding {
|
||||
padding: 32px;
|
||||
height: min-content;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.sideMenu {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.sideMenuButton {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.sideMenuTop {
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.sideMenuMiddle {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.sideMenuBottom {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.menuBg {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
width: 240px;
|
||||
box-sizing: border-box;
|
||||
contain: strict;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--navBg);
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
grid-gap: 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-webkit-backdrop-filter: var(--blur, blur(32px));
|
||||
backdrop-filter: var(--blur, blur(32px));
|
||||
background-color: var(--header);
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
.navButton {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
max-width: 60px;
|
||||
margin: auto;
|
||||
border-radius: 100%;
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
|
||||
&:hover {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--X2);
|
||||
}
|
||||
}
|
||||
|
||||
.postButton {
|
||||
composes: navButton;
|
||||
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
|
||||
color: var(--fgOnAccent);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(90deg, var(--X8), var(--X8));
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(90deg, var(--X8), var(--X8));
|
||||
}
|
||||
}
|
||||
|
||||
.navButtonIcon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.navButtonIndicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--indicator);
|
||||
font-size: 16px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
<template>
|
||||
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
|
||||
<section
|
||||
v-hotkey="keymap" class="dnpfarvg"
|
||||
:class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }"
|
||||
v-hotkey="keymap"
|
||||
:class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.isStacked]: isStacked, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]"
|
||||
@dragover.prevent.stop="onDragover"
|
||||
@dragleave="onDragleave"
|
||||
@drop.prevent.stop="onDrop"
|
||||
>
|
||||
<header
|
||||
:class="{ indicated }"
|
||||
:class="[$style.header]"
|
||||
draggable="true"
|
||||
@click="goTop"
|
||||
@dragstart="onDragstart"
|
||||
@dragend="onDragend"
|
||||
@contextmenu.prevent.stop="onContextmenu"
|
||||
>
|
||||
<button v-if="isStacked && !isMainColumn" class="toggleActive _button" @click="toggleActive">
|
||||
<button v-if="isStacked && !isMainColumn" :class="$style.toggleActive" class="_button" @click="toggleActive">
|
||||
<template v-if="active"><i class="ti ti-chevron-up"></i></template>
|
||||
<template v-else><i class="ti ti-chevron-down"></i></template>
|
||||
</button>
|
||||
<div class="action">
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
<span class="header"><slot name="header"></slot></span>
|
||||
<button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button>
|
||||
<span :class="$style.title"><slot name="header"></slot></span>
|
||||
<button v-tooltip="i18n.ts.settings" :class="$style.menu" class="_button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button>
|
||||
</header>
|
||||
<div v-show="active" ref="body">
|
||||
<div v-show="active" ref="body" :class="$style.body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</section>
|
||||
@@ -46,12 +43,10 @@ const props = withDefaults(defineProps<{
|
||||
column: Column;
|
||||
isStacked?: boolean;
|
||||
naked?: boolean;
|
||||
indicated?: boolean;
|
||||
menu?: MenuItem[];
|
||||
}>(), {
|
||||
isStacked: false,
|
||||
naked: false,
|
||||
indicated: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -245,8 +240,8 @@ function onDrop(ev) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dnpfarvg {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
--root-margin: 10px;
|
||||
--deckColumnHeaderHeight: 40px;
|
||||
|
||||
@@ -292,10 +287,6 @@ function onDrop(ev) {
|
||||
&:not(.active) {
|
||||
flex-basis: var(--deckColumnHeaderHeight);
|
||||
min-height: var(--deckColumnHeaderHeight);
|
||||
|
||||
> header.indicated {
|
||||
box-shadow: 4px 0px var(--accent) inset;
|
||||
}
|
||||
}
|
||||
|
||||
&.naked {
|
||||
@@ -303,97 +294,78 @@ function onDrop(ev) {
|
||||
-webkit-backdrop-filter: var(--blur, blur(10px));
|
||||
backdrop-filter: var(--blur, blur(10px));
|
||||
|
||||
> header {
|
||||
> .header {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
> button {
|
||||
color: var(--fg);
|
||||
}
|
||||
color: var(--fg);
|
||||
}
|
||||
}
|
||||
|
||||
&.paged {
|
||||
background: var(--bg) !important;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
line-height: var(--deckColumnHeaderHeight);
|
||||
height: var(--deckColumnHeaderHeight);
|
||||
padding: 0 16px;
|
||||
font-size: 0.9em;
|
||||
color: var(--panelHeaderFg);
|
||||
background: var(--panelHeaderBg);
|
||||
box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
|
||||
cursor: pointer;
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
line-height: var(--deckColumnHeaderHeight);
|
||||
height: var(--deckColumnHeaderHeight);
|
||||
padding: 0 16px;
|
||||
font-size: 0.9em;
|
||||
color: var(--panelHeaderFg);
|
||||
background: var(--panelHeaderBg);
|
||||
box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
|
||||
cursor: pointer;
|
||||
|
||||
&, * {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&.indicated {
|
||||
box-shadow: 0 3px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
> .header {
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
> span:only-of-type {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> .toggleActive,
|
||||
> .action > ::v-deep(*),
|
||||
> .menu {
|
||||
z-index: 1;
|
||||
width: var(--deckColumnHeaderHeight);
|
||||
line-height: var(--deckColumnHeaderHeight);
|
||||
color: var(--faceTextButton);
|
||||
|
||||
&:hover {
|
||||
color: var(--faceTextButtonHover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--faceTextButtonActive);
|
||||
}
|
||||
}
|
||||
|
||||
> .toggleActive, > .action {
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
> .action {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
> .action:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .menu {
|
||||
margin-left: auto;
|
||||
margin-right: -16px;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
height: calc(100% - var(--deckColumnHeaderHeight));
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden; // Safari does not supports clip
|
||||
overflow-x: clip;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
container-type: inline-size;
|
||||
background-color: var(--bg);
|
||||
&, * {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toggleActive,
|
||||
.menu {
|
||||
z-index: 1;
|
||||
width: var(--deckColumnHeaderHeight);
|
||||
line-height: var(--deckColumnHeaderHeight);
|
||||
color: var(--faceTextButton);
|
||||
|
||||
&:hover {
|
||||
color: var(--faceTextButtonHover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--faceTextButtonActive);
|
||||
}
|
||||
}
|
||||
|
||||
.toggleActive {
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin-left: auto;
|
||||
margin-right: -16px;
|
||||
}
|
||||
|
||||
.body {
|
||||
height: calc(100% - var(--deckColumnHeaderHeight));
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden; // Safari does not supports clip
|
||||
overflow-x: clip;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
container-type: inline-size;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)">
|
||||
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
|
||||
<template #header>
|
||||
<i v-if="column.tl === 'home'" class="ti ti-home"></i>
|
||||
<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
|
||||
@@ -15,7 +15,7 @@
|
||||
</p>
|
||||
<p class="desc">{{ $t('disabled-timeline.description') }}</p>
|
||||
</div>
|
||||
<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/>
|
||||
<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')"/>
|
||||
</XColumn>
|
||||
</template>
|
||||
|
||||
@@ -40,8 +40,6 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
let disabled = $ref(false);
|
||||
let indicated = $ref(false);
|
||||
let columnActive = $ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.column.tl == null) {
|
||||
@@ -77,26 +75,6 @@ async function setType() {
|
||||
});
|
||||
}
|
||||
|
||||
function queueUpdated(q) {
|
||||
if (columnActive) {
|
||||
indicated = q !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
function onNote() {
|
||||
if (!columnActive) {
|
||||
indicated = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeActiveState(state) {
|
||||
columnActive = state;
|
||||
|
||||
if (columnActive) {
|
||||
indicated = false;
|
||||
}
|
||||
}
|
||||
|
||||
const menu = [{
|
||||
icon: 'ti ti-pencil',
|
||||
text: i18n.ts.timeline,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="dkgtipfy" :class="{ wallpaper }">
|
||||
<XSidebar v-if="!isMobile" class="sidebar"/>
|
||||
<div :class="[$style.root, { [$style.withWallpaper]: wallpaper }]">
|
||||
<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
|
||||
|
||||
<MkStickyContainer class="contents">
|
||||
<MkStickyContainer :class="$style.contents">
|
||||
<template #header><XStatusBars :class="$style.statusbars"/></template>
|
||||
<main style="min-width: 0;" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
|
||||
<div :class="$style.content" style="container-type: inline-size;">
|
||||
@@ -12,44 +12,71 @@
|
||||
</main>
|
||||
</MkStickyContainer>
|
||||
|
||||
<div v-if="isDesktop" ref="widgetsEl" class="widgets">
|
||||
<XWidgets @mounted="attachSticky"/>
|
||||
<div v-if="isDesktop" ref="widgetsEl" :class="$style.widgets">
|
||||
<XWidgets :margin-top="'var(--margin)'" @mounted="attachSticky"/>
|
||||
</div>
|
||||
|
||||
<button v-if="!isDesktop && !isMobile" class="widgetButton _button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
|
||||
<button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
|
||||
|
||||
<div v-if="isMobile" class="buttons">
|
||||
<button class="button nav _button" @click="drawerMenuShowing = true"><i class="icon ti ti-menu-2"></i><span v-if="menuIndicated" class="indicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button class="button home _button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i class="icon ti ti-home"></i></button>
|
||||
<button class="button notifications _button" @click="mainRouter.push('/my/notifications')"><i class="icon ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button class="button widget _button" @click="widgetsShowing = true"><i class="icon ti ti-apps"></i></button>
|
||||
<button class="button post _button" @click="os.post()"><i class="icon ti ti-pencil"></i></button>
|
||||
<div v-if="isMobile" :class="$style.nav">
|
||||
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i :class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ti ti-apps"></i></button>
|
||||
<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
|
||||
</div>
|
||||
|
||||
<Transition :name="$store.state.animation ? 'menuDrawer-back' : ''">
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
|
||||
>
|
||||
<div
|
||||
v-if="drawerMenuShowing"
|
||||
class="menuDrawer-back _modalBg"
|
||||
:class="$style.menuDrawerBg"
|
||||
class="_modalBg"
|
||||
@click="drawerMenuShowing = false"
|
||||
@touchstart.passive="drawerMenuShowing = false"
|
||||
></div>
|
||||
</Transition>
|
||||
|
||||
<Transition :name="$store.state.animation ? 'menuDrawer' : ''">
|
||||
<XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_menuDrawer_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
|
||||
>
|
||||
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
|
||||
<XDrawerMenu/>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<Transition :name="$store.state.animation ? 'widgetsDrawer-back' : ''">
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
|
||||
>
|
||||
<div
|
||||
v-if="widgetsShowing"
|
||||
class="widgetsDrawer-back _modalBg"
|
||||
:class="$style.widgetsDrawerBg"
|
||||
class="_modalBg"
|
||||
@click="widgetsShowing = false"
|
||||
@touchstart.passive="widgetsShowing = false"
|
||||
></div>
|
||||
</Transition>
|
||||
|
||||
<Transition :name="$store.state.animation ? 'widgetsDrawer' : ''">
|
||||
<XWidgets v-if="widgetsShowing" class="widgetsDrawer"/>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
|
||||
>
|
||||
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
|
||||
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
|
||||
<XWidgets/>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<XCommon/>
|
||||
@@ -174,195 +201,210 @@ function top() {
|
||||
const wallpaper = miLocalStorage.getItem('wallpaper') != null;
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.widgetsDrawer-enter-active,
|
||||
.widgetsDrawer-leave-active {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.widgetsDrawer-enter-from,
|
||||
.widgetsDrawer-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(240px);
|
||||
}
|
||||
<style lang="scss" module>
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$widgets-hide-threshold: 1090px;
|
||||
|
||||
.widgetsDrawer-back-enter-active,
|
||||
.widgetsDrawer-back-leave-active {
|
||||
.transition_menuDrawerBg_enterActive,
|
||||
.transition_menuDrawerBg_leaveActive {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.widgetsDrawer-back-enter-from,
|
||||
.widgetsDrawer-back-leave-active {
|
||||
.transition_menuDrawerBg_enterFrom,
|
||||
.transition_menuDrawerBg_leaveTo {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.menuDrawer-enter-active,
|
||||
.menuDrawer-leave-active {
|
||||
.transition_menuDrawer_enterActive,
|
||||
.transition_menuDrawer_leaveActive {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.menuDrawer-enter-from,
|
||||
.menuDrawer-leave-active {
|
||||
.transition_menuDrawer_enterFrom,
|
||||
.transition_menuDrawer_leaveTo {
|
||||
opacity: 0;
|
||||
transform: translateX(-240px);
|
||||
}
|
||||
|
||||
.menuDrawer-back-enter-active,
|
||||
.menuDrawer-back-leave-active {
|
||||
.transition_widgetsDrawerBg_enterActive,
|
||||
.transition_widgetsDrawerBg_leaveActive {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.menuDrawer-back-enter-from,
|
||||
.menuDrawer-back-leave-active {
|
||||
.transition_widgetsDrawerBg_enterFrom,
|
||||
.transition_widgetsDrawerBg_leaveTo {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dkgtipfy {
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$widgets-hide-threshold: 1090px;
|
||||
.transition_widgetsDrawer_enterActive,
|
||||
.transition_widgetsDrawer_leaveActive {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.transition_widgetsDrawer_enterFrom,
|
||||
.transition_widgetsDrawer_leaveTo {
|
||||
opacity: 0;
|
||||
transform: translateX(240px);
|
||||
}
|
||||
|
||||
.root {
|
||||
min-height: 100dvh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.wallpaper {
|
||||
background: var(--wallpaperOverlay);
|
||||
//backdrop-filter: var(--blur, blur(4px));
|
||||
}
|
||||
.withWallpaper {
|
||||
background: var(--wallpaperOverlay);
|
||||
//backdrop-filter: var(--blur, blur(4px));
|
||||
}
|
||||
|
||||
> .sidebar {
|
||||
border-right: solid 0.5px var(--divider);
|
||||
}
|
||||
.sidebar {
|
||||
border-right: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
> .contents {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
.contents {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
> .widgets {
|
||||
padding: 0 var(--margin);
|
||||
border-left: solid 0.5px var(--divider);
|
||||
background: var(--bg);
|
||||
.widgets {
|
||||
padding: 0 var(--margin);
|
||||
border-left: solid 0.5px var(--divider);
|
||||
background: var(--bg);
|
||||
|
||||
@media (max-width: $widgets-hide-threshold) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .widgetButton {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
font-size: 22px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
> .widgetsDrawer-back {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
> .widgetsDrawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
padding: var(--margin) !important;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-gap: 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-webkit-backdrop-filter: var(--blur, blur(32px));
|
||||
backdrop-filter: var(--blur, blur(32px));
|
||||
background-color: var(--header);
|
||||
border-top: solid 0.5px var(--divider);
|
||||
|
||||
> .button {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
max-width: 60px;
|
||||
margin: auto;
|
||||
border-radius: 100%;
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
|
||||
&:hover {
|
||||
background: var(--X2);
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--indicator);
|
||||
font-size: 16px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
|
||||
> .icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.post {
|
||||
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
|
||||
color: var(--fgOnAccent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .menuDrawer-back {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
> .menuDrawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
width: 240px;
|
||||
box-sizing: border-box;
|
||||
contain: strict;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--navBg);
|
||||
@media (max-width: $widgets-hide-threshold) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
.widgetButton {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
font-size: 22px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.widgetsDrawerBg {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.widgetsDrawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
padding: var(--margin) !important;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.widgetsCloseButton {
|
||||
padding: 8px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 370px) {
|
||||
.widgetsCloseButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-gap: 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-webkit-backdrop-filter: var(--blur, blur(32px));
|
||||
backdrop-filter: var(--blur, blur(32px));
|
||||
background-color: var(--header);
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
.navButton {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
aspect-ratio: 1;
|
||||
width: 100%;
|
||||
max-width: 60px;
|
||||
margin: auto;
|
||||
border-radius: 100%;
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
|
||||
&:hover {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--X2);
|
||||
}
|
||||
}
|
||||
|
||||
.postButton {
|
||||
composes: navButton;
|
||||
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
|
||||
color: var(--fgOnAccent);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(90deg, var(--X8), var(--X8));
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(90deg, var(--X8), var(--X8));
|
||||
}
|
||||
}
|
||||
|
||||
.navButtonIcon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.navButtonIndicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--indicator);
|
||||
font-size: 16px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.menuDrawerBg {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.menuDrawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
width: 240px;
|
||||
box-sizing: border-box;
|
||||
contain: strict;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
background: var(--navBg);
|
||||
}
|
||||
|
||||
.statusbars {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -370,12 +412,6 @@ const wallpaper = miLocalStorage.getItem('wallpaper') != null;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
$widgets-hide-threshold: 1090px;
|
||||
|
||||
height: calc(env(safe-area-inset-bottom, 0px) + 96px);
|
||||
|
||||
@media (min-width: ($widgets-hide-threshold + 1px)) {
|
||||
display: none;
|
||||
}
|
||||
height: calc(var(--minBottomSpacing));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="efzpzdvf" :class="{ universal: !classic, classic }">
|
||||
<XWidgets :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
|
||||
<div :class="$style.root" :style="{ paddingTop: marginTop }">
|
||||
<XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
|
||||
|
||||
<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
|
||||
<button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
|
||||
@@ -21,10 +21,10 @@ const props = withDefaults(defineProps<{
|
||||
// left = place: leftだけを表示
|
||||
// right = rightとnullを表示
|
||||
place?: 'left' | null | 'right';
|
||||
classic?: boolean;
|
||||
marginTop?: string;
|
||||
}>(), {
|
||||
place: null,
|
||||
classic: false,
|
||||
marginTop: '0',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -81,31 +81,14 @@ function updateWidgets(thisWidgets) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.efzpzdvf {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: sticky;
|
||||
height: min-content;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&.universal {
|
||||
padding-top: var(--margin);
|
||||
|
||||
> * {
|
||||
margin: var(--margin) 0;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
width: 300px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .add {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.widgets {
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||
import XCalendar from './activity.calendar.vue';
|
||||
import XChart from './activity.chart.vue';
|
||||
import XCalendar from './WidgetActivity.calendar.vue';
|
||||
import XChart from './WidgetActivity.chart.vue';
|
||||
import { GetFormResultType } from '@/scripts/form';
|
||||
import * as os from '@/os';
|
||||
import MkContainer from '@/components/MkContainer.vue';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user