Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e53c383908 | ||
|
|
55c9c0436b | ||
|
|
66b79e5e24 | ||
|
|
514b830910 | ||
|
|
e4f799bf1d | ||
|
|
b383427d3d | ||
|
|
e969518139 | ||
|
|
113fe294bd | ||
|
|
a4d92f781f | ||
|
|
414cac49c3 | ||
|
|
95b157ac3e | ||
|
|
8e3d884081 | ||
|
|
9def6fcadd | ||
|
|
7837bd44fc | ||
|
|
a6c3663155 | ||
|
|
0b5afadbb8 | ||
|
|
43864f0da4 | ||
|
|
6a0d9d70ed | ||
|
|
63c6dce68e | ||
|
|
53422ffcb2 | ||
|
|
38ca514f53 | ||
|
|
caea0f0376 | ||
|
|
25a8b26977 | ||
|
|
bcaefe8d62 | ||
|
|
46f1e8c599 | ||
|
|
16230f320e | ||
|
|
ace6419aef | ||
|
|
77fb9eb2be |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,6 +1,19 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
10.85.1
|
||||
----------
|
||||
* ローカルのみ投稿をログイン画面のタイムラインに表示しないように
|
||||
* ナビゲーションバーを横にしてるとデッキに行けない問題を修正
|
||||
|
||||
10.85.0
|
||||
----------
|
||||
* デスクトップ版のUIを改良
|
||||
* 投稿ハイライトページを実装
|
||||
* 無効化されているタイムラインのフォールバック
|
||||
* 既にフォローされている場合はフォローリクエストを生成しないように
|
||||
* その他細かな修正
|
||||
|
||||
10.84.2
|
||||
----------
|
||||
* GIF画像にGIFバッジを表示
|
||||
|
||||
@@ -94,7 +94,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<!-- PATREON_START -->
|
||||
<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/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://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>
|
||||
@@ -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>
|
||||
</tr><tr>
|
||||
<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=16869916">見当かなみ</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>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Wed, 06 Feb 2019 18:18:05 UTC
|
||||
**Last updated:** Thu, 14 Feb 2019 12:02:06 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
||||
@@ -122,6 +122,8 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
|
||||
4. `npm run build`
|
||||
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
|
||||
|
||||
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
なにかお困りのことがありましたらお気軽にご連絡ください。
|
||||
|
||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
mobile/views/pages/user/home.photos.vue:
|
||||
|
||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "Schlagwörter"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
mobile/views/pages/user/home.photos.vue:
|
||||
|
||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
mobile/views/pages/user/home.photos.vue:
|
||||
|
||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
mobile/views/pages/user/home.photos.vue:
|
||||
|
||||
@@ -28,6 +28,8 @@ common:
|
||||
load-more: "もっと読み込む"
|
||||
enter-password: "パスワードを入力してください"
|
||||
2fa: "二段階認証"
|
||||
customize-home: "ホームをカスタマイズ"
|
||||
featured-notes: "ハイライト"
|
||||
|
||||
got-it: "わかった"
|
||||
customization-tips:
|
||||
@@ -862,6 +864,9 @@ desktop/views/components/renote-form.vue:
|
||||
desktop/views/components/renote-form-window.vue:
|
||||
title: "この投稿をRenoteしますか?"
|
||||
|
||||
desktop/views/components/timeline.core.vue:
|
||||
empty: "投稿がありません"
|
||||
|
||||
desktop/views/pages/user-following-or-followers.vue:
|
||||
following: "{user}のフォロー"
|
||||
followers: "{user}のフォロワー"
|
||||
@@ -893,14 +898,10 @@ desktop/views/components/settings.vue:
|
||||
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
|
||||
auto-popout: "ウィンドウの自動ポップアウト"
|
||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||
deck-nav: "デッキ内ナビゲーション"
|
||||
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
|
||||
keep-cw: "CW保持"
|
||||
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
|
||||
deck-default: "デッキをデフォルトのUIにする"
|
||||
|
||||
display: "デザインと表示"
|
||||
customize: "ホームをカスタマイズ"
|
||||
wallpaper: "壁紙"
|
||||
choose-wallpaper: "壁紙を選択"
|
||||
delete-wallpaper: "壁紙を削除"
|
||||
@@ -1076,7 +1077,6 @@ desktop/views/components/ui.header.account.vue:
|
||||
favorites: "お気に入り"
|
||||
lists: "リスト"
|
||||
follow-requests: "フォロー申請"
|
||||
customize: "ホームのカスタマイズ"
|
||||
admin: "管理"
|
||||
settings: "設定"
|
||||
signout: "サインアウト"
|
||||
@@ -1447,9 +1447,6 @@ desktop/views/pages/welcome.vue:
|
||||
desktop/views/pages/drive.vue:
|
||||
title: "Misskey Drive"
|
||||
|
||||
desktop/views/pages/home-customize.vue:
|
||||
title: "ホームのカスタマイズ"
|
||||
|
||||
desktop/views/pages/note.vue:
|
||||
prev: "前の投稿"
|
||||
next: "次の投稿"
|
||||
@@ -1490,10 +1487,6 @@ desktop/views/pages/user/user.photos.vue:
|
||||
loading: "読み込み中"
|
||||
no-photos: "写真はありません"
|
||||
|
||||
desktop/views/pages/user/user.profile.vue:
|
||||
follows-you: "フォローされています"
|
||||
menu: "メニュー"
|
||||
|
||||
desktop/views/pages/user/user.header.vue:
|
||||
posts: "投稿"
|
||||
following: "フォロー"
|
||||
@@ -1503,6 +1496,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
follows-you: "フォローされています"
|
||||
|
||||
desktop/views/pages/user/user.timeline.vue:
|
||||
default: "投稿"
|
||||
@@ -1689,6 +1683,9 @@ mobile/views/pages/home.vue:
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
|
||||
mobile/views/pages/home.timeline.vue:
|
||||
empty: "投稿がありません"
|
||||
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
||||
|
||||
@@ -1791,7 +1788,7 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
|
||||
@@ -1799,7 +1796,7 @@ mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
|
||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "Nøkkelord"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
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:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "Nenhuma mensagem"
|
||||
mobile/views/pages/user/home.photos.vue:
|
||||
|
||||
@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
|
||||
activity: "アクティビティ"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
frequently-replied-users: "よく話すユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
no-users: "よく話すユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
no-notes: "投稿はありません"
|
||||
mobile/views/pages/user/home.photos.vue:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.84.2",
|
||||
"clientVersion": "2.0.14267",
|
||||
"version": "10.85.1",
|
||||
"clientVersion": "2.0.14295",
|
||||
"codename": "nighthike",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-activity">
|
||||
<div>
|
||||
<div ref="chart"></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -9,7 +9,17 @@ import Vue from 'vue';
|
||||
import ApexCharts from 'apexcharts';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['user'],
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 21
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
@@ -21,7 +31,7 @@ export default Vue.extend({
|
||||
this.$root.api('charts/user/notes', {
|
||||
userId: this.user.id,
|
||||
span: 'day',
|
||||
limit: 21
|
||||
limit: this.limit
|
||||
}).then(stats => {
|
||||
const normal = [];
|
||||
const reply = [];
|
||||
@@ -32,7 +42,7 @@ export default Vue.extend({
|
||||
const m = now.getMonth();
|
||||
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);
|
||||
normal.push([
|
||||
x,
|
||||
@@ -99,10 +109,3 @@ export default Vue.extend({
|
||||
}
|
||||
});
|
||||
</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>
|
||||
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
|
||||
:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
|
||||
:class="{ wait, block, inline, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
|
||||
@click="onClick"
|
||||
:disabled="wait"
|
||||
:inline="inline"
|
||||
>
|
||||
<template v-if="!wait">
|
||||
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
|
||||
@@ -28,6 +29,11 @@ export default Vue.extend({
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
mini: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -128,6 +134,9 @@ export default Vue.extend({
|
||||
border solid 1px var(--primary)
|
||||
border-radius 36px
|
||||
|
||||
&.inline
|
||||
display inline-block
|
||||
|
||||
&.mini
|
||||
padding 0
|
||||
min-width 0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import dummy from './dummy.vue';
|
||||
import userName from './user-name.vue';
|
||||
import followButton from './follow-button.vue';
|
||||
import error from './error.vue';
|
||||
@@ -46,6 +47,7 @@ import formButton from './ui/form/button.vue';
|
||||
import formRadio from './ui/form/radio.vue';
|
||||
|
||||
Vue.component('mfm', misskeyFlavoredMarkdown);
|
||||
Vue.component('mk-dummy', dummy);
|
||||
Vue.component('mk-user-name', userName);
|
||||
Vue.component('mk-follow-button', followButton);
|
||||
Vue.component('mk-error', error);
|
||||
|
||||
@@ -76,6 +76,7 @@ export default Vue.extend({
|
||||
if (note.replyId != null) return;
|
||||
if (note.renoteId != null) return;
|
||||
if (note.poll != null) return;
|
||||
if (note.localOnly) return;
|
||||
|
||||
this.notes.unshift(note);
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ import wSlideshow from './slideshow.vue';
|
||||
import wTips from './tips.vue';
|
||||
import wNav from './nav.vue';
|
||||
import wHashtags from './hashtags.vue';
|
||||
import wInstance from './instance.vue';
|
||||
|
||||
Vue.component('mkw-analog-clock', wAnalogClock);
|
||||
Vue.component('mkw-nav', wNav);
|
||||
@@ -27,3 +28,4 @@ Vue.component('mkw-memo', wMemo);
|
||||
Vue.component('mkw-rss', wRss);
|
||||
Vue.component('mkw-version', wVersion);
|
||||
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 composeNotification from '../common/scripts/compose-notification';
|
||||
|
||||
import MkIndex from './views/pages/index.vue';
|
||||
import MkHome from './views/pages/home.vue';
|
||||
import MkDeck from './views/pages/deck/deck.vue';
|
||||
import MkUser from './views/pages/user/user.vue';
|
||||
import MkHome from './views/home/home.vue';
|
||||
import MkDeck from './views/deck/deck.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 MkDrive from './views/pages/drive.vue';
|
||||
import MkHomeCustomize from './views/pages/home-customize.vue';
|
||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
|
||||
import MkNote from './views/pages/note.vue';
|
||||
import MkSearch from './views/pages/search.vue';
|
||||
import MkTag from './views/pages/tag.vue';
|
||||
import MkReversi from './views/pages/games/reversi.vue';
|
||||
import MkShare from './views/pages/share.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 MkChooseFileFromDriveWindow from './views/components/choose-file-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 { url } from '../config';
|
||||
@@ -44,7 +39,7 @@ import MiOS from '../mios';
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
init(async (launch) => {
|
||||
init(async (launch, os) => {
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
$contextmenu(e, menu, opts?) {
|
||||
@@ -134,31 +129,40 @@ init(async (launch) => {
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
{ path: '/', name: 'index', component: MkIndex },
|
||||
{ path: '/home', name: 'home', component: MkHome },
|
||||
{ path: '/deck', name: 'deck', component: MkDeck },
|
||||
{ path: '/i/customize-home', component: MkHomeCustomize },
|
||||
{ path: '/i/favorites', component: MkFavorites },
|
||||
os.store.getters.isSignedIn && os.store.state.device.deckMode
|
||||
? { path: '/', name: 'index', component: MkDeck, children: [
|
||||
{ path: '/@:user', name: 'user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default) },
|
||||
{ path: '/notes/:note', name: 'note', component: () => import('./views/deck/deck.note-column.vue').then(m => m.default) },
|
||||
{ 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/drive', component: MkDrive },
|
||||
{ path: '/i/drive/folder/:folder', component: MkDrive },
|
||||
{ path: '/i/settings', component: MkSettings },
|
||||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/search', component: MkSearch },
|
||||
{ path: '/tags/:tag', name: 'tag', component: MkTag },
|
||||
{ path: '/share', component: MkShare },
|
||||
{ path: '/games/reversi/:game?', component: MkReversi },
|
||||
{ path: '/@:user', name: 'user', component: MkUser },
|
||||
{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
|
||||
{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
|
||||
{ path: '/notes/:note', name: 'note', component: MkNote },
|
||||
{ path: '/authorize-follow', component: MkFollow },
|
||||
{ path: '/deck', redirect: '/' },
|
||||
{ path: '*', component: MkNotFound }
|
||||
]
|
||||
});
|
||||
|
||||
// Launch the app
|
||||
const [app, os] = launch(router);
|
||||
const [app, _] = launch(router);
|
||||
|
||||
if (os.store.getters.isSignedIn) {
|
||||
/**
|
||||
|
||||
@@ -160,6 +160,7 @@ export default Vue.extend({
|
||||
color #222
|
||||
|
||||
> [data-icon]
|
||||
box-sizing initial
|
||||
padding 14px
|
||||
|
||||
</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 uiNotification from './ui-notification.vue';
|
||||
import home from './home.vue';
|
||||
import timeline from './timeline.vue';
|
||||
import notes from './notes.vue';
|
||||
import subNoteContent from './sub-note-content.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-notification', uiNotification);
|
||||
Vue.component('mk-home', home);
|
||||
Vue.component('mk-timeline', timeline);
|
||||
Vue.component('mk-notes', notes);
|
||||
Vue.component('mk-sub-note-content', subNoteContent);
|
||||
Vue.component('mk-window', window);
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
|
||||
<span slot="desc">{{ $t('auto-popout-desc') }}</span>
|
||||
</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') }}
|
||||
<span slot="desc">{{ $t('keep-cw-desc') }}</span>
|
||||
</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="right">{{ $t('navbar-position-right') }}</ui-radio>
|
||||
</section>
|
||||
<section>
|
||||
<ui-switch v-model="deckDefault">{{ $t('deck-default') }}</ui-switch>
|
||||
</section>
|
||||
<section>
|
||||
<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</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 }); }
|
||||
},
|
||||
|
||||
deckNav: {
|
||||
get() { return this.$store.state.settings.deckNav; },
|
||||
set(value) { this.$store.commit('settings/set', { key: 'deckNav', value }); }
|
||||
},
|
||||
|
||||
keepCw: {
|
||||
get() { return this.$store.state.settings.keepCw; },
|
||||
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 }); }
|
||||
},
|
||||
|
||||
deckDefault: {
|
||||
get() { return this.$store.state.device.deckDefault; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckDefault', value }); }
|
||||
},
|
||||
|
||||
enableSounds: {
|
||||
get() { return this.$store.state.device.enableSounds; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
||||
@@ -534,8 +518,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
customizeHome() {
|
||||
this.$router.push('/i/customize-home');
|
||||
this.$emit('done');
|
||||
location.href = '/?customize';
|
||||
},
|
||||
updateWallpaper() {
|
||||
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>
|
||||
</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>
|
||||
<router-link to="/i/settings">
|
||||
<i><fa icon="cog"/></i>
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<template v-if="$store.getters.isSignedIn">
|
||||
<template v-if="$store.state.device.deckDefault">
|
||||
<li class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
||||
<template v-if="$store.state.device.deckMode">
|
||||
<li class="deck active" @click="goToTop">
|
||||
<router-link to="/"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
||||
</li>
|
||||
<li class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
|
||||
<router-link to="/home"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
||||
<li class="home">
|
||||
<a @click="toggleDeckMode(false)"><fa icon="home"/><p>{{ $t('home') }}</p></a>
|
||||
</li>
|
||||
</template>
|
||||
<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>
|
||||
</li>
|
||||
<li class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
|
||||
<router-link to="/deck"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
||||
<li class="deck">
|
||||
<a @click="toggleDeckMode(true)"><fa icon="columns"/><p>{{ $t('deck') }}</p></a>
|
||||
</li>
|
||||
</template>
|
||||
<li class="messaging">
|
||||
@@ -25,14 +25,17 @@
|
||||
<template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template>
|
||||
</a>
|
||||
</li>
|
||||
<li class="game">
|
||||
<a @click="game">
|
||||
<fa icon="gamepad"/>
|
||||
<p>{{ $t('game') }}</p>
|
||||
<template v-if="hasGameInvitations"><fa icon="circle"/></template>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
<li class="featured">
|
||||
<router-link to="/featured"><fa :icon="faNewspaper"/><p>{{ $t('@.featured-notes') }}</p></router-link>
|
||||
</li>
|
||||
<li class="game">
|
||||
<a @click="game">
|
||||
<fa icon="gamepad"/>
|
||||
<p>{{ $t('game') }}</p>
|
||||
<template v-if="hasGameInvitations"><fa icon="circle"/></template>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,13 +45,15 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import MkMessagingWindow from './messaging-window.vue';
|
||||
import MkGameWindow from './game-window.vue';
|
||||
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/ui.header.nav.vue'),
|
||||
data() {
|
||||
return {
|
||||
hasGameInvitations: false,
|
||||
connection: null
|
||||
connection: null,
|
||||
faNewspaper
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -70,6 +75,11 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleDeckMode(deck) {
|
||||
this.$store.commit('device/set', { key: 'deckMode', value: deck });
|
||||
location.reload();
|
||||
},
|
||||
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitations = true;
|
||||
},
|
||||
|
||||
@@ -116,7 +116,7 @@ export default Vue.extend({
|
||||
> .container
|
||||
display flex
|
||||
width 100%
|
||||
max-width 1300px
|
||||
max-width 1208px
|
||||
margin 0 auto
|
||||
|
||||
> *
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
</div>
|
||||
|
||||
<div class="nav" v-if="$store.getters.isSignedIn">
|
||||
<template v-if="$store.state.device.deckDefault">
|
||||
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
||||
<template v-if="$store.state.device.deckMode">
|
||||
<div class="deck active" @click="goToTop">
|
||||
<router-link to="/"><fa icon="columns"/></router-link>
|
||||
</div>
|
||||
<div class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
|
||||
<router-link to="/home"><fa icon="home"/></router-link>
|
||||
<div class="home">
|
||||
<a @click="toggleDeckMode(false)"><fa icon="home"/></a>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
|
||||
<div class="home active" @click="goToTop">
|
||||
<router-link to="/"><fa icon="home"/></router-link>
|
||||
</div>
|
||||
<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
|
||||
<router-link to="/deck"><fa icon="columns"/></router-link>
|
||||
<div class="deck">
|
||||
<a @click="toggleDeckMode(true)"><fa icon="columns"/></a>
|
||||
</div>
|
||||
</template>
|
||||
<div class="messaging">
|
||||
@@ -122,6 +122,11 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleDeckMode(deck) {
|
||||
this.$store.commit('device/set', { key: 'deckMode', value: deck });
|
||||
location.reload();
|
||||
},
|
||||
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitations = true;
|
||||
},
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import Menu from '../../../../common/views/components/menu.vue';
|
||||
import { countIf } from '../../../../../../prelude/array';
|
||||
import i18n from '../../../i18n';
|
||||
import Menu from '../../../common/views/components/menu.vue';
|
||||
import { countIf } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('deck'),
|
||||
@@ -245,10 +245,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$store.commit('device/set', {
|
||||
key: 'deckTemporaryColumn',
|
||||
value: null
|
||||
});
|
||||
this.$router.push('/');
|
||||
},
|
||||
|
||||
goTop() {
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.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">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XMentions from './deck.mentions.vue';
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XNotes from './deck.notes.vue';
|
||||
import XNote from '../../components/note.vue';
|
||||
import XNote from '../components/note.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
@@ -31,13 +31,6 @@ export default Vue.extend({
|
||||
XNote
|
||||
},
|
||||
|
||||
props: {
|
||||
noteId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
note: null,
|
||||
@@ -45,11 +38,25 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route: 'fetch'
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.api('notes/show', { noteId: this.noteId }).then(note => {
|
||||
this.note = note;
|
||||
this.fetching = false;
|
||||
});
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.fetching = true;
|
||||
|
||||
this.$root.api('notes/show', {
|
||||
noteId: this.$route.params.note
|
||||
}).then(note => {
|
||||
this.note = note;
|
||||
this.fetching = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -38,10 +38,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import shouldMuteNote from '../../../../common/scripts/should-mute-note';
|
||||
import i18n from '../../../i18n';
|
||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||
|
||||
import XNote from '../../components/note.vue';
|
||||
import XNote from '../components/note.vue';
|
||||
|
||||
const displayLimit = 20;
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getNoteSummary from '../../../../../../misc/get-note-summary';
|
||||
import XNote from '../../components/note.vue';
|
||||
import getNoteSummary from '../../../../../misc/get-note-summary';
|
||||
import XNote from '../components/note.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XNotifications from './deck.notifications.vue';
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XNotification from './deck.notification.vue';
|
||||
|
||||
const displayLimit = 20;
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XTl from './deck.tl.vue';
|
||||
import XListTl from './deck.list-tl.vue';
|
||||
@@ -93,13 +93,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import parseAcct from '../../../../../../misc/acct/parse';
|
||||
import i18n from '../../../i18n';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XNotes from './deck.notes.vue';
|
||||
import XNote from '../../components/note.vue';
|
||||
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
||||
import { concat } from '../../../../../../prelude/array';
|
||||
import XNote from '../components/note.vue';
|
||||
import XUserMenu from '../../../common/views/components/user-menu.vue';
|
||||
import { concat } from '../../../../../prelude/array';
|
||||
import ApexCharts from 'apexcharts';
|
||||
|
||||
const fetchLimit = 10;
|
||||
@@ -112,13 +112,6 @@ export default Vue.extend({
|
||||
XNote
|
||||
},
|
||||
|
||||
props: {
|
||||
acct: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
@@ -144,119 +137,128 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route: 'fetch'
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.api('users/show', parseAcct(this.acct)).then(user => {
|
||||
this.user = user;
|
||||
this.fetching = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.timeline as any).init(() => this.initTl());
|
||||
});
|
||||
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
fileType: image,
|
||||
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
for (const note of notes) {
|
||||
for (const file of note.files) {
|
||||
file._note = note;
|
||||
}
|
||||
}
|
||||
const files = concat(notes.map((n: any): any[] => n.files));
|
||||
this.images = files.filter(f => image.includes(f.type)).slice(0, 9);
|
||||
});
|
||||
|
||||
this.$root.api('charts/user/notes', {
|
||||
userId: this.user.id,
|
||||
span: 'day',
|
||||
limit: 21
|
||||
}).then(stats => {
|
||||
const normal = [];
|
||||
const reply = [];
|
||||
const renote = [];
|
||||
|
||||
const now = new Date();
|
||||
const y = now.getFullYear();
|
||||
const m = now.getMonth();
|
||||
const d = now.getDate();
|
||||
|
||||
for (let i = 0; i < 21; i++) {
|
||||
const x = new Date(y, m, d - i);
|
||||
normal.push([
|
||||
x,
|
||||
stats.diffs.normal[i]
|
||||
]);
|
||||
reply.push([
|
||||
x,
|
||||
stats.diffs.reply[i]
|
||||
]);
|
||||
renote.push([
|
||||
x,
|
||||
stats.diffs.renote[i]
|
||||
]);
|
||||
}
|
||||
|
||||
const chart = new ApexCharts(this.$refs.chart, {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
stacked: true,
|
||||
height: 100,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '90%'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
clipMarkers: false,
|
||||
padding: {
|
||||
top: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
left: 16
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false
|
||||
},
|
||||
series: [{
|
||||
name: 'Normal',
|
||||
data: normal
|
||||
}, {
|
||||
name: 'Reply',
|
||||
data: reply
|
||||
}, {
|
||||
name: 'Renote',
|
||||
data: renote
|
||||
}],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
crosshairs: {
|
||||
width: 1,
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chart.render();
|
||||
});
|
||||
});
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.fetching = true;
|
||||
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
||||
this.user = user;
|
||||
this.fetching = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.timeline as any).init(() => this.initTl());
|
||||
});
|
||||
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
fileType: image,
|
||||
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
for (const note of notes) {
|
||||
for (const file of note.files) {
|
||||
file._note = note;
|
||||
}
|
||||
}
|
||||
const files = concat(notes.map((n: any): any[] => n.files));
|
||||
this.images = files.filter(f => image.includes(f.type)).slice(0, 9);
|
||||
});
|
||||
|
||||
this.$root.api('charts/user/notes', {
|
||||
userId: this.user.id,
|
||||
span: 'day',
|
||||
limit: 21
|
||||
}).then(stats => {
|
||||
const normal = [];
|
||||
const reply = [];
|
||||
const renote = [];
|
||||
|
||||
const now = new Date();
|
||||
const y = now.getFullYear();
|
||||
const m = now.getMonth();
|
||||
const d = now.getDate();
|
||||
|
||||
for (let i = 0; i < 21; i++) {
|
||||
const x = new Date(y, m, d - i);
|
||||
normal.push([
|
||||
x,
|
||||
stats.diffs.normal[i]
|
||||
]);
|
||||
reply.push([
|
||||
x,
|
||||
stats.diffs.reply[i]
|
||||
]);
|
||||
renote.push([
|
||||
x,
|
||||
stats.diffs.renote[i]
|
||||
]);
|
||||
}
|
||||
|
||||
const chart = new ApexCharts(this.$refs.chart, {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
stacked: true,
|
||||
height: 100,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: '90%'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
clipMarkers: false,
|
||||
padding: {
|
||||
top: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
left: 16
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false
|
||||
},
|
||||
series: [{
|
||||
name: 'Normal',
|
||||
data: normal
|
||||
}, {
|
||||
name: 'Reply',
|
||||
data: reply
|
||||
}, {
|
||||
name: 'Renote',
|
||||
data: renote
|
||||
}],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
crosshairs: {
|
||||
width: 1,
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chart.render();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
initTl() {
|
||||
return new Promise((res, rej) => {
|
||||
this.$root.api('users/notes', {
|
||||
@@ -9,11 +9,7 @@
|
||||
</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)"/>
|
||||
</template>
|
||||
<template v-if="temporaryColumn">
|
||||
<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>
|
||||
<router-view></router-view>
|
||||
<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
|
||||
</div>
|
||||
</mk-ui>
|
||||
@@ -21,20 +17,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumnCore from './deck.column-core.vue';
|
||||
import Menu from '../../../../common/views/components/menu.vue';
|
||||
import MkUserListsWindow from '../../components/user-lists-window.vue';
|
||||
import Menu from '../../../common/views/components/menu.vue';
|
||||
import MkUserListsWindow from '../components/user-lists-window.vue';
|
||||
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('deck'),
|
||||
components: {
|
||||
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)
|
||||
XColumnCore
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -55,10 +48,6 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
|
||||
temporaryColumn(): any {
|
||||
return this.$store.state.device.deckTemporaryColumn;
|
||||
},
|
||||
|
||||
keymap(): any {
|
||||
return {
|
||||
't': this.focus
|
||||
@@ -66,7 +55,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
watch: {/*
|
||||
temporaryColumn() {
|
||||
if (this.temporaryColumn != null) {
|
||||
this.$nextTick(() => {
|
||||
@@ -76,7 +65,7 @@ export default Vue.extend({
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}*/
|
||||
},
|
||||
|
||||
provide() {
|
||||
@@ -86,8 +75,6 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$store.commit('navHook', this.onNav);
|
||||
|
||||
if (this.$store.state.settings.deck == null) {
|
||||
const deck = {
|
||||
columns: [/*{
|
||||
@@ -133,8 +120,6 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.$store.commit('navHook', null);
|
||||
|
||||
document.documentElement.style.overflow = 'auto';
|
||||
},
|
||||
|
||||
@@ -143,39 +128,6 @@ export default Vue.extend({
|
||||
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() {
|
||||
this.$root.new(Menu, {
|
||||
source: this.$refs.add,
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import * as XDraggable from 'vuedraggable';
|
||||
import * as uuid from 'uuid';
|
||||
@@ -1,16 +1,14 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<main v-if="!fetching">
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
<div class="more" v-if="existMore">
|
||||
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
</main>
|
||||
</mk-ui>
|
||||
<div class="ecsvsegy" v-if="!fetching">
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
<div class="more" v-if="existMore">
|
||||
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -72,10 +70,8 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
main
|
||||
.ecsvsegy
|
||||
margin 0 auto
|
||||
padding 16px
|
||||
max-width 700px
|
||||
|
||||
> * > .post
|
||||
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>
|
||||
<mk-ui>
|
||||
<main v-if="!fetching">
|
||||
<mk-note-detail :note="note"/>
|
||||
<footer>
|
||||
<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>
|
||||
</footer>
|
||||
</main>
|
||||
</mk-ui>
|
||||
<div v-if="!fetching" class="kcthdwmv">
|
||||
<mk-note-detail :note="note"/>
|
||||
<footer>
|
||||
<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>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -48,8 +46,7 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
main
|
||||
padding 16px
|
||||
.kcthdwmv
|
||||
text-align center
|
||||
|
||||
> footer
|
||||
@@ -1,11 +1,8 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<header :class="$style.header">
|
||||
<h1>#{{ $route.params.tag }}</h1>
|
||||
</header>
|
||||
<div>
|
||||
<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-ui>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -96,17 +93,11 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" module>
|
||||
.header
|
||||
width 100%
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
color #555
|
||||
|
||||
.notes
|
||||
width 600px
|
||||
margin 0 auto
|
||||
border solid 1px rgba(#000, 0.075)
|
||||
border-radius 6px
|
||||
background var(--face)
|
||||
box-shadow var(--shadow)
|
||||
border-radius var(--round)
|
||||
overflow hidden
|
||||
overflow hidden
|
||||
|
||||
.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">
|
||||
<mk-avatar class="avatar" :user="friend"/>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -9,12 +9,19 @@
|
||||
</p>
|
||||
<div>
|
||||
<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>
|
||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||
<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">
|
||||
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||
</div>
|
||||
@@ -45,6 +52,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import * as age from 's-age';
|
||||
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/user/user.header.vue'),
|
||||
@@ -99,6 +107,13 @@ export default Vue.extend({
|
||||
this.$updateBanner().then(i => {
|
||||
this.user.bannerUrl = i.bannerUrl;
|
||||
});
|
||||
},
|
||||
|
||||
menu() {
|
||||
this.$root.new(XUserMenu, {
|
||||
source: this.$refs.menu.$el,
|
||||
user: this.user
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -187,6 +202,18 @@ export default Vue.extend({
|
||||
padding 16px 16px 16px 154px
|
||||
color var(--text)
|
||||
|
||||
> .actions
|
||||
text-align right
|
||||
padding-bottom 16px
|
||||
margin-bottom 16px
|
||||
border-bottom solid 1px var(--faceDivider)
|
||||
|
||||
> *
|
||||
margin-left 8px
|
||||
|
||||
> .follow
|
||||
width 180px
|
||||
|
||||
> .fields
|
||||
margin-top 16px
|
||||
|
||||
@@ -87,7 +87,7 @@ export default Vue.extend({
|
||||
> .img
|
||||
flex 1 1 33%
|
||||
width 33%
|
||||
height 80px
|
||||
height 120px
|
||||
background-position center center
|
||||
background-size cover
|
||||
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 wMessaging from './messaging.vue';
|
||||
import wProfile from './profile.vue';
|
||||
import wCustomize from './customize.vue';
|
||||
|
||||
Vue.component('mkw-notifications', wNotifications);
|
||||
Vue.component('mkw-timemachine', wTimemachine);
|
||||
@@ -19,3 +20,4 @@ Vue.component('mkw-polls', wPolls);
|
||||
Vue.component('mkw-post-form', wPostForm);
|
||||
Vue.component('mkw-messaging', wMessaging);
|
||||
Vue.component('mkw-profile', wProfile);
|
||||
Vue.component('mkw-customize', wCustomize);
|
||||
|
||||
@@ -350,7 +350,7 @@ if (localStorage.getItem('should-refresh') == 'true') {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
os.init(() => {
|
||||
@@ -436,11 +436,6 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
|
||||
});
|
||||
//#endregion
|
||||
|
||||
// Navigation hook
|
||||
router.beforeEach((to, from, next) => {
|
||||
next(os.store.state.navHook && os.store.state.navHook(to) ? false : undefined);
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
os.store.commit('clearBehindNotes');
|
||||
@@ -507,6 +502,6 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
|
||||
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 MkSignup from './views/pages/signup.vue';
|
||||
import MkUser from './views/pages/user.vue';
|
||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||
import MkDrive from './views/pages/drive.vue';
|
||||
import MkNotifications from './views/pages/notifications.vue';
|
||||
@@ -134,6 +133,7 @@ init((launch) => {
|
||||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/search', component: MkSearch },
|
||||
{ path: '/tags/:tag', component: MkTag },
|
||||
{ path: '/featured', name: 'featured', component: () => import('./views/pages/featured.vue').then(m => m.default) },
|
||||
{ path: '/share', component: MkShare },
|
||||
{ path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
|
||||
{ path: '/@:user', component: () => import('./views/pages/user.vue').then(m => m.default) },
|
||||
|
||||
@@ -124,6 +124,7 @@ export default Vue.extend({
|
||||
color #222
|
||||
|
||||
> [data-icon]
|
||||
box-sizing initial
|
||||
padding 10px
|
||||
|
||||
</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/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><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>
|
||||
</ul>
|
||||
<ul>
|
||||
@@ -50,6 +51,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { lang } from '../../../config';
|
||||
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
||||
@@ -62,6 +64,7 @@ export default Vue.extend({
|
||||
aboutUrl: `/docs/${lang}/about`,
|
||||
announcements: [],
|
||||
searching: false,
|
||||
faNewspaper
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
70
src/client/app/mobile/views/pages/featured.vue
Normal file
70
src/client/app/mobile/views/pages/featured.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<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>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
main
|
||||
width 100%
|
||||
max-width 680px
|
||||
margin 0 auto
|
||||
padding 8px
|
||||
|
||||
> * > .post
|
||||
margin-bottom 8px
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
> * > .post
|
||||
margin-bottom 16px
|
||||
|
||||
@media (min-width 600px)
|
||||
padding 32px
|
||||
|
||||
</style>
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
<mk-notes ref="timeline" :more="existMore ? more : null">
|
||||
<div slot="empty">
|
||||
<fa :icon="['far', 'comments']"/>
|
||||
%i18n:@empty%
|
||||
<fa :icon="['far', 'comments']"/>{{ $t('empty') }}
|
||||
</div>
|
||||
</mk-notes>
|
||||
</div>
|
||||
@@ -13,10 +12,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/home.timeline.vue'),
|
||||
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
|
||||
@@ -112,9 +112,13 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
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;
|
||||
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) {
|
||||
|
||||
@@ -49,7 +49,7 @@ export default Vue.extend({
|
||||
XPhotos,
|
||||
XFriends,
|
||||
XFollowersYouKnow,
|
||||
XActivity: () => import('../../components/activity.vue').then(m => m.default)
|
||||
XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
|
||||
},
|
||||
props: ['user']
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export default define({
|
||||
}).extend({
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XActivity: () => import('../components/activity.vue').then(m => m.default)
|
||||
XActivity: () => import('../../../common/views/components/activity.vue').then(m => m.default)
|
||||
},
|
||||
methods: {
|
||||
func() {
|
||||
|
||||
@@ -10,7 +10,6 @@ const defaultSettings = {
|
||||
home: null,
|
||||
mobileHome: [],
|
||||
deck: null,
|
||||
deckNav: true,
|
||||
keepCw: false,
|
||||
tagTimelines: [],
|
||||
fetchOnScroll: true,
|
||||
@@ -67,8 +66,7 @@ const defaultDeviceSettings = {
|
||||
deckColumnAlign: 'center',
|
||||
deckColumnWidth: 'normal',
|
||||
mobileNotificationPosition: 'bottom',
|
||||
deckTemporaryColumn: null,
|
||||
deckDefault: false,
|
||||
deckMode: false,
|
||||
useOsDefaultEmojis: false,
|
||||
disableShowingAnimatedImages: false
|
||||
};
|
||||
@@ -82,7 +80,6 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
i: null,
|
||||
indicate: false,
|
||||
uiHeaderHeight: 0,
|
||||
navHook: null,
|
||||
behindNotes: []
|
||||
},
|
||||
|
||||
@@ -107,10 +104,6 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
state.uiHeaderHeight = height;
|
||||
},
|
||||
|
||||
navHook(state, callback) {
|
||||
state.navHook = callback;
|
||||
},
|
||||
|
||||
pushBehindNote(state, note) {
|
||||
if (note.userId === state.i.id) return;
|
||||
if (state.behindNotes.some(n => n.id === note.id)) return;
|
||||
|
||||
@@ -82,7 +82,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
maxNoteTextLength: {
|
||||
validator: $.optional.num.min(1),
|
||||
validator: $.optional.num.min(0),
|
||||
desc: {
|
||||
'ja-JP': '投稿の最大文字数'
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
|
||||
};
|
||||
const query = {
|
||||
deletedAt: null,
|
||||
visibility: 'public'
|
||||
visibility: 'public',
|
||||
localOnly: { $ne: true },
|
||||
} as any;
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
|
||||
@@ -23,7 +23,7 @@ export const meta = {
|
||||
};
|
||||
|
||||
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
|
||||
.find({
|
||||
@@ -31,7 +31,8 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
$gt: new Date(Date.now() - day)
|
||||
},
|
||||
deletedAt: null,
|
||||
visibility: { $in: ['public', 'home'] }
|
||||
visibility: { $in: ['public', 'home'] },
|
||||
'_user.host': null
|
||||
}, {
|
||||
limit: ps.limit,
|
||||
sort: {
|
||||
|
||||
@@ -237,7 +237,7 @@ router.get('*', async ctx => {
|
||||
await ctx.render('base', {
|
||||
img: meta.bannerUrl
|
||||
});
|
||||
ctx.set('Cache-Control', 'public, max-age=86400');
|
||||
ctx.set('Cache-Control', 'public, max-age=300');
|
||||
});
|
||||
|
||||
// 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))) {
|
||||
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({
|
||||
followerId: followee._id,
|
||||
followeeId: follower._id
|
||||
|
||||
Reference in New Issue
Block a user