Compare commits
24 Commits
13.0.0-bet
...
13.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f6a41cea7 | ||
|
|
0d7ee20a77 | ||
|
|
dcca2350dd | ||
|
|
1cfdd4c41a | ||
|
|
25f4ee7030 | ||
|
|
5320f23017 | ||
|
|
4ffbbbe6d8 | ||
|
|
132e45dff4 | ||
|
|
01652b72b3 | ||
|
|
8b1fdb5a3b | ||
|
|
192add376c | ||
|
|
244ea9593a | ||
|
|
f20d7cba74 | ||
|
|
a3e282bc75 | ||
|
|
49a95c34bf | ||
|
|
ecbefce2aa | ||
|
|
91356b1805 | ||
|
|
2e2ed1385f | ||
|
|
49f3090edd | ||
|
|
4594fb11de | ||
|
|
b93e56d2e5 | ||
|
|
c550dafb81 | ||
|
|
8709574f3d | ||
|
|
1b7043fa79 |
@@ -30,6 +30,7 @@ You should also include the user name that made the change.
|
|||||||
|
|
||||||
#### For users
|
#### For users
|
||||||
- ノートのウォッチ機能が削除されました
|
- ノートのウォッチ機能が削除されました
|
||||||
|
- アンケートに投票された際に通知が作成されなくなりました
|
||||||
- 新たに動的なPagesを作ることはできなくなりました
|
- 新たに動的なPagesを作ることはできなくなりました
|
||||||
- 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。
|
- 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。
|
||||||
- AiScriptが0.12.2にアップデートされました
|
- AiScriptが0.12.2にアップデートされました
|
||||||
@@ -77,9 +78,12 @@ You should also include the user name that made the change.
|
|||||||
- Client: Improve RSS widget @tamaina
|
- Client: Improve RSS widget @tamaina
|
||||||
- Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
|
- Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
|
||||||
- Client: OpenSearch support @SoniEx2 @chaoticryptidz
|
- Client: OpenSearch support @SoniEx2 @chaoticryptidz
|
||||||
|
- Client: Support remote objects in search @SoniEx2
|
||||||
|
- Client: user activity page @syuilo
|
||||||
- Client: add user list widget @syuilo
|
- Client: add user list widget @syuilo
|
||||||
- Client: add heatmap of daily active users to about page @syuilo
|
- Client: add heatmap of daily active users to about page @syuilo
|
||||||
- Client: introduce fluent emoji @syuilo
|
- Client: introduce fluent emoji @syuilo
|
||||||
|
- Client: add new theme @syuilo
|
||||||
- Client: show fireworks when visit user who today is birthday @syuilo
|
- Client: show fireworks when visit user who today is birthday @syuilo
|
||||||
- Client: show bot warning on screen when logged in as bot account @syuilo
|
- Client: show bot warning on screen when logged in as bot account @syuilo
|
||||||
- Client: improve overall performance of client @syuilo
|
- Client: improve overall performance of client @syuilo
|
||||||
|
|||||||
@@ -920,6 +920,10 @@ like: "Gefällt mir"
|
|||||||
unlike: "\"Gefällt mir\" entfernen"
|
unlike: "\"Gefällt mir\" entfernen"
|
||||||
numberOfLikes: "\"Gefällt mir\"-Anzahl"
|
numberOfLikes: "\"Gefällt mir\"-Anzahl"
|
||||||
show: "Anzeigen"
|
show: "Anzeigen"
|
||||||
|
neverShow: "Nicht wieder anzeigen"
|
||||||
|
remindMeLater: "Vielleicht später"
|
||||||
|
didYouLikeMisskey: "Gefällt dir Misskey?"
|
||||||
|
pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
|
description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
|
||||||
sensitivity: "Erkennungssensitivität"
|
sensitivity: "Erkennungssensitivität"
|
||||||
|
|||||||
@@ -920,6 +920,10 @@ like: "Like"
|
|||||||
unlike: "Unlike"
|
unlike: "Unlike"
|
||||||
numberOfLikes: "Likes"
|
numberOfLikes: "Likes"
|
||||||
show: "Show"
|
show: "Show"
|
||||||
|
neverShow: "Don't show again"
|
||||||
|
remindMeLater: "Maybe later"
|
||||||
|
didYouLikeMisskey: "Have you taken a liking to Misskey?"
|
||||||
|
pleaseDonate: "{host} uses the free software, Misskey. We would highly appreciate your donations so development of Misskey can continue!"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
|
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
|
||||||
sensitivity: "Detection sensitivity"
|
sensitivity: "Detection sensitivity"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ timeline: "Timeline"
|
|||||||
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
|
noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
|
||||||
login: "Accedi"
|
login: "Accedi"
|
||||||
loggingIn: "Accesso in corso..."
|
loggingIn: "Accesso in corso..."
|
||||||
logout: "Esci"
|
logout: "Uscita"
|
||||||
signup: "Iscriviti"
|
signup: "Iscriviti"
|
||||||
uploading: "Caricamento..."
|
uploading: "Caricamento..."
|
||||||
save: "Salva"
|
save: "Salva"
|
||||||
|
|||||||
@@ -1550,7 +1550,6 @@ _notification:
|
|||||||
youGotReply: "{name}からのリプライ"
|
youGotReply: "{name}からのリプライ"
|
||||||
youGotQuote: "{name}による引用"
|
youGotQuote: "{name}による引用"
|
||||||
youRenoted: "{name}がRenoteしました"
|
youRenoted: "{name}がRenoteしました"
|
||||||
youGotPoll: "{name}が投票しました"
|
|
||||||
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
|
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
|
||||||
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
|
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
|
||||||
youWereFollowed: "フォローされました"
|
youWereFollowed: "フォローされました"
|
||||||
@@ -1569,7 +1568,6 @@ _notification:
|
|||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
pollVote: "アンケートに投票された"
|
|
||||||
pollEnded: "アンケートが終了"
|
pollEnded: "アンケートが終了"
|
||||||
receiveFollowRequest: "フォロー申請を受け取った"
|
receiveFollowRequest: "フォロー申請を受け取った"
|
||||||
followRequestAccepted: "フォローが受理された"
|
followRequestAccepted: "フォローが受理された"
|
||||||
|
|||||||
@@ -920,6 +920,10 @@ like: "좋아요!"
|
|||||||
unlike: "좋아요 취소"
|
unlike: "좋아요 취소"
|
||||||
numberOfLikes: "좋아요 수"
|
numberOfLikes: "좋아요 수"
|
||||||
show: "표시"
|
show: "표시"
|
||||||
|
neverShow: "다시 보지 않기"
|
||||||
|
remindMeLater: "나중에 알림"
|
||||||
|
didYouLikeMisskey: "Misskey가 마음에 드시나요?"
|
||||||
|
pleaseDonate: "{host}은(는) 무료 소프트웨어 Misskey를 사용합니다. 후원을 통해 저희의 개발이 이어질 수 있게 도와주세요!"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
|
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
|
||||||
sensitivity: "탐지 민감도"
|
sensitivity: "탐지 민감도"
|
||||||
|
|||||||
@@ -913,6 +913,10 @@ tools: "Nástroje"
|
|||||||
cannotLoad: "Nedá sa načítať."
|
cannotLoad: "Nedá sa načítať."
|
||||||
like: "Páči sa mi"
|
like: "Páči sa mi"
|
||||||
show: "Zobraziť"
|
show: "Zobraziť"
|
||||||
|
neverShow: "Nabudúce nezobrazovať"
|
||||||
|
remindMeLater: "Pripomenúť neskôr"
|
||||||
|
didYouLikeMisskey: "Páči sa vám Misskey?"
|
||||||
|
pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím, prispejte, aby sme ho mohli ďalej rozvíjať!"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera."
|
description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera."
|
||||||
sensitivity: "Citlivosť detekcie"
|
sensitivity: "Citlivosť detekcie"
|
||||||
|
|||||||
@@ -917,6 +917,8 @@ tools: "เครื่องมือ"
|
|||||||
cannotLoad: "ไม่สามารถโหลดได้"
|
cannotLoad: "ไม่สามารถโหลดได้"
|
||||||
numberOfProfileView: "มุมมองโปรไฟล์"
|
numberOfProfileView: "มุมมองโปรไฟล์"
|
||||||
like: "ชื่นชอบ"
|
like: "ชื่นชอบ"
|
||||||
|
unlike: "ไม่ชอบ"
|
||||||
|
numberOfLikes: "จำนวนไลค์"
|
||||||
show: "แสดงผล"
|
show: "แสดงผล"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
|
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
|
||||||
@@ -1317,6 +1319,7 @@ _widgets:
|
|||||||
jobQueue: "คิวงาน"
|
jobQueue: "คิวงาน"
|
||||||
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
|
serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
|
||||||
aiscript: "AiScript คอนโซล"
|
aiscript: "AiScript คอนโซล"
|
||||||
|
aiscriptApp: "AiScript แอพ"
|
||||||
aichan: "เอไอ"
|
aichan: "เอไอ"
|
||||||
userList: "รายชื่อผู้ใช้"
|
userList: "รายชื่อผู้ใช้"
|
||||||
_userList:
|
_userList:
|
||||||
@@ -1423,7 +1426,16 @@ _timelines:
|
|||||||
social: "โซเชี่ยล"
|
social: "โซเชี่ยล"
|
||||||
global: "ทั่วโลก"
|
global: "ทั่วโลก"
|
||||||
_play:
|
_play:
|
||||||
|
new: "สร้างการเล่น"
|
||||||
|
edit: "แก้ไขเล่น"
|
||||||
|
created: "สร้างการเล่นแล้ว"
|
||||||
|
updated: "แก้ไขการเล่นแล้ว"
|
||||||
|
deleted: "ลบการเล่นแล้ว"
|
||||||
|
pageSetting: "ตั้งค่าการเล่น"
|
||||||
|
editThisPage: "แก้ไข Play นี้"
|
||||||
viewSource: "ดูต้นฉบับ"
|
viewSource: "ดูต้นฉบับ"
|
||||||
|
my: "มาย เพลย์"
|
||||||
|
liked: "ไลค์ เพลย์"
|
||||||
featured: "เป็นที่นิยม"
|
featured: "เป็นที่นิยม"
|
||||||
title: "หัวข้อ"
|
title: "หัวข้อ"
|
||||||
script: "สคริปต์"
|
script: "สคริปต์"
|
||||||
|
|||||||
@@ -918,7 +918,11 @@ cannotLoad: "無法載入"
|
|||||||
numberOfProfileView: "個人檔案檢視次數"
|
numberOfProfileView: "個人檔案檢視次數"
|
||||||
like: "讚"
|
like: "讚"
|
||||||
unlike: "收回讚"
|
unlike: "收回讚"
|
||||||
|
numberOfLikes: "讚數"
|
||||||
show: "檢視"
|
show: "檢視"
|
||||||
|
neverShow: "不再顯示"
|
||||||
|
remindMeLater: "以後再說"
|
||||||
|
didYouLikeMisskey: "您是否喜愛Misskey呢?"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
||||||
sensitivity: "檢測敏感度"
|
sensitivity: "檢測敏感度"
|
||||||
@@ -1318,6 +1322,7 @@ _widgets:
|
|||||||
jobQueue: "佇列"
|
jobQueue: "佇列"
|
||||||
serverMetric: "服務器指標 "
|
serverMetric: "服務器指標 "
|
||||||
aiscript: "AiScript控制台"
|
aiscript: "AiScript控制台"
|
||||||
|
aiscriptApp: "AiScript App"
|
||||||
aichan: "小藍"
|
aichan: "小藍"
|
||||||
userList: "使用者列表"
|
userList: "使用者列表"
|
||||||
_userList:
|
_userList:
|
||||||
@@ -1424,7 +1429,16 @@ _timelines:
|
|||||||
social: "社群"
|
social: "社群"
|
||||||
global: "公開"
|
global: "公開"
|
||||||
_play:
|
_play:
|
||||||
|
new: "新增Play"
|
||||||
|
edit: "編輯Play"
|
||||||
|
created: "已新增Play"
|
||||||
|
updated: "已更新Play"
|
||||||
|
deleted: "已刪除Play"
|
||||||
|
pageSetting: "Play設定"
|
||||||
|
editThisPage: "編輯這個Play"
|
||||||
viewSource: "檢視原始碼"
|
viewSource: "檢視原始碼"
|
||||||
|
my: "自己的Play"
|
||||||
|
liked: "按了讚的Play"
|
||||||
featured: "人氣"
|
featured: "人氣"
|
||||||
title: "標題"
|
title: "標題"
|
||||||
script: "腳本"
|
script: "腳本"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "13.0.0-beta.27",
|
"version": "13.0.0-beta.30",
|
||||||
"codename": "indigo",
|
"codename": "indigo",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -92,13 +92,6 @@ export class PollService {
|
|||||||
choice: choice,
|
choice: choice,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify
|
|
||||||
this.createNotificationService.createNotification(note.userId, 'pollVote', {
|
|
||||||
notifierId: user.id,
|
|
||||||
noteId: note.id,
|
|
||||||
choice: choice,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||||||
}),
|
}),
|
||||||
reaction: notification.reaction,
|
reaction: notification.reaction,
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'pollVote' ? {
|
...(notification.type === 'pollVote' ? { // TODO: そのうち消す
|
||||||
note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
|
note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
|
||||||
detail: true,
|
detail: true,
|
||||||
_hint_: options._hintForEachNotes_,
|
_hint_: options._hintForEachNotes_,
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ export class Notification {
|
|||||||
* 通知の種類。
|
* 通知の種類。
|
||||||
* follow - フォローされた
|
* follow - フォローされた
|
||||||
* mention - 投稿で自分が言及された
|
* mention - 投稿で自分が言及された
|
||||||
* reply - (自分または自分がWatchしている)投稿が返信された
|
* reply - 投稿に返信された
|
||||||
* renote - (自分または自分がWatchしている)投稿がRenoteされた
|
* renote - 投稿がRenoteされた
|
||||||
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
|
* quote - 投稿が引用Renoteされた
|
||||||
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
* reaction - 投稿にリアクションされた
|
||||||
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
|
* pollVote - 投稿のアンケートに投票された (廃止)
|
||||||
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
||||||
* receiveFollowRequest - フォローリクエストされた
|
* receiveFollowRequest - フォローリクエストされた
|
||||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||||
|
|||||||
@@ -162,13 +162,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||||||
userId: me.id,
|
userId: me.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify
|
|
||||||
this.createNotificationService.createNotification(note.userId, 'pollVote', {
|
|
||||||
notifierId: me.id,
|
|
||||||
noteId: note.id,
|
|
||||||
choice: ps.choice,
|
|
||||||
});
|
|
||||||
|
|
||||||
// リモート投票の場合リプライ送信
|
// リモート投票の場合リプライ送信
|
||||||
if (note.userHost != null) {
|
if (note.userHost != null) {
|
||||||
const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser;
|
const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ function onMousedown(evt: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter-active, .fade-leave-active {
|
.fade-enter-active, .fade-leave-active {
|
||||||
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1), transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
transform-origin: left top;
|
transform-origin: left top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ssazuxis">
|
<div class="ssazuxis">
|
||||||
<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
|
<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
|
||||||
<div class="title"><slot name="header"></slot></div>
|
<div class="title"><div><slot name="header"></slot></div></div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<button class="_button">
|
<button class="_button">
|
||||||
<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
|
<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
|
||||||
@@ -127,14 +127,6 @@ export default defineComponent({
|
|||||||
place-content: center;
|
place-content: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 12px 16px 12px 0;
|
padding: 12px 16px 12px 0;
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .divider {
|
> .divider {
|
||||||
|
|||||||
@@ -251,17 +251,18 @@ onBeforeUnmount(() => {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
background: #d42e2e;
|
background: #d42e2e !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
&.active {
|
&.active {
|
||||||
color: var(--fgOnAccent);
|
color: var(--fgOnAccent) !important;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
background: var(--accent);
|
background: var(--accent) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Transition :name="transitionName" :duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
|
<Transition :name="transitionName" :duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
|
||||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||||
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||||
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
|
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
|
||||||
<slot :max-height="maxHeight" :type="type"></slot>
|
<slot :max-height="maxHeight" :type="type"></slot>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,6 +63,7 @@ let transformOrigin = $ref('center');
|
|||||||
let showing = $ref(true);
|
let showing = $ref(true);
|
||||||
let content = $shallowRef<HTMLElement>();
|
let content = $shallowRef<HTMLElement>();
|
||||||
const zIndex = os.claimZIndex(props.zPriority);
|
const zIndex = os.claimZIndex(props.zPriority);
|
||||||
|
let useSendAnime = $ref(false);
|
||||||
const type = $computed<ModalTypes>(() => {
|
const type = $computed<ModalTypes>(() => {
|
||||||
if (props.preferType === 'auto') {
|
if (props.preferType === 'auto') {
|
||||||
if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
|
if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
|
||||||
@@ -74,15 +75,34 @@ const type = $computed<ModalTypes>(() => {
|
|||||||
return props.preferType!;
|
return props.preferType!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let transitionName = $ref(defaultStore.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : '');
|
let transitionName = $computed((() =>
|
||||||
let transitionDuration = $ref(defaultStore.state.animation ? 200 : 0);
|
defaultStore.state.animation
|
||||||
|
? useSendAnime
|
||||||
|
? 'send'
|
||||||
|
: type === 'drawer'
|
||||||
|
? 'modal-drawer'
|
||||||
|
: type === 'popup'
|
||||||
|
? 'modal-popup'
|
||||||
|
: 'modal'
|
||||||
|
: ''
|
||||||
|
));
|
||||||
|
let transitionDuration = $computed((() =>
|
||||||
|
transitionName === 'send'
|
||||||
|
? 400
|
||||||
|
: transitionName === 'modal-popup'
|
||||||
|
? 100
|
||||||
|
: transitionName === 'modal'
|
||||||
|
? 200
|
||||||
|
: transitionName === 'modal-drawer'
|
||||||
|
? 200
|
||||||
|
: 0
|
||||||
|
));
|
||||||
|
|
||||||
let contentClicking = false;
|
let contentClicking = false;
|
||||||
|
|
||||||
function close(opts: { useSendAnimation?: boolean } = {}) {
|
function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||||
if (opts.useSendAnimation) {
|
if (opts.useSendAnimation) {
|
||||||
transitionName = 'send';
|
useSendAnime = true;
|
||||||
transitionDuration = 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
@@ -308,12 +328,12 @@ defineExpose({
|
|||||||
|
|
||||||
.modal-popup-enter-active, .modal-popup-leave-active {
|
.modal-popup-enter-active, .modal-popup-leave-active {
|
||||||
> .bg {
|
> .bg {
|
||||||
transition: opacity 0.2s !important;
|
transition: opacity 0.1s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .content {
|
> .content {
|
||||||
transform-origin: var(--transformOrigin);
|
transform-origin: var(--transformOrigin);
|
||||||
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
|
transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modal-popup-enter-from, .modal-popup-leave-to {
|
.modal-popup-enter-from, .modal-popup-leave-to {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<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._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="appearNote._featuredId_" class="info"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>
|
||||||
<div v-if="isRenote" class="renote">
|
<div v-if="isRenote" class="renote">
|
||||||
<MkAvatar class="avatar" :user="note.user"/>
|
<MkAvatar v-once class="avatar" :user="note.user"/>
|
||||||
<i class="ti ti-repeat"></i>
|
<i class="ti ti-repeat"></i>
|
||||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||||
<template #user>
|
<template #user>
|
||||||
@@ -27,11 +27,16 @@
|
|||||||
<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
|
<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
|
||||||
<MkTime :time="note.createdAt"/>
|
<MkTime :time="note.createdAt"/>
|
||||||
</button>
|
</button>
|
||||||
<MkVisibility :note="note"/>
|
<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
|
||||||
|
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
||||||
|
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
|
||||||
|
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||||
|
</span>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<article class="article" @contextmenu.stop="onContextmenu">
|
<article class="article" @contextmenu.stop="onContextmenu">
|
||||||
<MkAvatar class="avatar" :user="appearNote.user"/>
|
<MkAvatar v-once class="avatar" :user="appearNote.user"/>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<MkNoteHeader class="header" :note="appearNote" :mini="true"/>
|
<MkNoteHeader class="header" :note="appearNote" :mini="true"/>
|
||||||
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
|
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
|
||||||
@@ -44,7 +49,7 @@
|
|||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
<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="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/>
|
<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>
|
<a v-if="appearNote.renote != null" class="rp">RN:</a>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
@@ -75,14 +80,25 @@
|
|||||||
<i class="ti ti-arrow-back-up"></i>
|
<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="count">{{ appearNote.repliesCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<MkRenoteButton ref="renoteButton" class="button" :note="appearNote" :count="appearNote.renoteCount"/>
|
<button
|
||||||
<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()">
|
v-if="canRenote"
|
||||||
|
ref="renoteButton"
|
||||||
|
class="button _button"
|
||||||
|
@mousedown="renote()"
|
||||||
|
>
|
||||||
|
<i class="ti ti-repeat"></i>
|
||||||
|
<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
|
||||||
|
</button>
|
||||||
|
<button v-else class="button _button" disabled>
|
||||||
|
<i class="ti ti-ban"></i>
|
||||||
|
</button>
|
||||||
|
<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
|
||||||
<i class="ti ti-plus"></i>
|
<i class="ti ti-plus"></i>
|
||||||
</button>
|
</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="button _button reacted" @click="undoReact(appearNote)">
|
||||||
<i class="ti ti-minus"></i>
|
<i class="ti ti-minus"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" class="button _button" @click="menu()">
|
<button ref="menuButton" class="button _button" @mousedown="menu()">
|
||||||
<i class="ti ti-dots"></i>
|
<i class="ti ti-dots"></i>
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -111,10 +127,9 @@ import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
|
|||||||
import MkMediaList from '@/components/MkMediaList.vue';
|
import MkMediaList from '@/components/MkMediaList.vue';
|
||||||
import MkCwButton from '@/components/MkCwButton.vue';
|
import MkCwButton from '@/components/MkCwButton.vue';
|
||||||
import MkPoll from '@/components/MkPoll.vue';
|
import MkPoll from '@/components/MkPoll.vue';
|
||||||
import MkRenoteButton from '@/components/MkRenoteButton.vue';
|
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||||
import MkVisibility from '@/components/MkVisibility.vue';
|
|
||||||
import { pleaseLogin } from '@/scripts/please-login';
|
import { pleaseLogin } from '@/scripts/please-login';
|
||||||
import { focusPrev, focusNext } from '@/scripts/focus';
|
import { focusPrev, focusNext } from '@/scripts/focus';
|
||||||
import { checkWordMute } from '@/scripts/check-word-mute';
|
import { checkWordMute } from '@/scripts/check-word-mute';
|
||||||
@@ -128,6 +143,7 @@ import { i18n } from '@/i18n';
|
|||||||
import { getNoteMenu } from '@/scripts/get-note-menu';
|
import { getNoteMenu } from '@/scripts/get-note-menu';
|
||||||
import { useNoteCapture } from '@/scripts/use-note-capture';
|
import { useNoteCapture } from '@/scripts/use-note-capture';
|
||||||
import { deepClone } from '@/scripts/clone';
|
import { deepClone } from '@/scripts/clone';
|
||||||
|
import { useTooltip } from '@/scripts/use-tooltip';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
@@ -158,7 +174,7 @@ const isRenote = (
|
|||||||
|
|
||||||
const el = shallowRef<HTMLElement>();
|
const el = shallowRef<HTMLElement>();
|
||||||
const menuButton = shallowRef<HTMLElement>();
|
const menuButton = shallowRef<HTMLElement>();
|
||||||
const renoteButton = shallowRef<InstanceType<typeof MkRenoteButton>>();
|
const renoteButton = shallowRef<HTMLElement>();
|
||||||
const renoteTime = shallowRef<HTMLElement>();
|
const renoteTime = shallowRef<HTMLElement>();
|
||||||
const reactButton = shallowRef<HTMLElement>();
|
const reactButton = shallowRef<HTMLElement>();
|
||||||
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
|
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
|
||||||
@@ -175,6 +191,7 @@ const translation = ref(null);
|
|||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
|
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
|
||||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
|
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
|
||||||
|
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
'r': () => reply(true),
|
'r': () => reply(true),
|
||||||
@@ -193,6 +210,47 @@ useNoteCapture({
|
|||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useTooltip(renoteButton, async (showing) => {
|
||||||
|
const renotes = await os.api('notes/renotes', {
|
||||||
|
noteId: appearNote.id,
|
||||||
|
limit: 11,
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = renotes.map(x => x.user);
|
||||||
|
|
||||||
|
if (users.length < 1) return;
|
||||||
|
|
||||||
|
os.popup(MkUsersTooltip, {
|
||||||
|
showing,
|
||||||
|
users,
|
||||||
|
count: appearNote.renoteCount,
|
||||||
|
targetElement: renoteButton.value,
|
||||||
|
}, {}, 'closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
function renote(viaKeyboard = false) {
|
||||||
|
pleaseLogin();
|
||||||
|
os.popupMenu([{
|
||||||
|
text: i18n.ts.renote,
|
||||||
|
icon: 'ti ti-repeat',
|
||||||
|
action: () => {
|
||||||
|
os.api('notes/create', {
|
||||||
|
renoteId: appearNote.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.quote,
|
||||||
|
icon: 'ti ti-quote',
|
||||||
|
action: () => {
|
||||||
|
os.post({
|
||||||
|
renote: appearNote,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}], renoteButton.value, {
|
||||||
|
viaKeyboard,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function reply(viaKeyboard = false): void {
|
function reply(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
os.post({
|
os.post({
|
||||||
|
|||||||
@@ -25,7 +25,12 @@
|
|||||||
<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
|
<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
|
||||||
<MkTime :time="note.createdAt"/>
|
<MkTime :time="note.createdAt"/>
|
||||||
</button>
|
</button>
|
||||||
<MkVisibility :note="note"/>
|
<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
|
||||||
|
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
||||||
|
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
|
||||||
|
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||||
|
</span>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<article class="article" @contextmenu.stop="onContextmenu">
|
<article class="article" @contextmenu.stop="onContextmenu">
|
||||||
@@ -38,7 +43,12 @@
|
|||||||
</MkA>
|
</MkA>
|
||||||
<span v-if="appearNote.user.isBot" class="is-bot">bot</span>
|
<span v-if="appearNote.user.isBot" class="is-bot">bot</span>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<MkVisibility :note="appearNote"/>
|
<span v-if="appearNote.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[appearNote.visibility]">
|
||||||
|
<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
|
||||||
|
<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock-open"></i>
|
||||||
|
<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||||
|
</span>
|
||||||
|
<span v-if="appearNote.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="username"><MkAcct :user="appearNote.user"/></div>
|
<div class="username"><MkAcct :user="appearNote.user"/></div>
|
||||||
@@ -85,14 +95,25 @@
|
|||||||
<i class="ti ti-arrow-back-up"></i>
|
<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="count">{{ appearNote.repliesCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<MkRenoteButton ref="renoteButton" class="button" :note="appearNote" :count="appearNote.renoteCount"/>
|
<button
|
||||||
<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()">
|
v-if="canRenote"
|
||||||
|
ref="renoteButton"
|
||||||
|
class="button _button"
|
||||||
|
@mousedown="renote()"
|
||||||
|
>
|
||||||
|
<i class="ti ti-repeat"></i>
|
||||||
|
<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
|
||||||
|
</button>
|
||||||
|
<button v-else class="button _button" disabled>
|
||||||
|
<i class="ti ti-ban"></i>
|
||||||
|
</button>
|
||||||
|
<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
|
||||||
<i class="ti ti-plus"></i>
|
<i class="ti ti-plus"></i>
|
||||||
</button>
|
</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="button _button reacted" @click="undoReact(appearNote)">
|
||||||
<i class="ti ti-minus"></i>
|
<i class="ti ti-minus"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="menuButton" class="button _button" @click="menu()">
|
<button ref="menuButton" class="button _button" @mousedown="menu()">
|
||||||
<i class="ti ti-dots"></i>
|
<i class="ti ti-dots"></i>
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -121,10 +142,9 @@ import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
|
|||||||
import MkMediaList from '@/components/MkMediaList.vue';
|
import MkMediaList from '@/components/MkMediaList.vue';
|
||||||
import MkCwButton from '@/components/MkCwButton.vue';
|
import MkCwButton from '@/components/MkCwButton.vue';
|
||||||
import MkPoll from '@/components/MkPoll.vue';
|
import MkPoll from '@/components/MkPoll.vue';
|
||||||
import MkRenoteButton from '@/components/MkRenoteButton.vue';
|
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||||
import MkVisibility from '@/components/MkVisibility.vue';
|
|
||||||
import { pleaseLogin } from '@/scripts/please-login';
|
import { pleaseLogin } from '@/scripts/please-login';
|
||||||
import { checkWordMute } from '@/scripts/check-word-mute';
|
import { checkWordMute } from '@/scripts/check-word-mute';
|
||||||
import { userPage } from '@/filters/user';
|
import { userPage } from '@/filters/user';
|
||||||
@@ -138,6 +158,7 @@ import { i18n } from '@/i18n';
|
|||||||
import { getNoteMenu } from '@/scripts/get-note-menu';
|
import { getNoteMenu } from '@/scripts/get-note-menu';
|
||||||
import { useNoteCapture } from '@/scripts/use-note-capture';
|
import { useNoteCapture } from '@/scripts/use-note-capture';
|
||||||
import { deepClone } from '@/scripts/clone';
|
import { deepClone } from '@/scripts/clone';
|
||||||
|
import { useTooltip } from '@/scripts/use-tooltip';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
@@ -168,7 +189,7 @@ const isRenote = (
|
|||||||
|
|
||||||
const el = shallowRef<HTMLElement>();
|
const el = shallowRef<HTMLElement>();
|
||||||
const menuButton = shallowRef<HTMLElement>();
|
const menuButton = shallowRef<HTMLElement>();
|
||||||
const renoteButton = shallowRef<InstanceType<typeof MkRenoteButton>>();
|
const renoteButton = shallowRef<HTMLElement>();
|
||||||
const renoteTime = shallowRef<HTMLElement>();
|
const renoteTime = shallowRef<HTMLElement>();
|
||||||
const reactButton = shallowRef<HTMLElement>();
|
const reactButton = shallowRef<HTMLElement>();
|
||||||
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
|
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
|
||||||
@@ -182,6 +203,7 @@ const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : n
|
|||||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
|
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
|
||||||
const conversation = ref<misskey.entities.Note[]>([]);
|
const conversation = ref<misskey.entities.Note[]>([]);
|
||||||
const replies = ref<misskey.entities.Note[]>([]);
|
const replies = ref<misskey.entities.Note[]>([]);
|
||||||
|
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
'r': () => reply(true),
|
'r': () => reply(true),
|
||||||
@@ -198,6 +220,47 @@ useNoteCapture({
|
|||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useTooltip(renoteButton, async (showing) => {
|
||||||
|
const renotes = await os.api('notes/renotes', {
|
||||||
|
noteId: appearNote.id,
|
||||||
|
limit: 11,
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = renotes.map(x => x.user);
|
||||||
|
|
||||||
|
if (users.length < 1) return;
|
||||||
|
|
||||||
|
os.popup(MkUsersTooltip, {
|
||||||
|
showing,
|
||||||
|
users,
|
||||||
|
count: appearNote.renoteCount,
|
||||||
|
targetElement: renoteButton.value,
|
||||||
|
}, {}, 'closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
function renote(viaKeyboard = false) {
|
||||||
|
pleaseLogin();
|
||||||
|
os.popupMenu([{
|
||||||
|
text: i18n.ts.renote,
|
||||||
|
icon: 'ti ti-repeat',
|
||||||
|
action: () => {
|
||||||
|
os.api('notes/create', {
|
||||||
|
renoteId: appearNote.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.quote,
|
||||||
|
icon: 'ti ti-quote',
|
||||||
|
action: () => {
|
||||||
|
os.post({
|
||||||
|
renote: appearNote,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}], renoteButton.value, {
|
||||||
|
viaKeyboard,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function reply(viaKeyboard = false): void {
|
function reply(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
os.post({
|
os.post({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="kkwtjztg">
|
<header class="kkwtjztg">
|
||||||
<MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
|
<MkA v-once v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
|
||||||
<MkUserName :user="note.user"/>
|
<MkUserName :user="note.user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<div v-if="note.user.isBot" class="is-bot">bot</div>
|
<div v-if="note.user.isBot" class="is-bot">bot</div>
|
||||||
@@ -9,7 +9,12 @@
|
|||||||
<MkA class="created-at" :to="notePage(note)">
|
<MkA class="created-at" :to="notePage(note)">
|
||||||
<MkTime :time="note.createdAt"/>
|
<MkTime :time="note.createdAt"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkVisibility :note="note"/>
|
<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
|
||||||
|
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
||||||
|
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
|
||||||
|
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||||
|
</span>
|
||||||
|
<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>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,7 +22,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import MkVisibility from '@/components/MkVisibility.vue';
|
import { i18n } from '@/i18n';
|
||||||
import { notePage } from '@/filters/note';
|
import { notePage } from '@/filters/note';
|
||||||
import { userPage } from '@/filters/user';
|
import { userPage } from '@/filters/user';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="elRef" class="qglefbjs" :class="notification.type">
|
<div ref="elRef" class="qglefbjs" :class="notification.type">
|
||||||
<div class="head">
|
<div v-once class="head">
|
||||||
<MkAvatar v-if="notification.type === 'pollEnded'" class="icon" :user="notification.note.user"/>
|
<MkAvatar v-if="notification.type === 'pollEnded'" class="icon" :user="notification.note.user"/>
|
||||||
<MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/>
|
<MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/>
|
||||||
<img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/>
|
<img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/>
|
||||||
@@ -13,10 +13,9 @@
|
|||||||
<i v-else-if="notification.type === 'reply'" class="ti ti-arrow-back-up"></i>
|
<i v-else-if="notification.type === 'reply'" class="ti ti-arrow-back-up"></i>
|
||||||
<i v-else-if="notification.type === 'mention'" class="ti ti-at"></i>
|
<i v-else-if="notification.type === 'mention'" class="ti ti-at"></i>
|
||||||
<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
|
<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
|
||||||
<i v-else-if="notification.type === 'pollVote'" class="ti ti-chart-arrows"></i>
|
|
||||||
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
|
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
|
||||||
<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
|
<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
|
||||||
<XReactionIcon
|
<MkReactionIcon
|
||||||
v-else-if="notification.type === 'reaction'"
|
v-else-if="notification.type === 'reaction'"
|
||||||
ref="reactionRef"
|
ref="reactionRef"
|
||||||
:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
|
:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
|
||||||
@@ -32,50 +31,47 @@
|
|||||||
<span v-else>{{ notification.header }}</span>
|
<span v-else>{{ notification.header }}</span>
|
||||||
<MkTime v-if="withTime" :time="notification.createdAt" class="time"/>
|
<MkTime v-if="withTime" :time="notification.createdAt" class="time"/>
|
||||||
</header>
|
</header>
|
||||||
|
<div v-once class="content">
|
||||||
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||||
<i class="ti ti-quote"></i>
|
<i class="ti ti-quote"></i>
|
||||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||||
<i class="ti ti-quote"></i>
|
<i class="ti ti-quote"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
|
<MkA v-else-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
|
||||||
<i class="ti ti-quote"></i>
|
<i class="ti ti-quote"></i>
|
||||||
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
|
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
|
||||||
<i class="ti ti-quote"></i>
|
<i class="ti ti-quote"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
<MkA v-else-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
<MkA v-else-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
<MkA v-else-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
<MkA v-else-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||||
<i class="ti ti-quote"></i>
|
<i class="ti ti-quote"></i>
|
||||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
||||||
<i class="ti ti-quote"></i>
|
<i class="ti ti-quote"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
<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>
|
||||||
<i class="ti ti-quote"></i>
|
<span v-else-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
|
||||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
|
<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>
|
||||||
<i class="ti ti-quote"></i>
|
<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>
|
||||||
</MkA>
|
<span v-else-if="notification.type === 'app'" class="text">
|
||||||
<span v-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-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
|
|
||||||
<span v-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-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-if="notification.type === 'app'" class="text">
|
|
||||||
<Mfm :text="notification.body" :nowrap="!full"/>
|
<Mfm :text="notification.body" :nowrap="!full"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, shallowRef, onMounted, onUnmounted, watch } from 'vue';
|
import { ref, shallowRef, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import XReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
import MkFollowButton from '@/components/MkFollowButton.vue';
|
import MkFollowButton from '@/components/MkFollowButton.vue';
|
||||||
import XReactionTooltip from '@/components/MkReactionTooltip.vue';
|
import XReactionTooltip from '@/components/MkReactionTooltip.vue';
|
||||||
import { getNoteSummary } from '@/scripts/get-note-summary';
|
import { getNoteSummary } from '@/scripts/get-note-summary';
|
||||||
@@ -239,12 +235,6 @@ useTooltip(reactionRef, (showing) => {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.pollVote {
|
|
||||||
padding: 3px;
|
|
||||||
background: #88a6b7;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pollEnded {
|
&.pollEnded {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
background: #88a6b7;
|
background: #88a6b7;
|
||||||
@@ -275,6 +265,7 @@ useTooltip(reactionRef, (showing) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
> .text {
|
> .text {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -295,6 +286,7 @@ useTooltip(reactionRef, (showing) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@container (max-width: 600px) {
|
@container (max-width: 600px) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
|
<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
|
||||||
<span class="text" :class="{ up }">
|
<span class="text" :class="{ up }">
|
||||||
<XReactionIcon class="icon" :reaction="reaction"/>
|
<MkReactionIcon class="icon" :reaction="reaction"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import XReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
reaction: string;
|
reaction: string;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
||||||
<div class="beeadbfb">
|
<div class="beeadbfb">
|
||||||
<XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
|
<MkReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
|
||||||
<div class="name">{{ reaction.replace('@.', '') }}</div>
|
<div class="name">{{ reaction.replace('@.', '') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</MkTooltip>
|
</MkTooltip>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import MkTooltip from './MkTooltip.vue';
|
import MkTooltip from './MkTooltip.vue';
|
||||||
import XReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
showing: boolean;
|
showing: boolean;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
||||||
<div class="bqxuuuey">
|
<div class="bqxuuuey">
|
||||||
<div class="reaction">
|
<div class="reaction">
|
||||||
<XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
|
<MkReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
|
||||||
<div class="name">{{ getReactionName(reaction) }}</div>
|
<div class="name">{{ getReactionName(reaction) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="users">
|
<div class="users">
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import MkTooltip from './MkTooltip.vue';
|
import MkTooltip from './MkTooltip.vue';
|
||||||
import XReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
import { getEmojiName } from '@/scripts/emojilist';
|
import { getEmojiName } from '@/scripts/emojilist';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
ref="buttonRef"
|
ref="buttonEl"
|
||||||
v-ripple="canToggle"
|
v-ripple="canToggle"
|
||||||
class="hkzvhatu _button"
|
class="hkzvhatu _button"
|
||||||
:class="{ reacted: note.myReaction == reaction, canToggle }"
|
:class="{ reacted: note.myReaction == reaction, canToggle }"
|
||||||
@click="toggleReaction()"
|
@click="toggleReaction()"
|
||||||
>
|
>
|
||||||
<XReactionIcon class="icon" :reaction="reaction"/>
|
<MkReactionIcon class="icon" :reaction="reaction"/>
|
||||||
<span class="count">{{ count }}</span>
|
<span class="count">{{ count }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
import { computed, onMounted, ref, shallowRef, watch } from 'vue';
|
import { computed, onMounted, ref, shallowRef, watch } from 'vue';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
||||||
import XReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { useTooltip } from '@/scripts/use-tooltip';
|
import { useTooltip } from '@/scripts/use-tooltip';
|
||||||
import { $i } from '@/account';
|
import { $i } from '@/account';
|
||||||
@@ -28,7 +28,7 @@ const props = defineProps<{
|
|||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const buttonRef = shallowRef<HTMLElement>();
|
const buttonEl = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
|
const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ const toggleReaction = () => {
|
|||||||
const anime = () => {
|
const anime = () => {
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
|
|
||||||
const rect = buttonRef.value.getBoundingClientRect();
|
const rect = buttonEl.value.getBoundingClientRect();
|
||||||
const x = rect.left + (buttonRef.value.offsetWidth / 2);
|
const x = rect.left + 16;
|
||||||
const y = rect.top + (buttonRef.value.offsetHeight / 2);
|
const y = rect.top + (buttonEl.value.offsetHeight / 2);
|
||||||
os.popup(MkPlusOneEffect, { reaction: props.reaction, x, y }, {}, 'end');
|
os.popup(MkPlusOneEffect, { reaction: props.reaction, x, y }, {}, 'end');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ onMounted(() => {
|
|||||||
if (!props.isInitial) anime();
|
if (!props.isInitial) anime();
|
||||||
});
|
});
|
||||||
|
|
||||||
useTooltip(buttonRef, async (showing) => {
|
useTooltip(buttonEl, async (showing) => {
|
||||||
const reactions = await os.apiGet('notes/reactions', {
|
const reactions = await os.apiGet('notes/reactions', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
type: props.reaction,
|
type: props.reaction,
|
||||||
@@ -87,7 +87,7 @@ useTooltip(buttonRef, async (showing) => {
|
|||||||
reaction: props.reaction,
|
reaction: props.reaction,
|
||||||
users,
|
users,
|
||||||
count: props.count,
|
count: props.count,
|
||||||
targetElement: buttonRef.value,
|
targetElement: buttonEl.value,
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
}, 100);
|
}, 100);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button
|
|
||||||
v-if="canRenote"
|
|
||||||
ref="buttonRef"
|
|
||||||
class="eddddedb _button canRenote"
|
|
||||||
@click="renote()"
|
|
||||||
>
|
|
||||||
<i class="ti ti-repeat"></i>
|
|
||||||
<p v-if="count > 0" class="count">{{ count }}</p>
|
|
||||||
</button>
|
|
||||||
<button v-else class="eddddedb _button">
|
|
||||||
<i class="ti ti-ban"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, ref, shallowRef } from 'vue';
|
|
||||||
import * as misskey from 'misskey-js';
|
|
||||||
import XDetails from '@/components/MkUsersTooltip.vue';
|
|
||||||
import { pleaseLogin } from '@/scripts/please-login';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { $i } from '@/account';
|
|
||||||
import { useTooltip } from '@/scripts/use-tooltip';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
note: misskey.entities.Note;
|
|
||||||
count: number;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const buttonRef = shallowRef<HTMLElement>();
|
|
||||||
|
|
||||||
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
|
|
||||||
|
|
||||||
useTooltip(buttonRef, async (showing) => {
|
|
||||||
const renotes = await os.api('notes/renotes', {
|
|
||||||
noteId: props.note.id,
|
|
||||||
limit: 11,
|
|
||||||
});
|
|
||||||
|
|
||||||
const users = renotes.map(x => x.user);
|
|
||||||
|
|
||||||
if (users.length < 1) return;
|
|
||||||
|
|
||||||
os.popup(XDetails, {
|
|
||||||
showing,
|
|
||||||
users,
|
|
||||||
count: props.count,
|
|
||||||
targetElement: buttonRef.value,
|
|
||||||
}, {}, 'closed');
|
|
||||||
});
|
|
||||||
|
|
||||||
const renote = (viaKeyboard = false) => {
|
|
||||||
pleaseLogin();
|
|
||||||
os.popupMenu([{
|
|
||||||
text: i18n.ts.renote,
|
|
||||||
icon: 'ti ti-repeat',
|
|
||||||
action: () => {
|
|
||||||
os.api('notes/create', {
|
|
||||||
renoteId: props.note.id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
text: i18n.ts.quote,
|
|
||||||
icon: 'ti ti-quote',
|
|
||||||
action: () => {
|
|
||||||
os.post({
|
|
||||||
renote: props.note,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}], buttonRef.value, {
|
|
||||||
viaKeyboard,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.eddddedb {
|
|
||||||
display: inline-block;
|
|
||||||
height: 32px;
|
|
||||||
margin: 2px;
|
|
||||||
padding: 0 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:not(.canRenote) {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.renoted {
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .count {
|
|
||||||
display: inline;
|
|
||||||
margin-left: 8px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vblkjoeq">
|
<div class="vblkjoeq">
|
||||||
<div class="label" @click="focus"><slot name="label"></slot></div>
|
<div class="label" @click="focus"><slot name="label"></slot></div>
|
||||||
<div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick">
|
<div ref="container" class="input" :class="{ inline, disabled, focused }" @mousedown.prevent="show">
|
||||||
<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
|
<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
|
||||||
<select
|
<select
|
||||||
ref="inputEl"
|
ref="inputEl"
|
||||||
@@ -118,7 +118,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClick = (ev: MouseEvent) => {
|
function show(ev: MouseEvent) {
|
||||||
focused.value = true;
|
focused.value = true;
|
||||||
opening.value = true;
|
opening.value = true;
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ const onClick = (ev: MouseEvent) => {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
focused.value = false;
|
focused.value = false;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -285,7 +285,7 @@ const onClick = (ev: MouseEvent) => {
|
|||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.chevron {
|
.chevron {
|
||||||
transition: transform 0.5s ease;
|
transition: transform 0.1s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevronOpening {
|
.chevronOpening {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<template #prefix><i class="ti ti-lock"></i></template>
|
<template #prefix><i class="ti ti-lock"></i></template>
|
||||||
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
<MkButton type="submit" large primary rounded :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
|
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
|
||||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<template #label>{{ i18n.ts.token }}</template>
|
<template #label>{{ i18n.ts.token }}</template>
|
||||||
<template #prefix><i class="ti ti-123"></i></template>
|
<template #prefix><i class="ti ti-123"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
<MkButton type="submit" :disabled="signing" large primary rounded style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||||
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
|
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
|
||||||
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
|
<Mfm v-if="note.text" v-once :text="note.text" :author="note.user" :i="$i"/>
|
||||||
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.files.length > 0">
|
<details v-if="note.files.length > 0">
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<template>
|
|
||||||
<span v-if="note.visibility !== 'public'" :class="$style.visibility" :title="i18n.ts._visibility[note.visibility]">
|
|
||||||
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
|
||||||
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
|
|
||||||
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
|
||||||
</span>
|
|
||||||
<span v-if="note.localOnly" :class="$style.localOnly" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import XDetails from '@/components/MkUsersTooltip.vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import { useTooltip } from '@/scripts/use-tooltip';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
note: {
|
|
||||||
visibility: string;
|
|
||||||
localOnly?: boolean;
|
|
||||||
visibleUserIds?: string[];
|
|
||||||
},
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const specified = $shallowRef<HTMLElement>();
|
|
||||||
|
|
||||||
if (props.note.visibility === 'specified') {
|
|
||||||
useTooltip($$(specified), async (showing) => {
|
|
||||||
const users = await os.api('users/show', {
|
|
||||||
userIds: props.note.visibleUserIds,
|
|
||||||
limit: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
os.popup(XDetails, {
|
|
||||||
showing,
|
|
||||||
users,
|
|
||||||
count: props.note.visibleUserIds.length,
|
|
||||||
targetElement: specified,
|
|
||||||
}, {}, 'closed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.visibility, .localOnly {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<MkFolder v-for="category in customEmojiCategories" :key="category" class="emojis">
|
<MkFolder v-for="category in customEmojiCategories" :key="category" class="emojis">
|
||||||
<template #header>{{ category || $ts.other }}</template>
|
<template #header>{{ category || $ts.other }}</template>
|
||||||
<div class="zuvgdzyt">
|
<div class="zuvgdzyt">
|
||||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" v-once :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
||||||
<div class="dqokceoi">
|
<div class="dqokceoi">
|
||||||
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
|
<MkA v-for="instance in items" v-once :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
|
||||||
<MkInstanceCardMini :instance="instance"/>
|
<MkInstanceCardMini :instance="instance"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" class="users">
|
<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" class="users">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
|
<MkA v-for="user in items" v-once :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
|
||||||
<MkUserCardMini :user="user"/>
|
<MkUserCardMini :user="user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ definePageMetadata({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.ruryvtyk {
|
.ruryvtyk {
|
||||||
> .announcement {
|
> .announcement {
|
||||||
> .header {
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
|
> .header {
|
||||||
|
margin-bottom: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .content {
|
> .content {
|
||||||
padding: 0 16px;
|
|
||||||
|
|
||||||
> img {
|
> img {
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
@@ -69,7 +69,7 @@ definePageMetadata({
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .footer {
|
> .footer {
|
||||||
padding: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const headerActions = $computed(() => [{
|
|||||||
const headerTabs = $computed(() => [{
|
const headerTabs = $computed(() => [{
|
||||||
key: 'featured',
|
key: 'featured',
|
||||||
title: i18n.ts._play.featured,
|
title: i18n.ts._play.featured,
|
||||||
icon: 'fas fa-fire-alt',
|
icon: 'ti ti-flare',
|
||||||
}, {
|
}, {
|
||||||
key: 'my',
|
key: 'my',
|
||||||
title: i18n.ts._play.my,
|
title: i18n.ts._play.my,
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const headerActions = $computed(() => [{
|
|||||||
const headerTabs = $computed(() => [{
|
const headerTabs = $computed(() => [{
|
||||||
key: 'featured',
|
key: 'featured',
|
||||||
title: i18n.ts._pages.featured,
|
title: i18n.ts._pages.featured,
|
||||||
icon: 'fas fa-fire-alt',
|
icon: 'ti ti-flare',
|
||||||
}, {
|
}, {
|
||||||
key: 'my',
|
key: 'my',
|
||||||
title: i18n.ts._pages.my,
|
title: i18n.ts._pages.my,
|
||||||
|
|||||||
@@ -12,12 +12,37 @@ import { computed } from 'vue';
|
|||||||
import XNotes from '@/components/MkNotes.vue';
|
import XNotes from '@/components/MkNotes.vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
|
import * as os from '@/os';
|
||||||
|
import { useRouter } from '@/router';
|
||||||
|
import { $i } from '@/account';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
query: string;
|
query: string;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const query = props.query;
|
||||||
|
|
||||||
|
if ($i != null) {
|
||||||
|
if (query.startsWith('https://') || (query.startsWith('@') && !query.includes(' '))) {
|
||||||
|
const promise = os.api('ap/show', {
|
||||||
|
uri: props.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
||||||
|
|
||||||
|
const res = await promise;
|
||||||
|
|
||||||
|
if (res.type === 'User') {
|
||||||
|
router.replace(`/@${res.object.username}@${res.object.host}`);
|
||||||
|
} else if (res.type === 'Note') {
|
||||||
|
router.replace(`/notes/${res.object.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'notes/search' as const,
|
endpoint: 'notes/search' as const,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="_gaps_m">
|
<div class="">
|
||||||
<FormSuspense :p="init">
|
<FormSuspense :p="init">
|
||||||
|
<div class="_gaps">
|
||||||
<MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton>
|
<MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton>
|
||||||
|
|
||||||
<div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
|
<div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
174
packages/frontend/src/pages/user/activity.following.vue
Normal file
174
packages/frontend/src/pages/user/activity.following.vue
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MkLoading v-if="fetching"/>
|
||||||
|
<div v-show="!fetching" :class="$style.root" class="_panel">
|
||||||
|
<canvas ref="chartEl"></canvas>
|
||||||
|
<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
import { Chart, ChartDataset } from 'chart.js';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
import * as misskey from 'misskey-js';
|
||||||
|
import gradient from 'chartjs-plugin-gradient';
|
||||||
|
import { satisfies } from 'compare-versions';
|
||||||
|
import * as os from '@/os';
|
||||||
|
import { defaultStore } from '@/store';
|
||||||
|
import { useChartTooltip } from '@/scripts/use-chart-tooltip';
|
||||||
|
import { chartVLine } from '@/scripts/chart-vline';
|
||||||
|
import { alpha } from '@/scripts/color';
|
||||||
|
import { initChart } from '@/scripts/init-chart';
|
||||||
|
import { chartLegend } from '@/scripts/chart-legend';
|
||||||
|
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||||
|
|
||||||
|
initChart();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
user: misskey.entities.User;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const chartEl = $shallowRef<HTMLCanvasElement>(null);
|
||||||
|
let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
|
||||||
|
const now = new Date();
|
||||||
|
let chartInstance: Chart = null;
|
||||||
|
const chartLimit = 50;
|
||||||
|
let fetching = $ref(true);
|
||||||
|
|
||||||
|
const { handler: externalTooltipHandler } = useChartTooltip();
|
||||||
|
|
||||||
|
async function renderChart() {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDate = (ago: number) => {
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
|
||||||
|
return new Date(y, m, d - ago);
|
||||||
|
};
|
||||||
|
|
||||||
|
const format = (arr) => {
|
||||||
|
return arr.map((v, i) => ({
|
||||||
|
x: getDate(i).getTime(),
|
||||||
|
y: v,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const raw = await os.api('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
||||||
|
|
||||||
|
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||||
|
|
||||||
|
const colorFollowLocal = '#008FFB';
|
||||||
|
const colorFollowRemote = '#008FFB88';
|
||||||
|
const colorFollowedLocal = '#2ecc71';
|
||||||
|
const colorFollowedRemote = '#2ecc7188';
|
||||||
|
|
||||||
|
function makeDataset(label: string, data: ChartDataset['data'], extra: Partial<ChartDataset> = {}): ChartDataset {
|
||||||
|
return Object.assign({
|
||||||
|
label: label,
|
||||||
|
data: data,
|
||||||
|
parsing: false,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderWidth: 0,
|
||||||
|
borderJoinStyle: 'round',
|
||||||
|
borderRadius: 4,
|
||||||
|
barPercentage: 0.9,
|
||||||
|
fill: true,
|
||||||
|
} satisfies ChartDataset, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = new Chart(chartEl, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
makeDataset('Follow (local)', format(raw.local.followings.inc).slice().reverse(), { backgroundColor: colorFollowLocal }),
|
||||||
|
makeDataset('Follow (remote)', format(raw.remote.followings.inc).slice().reverse(), { backgroundColor: colorFollowRemote }),
|
||||||
|
makeDataset('Followed (local)', format(raw.local.followers.inc).slice().reverse(), { backgroundColor: colorFollowedLocal }),
|
||||||
|
makeDataset('Followed (remote)', format(raw.remote.followers.inc).slice().reverse(), { backgroundColor: colorFollowedRemote }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
aspectRatio: 3,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 8,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
offset: true,
|
||||||
|
stacked: true,
|
||||||
|
time: {
|
||||||
|
stepSize: 1,
|
||||||
|
unit: 'day',
|
||||||
|
displayFormats: {
|
||||||
|
day: 'M/d',
|
||||||
|
month: 'Y/M',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
maxRotation: 0,
|
||||||
|
autoSkipPadding: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
position: 'left',
|
||||||
|
stacked: true,
|
||||||
|
suggestedMax: 10,
|
||||||
|
grid: {
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
//mirror: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false,
|
||||||
|
mode: 'index',
|
||||||
|
animation: {
|
||||||
|
duration: 0,
|
||||||
|
},
|
||||||
|
external: externalTooltipHandler,
|
||||||
|
},
|
||||||
|
gradient,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
|
||||||
|
});
|
||||||
|
|
||||||
|
fetching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
renderChart();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
174
packages/frontend/src/pages/user/activity.notes.vue
Normal file
174
packages/frontend/src/pages/user/activity.notes.vue
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MkLoading v-if="fetching"/>
|
||||||
|
<div v-show="!fetching" :class="$style.root" class="_panel">
|
||||||
|
<canvas ref="chartEl"></canvas>
|
||||||
|
<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
import { Chart, ChartDataset } from 'chart.js';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
import * as misskey from 'misskey-js';
|
||||||
|
import gradient from 'chartjs-plugin-gradient';
|
||||||
|
import { satisfies } from 'compare-versions';
|
||||||
|
import * as os from '@/os';
|
||||||
|
import { defaultStore } from '@/store';
|
||||||
|
import { useChartTooltip } from '@/scripts/use-chart-tooltip';
|
||||||
|
import { chartVLine } from '@/scripts/chart-vline';
|
||||||
|
import { alpha } from '@/scripts/color';
|
||||||
|
import { initChart } from '@/scripts/init-chart';
|
||||||
|
import { chartLegend } from '@/scripts/chart-legend';
|
||||||
|
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||||
|
|
||||||
|
initChart();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
user: misskey.entities.User;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const chartEl = $shallowRef<HTMLCanvasElement>(null);
|
||||||
|
let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
|
||||||
|
const now = new Date();
|
||||||
|
let chartInstance: Chart = null;
|
||||||
|
const chartLimit = 50;
|
||||||
|
let fetching = $ref(true);
|
||||||
|
|
||||||
|
const { handler: externalTooltipHandler } = useChartTooltip();
|
||||||
|
|
||||||
|
async function renderChart() {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDate = (ago: number) => {
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
|
||||||
|
return new Date(y, m, d - ago);
|
||||||
|
};
|
||||||
|
|
||||||
|
const format = (arr) => {
|
||||||
|
return arr.map((v, i) => ({
|
||||||
|
x: getDate(i).getTime(),
|
||||||
|
y: v,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const raw = await os.api('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
||||||
|
|
||||||
|
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||||
|
|
||||||
|
const colorNormal = '#008FFB';
|
||||||
|
const colorReply = '#FEB019';
|
||||||
|
const colorRenote = '#00E396';
|
||||||
|
const colorFile = '#e300db';
|
||||||
|
|
||||||
|
function makeDataset(label: string, data: ChartDataset['data'], extra: Partial<ChartDataset> = {}): ChartDataset {
|
||||||
|
return Object.assign({
|
||||||
|
label: label,
|
||||||
|
data: data,
|
||||||
|
parsing: false,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderWidth: 0,
|
||||||
|
borderJoinStyle: 'round',
|
||||||
|
borderRadius: 4,
|
||||||
|
barPercentage: 0.9,
|
||||||
|
fill: true,
|
||||||
|
} satisfies ChartDataset, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = new Chart(chartEl, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
makeDataset('Normal', format(raw.diffs.normal).slice().reverse(), { backgroundColor: colorNormal }),
|
||||||
|
makeDataset('Reply', format(raw.diffs.reply).slice().reverse(), { backgroundColor: colorReply }),
|
||||||
|
makeDataset('Renote', format(raw.diffs.renote).slice().reverse(), { backgroundColor: colorRenote }),
|
||||||
|
makeDataset('File', format(raw.diffs.withFile).slice().reverse(), { backgroundColor: colorFile }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
aspectRatio: 3,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 8,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
offset: true,
|
||||||
|
stacked: true,
|
||||||
|
time: {
|
||||||
|
stepSize: 1,
|
||||||
|
unit: 'day',
|
||||||
|
displayFormats: {
|
||||||
|
day: 'M/d',
|
||||||
|
month: 'Y/M',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
maxRotation: 0,
|
||||||
|
autoSkipPadding: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
position: 'left',
|
||||||
|
stacked: true,
|
||||||
|
suggestedMax: 10,
|
||||||
|
grid: {
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
//mirror: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false,
|
||||||
|
mode: 'index',
|
||||||
|
animation: {
|
||||||
|
duration: 0,
|
||||||
|
},
|
||||||
|
external: externalTooltipHandler,
|
||||||
|
},
|
||||||
|
gradient,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
|
||||||
|
});
|
||||||
|
|
||||||
|
fetching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
renderChart();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart, ChartDataset } from 'chart.js';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import gradient from 'chartjs-plugin-gradient';
|
import gradient from 'chartjs-plugin-gradient';
|
||||||
@@ -67,65 +67,33 @@ async function renderChart() {
|
|||||||
const colorUser2 = '#3498db88';
|
const colorUser2 = '#3498db88';
|
||||||
const colorVisitor2 = '#2ecc7188';
|
const colorVisitor2 = '#2ecc7188';
|
||||||
|
|
||||||
|
function makeDataset(label: string, data: ChartDataset['data'], extra: Partial<ChartDataset> = {}): ChartDataset {
|
||||||
|
return Object.assign({
|
||||||
|
label: label,
|
||||||
|
data: data,
|
||||||
|
parsing: false,
|
||||||
|
pointRadius: 0,
|
||||||
|
borderWidth: 0,
|
||||||
|
borderJoinStyle: 'round',
|
||||||
|
borderRadius: 4,
|
||||||
|
barPercentage: 0.7,
|
||||||
|
categoryPercentage: 0.7,
|
||||||
|
fill: true,
|
||||||
|
} satisfies ChartDataset, extra);
|
||||||
|
}
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl, {
|
chartInstance = new Chart(chartEl, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
datasets: [{
|
datasets: [
|
||||||
parsing: false,
|
makeDataset('UPV (user)', format(raw.upv.user).slice().reverse(), { backgroundColor: colorUser, stack: 'u' }),
|
||||||
label: 'UPV (user)',
|
makeDataset('UPV (visitor)', format(raw.upv.visitor).slice().reverse(), { backgroundColor: colorVisitor, stack: 'u' }),
|
||||||
data: format(raw.upv.user).slice().reverse(),
|
makeDataset('NPV (user)', format(raw.pv.user).slice().reverse(), { backgroundColor: colorUser2, stack: 'n' }),
|
||||||
pointRadius: 0,
|
makeDataset('UPV (visitor)', format(raw.pv.visitor).slice().reverse(), { backgroundColor: colorVisitor2, stack: 'n' }),
|
||||||
borderWidth: 0,
|
],
|
||||||
borderJoinStyle: 'round',
|
|
||||||
borderRadius: 4,
|
|
||||||
backgroundColor: colorUser,
|
|
||||||
barPercentage: 0.7,
|
|
||||||
categoryPercentage: 0.7,
|
|
||||||
fill: true,
|
|
||||||
stack: 'u',
|
|
||||||
}, {
|
|
||||||
parsing: false,
|
|
||||||
label: 'UPV (visitor)',
|
|
||||||
data: format(raw.upv.visitor).slice().reverse(),
|
|
||||||
pointRadius: 0,
|
|
||||||
borderWidth: 0,
|
|
||||||
borderJoinStyle: 'round',
|
|
||||||
borderRadius: 4,
|
|
||||||
backgroundColor: colorVisitor,
|
|
||||||
barPercentage: 0.7,
|
|
||||||
categoryPercentage: 0.7,
|
|
||||||
fill: true,
|
|
||||||
stack: 'u',
|
|
||||||
}, {
|
|
||||||
parsing: false,
|
|
||||||
label: 'NPV (user)',
|
|
||||||
data: format(raw.pv.user).slice().reverse(),
|
|
||||||
pointRadius: 0,
|
|
||||||
borderWidth: 0,
|
|
||||||
borderJoinStyle: 'round',
|
|
||||||
borderRadius: 4,
|
|
||||||
backgroundColor: colorUser2,
|
|
||||||
barPercentage: 0.7,
|
|
||||||
categoryPercentage: 0.7,
|
|
||||||
fill: true,
|
|
||||||
stack: 'n',
|
|
||||||
}, {
|
|
||||||
parsing: false,
|
|
||||||
label: 'NPV (visitor)',
|
|
||||||
data: format(raw.pv.visitor).slice().reverse(),
|
|
||||||
pointRadius: 0,
|
|
||||||
borderWidth: 0,
|
|
||||||
borderJoinStyle: 'round',
|
|
||||||
borderRadius: 4,
|
|
||||||
backgroundColor: colorVisitor2,
|
|
||||||
barPercentage: 0.7,
|
|
||||||
categoryPercentage: 0.7,
|
|
||||||
fill: true,
|
|
||||||
stack: 'n',
|
|
||||||
}],
|
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
aspectRatio: 2.5,
|
aspectRatio: 3,
|
||||||
layout: {
|
layout: {
|
||||||
padding: {
|
padding: {
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|||||||
@@ -2,11 +2,19 @@
|
|||||||
<MkSpacer :content-max="700">
|
<MkSpacer :content-max="700">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkFolder class="item">
|
<MkFolder class="item">
|
||||||
<template #header>Heatmap</template>
|
<template #header><i class="ti ti-activity"></i> Heatmap</template>
|
||||||
<XHeatmap :user="user" :src="'notes'"/>
|
<XHeatmap :user="user" :src="'notes'"/>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
<MkFolder class="item">
|
<MkFolder class="item">
|
||||||
<template #header>PV</template>
|
<template #header><i class="ti ti-pencil"></i> Notes</template>
|
||||||
|
<XNotes :user="user"/>
|
||||||
|
</MkFolder>
|
||||||
|
<MkFolder class="item">
|
||||||
|
<template #header><i class="ti ti-users"></i> Following</template>
|
||||||
|
<XFollowing :user="user"/>
|
||||||
|
</MkFolder>
|
||||||
|
<MkFolder class="item">
|
||||||
|
<template #header><i class="ti ti-eye"></i> PV</template>
|
||||||
<XPv :user="user"/>
|
<XPv :user="user"/>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,6 +26,8 @@ import { computed } from 'vue';
|
|||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import XHeatmap from './activity.heatmap.vue';
|
import XHeatmap from './activity.heatmap.vue';
|
||||||
import XPv from './activity.pv.vue';
|
import XPv from './activity.pv.vue';
|
||||||
|
import XNotes from './activity.notes.vue';
|
||||||
|
import XFollowing from './activity.following.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const getBuiltinThemes = () => Promise.all(
|
|||||||
'l-coffee',
|
'l-coffee',
|
||||||
'l-apricot',
|
'l-apricot',
|
||||||
'l-rainy',
|
'l-rainy',
|
||||||
|
'l-botanical',
|
||||||
'l-vivid',
|
'l-vivid',
|
||||||
'l-cherry',
|
'l-cherry',
|
||||||
'l-sushi',
|
'l-sushi',
|
||||||
|
|||||||
29
packages/frontend/src/themes/l-botanical.json5
Normal file
29
packages/frontend/src/themes/l-botanical.json5
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
id: '1100673c-f902-4ccd-93aa-7cb88be56178',
|
||||||
|
|
||||||
|
name: 'Mi Botanical Light',
|
||||||
|
author: 'ThinaticSystem',
|
||||||
|
|
||||||
|
base: 'light',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
accent: '#77b58c',
|
||||||
|
bg: 'e2deda',
|
||||||
|
fg: '#3d3d3d',
|
||||||
|
fgHighlighted: '#6bc9a0',
|
||||||
|
divider: '#cfcfcf',
|
||||||
|
panel: '@X14',
|
||||||
|
panelHeaderBg: '@panel',
|
||||||
|
panelHeaderDivider: '@divider',
|
||||||
|
header: ':alpha<0.7<@panel',
|
||||||
|
navBg: '@X14',
|
||||||
|
renote: '#229e92',
|
||||||
|
mention: '#da6d35',
|
||||||
|
mentionMe: '#d44c4c',
|
||||||
|
hashtag: '#4cb8d4',
|
||||||
|
link: '@accent',
|
||||||
|
buttonGradateB: ':hue<-70<@accent',
|
||||||
|
success: '#86b300',
|
||||||
|
X14: '#ebe7e5'
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<div v-if="showMenu" class="menu">
|
<div v-if="showMenu" class="menu">
|
||||||
<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ $ts.home }}</MkA>
|
<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ $ts.home }}</MkA>
|
||||||
<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ $ts.explore }}</MkA>
|
<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ $ts.explore }}</MkA>
|
||||||
<MkA to="/featured" class="link" active-class="active"><i class="fas fa-fire-alt icon"></i>{{ $ts.featured }}</MkA>
|
<MkA to="/featured" class="link" active-class="active"><i class="ti ti-flare icon"></i>{{ $ts.featured }}</MkA>
|
||||||
<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ $ts.channel }}</MkA>
|
<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ $ts.channel }}</MkA>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<button class="_buttonPrimary" @click="signup()">{{ $ts.signup }}</button>
|
<button class="_buttonPrimary" @click="signup()">{{ $ts.signup }}</button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ $ts.home }}</MkA>
|
<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ $ts.home }}</MkA>
|
||||||
<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ $ts.explore }}</MkA>
|
<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ $ts.explore }}</MkA>
|
||||||
<MkA to="/featured" class="link" active-class="active"><i class="fas fa-fire-alt icon"></i>{{ $ts.featured }}</MkA>
|
<MkA to="/featured" class="link" active-class="active"><i class="ti ti-flare icon"></i>{{ $ts.featured }}</MkA>
|
||||||
<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ $ts.channel }}</MkA>
|
<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ $ts.channel }}</MkA>
|
||||||
<div v-if="info" class="page active link">
|
<div v-if="info" class="page active link">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
|||||||
@@ -11,19 +11,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div>
|
<div>
|
||||||
<p>{{ i18n.ts.today }}: <b>{{ dayP.toFixed(1) }}%</b></p>
|
<p>{{ i18n.ts.today }}<b>{{ dayP.toFixed(1) }}%</b></p>
|
||||||
<div class="meter">
|
<div class="meter">
|
||||||
<div class="val" :style="{ width: `${dayP}%` }"></div>
|
<div class="val" :style="{ width: `${dayP}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>{{ i18n.ts.thisMonth }}: <b>{{ monthP.toFixed(1) }}%</b></p>
|
<p>{{ i18n.ts.thisMonth }}<b>{{ monthP.toFixed(1) }}%</b></p>
|
||||||
<div class="meter">
|
<div class="meter">
|
||||||
<div class="val" :style="{ width: `${monthP}%` }"></div>
|
<div class="val" :style="{ width: `${monthP}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>{{ i18n.ts.thisYear }}: <b>{{ yearP.toFixed(1) }}%</b></p>
|
<p>{{ i18n.ts.thisYear }}<b>{{ yearP.toFixed(1) }}%</b></p>
|
||||||
<div class="meter">
|
<div class="meter">
|
||||||
<div class="val" :style="{ width: `${yearP}%` }"></div>
|
<div class="val" :style="{ width: `${yearP}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,13 +168,14 @@ defineExpose<WidgetComponentExpose>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
> p {
|
> p {
|
||||||
|
display: flex;
|
||||||
margin: 0 0 2px 0;
|
margin: 0 0 2px 0;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
||||||
> b {
|
> b {
|
||||||
margin-left: 2px;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: userDetail.isFollowing ? [] : [
|
actions: userDetail.isFollowing ? [] : [
|
||||||
{
|
{
|
||||||
action: 'follow',
|
action: 'follow',
|
||||||
title: t('_notification._actions.followBack')
|
title: t('_notification._actions.followBack'),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -66,8 +66,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'reply',
|
action: 'reply',
|
||||||
title: t('_notification._actions.reply')
|
title: t('_notification._actions.reply'),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'reply',
|
action: 'reply',
|
||||||
title: t('_notification._actions.reply')
|
title: t('_notification._actions.reply'),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'showUser',
|
action: 'showUser',
|
||||||
title: getUserName(data.body.user)
|
title: getUserName(data.body.user),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -108,14 +108,14 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'reply',
|
action: 'reply',
|
||||||
title: t('_notification._actions.reply')
|
title: t('_notification._actions.reply'),
|
||||||
},
|
},
|
||||||
...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [
|
...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [
|
||||||
{
|
{
|
||||||
action: 'renote',
|
action: 'renote',
|
||||||
title: t('_notification._actions.renote')
|
title: t('_notification._actions.renote'),
|
||||||
}
|
},
|
||||||
] : [])
|
] : []),
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
const dummy = `${encodeURIComponent(`${u.host}${u.pathname}`)}.png`;
|
const dummy = `${encodeURIComponent(`${u.host}${u.pathname}`)}.png`;
|
||||||
badge = `${origin}/proxy/${dummy}?${url.query({
|
badge = `${origin}/proxy/${dummy}?${url.query({
|
||||||
url: u.href,
|
url: u.href,
|
||||||
badge: '1'
|
badge: '1',
|
||||||
})}`;
|
})}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,20 +162,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'showUser',
|
action: 'showUser',
|
||||||
title: getUserName(data.body.user)
|
title: getUserName(data.body.user),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'pollVote':
|
|
||||||
return [t('_notification.youGotPoll', { name: getUserName(data.body.user) }), {
|
|
||||||
body: data.body.note.text || '',
|
|
||||||
icon: data.body.user.avatarUrl,
|
|
||||||
badge: iconUrl('poll-h'),
|
|
||||||
data,
|
|
||||||
}];
|
|
||||||
|
|
||||||
case 'pollEnded':
|
case 'pollEnded':
|
||||||
return [t('_notification.pollEnded'), {
|
return [t('_notification.pollEnded'), {
|
||||||
body: data.body.note.text || '',
|
body: data.body.note.text || '',
|
||||||
@@ -192,12 +184,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'accept',
|
action: 'accept',
|
||||||
title: t('accept')
|
title: t('accept'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'reject',
|
action: 'reject',
|
||||||
title: t('reject')
|
title: t('reject'),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -217,12 +209,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: 'accept',
|
action: 'accept',
|
||||||
title: t('accept')
|
title: t('accept'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'reject',
|
action: 'reject',
|
||||||
title: t('reject')
|
title: t('reject'),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -230,7 +222,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
return [data.body.header || data.body.body, {
|
return [data.body.header || data.body.body, {
|
||||||
body: data.body.header && data.body.body,
|
body: data.body.header && data.body.body,
|
||||||
icon: data.body.icon,
|
icon: data.body.icon,
|
||||||
data
|
data,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -279,7 +271,7 @@ export async function createEmptyNotification() {
|
|||||||
silent: true,
|
silent: true,
|
||||||
badge: iconUrl('null'),
|
badge: iconUrl('null'),
|
||||||
tag: 'read_notification',
|
tag: 'read_notification',
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
res();
|
res();
|
||||||
@@ -288,7 +280,7 @@ export async function createEmptyNotification() {
|
|||||||
for (const n of
|
for (const n of
|
||||||
[
|
[
|
||||||
...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })),
|
...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })),
|
||||||
...(await self.registration.getNotifications({ tag: 'read_notification' }))
|
...(await self.registration.getNotifications({ tag: 'read_notification' })),
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
n.close();
|
n.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user