Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d92f781f | ||
|
|
414cac49c3 | ||
|
|
95b157ac3e | ||
|
|
8e3d884081 | ||
|
|
9def6fcadd | ||
|
|
7837bd44fc | ||
|
|
a6c3663155 | ||
|
|
0b5afadbb8 | ||
|
|
43864f0da4 | ||
|
|
6a0d9d70ed | ||
|
|
63c6dce68e | ||
|
|
53422ffcb2 | ||
|
|
38ca514f53 | ||
|
|
caea0f0376 | ||
|
|
25a8b26977 | ||
|
|
bcaefe8d62 | ||
|
|
46f1e8c599 | ||
|
|
16230f320e | ||
|
|
ace6419aef | ||
|
|
77fb9eb2be |
@@ -1,6 +1,14 @@
|
|||||||
ChangeLog
|
ChangeLog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
10.85.0
|
||||||
|
----------
|
||||||
|
* デスクトップ版のUIを改良
|
||||||
|
* 投稿ハイライトページを実装
|
||||||
|
* 無効化されているタイムラインのフォールバック
|
||||||
|
* 既にフォローされている場合はフォローリクエストを生成しないように
|
||||||
|
* その他細かな修正
|
||||||
|
|
||||||
10.84.2
|
10.84.2
|
||||||
----------
|
----------
|
||||||
* GIF画像にGIFバッジを表示
|
* GIF画像にGIFバッジを表示
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<!-- PATREON_START -->
|
<!-- PATREON_START -->
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=prtYqPOiSHBulhM7NU0VzMaWx39-9ntdq25b6kafDNA%3D" alt="negao" width="100"></td>
|
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
|
||||||
@@ -102,7 +101,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
|
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
|
||||||
<td><a href="https://www.patreon.com/negao">negao</a></td>
|
|
||||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
||||||
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
|
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
|
||||||
@@ -142,7 +140,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
|
|
||||||
**Last updated:** Wed, 06 Feb 2019 18:18:05 UTC
|
**Last updated:** Thu, 14 Feb 2019 12:02:06 UTC
|
||||||
<!-- PATREON_END -->
|
<!-- PATREON_END -->
|
||||||
|
|
||||||
:four_leaf_clover: Copyright
|
:four_leaf_clover: Copyright
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
|
|||||||
4. `npm run build`
|
4. `npm run build`
|
||||||
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
|
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
|
||||||
|
|
||||||
|
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
なにかお困りのことがありましたらお気軽にご連絡ください。
|
なにかお困りのことがありましたらお気軽にご連絡ください。
|
||||||
|
|||||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "キーワード"
|
keywords: "キーワード"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "Schlagwörter"
|
keywords: "Schlagwörter"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "キーワード"
|
keywords: "キーワード"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "キーワード"
|
keywords: "キーワード"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ common:
|
|||||||
load-more: "もっと読み込む"
|
load-more: "もっと読み込む"
|
||||||
enter-password: "パスワードを入力してください"
|
enter-password: "パスワードを入力してください"
|
||||||
2fa: "二段階認証"
|
2fa: "二段階認証"
|
||||||
|
customize-home: "ホームをカスタマイズ"
|
||||||
|
featured-notes: "ハイライト"
|
||||||
|
|
||||||
got-it: "わかった"
|
got-it: "わかった"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
@@ -862,6 +864,9 @@ desktop/views/components/renote-form.vue:
|
|||||||
desktop/views/components/renote-form-window.vue:
|
desktop/views/components/renote-form-window.vue:
|
||||||
title: "この投稿をRenoteしますか?"
|
title: "この投稿をRenoteしますか?"
|
||||||
|
|
||||||
|
desktop/views/components/timeline.core.vue:
|
||||||
|
empty: "投稿がありません"
|
||||||
|
|
||||||
desktop/views/pages/user-following-or-followers.vue:
|
desktop/views/pages/user-following-or-followers.vue:
|
||||||
following: "{user}のフォロー"
|
following: "{user}のフォロー"
|
||||||
followers: "{user}のフォロワー"
|
followers: "{user}のフォロワー"
|
||||||
@@ -893,14 +898,10 @@ desktop/views/components/settings.vue:
|
|||||||
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
|
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
|
||||||
auto-popout: "ウィンドウの自動ポップアウト"
|
auto-popout: "ウィンドウの自動ポップアウト"
|
||||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||||
deck-nav: "デッキ内ナビゲーション"
|
|
||||||
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
|
|
||||||
keep-cw: "CW保持"
|
keep-cw: "CW保持"
|
||||||
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
|
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
|
||||||
deck-default: "デッキをデフォルトのUIにする"
|
|
||||||
|
|
||||||
display: "デザインと表示"
|
display: "デザインと表示"
|
||||||
customize: "ホームをカスタマイズ"
|
|
||||||
wallpaper: "壁紙"
|
wallpaper: "壁紙"
|
||||||
choose-wallpaper: "壁紙を選択"
|
choose-wallpaper: "壁紙を選択"
|
||||||
delete-wallpaper: "壁紙を削除"
|
delete-wallpaper: "壁紙を削除"
|
||||||
@@ -1076,7 +1077,6 @@ desktop/views/components/ui.header.account.vue:
|
|||||||
favorites: "お気に入り"
|
favorites: "お気に入り"
|
||||||
lists: "リスト"
|
lists: "リスト"
|
||||||
follow-requests: "フォロー申請"
|
follow-requests: "フォロー申請"
|
||||||
customize: "ホームのカスタマイズ"
|
|
||||||
admin: "管理"
|
admin: "管理"
|
||||||
settings: "設定"
|
settings: "設定"
|
||||||
signout: "サインアウト"
|
signout: "サインアウト"
|
||||||
@@ -1447,9 +1447,6 @@ desktop/views/pages/welcome.vue:
|
|||||||
desktop/views/pages/drive.vue:
|
desktop/views/pages/drive.vue:
|
||||||
title: "Misskey Drive"
|
title: "Misskey Drive"
|
||||||
|
|
||||||
desktop/views/pages/home-customize.vue:
|
|
||||||
title: "ホームのカスタマイズ"
|
|
||||||
|
|
||||||
desktop/views/pages/note.vue:
|
desktop/views/pages/note.vue:
|
||||||
prev: "前の投稿"
|
prev: "前の投稿"
|
||||||
next: "次の投稿"
|
next: "次の投稿"
|
||||||
@@ -1490,10 +1487,6 @@ desktop/views/pages/user/user.photos.vue:
|
|||||||
loading: "読み込み中"
|
loading: "読み込み中"
|
||||||
no-photos: "写真はありません"
|
no-photos: "写真はありません"
|
||||||
|
|
||||||
desktop/views/pages/user/user.profile.vue:
|
|
||||||
follows-you: "フォローされています"
|
|
||||||
menu: "メニュー"
|
|
||||||
|
|
||||||
desktop/views/pages/user/user.header.vue:
|
desktop/views/pages/user/user.header.vue:
|
||||||
posts: "投稿"
|
posts: "投稿"
|
||||||
following: "フォロー"
|
following: "フォロー"
|
||||||
@@ -1503,6 +1496,7 @@ desktop/views/pages/user/user.header.vue:
|
|||||||
year: "年"
|
year: "年"
|
||||||
month: "月"
|
month: "月"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
follows-you: "フォローされています"
|
||||||
|
|
||||||
desktop/views/pages/user/user.timeline.vue:
|
desktop/views/pages/user/user.timeline.vue:
|
||||||
default: "投稿"
|
default: "投稿"
|
||||||
@@ -1689,6 +1683,9 @@ mobile/views/pages/home.vue:
|
|||||||
mentions: "あなた宛て"
|
mentions: "あなた宛て"
|
||||||
messages: "メッセージ"
|
messages: "メッセージ"
|
||||||
|
|
||||||
|
mobile/views/pages/home.timeline.vue:
|
||||||
|
empty: "投稿がありません"
|
||||||
|
|
||||||
mobile/views/pages/tag.vue:
|
mobile/views/pages/tag.vue:
|
||||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
||||||
|
|
||||||
@@ -1791,7 +1788,7 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "キーワード"
|
keywords: "キーワード"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
|
|
||||||
@@ -1799,7 +1796,7 @@ mobile/views/pages/user/home.followers-you-know.vue:
|
|||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
|
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
|
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
|
|||||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "Nøkkelord"
|
keywords: "Nøkkelord"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -1597,7 +1597,7 @@ mobile/views/pages/user/home.vue:
|
|||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "Nenhuma mensagem"
|
no-notes: "Nenhuma mensagem"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
|||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "キーワード"
|
keywords: "キーワード"
|
||||||
domains: "頻出ドメイン"
|
domains: "頻出ドメイン"
|
||||||
frequently-replied-users: "よく会話するユーザー"
|
frequently-replied-users: "よく話すユーザー"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
mobile/views/pages/user/home.followers-you-know.vue:
|
mobile/views/pages/user/home.followers-you-know.vue:
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
mobile/views/pages/user/home.friends.vue:
|
mobile/views/pages/user/home.friends.vue:
|
||||||
no-users: "よく会話するユーザーはいません"
|
no-users: "よく話すユーザーはいません"
|
||||||
mobile/views/pages/user/home.notes.vue:
|
mobile/views/pages/user/home.notes.vue:
|
||||||
no-notes: "投稿はありません"
|
no-notes: "投稿はありません"
|
||||||
mobile/views/pages/user/home.photos.vue:
|
mobile/views/pages/user/home.photos.vue:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "10.84.2",
|
"version": "10.85.0",
|
||||||
"clientVersion": "2.0.14267",
|
"clientVersion": "2.0.14287",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-activity">
|
<div>
|
||||||
<div ref="chart"></div>
|
<div ref="chart"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -9,7 +9,17 @@ import Vue from 'vue';
|
|||||||
import ApexCharts from 'apexcharts';
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['user'],
|
props: {
|
||||||
|
user: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 21
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fetching: true,
|
fetching: true,
|
||||||
@@ -21,7 +31,7 @@ export default Vue.extend({
|
|||||||
this.$root.api('charts/user/notes', {
|
this.$root.api('charts/user/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
span: 'day',
|
span: 'day',
|
||||||
limit: 21
|
limit: this.limit
|
||||||
}).then(stats => {
|
}).then(stats => {
|
||||||
const normal = [];
|
const normal = [];
|
||||||
const reply = [];
|
const reply = [];
|
||||||
@@ -32,7 +42,7 @@ export default Vue.extend({
|
|||||||
const m = now.getMonth();
|
const m = now.getMonth();
|
||||||
const d = now.getDate();
|
const d = now.getDate();
|
||||||
|
|
||||||
for (let i = 0; i < 21; i++) {
|
for (let i = 0; i < this.limit; i++) {
|
||||||
const x = new Date(y, m, d - i);
|
const x = new Date(y, m, d - i);
|
||||||
normal.push([
|
normal.push([
|
||||||
x,
|
x,
|
||||||
@@ -99,10 +109,3 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mk-activity
|
|
||||||
max-width 600px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
</style>
|
|
||||||
11
src/client/app/common/views/components/dummy.vue
Normal file
11
src/client/app/common/views/components/dummy.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
|
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
|
||||||
:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
|
:class="{ wait, block, inline, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
|
:inline="inline"
|
||||||
>
|
>
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
|
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
|
||||||
@@ -28,6 +29,11 @@ export default Vue.extend({
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
inline: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
mini: {
|
mini: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -128,6 +134,9 @@ export default Vue.extend({
|
|||||||
border solid 1px var(--primary)
|
border solid 1px var(--primary)
|
||||||
border-radius 36px
|
border-radius 36px
|
||||||
|
|
||||||
|
&.inline
|
||||||
|
display inline-block
|
||||||
|
|
||||||
&.mini
|
&.mini
|
||||||
padding 0
|
padding 0
|
||||||
min-width 0
|
min-width 0
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import dummy from './dummy.vue';
|
||||||
import userName from './user-name.vue';
|
import userName from './user-name.vue';
|
||||||
import followButton from './follow-button.vue';
|
import followButton from './follow-button.vue';
|
||||||
import error from './error.vue';
|
import error from './error.vue';
|
||||||
@@ -46,6 +47,7 @@ import formButton from './ui/form/button.vue';
|
|||||||
import formRadio from './ui/form/radio.vue';
|
import formRadio from './ui/form/radio.vue';
|
||||||
|
|
||||||
Vue.component('mfm', misskeyFlavoredMarkdown);
|
Vue.component('mfm', misskeyFlavoredMarkdown);
|
||||||
|
Vue.component('mk-dummy', dummy);
|
||||||
Vue.component('mk-user-name', userName);
|
Vue.component('mk-user-name', userName);
|
||||||
Vue.component('mk-follow-button', followButton);
|
Vue.component('mk-follow-button', followButton);
|
||||||
Vue.component('mk-error', error);
|
Vue.component('mk-error', error);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import wSlideshow from './slideshow.vue';
|
|||||||
import wTips from './tips.vue';
|
import wTips from './tips.vue';
|
||||||
import wNav from './nav.vue';
|
import wNav from './nav.vue';
|
||||||
import wHashtags from './hashtags.vue';
|
import wHashtags from './hashtags.vue';
|
||||||
|
import wInstance from './instance.vue';
|
||||||
|
|
||||||
Vue.component('mkw-analog-clock', wAnalogClock);
|
Vue.component('mkw-analog-clock', wAnalogClock);
|
||||||
Vue.component('mkw-nav', wNav);
|
Vue.component('mkw-nav', wNav);
|
||||||
@@ -27,3 +28,4 @@ Vue.component('mkw-memo', wMemo);
|
|||||||
Vue.component('mkw-rss', wRss);
|
Vue.component('mkw-rss', wRss);
|
||||||
Vue.component('mkw-version', wVersion);
|
Vue.component('mkw-version', wVersion);
|
||||||
Vue.component('mkw-hashtags', wHashtags);
|
Vue.component('mkw-hashtags', wHashtags);
|
||||||
|
Vue.component('mkw-instance', wInstance);
|
||||||
|
|||||||
14
src/client/app/common/views/widgets/instance.vue
Normal file
14
src/client/app/common/views/widgets/instance.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mkw-instance">
|
||||||
|
<mk-widget-container>
|
||||||
|
<mk-instance/>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
export default define({
|
||||||
|
name: 'instance'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -12,19 +12,13 @@ import init from '../init';
|
|||||||
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
||||||
import composeNotification from '../common/scripts/compose-notification';
|
import composeNotification from '../common/scripts/compose-notification';
|
||||||
|
|
||||||
import MkIndex from './views/pages/index.vue';
|
import MkHome from './views/home/home.vue';
|
||||||
import MkHome from './views/pages/home.vue';
|
import MkDeck from './views/deck/deck.vue';
|
||||||
import MkDeck from './views/pages/deck/deck.vue';
|
|
||||||
import MkUser from './views/pages/user/user.vue';
|
|
||||||
import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue';
|
import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue';
|
||||||
import MkFavorites from './views/pages/favorites.vue';
|
|
||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||||
import MkDrive from './views/pages/drive.vue';
|
import MkDrive from './views/pages/drive.vue';
|
||||||
import MkHomeCustomize from './views/pages/home-customize.vue';
|
|
||||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
|
import MkMessagingRoom from './views/pages/messaging-room.vue';
|
||||||
import MkNote from './views/pages/note.vue';
|
|
||||||
import MkSearch from './views/pages/search.vue';
|
import MkSearch from './views/pages/search.vue';
|
||||||
import MkTag from './views/pages/tag.vue';
|
|
||||||
import MkReversi from './views/pages/games/reversi.vue';
|
import MkReversi from './views/pages/games/reversi.vue';
|
||||||
import MkShare from './views/pages/share.vue';
|
import MkShare from './views/pages/share.vue';
|
||||||
import MkFollow from '../common/views/pages/follow.vue';
|
import MkFollow from '../common/views/pages/follow.vue';
|
||||||
@@ -36,6 +30,7 @@ import PostFormWindow from './views/components/post-form-window.vue';
|
|||||||
import RenoteFormWindow from './views/components/renote-form-window.vue';
|
import RenoteFormWindow from './views/components/renote-form-window.vue';
|
||||||
import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue';
|
import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue';
|
||||||
import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue';
|
import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue';
|
||||||
|
import MkHomeTimeline from './views/home/timeline.vue';
|
||||||
import Notification from './views/components/ui-notification.vue';
|
import Notification from './views/components/ui-notification.vue';
|
||||||
|
|
||||||
import { url } from '../config';
|
import { url } from '../config';
|
||||||
@@ -44,7 +39,7 @@ import MiOS from '../mios';
|
|||||||
/**
|
/**
|
||||||
* init
|
* init
|
||||||
*/
|
*/
|
||||||
init(async (launch) => {
|
init(async (launch, os) => {
|
||||||
Vue.mixin({
|
Vue.mixin({
|
||||||
methods: {
|
methods: {
|
||||||
$contextmenu(e, menu, opts?) {
|
$contextmenu(e, menu, opts?) {
|
||||||
@@ -134,31 +129,39 @@ init(async (launch) => {
|
|||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/', name: 'index', component: MkIndex },
|
os.store.getters.isSignedIn && os.store.state.device.deckMode
|
||||||
{ path: '/home', name: 'home', component: MkHome },
|
? { path: '/', name: 'index', component: MkDeck, children: [
|
||||||
{ path: '/deck', name: 'deck', component: MkDeck },
|
{ path: '/@:user', name: 'user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default) },
|
||||||
{ path: '/i/customize-home', component: MkHomeCustomize },
|
{ path: '/notes/:note', name: 'note', component: () => import('./views/deck/deck.note-column.vue').then(m => m.default) },
|
||||||
{ path: '/i/favorites', component: MkFavorites },
|
{ path: '/tags/:tag', name: 'tag', component: () => import('./views/deck/deck.hashtag-column.vue').then(m => m.default) },
|
||||||
|
{ path: '/featured', component: () => import('./views/deck/deck.featured-column.vue').then(m => m.default) },
|
||||||
|
{ path: '/i/favorites', component: () => import('./views/deck/deck.favorites-column.vue').then(m => m.default) }
|
||||||
|
]}
|
||||||
|
: { path: '/', component: MkHome, children: [
|
||||||
|
{ path: '', name: 'index', component: MkHomeTimeline },
|
||||||
|
{ path: '/@:user', name: 'user', component: () => import('./views/home/user/user.vue').then(m => m.default) },
|
||||||
|
{ path: '/notes/:note', name: 'note', component: () => import('./views/home/note.vue').then(m => m.default) },
|
||||||
|
{ path: '/tags/:tag', name: 'tag', component: () => import('./views/home/tag.vue').then(m => m.default) },
|
||||||
|
{ path: '/featured', component: () => import('./views/home/featured.vue').then(m => m.default) },
|
||||||
|
{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) }
|
||||||
|
]},
|
||||||
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
||||||
{ path: '/i/drive', component: MkDrive },
|
{ path: '/i/drive', component: MkDrive },
|
||||||
{ path: '/i/drive/folder/:folder', component: MkDrive },
|
{ path: '/i/drive/folder/:folder', component: MkDrive },
|
||||||
{ path: '/i/settings', component: MkSettings },
|
{ path: '/i/settings', component: MkSettings },
|
||||||
{ path: '/selectdrive', component: MkSelectDrive },
|
{ path: '/selectdrive', component: MkSelectDrive },
|
||||||
{ path: '/search', component: MkSearch },
|
{ path: '/search', component: MkSearch },
|
||||||
{ path: '/tags/:tag', name: 'tag', component: MkTag },
|
|
||||||
{ path: '/share', component: MkShare },
|
{ path: '/share', component: MkShare },
|
||||||
{ path: '/games/reversi/:game?', component: MkReversi },
|
{ path: '/games/reversi/:game?', component: MkReversi },
|
||||||
{ path: '/@:user', name: 'user', component: MkUser },
|
|
||||||
{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
|
{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
|
||||||
{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
|
{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
|
||||||
{ path: '/notes/:note', name: 'note', component: MkNote },
|
|
||||||
{ path: '/authorize-follow', component: MkFollow },
|
{ path: '/authorize-follow', component: MkFollow },
|
||||||
{ path: '*', component: MkNotFound }
|
{ path: '*', component: MkNotFound }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Launch the app
|
// Launch the app
|
||||||
const [app, os] = launch(router);
|
const [app, _] = launch(router);
|
||||||
|
|
||||||
if (os.store.getters.isSignedIn) {
|
if (os.store.getters.isSignedIn) {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ export default Vue.extend({
|
|||||||
color #222
|
color #222
|
||||||
|
|
||||||
> [data-icon]
|
> [data-icon]
|
||||||
|
box-sizing initial
|
||||||
padding 14px
|
padding 14px
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,396 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mk-home" :data-customize="customize">
|
|
||||||
<div class="customize" v-if="customize">
|
|
||||||
<router-link to="/"><fa icon="check"/>{{ $t('done') }}</router-link>
|
|
||||||
<div>
|
|
||||||
<div class="adder">
|
|
||||||
<p>{{ $t('add-widget') }}</p>
|
|
||||||
<select v-model="widgetAdderSelected">
|
|
||||||
<option value="profile">{{ $t('@.widgets.profile') }}</option>
|
|
||||||
<option value="analog-clock">{{ $t('@.widgets.analog-clock') }}</option>
|
|
||||||
<option value="calendar">{{ $t('@.widgets.calendar') }}</option>
|
|
||||||
<option value="timemachine">{{ $t('@.widgets.timemachine') }}</option>
|
|
||||||
<option value="activity">{{ $t('@.widgets.activity') }}</option>
|
|
||||||
<option value="rss">{{ $t('@.widgets.rss') }}</option>
|
|
||||||
<option value="trends">{{ $t('@.widgets.trends') }}</option>
|
|
||||||
<option value="photo-stream">{{ $t('@.widgets.photo-stream') }}</option>
|
|
||||||
<option value="slideshow">{{ $t('@.widgets.slideshow') }}</option>
|
|
||||||
<option value="version">{{ $t('@.widgets.version') }}</option>
|
|
||||||
<option value="broadcast">{{ $t('@.widgets.broadcast') }}</option>
|
|
||||||
<option value="notifications">{{ $t('@.widgets.notifications') }}</option>
|
|
||||||
<option value="users">{{ $t('@.widgets.users') }}</option>
|
|
||||||
<option value="polls">{{ $t('@.widgets.polls') }}</option>
|
|
||||||
<option value="post-form">{{ $t('@.widgets.post-form') }}</option>
|
|
||||||
<option value="messaging">{{ $t('@.widgets.messaging') }}</option>
|
|
||||||
<option value="memo">{{ $t('@.widgets.memo') }}</option>
|
|
||||||
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
|
||||||
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
|
||||||
<option value="server">{{ $t('@.widgets.server') }}</option>
|
|
||||||
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
|
||||||
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
|
||||||
</select>
|
|
||||||
<button @click="addWidget">{{ $t('add') }}</button>
|
|
||||||
</div>
|
|
||||||
<div class="trash">
|
|
||||||
<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
|
|
||||||
<p>{{ $t('@.trash') }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
|
|
||||||
<template v-if="customize">
|
|
||||||
<x-draggable v-for="place in ['left', 'right']"
|
|
||||||
:list="widgets[place]"
|
|
||||||
:class="place"
|
|
||||||
:data-place="place"
|
|
||||||
:options="{ group: 'x', animation: 150 }"
|
|
||||||
@sort="onWidgetSort"
|
|
||||||
:key="place"
|
|
||||||
>
|
|
||||||
<div v-for="widget in widgets[place]" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
|
|
||||||
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="desktop"/>
|
|
||||||
</div>
|
|
||||||
</x-draggable>
|
|
||||||
<div class="main">
|
|
||||||
<a @click="hint">{{ $t('@.customization-tips.title') }}</a>
|
|
||||||
<div>
|
|
||||||
<mk-post-form v-if="$store.state.settings.showPostFormOnTopOfTl"/>
|
|
||||||
<mk-timeline ref="tl" @loaded="onTlLoaded"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div v-for="place in ['left', 'right']" :class="place">
|
|
||||||
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp" platform="desktop"/>
|
|
||||||
</div>
|
|
||||||
<div class="main">
|
|
||||||
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
|
|
||||||
<mk-timeline class="tl" ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../i18n';
|
|
||||||
import * as XDraggable from 'vuedraggable';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
|
|
||||||
const defaultDesktopHomeWidgets = {
|
|
||||||
left: [
|
|
||||||
'profile',
|
|
||||||
'calendar',
|
|
||||||
'activity',
|
|
||||||
'rss',
|
|
||||||
'hashtags',
|
|
||||||
'photo-stream',
|
|
||||||
'version'
|
|
||||||
],
|
|
||||||
right: [
|
|
||||||
'broadcast',
|
|
||||||
'notifications',
|
|
||||||
'users',
|
|
||||||
'polls',
|
|
||||||
'server',
|
|
||||||
'nav',
|
|
||||||
'tips'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
//#region Construct home data
|
|
||||||
const _defaultDesktopHomeWidgets = [];
|
|
||||||
|
|
||||||
for (const widget of defaultDesktopHomeWidgets.left) {
|
|
||||||
_defaultDesktopHomeWidgets.push({
|
|
||||||
name: widget,
|
|
||||||
id: uuid(),
|
|
||||||
place: 'left',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const widget of defaultDesktopHomeWidgets.right) {
|
|
||||||
_defaultDesktopHomeWidgets.push({
|
|
||||||
name: widget,
|
|
||||||
id: uuid(),
|
|
||||||
place: 'right',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n('desktop/views/components/home.vue'),
|
|
||||||
components: {
|
|
||||||
XDraggable
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
customize: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'timeline'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
connection: null,
|
|
||||||
widgetAdderSelected: null,
|
|
||||||
trash: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
home(): any[] {
|
|
||||||
return this.$store.state.settings.home || [];
|
|
||||||
},
|
|
||||||
left(): any[] {
|
|
||||||
return this.home.filter(w => w.place == 'left');
|
|
||||||
},
|
|
||||||
right(): any[] {
|
|
||||||
return this.home.filter(w => w.place == 'right');
|
|
||||||
},
|
|
||||||
widgets(): any {
|
|
||||||
return {
|
|
||||||
left: this.left,
|
|
||||||
right: this.right
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
if (this.$store.state.settings.home == null) {
|
|
||||||
this.$root.api('i/update_home', {
|
|
||||||
home: _defaultDesktopHomeWidgets
|
|
||||||
}).then(() => {
|
|
||||||
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.connection = this.$root.stream.useSharedConnection('main');
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
hint() {
|
|
||||||
this.$root.dialog({
|
|
||||||
title: this.$t('@.customization-tips.title'),
|
|
||||||
text: this.$t('@.customization-tips.paragraph')
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onTlLoaded() {
|
|
||||||
this.$emit('loaded');
|
|
||||||
},
|
|
||||||
|
|
||||||
onWidgetContextmenu(widgetId) {
|
|
||||||
const w = (this.$refs[widgetId] as any)[0];
|
|
||||||
if (w.func) w.func();
|
|
||||||
},
|
|
||||||
|
|
||||||
onWidgetSort() {
|
|
||||||
this.saveHome();
|
|
||||||
},
|
|
||||||
|
|
||||||
onTrash(evt) {
|
|
||||||
this.saveHome();
|
|
||||||
},
|
|
||||||
|
|
||||||
addWidget() {
|
|
||||||
this.$store.dispatch('settings/addHomeWidget', {
|
|
||||||
name: this.widgetAdderSelected,
|
|
||||||
id: uuid(),
|
|
||||||
place: 'left',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
saveHome() {
|
|
||||||
const left = this.widgets.left;
|
|
||||||
const right = this.widgets.right;
|
|
||||||
this.$store.commit('settings/setHome', left.concat(right));
|
|
||||||
for (const w of left) w.place = 'left';
|
|
||||||
for (const w of right) w.place = 'right';
|
|
||||||
this.$root.api('i/update_home', {
|
|
||||||
home: this.home
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
warp(date) {
|
|
||||||
(this.$refs.tl as any).warp(date);
|
|
||||||
},
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
(this.$refs.tl as any).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mk-home
|
|
||||||
display block
|
|
||||||
|
|
||||||
&[data-customize]
|
|
||||||
padding-top 48px
|
|
||||||
background-image url('/assets/desktop/grid.svg')
|
|
||||||
|
|
||||||
> .main > .main
|
|
||||||
> a
|
|
||||||
display block
|
|
||||||
margin-bottom 8px
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
> div
|
|
||||||
cursor not-allowed !important
|
|
||||||
|
|
||||||
> *
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
&:not([data-customize])
|
|
||||||
> .main > *:empty
|
|
||||||
display none
|
|
||||||
|
|
||||||
> .customize
|
|
||||||
position fixed
|
|
||||||
z-index 1000
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
height 48px
|
|
||||||
color var(--text)
|
|
||||||
background var(--desktopHeaderBg)
|
|
||||||
box-shadow 0 1px 1px rgba(#000, 0.075)
|
|
||||||
|
|
||||||
> a
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
z-index 1001
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
padding 0 16px
|
|
||||||
line-height 48px
|
|
||||||
text-decoration none
|
|
||||||
color var(--primaryForeground)
|
|
||||||
background var(--primary)
|
|
||||||
transition background 0.1s ease
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background var(--primaryLighten10)
|
|
||||||
|
|
||||||
&:active
|
|
||||||
background var(--primaryDarken10)
|
|
||||||
transition background 0s ease
|
|
||||||
|
|
||||||
> [data-icon]
|
|
||||||
margin-right 8px
|
|
||||||
|
|
||||||
> div
|
|
||||||
display flex
|
|
||||||
margin 0 auto
|
|
||||||
max-width 1220px - 32px
|
|
||||||
|
|
||||||
> div
|
|
||||||
width 50%
|
|
||||||
|
|
||||||
&.adder
|
|
||||||
> p
|
|
||||||
display inline
|
|
||||||
line-height 48px
|
|
||||||
|
|
||||||
&.trash
|
|
||||||
border-left solid 1px var(--faceDivider)
|
|
||||||
|
|
||||||
> div
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
|
|
||||||
> p
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
line-height 48px
|
|
||||||
margin 0
|
|
||||||
text-align center
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
margin 0 auto
|
|
||||||
max-width 1240px
|
|
||||||
|
|
||||||
> *
|
|
||||||
.customize-container
|
|
||||||
cursor move
|
|
||||||
border-radius 6px
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
box-shadow 0 0 8px rgba(64, 120, 200, 0.3)
|
|
||||||
|
|
||||||
> *
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
padding 16px
|
|
||||||
width calc(100% - 280px * 2)
|
|
||||||
order 2
|
|
||||||
|
|
||||||
> .form
|
|
||||||
margin-bottom 16px
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
&.side
|
|
||||||
> .main
|
|
||||||
width calc(100% - 280px)
|
|
||||||
max-width 680px
|
|
||||||
|
|
||||||
> *:not(.main)
|
|
||||||
width 280px
|
|
||||||
padding 16px 0 16px 0
|
|
||||||
|
|
||||||
> *:not(:last-child)
|
|
||||||
margin-bottom 16px
|
|
||||||
|
|
||||||
> .left
|
|
||||||
padding-left 16px
|
|
||||||
order 1
|
|
||||||
|
|
||||||
> .right
|
|
||||||
padding-right 16px
|
|
||||||
order 3
|
|
||||||
|
|
||||||
&.side
|
|
||||||
@media (max-width 1000px)
|
|
||||||
> *:not(.main)
|
|
||||||
display none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
width 100%
|
|
||||||
max-width 700px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
&:not(.side)
|
|
||||||
@media (max-width 1200px)
|
|
||||||
> *:not(.main)
|
|
||||||
display none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
width 100%
|
|
||||||
max-width 700px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -2,8 +2,6 @@ import Vue from 'vue';
|
|||||||
|
|
||||||
import ui from './ui.vue';
|
import ui from './ui.vue';
|
||||||
import uiNotification from './ui-notification.vue';
|
import uiNotification from './ui-notification.vue';
|
||||||
import home from './home.vue';
|
|
||||||
import timeline from './timeline.vue';
|
|
||||||
import notes from './notes.vue';
|
import notes from './notes.vue';
|
||||||
import subNoteContent from './sub-note-content.vue';
|
import subNoteContent from './sub-note-content.vue';
|
||||||
import window from './window.vue';
|
import window from './window.vue';
|
||||||
@@ -24,8 +22,6 @@ import widgetContainer from './widget-container.vue';
|
|||||||
|
|
||||||
Vue.component('mk-ui', ui);
|
Vue.component('mk-ui', ui);
|
||||||
Vue.component('mk-ui-notification', uiNotification);
|
Vue.component('mk-ui-notification', uiNotification);
|
||||||
Vue.component('mk-home', home);
|
|
||||||
Vue.component('mk-timeline', timeline);
|
|
||||||
Vue.component('mk-notes', notes);
|
Vue.component('mk-notes', notes);
|
||||||
Vue.component('mk-sub-note-content', subNoteContent);
|
Vue.component('mk-sub-note-content', subNoteContent);
|
||||||
Vue.component('mk-window', window);
|
Vue.component('mk-window', window);
|
||||||
|
|||||||
@@ -31,9 +31,6 @@
|
|||||||
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
|
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
|
||||||
<span slot="desc">{{ $t('auto-popout-desc') }}</span>
|
<span slot="desc">{{ $t('auto-popout-desc') }}</span>
|
||||||
</ui-switch>
|
</ui-switch>
|
||||||
<ui-switch v-model="deckNav">{{ $t('deck-nav') }}
|
|
||||||
<span slot="desc">{{ $t('deck-nav-desc') }}</span>
|
|
||||||
</ui-switch>
|
|
||||||
<ui-switch v-model="keepCw">{{ $t('keep-cw') }}
|
<ui-switch v-model="keepCw">{{ $t('keep-cw') }}
|
||||||
<span slot="desc">{{ $t('keep-cw-desc') }}</span>
|
<span slot="desc">{{ $t('keep-cw-desc') }}</span>
|
||||||
</ui-switch>
|
</ui-switch>
|
||||||
@@ -89,9 +86,6 @@
|
|||||||
<ui-radio v-model="navbar" value="left">{{ $t('navbar-position-left') }}</ui-radio>
|
<ui-radio v-model="navbar" value="left">{{ $t('navbar-position-left') }}</ui-radio>
|
||||||
<ui-radio v-model="navbar" value="right">{{ $t('navbar-position-right') }}</ui-radio>
|
<ui-radio v-model="navbar" value="right">{{ $t('navbar-position-right') }}</ui-radio>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
|
||||||
<ui-switch v-model="deckDefault">{{ $t('deck-default') }}</ui-switch>
|
|
||||||
</section>
|
|
||||||
<section>
|
<section>
|
||||||
<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</ui-switch>
|
<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</ui-switch>
|
||||||
<ui-switch v-model="useShadow">{{ $t('use-shadow') }}</ui-switch>
|
<ui-switch v-model="useShadow">{{ $t('use-shadow') }}</ui-switch>
|
||||||
@@ -337,11 +331,6 @@ export default Vue.extend({
|
|||||||
set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
deckNav: {
|
|
||||||
get() { return this.$store.state.settings.deckNav; },
|
|
||||||
set(value) { this.$store.commit('settings/set', { key: 'deckNav', value }); }
|
|
||||||
},
|
|
||||||
|
|
||||||
keepCw: {
|
keepCw: {
|
||||||
get() { return this.$store.state.settings.keepCw; },
|
get() { return this.$store.state.settings.keepCw; },
|
||||||
set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); }
|
set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); }
|
||||||
@@ -367,11 +356,6 @@ export default Vue.extend({
|
|||||||
set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
deckDefault: {
|
|
||||||
get() { return this.$store.state.device.deckDefault; },
|
|
||||||
set(value) { this.$store.commit('device/set', { key: 'deckDefault', value }); }
|
|
||||||
},
|
|
||||||
|
|
||||||
enableSounds: {
|
enableSounds: {
|
||||||
get() { return this.$store.state.device.enableSounds; },
|
get() { return this.$store.state.device.enableSounds; },
|
||||||
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
||||||
@@ -534,8 +518,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
customizeHome() {
|
customizeHome() {
|
||||||
this.$router.push('/i/customize-home');
|
location.href = '/?customize';
|
||||||
this.$emit('done');
|
|
||||||
},
|
},
|
||||||
updateWallpaper() {
|
updateWallpaper() {
|
||||||
this.$chooseDriveFile({
|
this.$chooseDriveFile({
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mk-timeline">
|
|
||||||
<header>
|
|
||||||
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
|
|
||||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
|
|
||||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
|
|
||||||
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
|
|
||||||
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
|
|
||||||
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
|
|
||||||
<div class="buttons">
|
|
||||||
<button :data-active="src == 'mentions'" @click="src = 'mentions'" :title="$t('mentions')"><fa icon="at"/><i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></button>
|
|
||||||
<button :data-active="src == 'messages'" @click="src = 'messages'" :title="$t('messages')"><fa :icon="['far', 'envelope']"/><i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></button>
|
|
||||||
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
|
|
||||||
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
|
|
||||||
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
|
|
||||||
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
|
|
||||||
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
|
|
||||||
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
|
|
||||||
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
|
|
||||||
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
|
|
||||||
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../i18n';
|
|
||||||
import XCore from './timeline.core.vue';
|
|
||||||
import Menu from '../../../common/views/components/menu.vue';
|
|
||||||
import MkSettingsWindow from './settings-window.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n('desktop/views/components/timeline.vue'),
|
|
||||||
components: {
|
|
||||||
XCore
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
src: 'home',
|
|
||||||
list: null,
|
|
||||||
tagTl: null,
|
|
||||||
enableLocalTimeline: false,
|
|
||||||
enableGlobalTimeline: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
src() {
|
|
||||||
this.saveSrc();
|
|
||||||
},
|
|
||||||
|
|
||||||
list(x) {
|
|
||||||
this.saveSrc();
|
|
||||||
if (x != null) this.tagTl = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
tagTl(x) {
|
|
||||||
this.saveSrc();
|
|
||||||
if (x != null) this.list = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.$root.getMeta().then(meta => {
|
|
||||||
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
|
||||||
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.$store.state.device.tl) {
|
|
||||||
this.src = this.$store.state.device.tl.src;
|
|
||||||
if (this.src == 'list') {
|
|
||||||
this.list = this.$store.state.device.tl.arg;
|
|
||||||
} else if (this.src == 'tag') {
|
|
||||||
this.tagTl = this.$store.state.device.tl.arg;
|
|
||||||
}
|
|
||||||
} else if (this.$store.state.i.followingCount == 0) {
|
|
||||||
this.src = 'hybrid';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
(this.$refs.tl as any).$once('loaded', () => {
|
|
||||||
this.$emit('loaded');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
saveSrc() {
|
|
||||||
this.$store.commit('device/setTl', {
|
|
||||||
src: this.src,
|
|
||||||
arg: this.src == 'list' ? this.list : this.tagTl
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
(this.$refs.tl as any).focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
warp(date) {
|
|
||||||
(this.$refs.tl as any).warp(date);
|
|
||||||
},
|
|
||||||
|
|
||||||
async chooseList() {
|
|
||||||
const lists = await this.$root.api('users/lists/list');
|
|
||||||
|
|
||||||
let menu = [{
|
|
||||||
icon: 'plus',
|
|
||||||
text: this.$t('add-list'),
|
|
||||||
action: () => {
|
|
||||||
this.$root.dialog({
|
|
||||||
title: this.$t('list-name'),
|
|
||||||
input: true
|
|
||||||
}).then(async ({ canceled, result: title }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
const list = await this.$root.api('users/lists/create', {
|
|
||||||
title
|
|
||||||
});
|
|
||||||
|
|
||||||
this.list = list;
|
|
||||||
this.src = 'list';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
if (lists.length > 0) {
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = menu.concat(lists.map(list => ({
|
|
||||||
icon: 'list',
|
|
||||||
text: list.title,
|
|
||||||
action: () => {
|
|
||||||
this.list = list;
|
|
||||||
this.src = 'list';
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
|
|
||||||
this.$root.new(Menu, {
|
|
||||||
source: this.$refs.listButton,
|
|
||||||
items: menu
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
chooseTag() {
|
|
||||||
let menu = [{
|
|
||||||
icon: 'plus',
|
|
||||||
text: this.$t('add-tag-timeline'),
|
|
||||||
action: () => {
|
|
||||||
this.$root.new(MkSettingsWindow, {
|
|
||||||
initialPage: 'hashtags'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
if (this.$store.state.settings.tagTimelines.length > 0) {
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = menu.concat(this.$store.state.settings.tagTimelines.map(t => ({
|
|
||||||
icon: 'hashtag',
|
|
||||||
text: t.title,
|
|
||||||
action: () => {
|
|
||||||
this.tagTl = t;
|
|
||||||
this.src = 'tag';
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
|
|
||||||
this.$root.new(Menu, {
|
|
||||||
source: this.$refs.tagButton,
|
|
||||||
items: menu
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mk-timeline
|
|
||||||
background var(--face)
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
overflow hidden
|
|
||||||
|
|
||||||
> header
|
|
||||||
padding 0 8px
|
|
||||||
z-index 10
|
|
||||||
background var(--faceHeader)
|
|
||||||
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
|
|
||||||
|
|
||||||
> .buttons
|
|
||||||
position absolute
|
|
||||||
z-index 2
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
padding-right 8px
|
|
||||||
|
|
||||||
> button
|
|
||||||
padding 0 8px
|
|
||||||
font-size 0.9em
|
|
||||||
line-height 42px
|
|
||||||
color var(--faceTextButton)
|
|
||||||
|
|
||||||
> .badge
|
|
||||||
position absolute
|
|
||||||
top -4px
|
|
||||||
right 4px
|
|
||||||
font-size 10px
|
|
||||||
color var(--notificationIndicator)
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color var(--faceTextButtonHover)
|
|
||||||
|
|
||||||
&[data-active]
|
|
||||||
color var(--primary)
|
|
||||||
cursor default
|
|
||||||
|
|
||||||
&:before
|
|
||||||
content ""
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
height 2px
|
|
||||||
background var(--primary)
|
|
||||||
|
|
||||||
> span
|
|
||||||
display inline-block
|
|
||||||
padding 0 10px
|
|
||||||
line-height 42px
|
|
||||||
font-size 12px
|
|
||||||
user-select none
|
|
||||||
|
|
||||||
&[data-active]
|
|
||||||
color var(--primary)
|
|
||||||
cursor default
|
|
||||||
font-weight bold
|
|
||||||
|
|
||||||
&:before
|
|
||||||
content ""
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
left -8px
|
|
||||||
width calc(100% + 16px)
|
|
||||||
height 2px
|
|
||||||
background var(--primary)
|
|
||||||
|
|
||||||
&:not([data-active])
|
|
||||||
color var(--desktopTimelineSrc)
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color var(--desktopTimelineSrcHover)
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -44,13 +44,6 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<router-link to="/i/customize-home">
|
|
||||||
<i><fa icon="wrench"/></i>
|
|
||||||
<span>{{ $t('customize') }}</span>
|
|
||||||
<i><fa icon="angle-right"/></i>
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<router-link to="/i/settings">
|
<router-link to="/i/settings">
|
||||||
<i><fa icon="cog"/></i>
|
<i><fa icon="cog"/></i>
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
<div class="nav">
|
<div class="nav">
|
||||||
<ul>
|
<ul>
|
||||||
<template v-if="$store.getters.isSignedIn">
|
<template v-if="$store.getters.isSignedIn">
|
||||||
<template v-if="$store.state.device.deckDefault">
|
<template v-if="$store.state.device.deckMode">
|
||||||
<li class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
<li class="deck active" @click="goToTop">
|
||||||
<router-link to="/"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
<router-link to="/"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
|
<li class="home">
|
||||||
<router-link to="/home"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
<a @click="toggleDeckMode(false)"><fa icon="home"/><p>{{ $t('home') }}</p></a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<li class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
|
<li class="home active" @click="goToTop">
|
||||||
<router-link to="/"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
<router-link to="/"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
|
<li class="deck">
|
||||||
<router-link to="/deck"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
<a @click="toggleDeckMode(true)"><fa icon="columns"/><p>{{ $t('deck') }}</p></a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<li class="messaging">
|
<li class="messaging">
|
||||||
@@ -25,6 +25,10 @@
|
|||||||
<template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template>
|
<template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
</template>
|
||||||
|
<li class="featured">
|
||||||
|
<router-link to="/featured"><fa :icon="faNewspaper"/><p>{{ $t('@.featured-notes') }}</p></router-link>
|
||||||
|
</li>
|
||||||
<li class="game">
|
<li class="game">
|
||||||
<a @click="game">
|
<a @click="game">
|
||||||
<fa icon="gamepad"/>
|
<fa icon="gamepad"/>
|
||||||
@@ -32,7 +36,6 @@
|
|||||||
<template v-if="hasGameInvitations"><fa icon="circle"/></template>
|
<template v-if="hasGameInvitations"><fa icon="circle"/></template>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -42,13 +45,15 @@ import Vue from 'vue';
|
|||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import MkMessagingWindow from './messaging-window.vue';
|
import MkMessagingWindow from './messaging-window.vue';
|
||||||
import MkGameWindow from './game-window.vue';
|
import MkGameWindow from './game-window.vue';
|
||||||
|
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/ui.header.nav.vue'),
|
i18n: i18n('desktop/views/components/ui.header.nav.vue'),
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hasGameInvitations: false,
|
hasGameInvitations: false,
|
||||||
connection: null
|
connection: null,
|
||||||
|
faNewspaper
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -70,6 +75,11 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleDeckMode(deck) {
|
||||||
|
this.$store.commit('device/set', { key: 'deckMode', value: deck });
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
|
||||||
onReversiInvited() {
|
onReversiInvited() {
|
||||||
this.hasGameInvitations = true;
|
this.hasGameInvitations = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default Vue.extend({
|
|||||||
> .container
|
> .container
|
||||||
display flex
|
display flex
|
||||||
width 100%
|
width 100%
|
||||||
max-width 1300px
|
max-width 1208px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
|
||||||
> *
|
> *
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav" v-if="$store.getters.isSignedIn">
|
<div class="nav" v-if="$store.getters.isSignedIn">
|
||||||
<template v-if="$store.state.device.deckDefault">
|
<template v-if="$store.state.device.deckMode">
|
||||||
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
||||||
<router-link to="/"><fa icon="columns"/></router-link>
|
<router-link to="/"><fa icon="columns"/></router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import Menu from '../../../../common/views/components/menu.vue';
|
import Menu from '../../../common/views/components/menu.vue';
|
||||||
import { countIf } from '../../../../../../prelude/array';
|
import { countIf } from '../../../../../prelude/array';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('deck'),
|
i18n: i18n('deck'),
|
||||||
@@ -245,10 +245,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.$store.commit('device/set', {
|
this.$router.push('/');
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: null
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
goTop() {
|
goTop() {
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XDirect from './deck.direct.vue';
|
import XDirect from './deck.direct.vue';
|
||||||
|
|
||||||
88
src/client/app/desktop/views/deck/deck.favorites-column.vue
Normal file
88
src/client/app/desktop/views/deck/deck.favorites-column.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<x-column>
|
||||||
|
<span slot="header">
|
||||||
|
<fa :icon="['fa', 'star']"/>{{ $t('favorites') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-notes ref="timeline" :more="existMore ? more : null"/>
|
||||||
|
</div>
|
||||||
|
</x-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import XColumn from './deck.column.vue';
|
||||||
|
import XNotes from './deck.notes.vue';
|
||||||
|
|
||||||
|
const fetchLimit = 10;
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XColumn,
|
||||||
|
XNotes,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
moreFetching: false,
|
||||||
|
existMore: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||||
|
this.$root.api('i/favorites', {
|
||||||
|
limit: fetchLimit + 1,
|
||||||
|
}).then(notes => {
|
||||||
|
if (notes.length == fetchLimit + 1) {
|
||||||
|
notes.pop();
|
||||||
|
this.existMore = true;
|
||||||
|
}
|
||||||
|
res(notes.map(x => x.note));
|
||||||
|
this.fetching = false;
|
||||||
|
this.$emit('loaded');
|
||||||
|
}, rej);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
more() {
|
||||||
|
this.moreFetching = true;
|
||||||
|
|
||||||
|
const promise = this.$root.api('i/favorites', {
|
||||||
|
limit: fetchLimit + 1,
|
||||||
|
untilId: (this.$refs.timeline as any).tail().id,
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.then(notes => {
|
||||||
|
if (notes.length == fetchLimit + 1) {
|
||||||
|
notes.pop();
|
||||||
|
} else {
|
||||||
|
this.existMore = false;
|
||||||
|
}
|
||||||
|
for (const n of notes) {
|
||||||
|
(this.$refs.timeline as any).append(n);
|
||||||
|
}
|
||||||
|
this.moreFetching = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.$refs.timeline.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
59
src/client/app/desktop/views/deck/deck.featured-column.vue
Normal file
59
src/client/app/desktop/views/deck/deck.featured-column.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<x-column>
|
||||||
|
<span slot="header">
|
||||||
|
<fa :icon="faNewspaper"/>{{ $t('@.featured-notes') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-notes ref="timeline" :more="null"/>
|
||||||
|
</div>
|
||||||
|
</x-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import XColumn from './deck.column.vue';
|
||||||
|
import XNotes from './deck.notes.vue';
|
||||||
|
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XColumn,
|
||||||
|
XNotes,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
faNewspaper
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||||
|
this.$root.api('notes/featured', {
|
||||||
|
limit: 15,
|
||||||
|
}).then(notes => {
|
||||||
|
res(notes);
|
||||||
|
this.fetching = false;
|
||||||
|
this.$emit('loaded');
|
||||||
|
}, rej);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.$refs.timeline.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
119
src/client/app/desktop/views/deck/deck.hashtag-column.vue
Normal file
119
src/client/app/desktop/views/deck/deck.hashtag-column.vue
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<x-column>
|
||||||
|
<span slot="header">
|
||||||
|
<fa icon="hashtag"/><span>{{ tag }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
|
||||||
|
<div ref="chart" class="chart"></div>
|
||||||
|
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
|
||||||
|
</div>
|
||||||
|
</x-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import XColumn from './deck.column.vue';
|
||||||
|
import XHashtagTl from './deck.hashtag-tl.vue';
|
||||||
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XColumn,
|
||||||
|
XHashtagTl
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
tag(): string {
|
||||||
|
return this.$route.params.tag;
|
||||||
|
},
|
||||||
|
|
||||||
|
tagTl(): any {
|
||||||
|
return {
|
||||||
|
query: [[this.tag]]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.$root.api('charts/hashtag', {
|
||||||
|
tag: this.tag,
|
||||||
|
span: 'hour',
|
||||||
|
limit: 24
|
||||||
|
}).then(stats => {
|
||||||
|
const local = [];
|
||||||
|
const remote = [];
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
const h = now.getHours();
|
||||||
|
|
||||||
|
for (let i = 0; i < 24; i++) {
|
||||||
|
const x = new Date(y, m, d, h - i);
|
||||||
|
local.push([x, stats.local.count[i]]);
|
||||||
|
remote.push([x, stats.remote.count[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = new ApexCharts(this.$refs.chart, {
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
height: 70,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
clipMarkers: false,
|
||||||
|
padding: {
|
||||||
|
top: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 16,
|
||||||
|
left: 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: 'Local',
|
||||||
|
data: local
|
||||||
|
}, {
|
||||||
|
name: 'Remote',
|
||||||
|
data: remote
|
||||||
|
}],
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chart.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
|
||||||
|
background var(--deckColumnBg)
|
||||||
|
|
||||||
|
> .chart
|
||||||
|
margin-bottom 16px
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
> .tl
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XMentions from './deck.mentions.vue';
|
import XMentions from './deck.mentions.vue';
|
||||||
|
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XNotes from './deck.notes.vue';
|
import XNotes from './deck.notes.vue';
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n(),
|
i18n: i18n(),
|
||||||
@@ -31,13 +31,6 @@ export default Vue.extend({
|
|||||||
XNote
|
XNote
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
|
||||||
noteId: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
note: null,
|
note: null,
|
||||||
@@ -45,12 +38,26 @@ export default Vue.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$root.api('notes/show', { noteId: this.noteId }).then(note => {
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
this.$root.api('notes/show', {
|
||||||
|
noteId: this.$route.params.note
|
||||||
|
}).then(note => {
|
||||||
this.note = note;
|
this.note = note;
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import shouldMuteNote from '../../../../common/scripts/should-mute-note';
|
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||||
|
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
|
|
||||||
const displayLimit = 20;
|
const displayLimit = 20;
|
||||||
|
|
||||||
@@ -96,8 +96,8 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import getNoteSummary from '../../../../../../misc/get-note-summary';
|
import getNoteSummary from '../../../../../misc/get-note-summary';
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XNotifications from './deck.notifications.vue';
|
import XNotifications from './deck.notifications.vue';
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XNotification from './deck.notification.vue';
|
import XNotification from './deck.notification.vue';
|
||||||
|
|
||||||
const displayLimit = 20;
|
const displayLimit = 20;
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XTl from './deck.tl.vue';
|
import XTl from './deck.tl.vue';
|
||||||
import XListTl from './deck.list-tl.vue';
|
import XListTl from './deck.list-tl.vue';
|
||||||
@@ -93,13 +93,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import parseAcct from '../../../../../../misc/acct/parse';
|
import parseAcct from '../../../../../misc/acct/parse';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XNotes from './deck.notes.vue';
|
import XNotes from './deck.notes.vue';
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
import XUserMenu from '../../../common/views/components/user-menu.vue';
|
||||||
import { concat } from '../../../../../../prelude/array';
|
import { concat } from '../../../../../prelude/array';
|
||||||
import ApexCharts from 'apexcharts';
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
const fetchLimit = 10;
|
const fetchLimit = 10;
|
||||||
@@ -112,13 +112,6 @@ export default Vue.extend({
|
|||||||
XNote
|
XNote
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
|
||||||
acct: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: null,
|
user: null,
|
||||||
@@ -144,8 +137,18 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$root.api('users/show', parseAcct(this.acct)).then(user => {
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
|
|
||||||
@@ -256,7 +259,6 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
|
||||||
initTl() {
|
initTl() {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
this.$root.api('users/notes', {
|
this.$root.api('users/notes', {
|
||||||
@@ -9,11 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/>
|
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="temporaryColumn">
|
<router-view></router-view>
|
||||||
<x-user-column v-if="temporaryColumn.type == 'user'" :acct="temporaryColumn.acct" :key="temporaryColumn.acct"/>
|
|
||||||
<x-note-column v-else-if="temporaryColumn.type == 'note'" :note-id="temporaryColumn.noteId" :key="temporaryColumn.noteId"/>
|
|
||||||
<x-hashtag-column v-else-if="temporaryColumn.type == 'tag'" :tag="temporaryColumn.tag" :key="temporaryColumn.tag"/>
|
|
||||||
</template>
|
|
||||||
<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
|
<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
|
||||||
</div>
|
</div>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
@@ -21,20 +17,17 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumnCore from './deck.column-core.vue';
|
import XColumnCore from './deck.column-core.vue';
|
||||||
import Menu from '../../../../common/views/components/menu.vue';
|
import Menu from '../../../common/views/components/menu.vue';
|
||||||
import MkUserListsWindow from '../../components/user-lists-window.vue';
|
import MkUserListsWindow from '../components/user-lists-window.vue';
|
||||||
|
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('deck'),
|
i18n: i18n('deck'),
|
||||||
components: {
|
components: {
|
||||||
XColumnCore,
|
XColumnCore
|
||||||
XUserColumn: () => import('./deck.user-column.vue').then(m => m.default),
|
|
||||||
XNoteColumn: () => import('./deck.note-column.vue').then(m => m.default),
|
|
||||||
XHashtagColumn: () => import('./deck.hashtag-column.vue').then(m => m.default)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -55,10 +48,6 @@ export default Vue.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
temporaryColumn(): any {
|
|
||||||
return this.$store.state.device.deckTemporaryColumn;
|
|
||||||
},
|
|
||||||
|
|
||||||
keymap(): any {
|
keymap(): any {
|
||||||
return {
|
return {
|
||||||
't': this.focus
|
't': this.focus
|
||||||
@@ -66,7 +55,7 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {/*
|
||||||
temporaryColumn() {
|
temporaryColumn() {
|
||||||
if (this.temporaryColumn != null) {
|
if (this.temporaryColumn != null) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -76,7 +65,7 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
},
|
},
|
||||||
|
|
||||||
provide() {
|
provide() {
|
||||||
@@ -86,8 +75,6 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$store.commit('navHook', this.onNav);
|
|
||||||
|
|
||||||
if (this.$store.state.settings.deck == null) {
|
if (this.$store.state.settings.deck == null) {
|
||||||
const deck = {
|
const deck = {
|
||||||
columns: [/*{
|
columns: [/*{
|
||||||
@@ -133,8 +120,6 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$store.commit('navHook', null);
|
|
||||||
|
|
||||||
document.documentElement.style.overflow = 'auto';
|
document.documentElement.style.overflow = 'auto';
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -143,39 +128,6 @@ export default Vue.extend({
|
|||||||
return this.$refs[id][0];
|
return this.$refs[id][0];
|
||||||
},
|
},
|
||||||
|
|
||||||
onNav(to) {
|
|
||||||
if (!this.$store.state.settings.deckNav) return false;
|
|
||||||
|
|
||||||
if (to.name == 'user') {
|
|
||||||
this.$store.commit('device/set', {
|
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: {
|
|
||||||
type: 'user',
|
|
||||||
acct: to.params.user
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else if (to.name == 'note') {
|
|
||||||
this.$store.commit('device/set', {
|
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: {
|
|
||||||
type: 'note',
|
|
||||||
noteId: to.params.note
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else if (to.name == 'tag') {
|
|
||||||
this.$store.commit('device/set', {
|
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: {
|
|
||||||
type: 'tag',
|
|
||||||
tag: to.params.tag
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
add() {
|
add() {
|
||||||
this.$root.new(Menu, {
|
this.$root.new(Menu, {
|
||||||
source: this.$refs.add,
|
source: this.$refs.add,
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import * as XDraggable from 'vuedraggable';
|
import * as XDraggable from 'vuedraggable';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<div class="ecsvsegy" v-if="!fetching">
|
||||||
<main v-if="!fetching">
|
|
||||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||||
<template v-for="favorite in favorites">
|
<template v-for="favorite in favorites">
|
||||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||||
@@ -9,8 +8,7 @@
|
|||||||
<div class="more" v-if="existMore">
|
<div class="more" v-if="existMore">
|
||||||
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</mk-ui>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -72,10 +70,8 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
main
|
.ecsvsegy
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 16px
|
|
||||||
max-width 700px
|
|
||||||
|
|
||||||
> * > .post
|
> * > .post
|
||||||
margin-bottom 16px
|
margin-bottom 16px
|
||||||
54
src/client/app/desktop/views/home/featured.vue
Normal file
54
src/client/app/desktop/views/home/featured.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="glowckho" v-if="!fetching">
|
||||||
|
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||||
|
<template v-for="note in notes">
|
||||||
|
<mk-note-detail class="post" :note="note" :key="note.id"/>
|
||||||
|
</template>
|
||||||
|
</sequential-entrance>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Progress from '../../../common/scripts/loading';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
notes: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
Progress.start();
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
this.$root.api('notes/featured', {
|
||||||
|
limit: 10
|
||||||
|
}).then(notes => {
|
||||||
|
this.notes = notes;
|
||||||
|
this.fetching = false;
|
||||||
|
|
||||||
|
Progress.done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.glowckho
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
> * > .post
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> .more
|
||||||
|
margin 32px 16px 16px 16px
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
</style>
|
||||||
404
src/client/app/desktop/views/home/home.vue
Normal file
404
src/client/app/desktop/views/home/home.vue
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="customize ? 'mk-dummy' : 'mk-ui'" v-hotkey.global="keymap" v-if="$store.getters.isSignedIn || $route.name != 'index'">
|
||||||
|
<div class="wqsofvpm" :data-customize="customize">
|
||||||
|
<div class="customize" v-if="customize">
|
||||||
|
<a @click="done()"><fa icon="check"/>{{ $t('done') }}</a>
|
||||||
|
<div>
|
||||||
|
<div class="adder">
|
||||||
|
<p>{{ $t('add-widget') }}</p>
|
||||||
|
<select v-model="widgetAdderSelected">
|
||||||
|
<option value="profile">{{ $t('@.widgets.profile') }}</option>
|
||||||
|
<option value="analog-clock">{{ $t('@.widgets.analog-clock') }}</option>
|
||||||
|
<option value="calendar">{{ $t('@.widgets.calendar') }}</option>
|
||||||
|
<option value="timemachine">{{ $t('@.widgets.timemachine') }}</option>
|
||||||
|
<option value="activity">{{ $t('@.widgets.activity') }}</option>
|
||||||
|
<option value="rss">{{ $t('@.widgets.rss') }}</option>
|
||||||
|
<option value="trends">{{ $t('@.widgets.trends') }}</option>
|
||||||
|
<option value="photo-stream">{{ $t('@.widgets.photo-stream') }}</option>
|
||||||
|
<option value="slideshow">{{ $t('@.widgets.slideshow') }}</option>
|
||||||
|
<option value="version">{{ $t('@.widgets.version') }}</option>
|
||||||
|
<option value="broadcast">{{ $t('@.widgets.broadcast') }}</option>
|
||||||
|
<option value="notifications">{{ $t('@.widgets.notifications') }}</option>
|
||||||
|
<option value="users">{{ $t('@.widgets.users') }}</option>
|
||||||
|
<option value="polls">{{ $t('@.widgets.polls') }}</option>
|
||||||
|
<option value="post-form">{{ $t('@.widgets.post-form') }}</option>
|
||||||
|
<option value="messaging">{{ $t('@.widgets.messaging') }}</option>
|
||||||
|
<option value="memo">{{ $t('@.widgets.memo') }}</option>
|
||||||
|
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
||||||
|
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
||||||
|
<option value="server">{{ $t('@.widgets.server') }}</option>
|
||||||
|
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
||||||
|
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
||||||
|
</select>
|
||||||
|
<button @click="addWidget">{{ $t('add') }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="trash">
|
||||||
|
<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
|
||||||
|
<p>{{ $t('@.trash') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
|
||||||
|
<template v-if="customize">
|
||||||
|
<x-draggable v-for="place in ['left', 'right']"
|
||||||
|
:list="widgets[place]"
|
||||||
|
:class="place"
|
||||||
|
:data-place="place"
|
||||||
|
:options="{ group: 'x', animation: 150 }"
|
||||||
|
@sort="onWidgetSort"
|
||||||
|
:key="place"
|
||||||
|
>
|
||||||
|
<div v-for="widget in widgets[place]" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
|
||||||
|
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="desktop"/>
|
||||||
|
</div>
|
||||||
|
</x-draggable>
|
||||||
|
<div class="main">
|
||||||
|
<a @click="hint">{{ $t('@.customization-tips.title') }}</a>
|
||||||
|
<div>
|
||||||
|
<x-timeline/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div v-for="place in ['left', 'right']" :class="place">
|
||||||
|
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="desktop"/>
|
||||||
|
</div>
|
||||||
|
<div class="main">
|
||||||
|
<router-view ref="content"></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
<x-welcome v-else/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import * as XDraggable from 'vuedraggable';
|
||||||
|
import * as uuid from 'uuid';
|
||||||
|
import XWelcome from '../pages/welcome.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n('desktop/views/components/home.vue'),
|
||||||
|
components: {
|
||||||
|
XDraggable,
|
||||||
|
XWelcome
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
customize: window.location.search == '?customize',
|
||||||
|
connection: null,
|
||||||
|
widgetAdderSelected: null,
|
||||||
|
trash: [],
|
||||||
|
view: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
home(): any[] {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
return this.$store.state.settings.home || [];
|
||||||
|
} else {
|
||||||
|
return [{
|
||||||
|
name: 'instance',
|
||||||
|
place: 'right'
|
||||||
|
}, {
|
||||||
|
name: 'broadcast',
|
||||||
|
place: 'right',
|
||||||
|
data: {}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
left(): any[] {
|
||||||
|
return this.home.filter(w => w.place == 'left');
|
||||||
|
},
|
||||||
|
right(): any[] {
|
||||||
|
return this.home.filter(w => w.place == 'right');
|
||||||
|
},
|
||||||
|
widgets(): any {
|
||||||
|
return {
|
||||||
|
left: this.left,
|
||||||
|
right: this.right
|
||||||
|
};
|
||||||
|
},
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
't': this.focus
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
const defaultDesktopHomeWidgets = {
|
||||||
|
left: [
|
||||||
|
'profile',
|
||||||
|
'calendar',
|
||||||
|
'activity',
|
||||||
|
'rss',
|
||||||
|
'hashtags',
|
||||||
|
'photo-stream',
|
||||||
|
'version'
|
||||||
|
],
|
||||||
|
right: [
|
||||||
|
'customize',
|
||||||
|
'broadcast',
|
||||||
|
'notifications',
|
||||||
|
'users',
|
||||||
|
'polls',
|
||||||
|
'server',
|
||||||
|
'nav',
|
||||||
|
'tips'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
//#region Construct home data
|
||||||
|
const _defaultDesktopHomeWidgets = [];
|
||||||
|
|
||||||
|
for (const widget of defaultDesktopHomeWidgets.left) {
|
||||||
|
_defaultDesktopHomeWidgets.push({
|
||||||
|
name: widget,
|
||||||
|
id: uuid(),
|
||||||
|
place: 'left',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const widget of defaultDesktopHomeWidgets.right) {
|
||||||
|
_defaultDesktopHomeWidgets.push({
|
||||||
|
name: widget,
|
||||||
|
id: uuid(),
|
||||||
|
place: 'right',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
if (this.$store.state.settings.home == null) {
|
||||||
|
this.$root.api('i/update_home', {
|
||||||
|
home: _defaultDesktopHomeWidgets
|
||||||
|
}).then(() => {
|
||||||
|
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.connection = this.$root.stream.useSharedConnection('main');
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.connection.dispose();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
hint() {
|
||||||
|
this.$root.dialog({
|
||||||
|
title: this.$t('@.customization-tips.title'),
|
||||||
|
text: this.$t('@.customization-tips.paragraph')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onTlLoaded() {
|
||||||
|
this.$emit('loaded');
|
||||||
|
},
|
||||||
|
|
||||||
|
onWidgetContextmenu(widgetId) {
|
||||||
|
const w = (this.$refs[widgetId] as any)[0];
|
||||||
|
if (w.func) w.func();
|
||||||
|
},
|
||||||
|
|
||||||
|
onWidgetSort() {
|
||||||
|
this.saveHome();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTrash(evt) {
|
||||||
|
this.saveHome();
|
||||||
|
},
|
||||||
|
|
||||||
|
addWidget() {
|
||||||
|
this.$store.dispatch('settings/addHomeWidget', {
|
||||||
|
name: this.widgetAdderSelected,
|
||||||
|
id: uuid(),
|
||||||
|
place: 'left',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
saveHome() {
|
||||||
|
const left = this.widgets.left;
|
||||||
|
const right = this.widgets.right;
|
||||||
|
this.$store.commit('settings/setHome', left.concat(right));
|
||||||
|
for (const w of left) w.place = 'left';
|
||||||
|
for (const w of right) w.place = 'right';
|
||||||
|
this.$root.api('i/update_home', {
|
||||||
|
home: this.home
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
|
location.href = '/';
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
(this.$refs.content as any).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.wqsofvpm
|
||||||
|
display block
|
||||||
|
|
||||||
|
&[data-customize]
|
||||||
|
padding-top 48px
|
||||||
|
background-image url('/assets/desktop/grid.svg')
|
||||||
|
|
||||||
|
> .main > .main
|
||||||
|
> a
|
||||||
|
display block
|
||||||
|
margin-bottom 8px
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
> div
|
||||||
|
cursor not-allowed !important
|
||||||
|
|
||||||
|
> *
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&:not([data-customize])
|
||||||
|
> .main > *:not(.main):empty
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .customize
|
||||||
|
position fixed
|
||||||
|
z-index 1000
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
height 48px
|
||||||
|
color var(--text)
|
||||||
|
background var(--desktopHeaderBg)
|
||||||
|
box-shadow 0 1px 1px rgba(#000, 0.075)
|
||||||
|
|
||||||
|
> a
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
z-index 1001
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
padding 0 16px
|
||||||
|
line-height 48px
|
||||||
|
text-decoration none
|
||||||
|
color var(--primaryForeground)
|
||||||
|
background var(--primary)
|
||||||
|
transition background 0.1s ease
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--primaryLighten10)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background var(--primaryDarken10)
|
||||||
|
transition background 0s ease
|
||||||
|
|
||||||
|
> [data-icon]
|
||||||
|
margin-right 8px
|
||||||
|
|
||||||
|
> div
|
||||||
|
display flex
|
||||||
|
margin 0 auto
|
||||||
|
max-width 1220px - 32px
|
||||||
|
|
||||||
|
> div
|
||||||
|
width 50%
|
||||||
|
|
||||||
|
&.adder
|
||||||
|
> p
|
||||||
|
display inline
|
||||||
|
line-height 48px
|
||||||
|
|
||||||
|
&.trash
|
||||||
|
border-left solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
> div
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
> p
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
line-height 48px
|
||||||
|
margin 0
|
||||||
|
text-align center
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
margin 0 auto
|
||||||
|
max-width 1240px
|
||||||
|
|
||||||
|
> *
|
||||||
|
.customize-container
|
||||||
|
cursor move
|
||||||
|
border-radius 6px
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
box-shadow 0 0 8px rgba(64, 120, 200, 0.3)
|
||||||
|
|
||||||
|
> *
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
padding 16px
|
||||||
|
width calc(100% - 280px * 2)
|
||||||
|
order 2
|
||||||
|
|
||||||
|
&.side
|
||||||
|
> .main
|
||||||
|
width calc(100% - 280px)
|
||||||
|
max-width 680px
|
||||||
|
|
||||||
|
> *:not(.main)
|
||||||
|
width 280px
|
||||||
|
padding 16px 0 16px 0
|
||||||
|
|
||||||
|
> *:not(:last-child)
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> .left
|
||||||
|
padding-left 16px
|
||||||
|
order 1
|
||||||
|
|
||||||
|
> .right
|
||||||
|
padding-right 16px
|
||||||
|
order 3
|
||||||
|
|
||||||
|
&.side
|
||||||
|
@media (max-width 1000px)
|
||||||
|
> *:not(.main)
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
width 100%
|
||||||
|
max-width 700px
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
&:not(.side)
|
||||||
|
@media (max-width 1200px)
|
||||||
|
> *:not(.main)
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
width 100%
|
||||||
|
max-width 700px
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<div v-if="!fetching" class="kcthdwmv">
|
||||||
<main v-if="!fetching">
|
|
||||||
<mk-note-detail :note="note"/>
|
<mk-note-detail :note="note"/>
|
||||||
<footer>
|
<footer>
|
||||||
<router-link v-if="note.next" :to="note.next"><fa icon="angle-left"/> {{ $t('next') }}</router-link>
|
<router-link v-if="note.next" :to="note.next"><fa icon="angle-left"/> {{ $t('next') }}</router-link>
|
||||||
<router-link v-if="note.prev" :to="note.prev">{{ $t('prev') }} <fa icon="angle-right"/></router-link>
|
<router-link v-if="note.prev" :to="note.prev">{{ $t('prev') }} <fa icon="angle-right"/></router-link>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</div>
|
||||||
</mk-ui>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -48,8 +46,7 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
main
|
.kcthdwmv
|
||||||
padding 16px
|
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
> footer
|
> footer
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<div>
|
||||||
<header :class="$style.header">
|
|
||||||
<h1>#{{ $route.params.tag }}</h1>
|
|
||||||
</header>
|
|
||||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||||
</mk-ui>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -96,17 +93,11 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" module>
|
<style lang="stylus" module>
|
||||||
.header
|
|
||||||
width 100%
|
|
||||||
max-width 600px
|
|
||||||
margin 0 auto
|
|
||||||
color #555
|
|
||||||
|
|
||||||
.notes
|
.notes
|
||||||
width 600px
|
background var(--face)
|
||||||
margin 0 auto
|
box-shadow var(--shadow)
|
||||||
border solid 1px rgba(#000, 0.075)
|
border-radius var(--round)
|
||||||
border-radius 6px
|
overflow hidden
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.empty
|
.empty
|
||||||
273
src/client/app/desktop/views/home/timeline.vue
Normal file
273
src/client/app/desktop/views/home/timeline.vue
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mk-timeline">
|
||||||
|
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
|
||||||
|
<div class="main">
|
||||||
|
<header>
|
||||||
|
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
|
||||||
|
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
|
||||||
|
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
|
||||||
|
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
|
||||||
|
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
|
||||||
|
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
|
||||||
|
<div class="buttons">
|
||||||
|
<button :data-active="src == 'mentions'" @click="src = 'mentions'" :title="$t('mentions')"><fa icon="at"/><i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></button>
|
||||||
|
<button :data-active="src == 'messages'" @click="src = 'messages'" :title="$t('messages')"><fa :icon="['far', 'envelope']"/><i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></button>
|
||||||
|
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
|
||||||
|
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
|
||||||
|
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
|
||||||
|
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
|
||||||
|
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
|
||||||
|
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
|
||||||
|
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
|
||||||
|
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
|
||||||
|
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import XCore from './timeline.core.vue';
|
||||||
|
import Menu from '../../../common/views/components/menu.vue';
|
||||||
|
import MkSettingsWindow from '../components/settings-window.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n('desktop/views/components/timeline.vue'),
|
||||||
|
components: {
|
||||||
|
XCore
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
src: 'home',
|
||||||
|
list: null,
|
||||||
|
tagTl: null,
|
||||||
|
enableLocalTimeline: false,
|
||||||
|
enableGlobalTimeline: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
src() {
|
||||||
|
this.saveSrc();
|
||||||
|
},
|
||||||
|
|
||||||
|
list(x) {
|
||||||
|
this.saveSrc();
|
||||||
|
if (x != null) this.tagTl = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
tagTl(x) {
|
||||||
|
this.saveSrc();
|
||||||
|
if (x != null) this.list = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$root.getMeta().then((meta: Record<string, any>) => {
|
||||||
|
if (!(
|
||||||
|
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
|
||||||
|
) && this.src === 'global') this.src = 'local';
|
||||||
|
if (!(
|
||||||
|
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
|
||||||
|
) && ['local', 'hybrid'].includes(this.src)) this.src = 'home';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.$store.state.device.tl) {
|
||||||
|
this.src = this.$store.state.device.tl.src;
|
||||||
|
if (this.src == 'list') {
|
||||||
|
this.list = this.$store.state.device.tl.arg;
|
||||||
|
} else if (this.src == 'tag') {
|
||||||
|
this.tagTl = this.$store.state.device.tl.arg;
|
||||||
|
}
|
||||||
|
} else if (this.$store.state.i.followingCount == 0) {
|
||||||
|
this.src = 'hybrid';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
(this.$refs.tl as any).$once('loaded', () => {
|
||||||
|
this.$emit('loaded');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
saveSrc() {
|
||||||
|
this.$store.commit('device/setTl', {
|
||||||
|
src: this.src,
|
||||||
|
arg: this.src == 'list' ? this.list : this.tagTl
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
(this.$refs.tl as any).focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
(this.$refs.tl as any).warp(date);
|
||||||
|
},
|
||||||
|
|
||||||
|
async chooseList() {
|
||||||
|
const lists = await this.$root.api('users/lists/list');
|
||||||
|
|
||||||
|
let menu = [{
|
||||||
|
icon: 'plus',
|
||||||
|
text: this.$t('add-list'),
|
||||||
|
action: () => {
|
||||||
|
this.$root.dialog({
|
||||||
|
title: this.$t('list-name'),
|
||||||
|
input: true
|
||||||
|
}).then(async ({ canceled, result: title }) => {
|
||||||
|
if (canceled) return;
|
||||||
|
const list = await this.$root.api('users/lists/create', {
|
||||||
|
title
|
||||||
|
});
|
||||||
|
|
||||||
|
this.list = list;
|
||||||
|
this.src = 'list';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (lists.length > 0) {
|
||||||
|
menu.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu = menu.concat(lists.map(list => ({
|
||||||
|
icon: 'list',
|
||||||
|
text: list.title,
|
||||||
|
action: () => {
|
||||||
|
this.list = list;
|
||||||
|
this.src = 'list';
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
this.$root.new(Menu, {
|
||||||
|
source: this.$refs.listButton,
|
||||||
|
items: menu
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
chooseTag() {
|
||||||
|
let menu = [{
|
||||||
|
icon: 'plus',
|
||||||
|
text: this.$t('add-tag-timeline'),
|
||||||
|
action: () => {
|
||||||
|
this.$root.new(MkSettingsWindow, {
|
||||||
|
initialPage: 'hashtags'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (this.$store.state.settings.tagTimelines.length > 0) {
|
||||||
|
menu.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu = menu.concat(this.$store.state.settings.tagTimelines.map(t => ({
|
||||||
|
icon: 'hashtag',
|
||||||
|
text: t.title,
|
||||||
|
action: () => {
|
||||||
|
this.tagTl = t;
|
||||||
|
this.src = 'tag';
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
this.$root.new(Menu, {
|
||||||
|
source: this.$refs.tagButton,
|
||||||
|
items: menu
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.mk-timeline
|
||||||
|
> .form
|
||||||
|
margin-bottom 16px
|
||||||
|
box-shadow var(--shadow)
|
||||||
|
border-radius var(--round)
|
||||||
|
|
||||||
|
> .main
|
||||||
|
background var(--face)
|
||||||
|
box-shadow var(--shadow)
|
||||||
|
border-radius var(--round)
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
|
> header
|
||||||
|
padding 0 8px
|
||||||
|
z-index 10
|
||||||
|
background var(--faceHeader)
|
||||||
|
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
|
||||||
|
|
||||||
|
> .buttons
|
||||||
|
position absolute
|
||||||
|
z-index 2
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
padding-right 8px
|
||||||
|
|
||||||
|
> button
|
||||||
|
padding 0 8px
|
||||||
|
font-size 0.9em
|
||||||
|
line-height 42px
|
||||||
|
color var(--faceTextButton)
|
||||||
|
|
||||||
|
> .badge
|
||||||
|
position absolute
|
||||||
|
top -4px
|
||||||
|
right 4px
|
||||||
|
font-size 10px
|
||||||
|
color var(--notificationIndicator)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color var(--faceTextButtonHover)
|
||||||
|
|
||||||
|
&[data-active]
|
||||||
|
color var(--primary)
|
||||||
|
cursor default
|
||||||
|
|
||||||
|
&:before
|
||||||
|
content ""
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
height 2px
|
||||||
|
background var(--primary)
|
||||||
|
|
||||||
|
> span
|
||||||
|
display inline-block
|
||||||
|
padding 0 10px
|
||||||
|
line-height 42px
|
||||||
|
font-size 12px
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
&[data-active]
|
||||||
|
color var(--primary)
|
||||||
|
cursor default
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
&:before
|
||||||
|
content ""
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left -8px
|
||||||
|
width calc(100% + 16px)
|
||||||
|
height 2px
|
||||||
|
background var(--primary)
|
||||||
|
|
||||||
|
&:not([data-active])
|
||||||
|
color var(--desktopTimelineSrc)
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color var(--desktopTimelineSrcHover)
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="user" v-for="friend in users">
|
<div class="user" v-for="friend in users">
|
||||||
<mk-avatar class="avatar" :user="friend"/>
|
<mk-avatar class="avatar" :user="friend"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link>
|
<router-link class="name" :to="friend | userPage" v-user-preview="friend.id"><mk-user-name :user="friend"/></router-link>
|
||||||
<p class="username">@{{ friend | acct }}</p>
|
<p class="username">@{{ friend | acct }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -9,12 +9,19 @@
|
|||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||||
<span v-if="user.isBot" :title="$t('title')"><fa icon="robot"/></span>
|
<span v-if="user.isBot" :title="$t('is-bot')"><fa icon="robot"/></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
<div class="actions" v-if="$store.getters.isSignedIn">
|
||||||
|
<template v-if="$store.state.i.id != user.id">
|
||||||
|
<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
|
||||||
|
<mk-follow-button :user="user" :inline="true" class="follow"/>
|
||||||
|
</template>
|
||||||
|
<ui-button @click="menu" ref="menu" :inline="true"><fa icon="ellipsis-h"/></ui-button>
|
||||||
|
</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,6 +52,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import * as age from 's-age';
|
import * as age from 's-age';
|
||||||
|
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/pages/user/user.header.vue'),
|
i18n: i18n('desktop/views/pages/user/user.header.vue'),
|
||||||
@@ -99,6 +107,13 @@ export default Vue.extend({
|
|||||||
this.$updateBanner().then(i => {
|
this.$updateBanner().then(i => {
|
||||||
this.user.bannerUrl = i.bannerUrl;
|
this.user.bannerUrl = i.bannerUrl;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
menu() {
|
||||||
|
this.$root.new(XUserMenu, {
|
||||||
|
source: this.$refs.menu.$el,
|
||||||
|
user: this.user
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -187,6 +202,17 @@ export default Vue.extend({
|
|||||||
padding 16px 16px 16px 154px
|
padding 16px 16px 16px 154px
|
||||||
color var(--text)
|
color var(--text)
|
||||||
|
|
||||||
|
> .actions
|
||||||
|
text-align right
|
||||||
|
padding-bottom 16px
|
||||||
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
> *
|
||||||
|
margin-left 8px
|
||||||
|
|
||||||
|
> .follow
|
||||||
|
width 180px
|
||||||
|
|
||||||
> .fields
|
> .fields
|
||||||
margin-top 16px
|
margin-top 16px
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export default Vue.extend({
|
|||||||
> .img
|
> .img
|
||||||
flex 1 1 33%
|
flex 1 1 33%
|
||||||
width 33%
|
width 33%
|
||||||
height 80px
|
height 120px
|
||||||
background-position center center
|
background-position center center
|
||||||
background-size cover
|
background-size cover
|
||||||
background-clip content-box
|
background-clip content-box
|
||||||
109
src/client/app/desktop/views/home/user/user.vue
Normal file
109
src/client/app/desktop/views/home/user/user.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
|
||||||
|
<div class="is-suspended" v-if="user.isSuspended"><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</div>
|
||||||
|
<div class="is-remote" v-if="user.host != null"><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></div>
|
||||||
|
<div class="main">
|
||||||
|
<x-header :user="user"/>
|
||||||
|
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
||||||
|
<x-integrations :user="user"/>
|
||||||
|
<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>-->
|
||||||
|
<div class="activity">
|
||||||
|
<mk-widget-container :show-header="true" :naked="false">
|
||||||
|
<template slot="header"><fa icon="chart-bar"/>{{ $t('activity') }}</template>
|
||||||
|
<x-activity :user="user" :limit="35" style="padding: 16px;"/>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
<x-photos :user="user"/>
|
||||||
|
<x-friends :user="user"/>
|
||||||
|
<x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
|
||||||
|
<x-timeline class="timeline" ref="tl" :user="user"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../../i18n';
|
||||||
|
import parseAcct from '../../../../../../misc/acct/parse';
|
||||||
|
import Progress from '../../../../common/scripts/loading';
|
||||||
|
import XHeader from './user.header.vue';
|
||||||
|
import XTimeline from './user.timeline.vue';
|
||||||
|
import XPhotos from './user.photos.vue';
|
||||||
|
import XFollowersYouKnow from './user.followers-you-know.vue';
|
||||||
|
import XFriends from './user.friends.vue';
|
||||||
|
import XIntegrations from './user.integrations.vue';
|
||||||
|
import XActivity from '../../../../common/views/components/activity.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
components: {
|
||||||
|
XHeader,
|
||||||
|
XTimeline,
|
||||||
|
XPhotos,
|
||||||
|
XFollowersYouKnow,
|
||||||
|
XFriends,
|
||||||
|
XIntegrations,
|
||||||
|
XActivity
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
user: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
Progress.start();
|
||||||
|
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
||||||
|
this.user = user;
|
||||||
|
this.fetching = false;
|
||||||
|
Progress.done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
(this.$refs.tl as any).warp(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.xygkxeaeontfaokvqmiblezmhvhostak
|
||||||
|
width 100%
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
> .is-suspended
|
||||||
|
> .is-remote
|
||||||
|
margin-bottom 16px
|
||||||
|
padding 14px 16px
|
||||||
|
font-size 14px
|
||||||
|
box-shadow var(--shadow)
|
||||||
|
border-radius var(--round)
|
||||||
|
|
||||||
|
&.is-suspended
|
||||||
|
color var(--suspendedInfoFg)
|
||||||
|
background var(--suspendedInfoBg)
|
||||||
|
|
||||||
|
&.is-remote
|
||||||
|
color var(--remoteInfoFg)
|
||||||
|
background var(--remoteInfoBg)
|
||||||
|
|
||||||
|
> a
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
> .main
|
||||||
|
> *
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> .timeline
|
||||||
|
box-shadow var(--shadow)
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<x-column>
|
|
||||||
<span slot="header">
|
|
||||||
<fa icon="hashtag"/><span>{{ tag }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
|
|
||||||
<div ref="chart" class="chart"></div>
|
|
||||||
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
|
|
||||||
</div>
|
|
||||||
</x-column>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import XColumn from './deck.column.vue';
|
|
||||||
import XHashtagTl from './deck.hashtag-tl.vue';
|
|
||||||
import ApexCharts from 'apexcharts';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
XColumn,
|
|
||||||
XHashtagTl
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
tag: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
tagTl(): any {
|
|
||||||
return {
|
|
||||||
query: [[this.tag]]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.$root.api('charts/hashtag', {
|
|
||||||
tag: this.tag,
|
|
||||||
span: 'hour',
|
|
||||||
limit: 24
|
|
||||||
}).then(stats => {
|
|
||||||
const local = [];
|
|
||||||
const remote = [];
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const y = now.getFullYear();
|
|
||||||
const m = now.getMonth();
|
|
||||||
const d = now.getDate();
|
|
||||||
const h = now.getHours();
|
|
||||||
|
|
||||||
for (let i = 0; i < 24; i++) {
|
|
||||||
const x = new Date(y, m, d, h - i);
|
|
||||||
local.push([x, stats.local.count[i]]);
|
|
||||||
remote.push([x, stats.remote.count[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chart = new ApexCharts(this.$refs.chart, {
|
|
||||||
chart: {
|
|
||||||
type: 'area',
|
|
||||||
height: 70,
|
|
||||||
sparkline: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
clipMarkers: false,
|
|
||||||
padding: {
|
|
||||||
top: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
left: 16
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
curve: 'straight',
|
|
||||||
width: 2
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: 'Local',
|
|
||||||
data: local
|
|
||||||
}, {
|
|
||||||
name: 'Remote',
|
|
||||||
data: remote
|
|
||||||
}],
|
|
||||||
xaxis: {
|
|
||||||
type: 'datetime',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chart.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
|
|
||||||
background var(--deckColumnBg)
|
|
||||||
|
|
||||||
> .chart
|
|
||||||
margin-bottom 16px
|
|
||||||
background var(--face)
|
|
||||||
|
|
||||||
> .tl
|
|
||||||
background var(--face)
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-home customize/>
|
|
||||||
</template>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-ui>
|
|
||||||
<mk-home :mode="mode" @loaded="loaded" ref="home" v-hotkey.global="keymap"/>
|
|
||||||
</mk-ui>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Progress from '../../../common/scripts/loading';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
props: {
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'timeline'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
keymap(): any {
|
|
||||||
return {
|
|
||||||
't': this.focus
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.title = this.$root.instanceName;
|
|
||||||
|
|
||||||
Progress.start();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loaded() {
|
|
||||||
Progress.done();
|
|
||||||
},
|
|
||||||
focus() {
|
|
||||||
this.$refs.home.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<component :is="page"></component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Home from './home.vue';
|
|
||||||
import Welcome from './welcome.vue';
|
|
||||||
import Deck from './deck/deck.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
Home,
|
|
||||||
Deck,
|
|
||||||
Welcome
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
page(): string {
|
|
||||||
if (!this.$store.getters.isSignedIn) return 'welcome';
|
|
||||||
return this.$store.state.device.deckDefault ? 'deck' : 'home';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="profile" v-if="$store.getters.isSignedIn">
|
|
||||||
<div class="friend-form" v-if="$store.state.i.id != user.id">
|
|
||||||
<mk-follow-button :user="user" block/>
|
|
||||||
<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="action-form">
|
|
||||||
<ui-button @click="menu" ref="menu">{{ $t('menu') }}</ui-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../../i18n';
|
|
||||||
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n('desktop/views/pages/user/user.profile.vue'),
|
|
||||||
props: ['user'],
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
menu() {
|
|
||||||
this.$root.new(XUserMenu, {
|
|
||||||
source: this.$refs.menu.$el,
|
|
||||||
user: this.user
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.profile
|
|
||||||
background var(--face)
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
> *:first-child
|
|
||||||
border-top none !important
|
|
||||||
|
|
||||||
> .friend-form
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
border-bottom solid 1px var(--faceDivider)
|
|
||||||
|
|
||||||
> .followed
|
|
||||||
margin 12px 0 0 0
|
|
||||||
padding 0
|
|
||||||
text-align center
|
|
||||||
line-height 24px
|
|
||||||
font-size 0.8em
|
|
||||||
color var(--text)
|
|
||||||
border-radius 4px
|
|
||||||
|
|
||||||
> .action-form
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
> *
|
|
||||||
width 100%
|
|
||||||
|
|
||||||
&:not(:last-child)
|
|
||||||
margin-bottom 12px
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<template>
|
|
||||||
<mk-ui>
|
|
||||||
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
|
|
||||||
<div class="is-suspended" v-if="user.isSuspended"><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</div>
|
|
||||||
<div class="is-remote" v-if="user.host != null"><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></div>
|
|
||||||
<main>
|
|
||||||
<div class="main">
|
|
||||||
<x-header :user="user"/>
|
|
||||||
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
|
||||||
<x-timeline class="timeline" ref="tl" :user="user"/>
|
|
||||||
</div>
|
|
||||||
<div class="side">
|
|
||||||
<div class="instance" v-if="!$store.getters.isSignedIn"><mk-instance/></div>
|
|
||||||
<x-profile :user="user"/>
|
|
||||||
<x-integrations :user="user"/>
|
|
||||||
<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>
|
|
||||||
<mk-activity :user="user"/>
|
|
||||||
<x-photos :user="user"/>
|
|
||||||
<x-friends :user="user"/>
|
|
||||||
<x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
|
|
||||||
<div class="nav"><mk-nav/></div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</mk-ui>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../../i18n';
|
|
||||||
import parseAcct from '../../../../../../misc/acct/parse';
|
|
||||||
import Progress from '../../../../common/scripts/loading';
|
|
||||||
import XHeader from './user.header.vue';
|
|
||||||
import XTimeline from './user.timeline.vue';
|
|
||||||
import XProfile from './user.profile.vue';
|
|
||||||
import XPhotos from './user.photos.vue';
|
|
||||||
import XFollowersYouKnow from './user.followers-you-know.vue';
|
|
||||||
import XFriends from './user.friends.vue';
|
|
||||||
import XIntegrations from './user.integrations.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n(),
|
|
||||||
components: {
|
|
||||||
XHeader,
|
|
||||||
XTimeline,
|
|
||||||
XProfile,
|
|
||||||
XPhotos,
|
|
||||||
XFollowersYouKnow,
|
|
||||||
XFriends,
|
|
||||||
XIntegrations
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fetching: true,
|
|
||||||
user: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
$route: 'fetch'
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fetch();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch() {
|
|
||||||
this.fetching = true;
|
|
||||||
Progress.start();
|
|
||||||
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
|
||||||
this.user = user;
|
|
||||||
this.fetching = false;
|
|
||||||
Progress.done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
warp(date) {
|
|
||||||
(this.$refs.tl as any).warp(date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.xygkxeaeontfaokvqmiblezmhvhostak
|
|
||||||
max-width 980px
|
|
||||||
min-width 720px
|
|
||||||
padding 16px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
> .is-suspended
|
|
||||||
> .is-remote
|
|
||||||
margin-bottom 16px
|
|
||||||
padding 14px 16px
|
|
||||||
font-size 14px
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
&.is-suspended
|
|
||||||
color var(--suspendedInfoFg)
|
|
||||||
background var(--suspendedInfoBg)
|
|
||||||
|
|
||||||
&.is-remote
|
|
||||||
color var(--remoteInfoFg)
|
|
||||||
background var(--remoteInfoBg)
|
|
||||||
|
|
||||||
> a
|
|
||||||
font-weight bold
|
|
||||||
|
|
||||||
> main
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
|
|
||||||
> .main
|
|
||||||
> .side
|
|
||||||
> *:not(:last-child)
|
|
||||||
margin-bottom 16px
|
|
||||||
|
|
||||||
> .main
|
|
||||||
flex 1
|
|
||||||
min-width 0 // SEE: http://kudakurage.hatenadiary.com/entry/2016/04/01/232722
|
|
||||||
margin-right 16px
|
|
||||||
|
|
||||||
> .timeline
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
|
|
||||||
> .side
|
|
||||||
width 275px
|
|
||||||
flex-shrink 0
|
|
||||||
|
|
||||||
> p
|
|
||||||
display block
|
|
||||||
margin 0
|
|
||||||
padding 0 12px
|
|
||||||
text-align center
|
|
||||||
font-size 0.8em
|
|
||||||
color var(--text)
|
|
||||||
|
|
||||||
> .instance
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
> .nav
|
|
||||||
padding 16px
|
|
||||||
font-size 12px
|
|
||||||
color var(--text)
|
|
||||||
background var(--face)
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
a
|
|
||||||
color var(--text)99
|
|
||||||
|
|
||||||
i
|
|
||||||
color var(--text)
|
|
||||||
|
|
||||||
</style>
|
|
||||||
21
src/client/app/desktop/views/widgets/customize.vue
Normal file
21
src/client/app/desktop/views/widgets/customize.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mkw-customize">
|
||||||
|
<ui-button @click="customize()">{{ $t('@.customize-home') }}</ui-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
|
export default define({
|
||||||
|
name: 'customize',
|
||||||
|
}).extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
methods: {
|
||||||
|
customize(date) {
|
||||||
|
location.href = '/?customize';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -9,6 +9,7 @@ import wPolls from './polls.vue';
|
|||||||
import wPostForm from './post-form.vue';
|
import wPostForm from './post-form.vue';
|
||||||
import wMessaging from './messaging.vue';
|
import wMessaging from './messaging.vue';
|
||||||
import wProfile from './profile.vue';
|
import wProfile from './profile.vue';
|
||||||
|
import wCustomize from './customize.vue';
|
||||||
|
|
||||||
Vue.component('mkw-notifications', wNotifications);
|
Vue.component('mkw-notifications', wNotifications);
|
||||||
Vue.component('mkw-timemachine', wTimemachine);
|
Vue.component('mkw-timemachine', wTimemachine);
|
||||||
@@ -19,3 +20,4 @@ Vue.component('mkw-polls', wPolls);
|
|||||||
Vue.component('mkw-post-form', wPostForm);
|
Vue.component('mkw-post-form', wPostForm);
|
||||||
Vue.component('mkw-messaging', wMessaging);
|
Vue.component('mkw-messaging', wMessaging);
|
||||||
Vue.component('mkw-profile', wProfile);
|
Vue.component('mkw-profile', wProfile);
|
||||||
|
Vue.component('mkw-customize', wCustomize);
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ if (localStorage.getItem('should-refresh') == 'true') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MiOSを初期化してコールバックする
|
// MiOSを初期化してコールバックする
|
||||||
export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void, sw = false) => {
|
export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS) => void, sw = false) => {
|
||||||
const os = new MiOS(sw);
|
const os = new MiOS(sw);
|
||||||
|
|
||||||
os.init(() => {
|
os.init(() => {
|
||||||
@@ -436,11 +436,6 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
|
|||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// Navigation hook
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
next(os.store.state.navHook && os.store.state.navHook(to) ? false : undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (!document.hidden) {
|
if (!document.hidden) {
|
||||||
os.store.commit('clearBehindNotes');
|
os.store.commit('clearBehindNotes');
|
||||||
@@ -507,6 +502,6 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
|
|||||||
return [app, os] as [Vue, MiOS];
|
return [app, os] as [Vue, MiOS];
|
||||||
};
|
};
|
||||||
|
|
||||||
callback(launch);
|
callback(launch, os);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import init from '../init';
|
|||||||
|
|
||||||
import MkIndex from './views/pages/index.vue';
|
import MkIndex from './views/pages/index.vue';
|
||||||
import MkSignup from './views/pages/signup.vue';
|
import MkSignup from './views/pages/signup.vue';
|
||||||
import MkUser from './views/pages/user.vue';
|
|
||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||||
import MkDrive from './views/pages/drive.vue';
|
import MkDrive from './views/pages/drive.vue';
|
||||||
import MkNotifications from './views/pages/notifications.vue';
|
import MkNotifications from './views/pages/notifications.vue';
|
||||||
@@ -134,6 +133,7 @@ init((launch) => {
|
|||||||
{ path: '/selectdrive', component: MkSelectDrive },
|
{ path: '/selectdrive', component: MkSelectDrive },
|
||||||
{ path: '/search', component: MkSearch },
|
{ path: '/search', component: MkSearch },
|
||||||
{ path: '/tags/:tag', component: MkTag },
|
{ path: '/tags/:tag', component: MkTag },
|
||||||
|
{ path: '/featured', name: 'featured', component: () => import('./views/pages/featured.vue').then(m => m.default) },
|
||||||
{ path: '/share', component: MkShare },
|
{ path: '/share', component: MkShare },
|
||||||
{ path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
|
{ path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
|
||||||
{ path: '/@:user', component: () => import('./views/pages/user.vue').then(m => m.default) },
|
{ path: '/@:user', component: () => import('./views/pages/user.vue').then(m => m.default) },
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ export default Vue.extend({
|
|||||||
color #222
|
color #222
|
||||||
|
|
||||||
> [data-icon]
|
> [data-icon]
|
||||||
|
box-sizing initial
|
||||||
padding 10px
|
padding 10px
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
||||||
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
||||||
<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
||||||
|
<li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li>
|
||||||
<li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
<li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import { lang } from '../../../config';
|
import { lang } from '../../../config';
|
||||||
|
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
||||||
@@ -62,6 +64,7 @@ export default Vue.extend({
|
|||||||
aboutUrl: `/docs/${lang}/about`,
|
aboutUrl: `/docs/${lang}/about`,
|
||||||
announcements: [],
|
announcements: [],
|
||||||
searching: false,
|
searching: false,
|
||||||
|
faNewspaper
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
49
src/client/app/mobile/views/pages/featured.vue
Normal file
49
src/client/app/mobile/views/pages/featured.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<mk-ui>
|
||||||
|
<span slot="header"><span style="margin-right:4px;"><fa :icon="faNewspaper"/></span>{{ $t('@.featured-notes') }}</span>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||||
|
<template v-for="note in notes">
|
||||||
|
<mk-note-detail class="post" :note="note" :key="note.id"/>
|
||||||
|
</template>
|
||||||
|
</sequential-entrance>
|
||||||
|
</main>
|
||||||
|
</mk-ui>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import Progress from '../../../common/scripts/loading';
|
||||||
|
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(''),
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
notes: [],
|
||||||
|
faNewspaper
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
Progress.start();
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
this.$root.api('notes/featured', {
|
||||||
|
limit: 10
|
||||||
|
}).then(notes => {
|
||||||
|
this.notes = notes;
|
||||||
|
this.fetching = false;
|
||||||
|
|
||||||
|
Progress.done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -4,8 +4,7 @@
|
|||||||
|
|
||||||
<mk-notes ref="timeline" :more="existMore ? more : null">
|
<mk-notes ref="timeline" :more="existMore ? more : null">
|
||||||
<div slot="empty">
|
<div slot="empty">
|
||||||
<fa :icon="['far', 'comments']"/>
|
<fa :icon="['far', 'comments']"/>{{ $t('empty') }}
|
||||||
%i18n:@empty%
|
|
||||||
</div>
|
</div>
|
||||||
</mk-notes>
|
</mk-notes>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,10 +12,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
const fetchLimit = 10;
|
const fetchLimit = 10;
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
i18n: i18n('mobile/views/pages/home.timeline.vue'),
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
src: {
|
src: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -112,9 +112,13 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$root.getMeta().then(meta => {
|
this.$root.getMeta().then((meta: Record<string, any>) => {
|
||||||
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
if (!(
|
||||||
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
|
||||||
|
) && this.src === 'global') this.src = 'local';
|
||||||
|
if (!(
|
||||||
|
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
|
||||||
|
) && ['local', 'hybrid'].includes(this.src)) this.src = 'home';
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.$store.state.device.tl) {
|
if (this.$store.state.device.tl) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default Vue.extend({
|
|||||||
XPhotos,
|
XPhotos,
|
||||||
XFriends,
|
XFriends,
|
||||||
XFollowersYouKnow,
|
XFollowersYouKnow,
|
||||||
XActivity: () => import('../../components/activity.vue').then(m => m.default)
|
XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
props: ['user']
|
props: ['user']
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default define({
|
|||||||
}).extend({
|
}).extend({
|
||||||
i18n: i18n(),
|
i18n: i18n(),
|
||||||
components: {
|
components: {
|
||||||
XActivity: () => import('../components/activity.vue').then(m => m.default)
|
XActivity: () => import('../../../common/views/components/activity.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
func() {
|
func() {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const defaultSettings = {
|
|||||||
home: null,
|
home: null,
|
||||||
mobileHome: [],
|
mobileHome: [],
|
||||||
deck: null,
|
deck: null,
|
||||||
deckNav: true,
|
|
||||||
keepCw: false,
|
keepCw: false,
|
||||||
tagTimelines: [],
|
tagTimelines: [],
|
||||||
fetchOnScroll: true,
|
fetchOnScroll: true,
|
||||||
@@ -67,8 +66,7 @@ const defaultDeviceSettings = {
|
|||||||
deckColumnAlign: 'center',
|
deckColumnAlign: 'center',
|
||||||
deckColumnWidth: 'normal',
|
deckColumnWidth: 'normal',
|
||||||
mobileNotificationPosition: 'bottom',
|
mobileNotificationPosition: 'bottom',
|
||||||
deckTemporaryColumn: null,
|
deckMode: false,
|
||||||
deckDefault: false,
|
|
||||||
useOsDefaultEmojis: false,
|
useOsDefaultEmojis: false,
|
||||||
disableShowingAnimatedImages: false
|
disableShowingAnimatedImages: false
|
||||||
};
|
};
|
||||||
@@ -82,7 +80,6 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
i: null,
|
i: null,
|
||||||
indicate: false,
|
indicate: false,
|
||||||
uiHeaderHeight: 0,
|
uiHeaderHeight: 0,
|
||||||
navHook: null,
|
|
||||||
behindNotes: []
|
behindNotes: []
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -107,10 +104,6 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
state.uiHeaderHeight = height;
|
state.uiHeaderHeight = height;
|
||||||
},
|
},
|
||||||
|
|
||||||
navHook(state, callback) {
|
|
||||||
state.navHook = callback;
|
|
||||||
},
|
|
||||||
|
|
||||||
pushBehindNote(state, note) {
|
pushBehindNote(state, note) {
|
||||||
if (note.userId === state.i.id) return;
|
if (note.userId === state.i.id) return;
|
||||||
if (state.behindNotes.some(n => n.id === note.id)) return;
|
if (state.behindNotes.some(n => n.id === note.id)) return;
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export const meta = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
maxNoteTextLength: {
|
maxNoteTextLength: {
|
||||||
validator: $.optional.num.min(1),
|
validator: $.optional.num.min(0),
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': '投稿の最大文字数'
|
'ja-JP': '投稿の最大文字数'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||||
const day = 1000 * 60 * 60 * 24;
|
const day = 1000 * 60 * 60 * 24 * 2;
|
||||||
|
|
||||||
const notes = await Note
|
const notes = await Note
|
||||||
.find({
|
.find({
|
||||||
@@ -31,7 +31,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
|||||||
$gt: new Date(Date.now() - day)
|
$gt: new Date(Date.now() - day)
|
||||||
},
|
},
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
visibility: { $in: ['public', 'home'] }
|
visibility: { $in: ['public', 'home'] },
|
||||||
|
'_user.host': null
|
||||||
}, {
|
}, {
|
||||||
limit: ps.limit,
|
limit: ps.limit,
|
||||||
sort: {
|
sort: {
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ router.get('*', async ctx => {
|
|||||||
await ctx.render('base', {
|
await ctx.render('base', {
|
||||||
img: meta.bannerUrl
|
img: meta.bannerUrl
|
||||||
});
|
});
|
||||||
ctx.set('Cache-Control', 'public, max-age=86400');
|
ctx.set('Cache-Control', 'public, max-age=300');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register router
|
// Register router
|
||||||
|
|||||||
@@ -153,8 +153,17 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
|||||||
if (followee.isLocked || (followee.carefulBot && follower.isBot) || (isLocalUser(follower) && isRemoteUser(followee))) {
|
if (followee.isLocked || (followee.carefulBot && follower.isBot) || (isLocalUser(follower) && isRemoteUser(followee))) {
|
||||||
let autoAccept = false;
|
let autoAccept = false;
|
||||||
|
|
||||||
|
// 鍵アカウントであっても、既にフォローされていた場合はスルー
|
||||||
|
const following = await Following.findOne({
|
||||||
|
followerId: follower._id,
|
||||||
|
followeeId: followee._id,
|
||||||
|
});
|
||||||
|
if (following) {
|
||||||
|
autoAccept = true;
|
||||||
|
}
|
||||||
|
|
||||||
// フォローしているユーザーは自動承認オプション
|
// フォローしているユーザーは自動承認オプション
|
||||||
if (isLocalUser(followee) && followee.autoAcceptFollowed) {
|
if (!autoAccept && (isLocalUser(followee) && followee.autoAcceptFollowed)) {
|
||||||
const followed = await Following.findOne({
|
const followed = await Following.findOne({
|
||||||
followerId: followee._id,
|
followerId: followee._id,
|
||||||
followeeId: follower._id
|
followeeId: follower._id
|
||||||
|
|||||||
Reference in New Issue
Block a user