mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-27 21:10:20 +01:00
Improve desktop UX (#4262)
* wip * wip * wip * wip * wip * wip * Merge * wip * wip * wip * wip * wip * wip
This commit is contained in:
parent
38ca514f53
commit
53422ffcb2
60 changed files with 1132 additions and 1222 deletions
|
@ -28,6 +28,7 @@ common:
|
||||||
load-more: "もっと読み込む"
|
load-more: "もっと読み込む"
|
||||||
enter-password: "パスワードを入力してください"
|
enter-password: "パスワードを入力してください"
|
||||||
2fa: "二段階認証"
|
2fa: "二段階認証"
|
||||||
|
customize-home: "ホームをカスタマイズ"
|
||||||
|
|
||||||
got-it: "わかった"
|
got-it: "わかった"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
|
@ -893,14 +894,10 @@ desktop/views/components/settings.vue:
|
||||||
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
|
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
|
||||||
auto-popout: "ウィンドウの自動ポップアウト"
|
auto-popout: "ウィンドウの自動ポップアウト"
|
||||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||||
deck-nav: "デッキ内ナビゲーション"
|
|
||||||
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
|
|
||||||
keep-cw: "CW保持"
|
keep-cw: "CW保持"
|
||||||
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
|
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
|
||||||
deck-default: "デッキをデフォルトのUIにする"
|
|
||||||
|
|
||||||
display: "デザインと表示"
|
display: "デザインと表示"
|
||||||
customize: "ホームをカスタマイズ"
|
|
||||||
wallpaper: "壁紙"
|
wallpaper: "壁紙"
|
||||||
choose-wallpaper: "壁紙を選択"
|
choose-wallpaper: "壁紙を選択"
|
||||||
delete-wallpaper: "壁紙を削除"
|
delete-wallpaper: "壁紙を削除"
|
||||||
|
@ -1076,7 +1073,6 @@ desktop/views/components/ui.header.account.vue:
|
||||||
favorites: "お気に入り"
|
favorites: "お気に入り"
|
||||||
lists: "リスト"
|
lists: "リスト"
|
||||||
follow-requests: "フォロー申請"
|
follow-requests: "フォロー申請"
|
||||||
customize: "ホームのカスタマイズ"
|
|
||||||
admin: "管理"
|
admin: "管理"
|
||||||
settings: "設定"
|
settings: "設定"
|
||||||
signout: "サインアウト"
|
signout: "サインアウト"
|
||||||
|
@ -1447,9 +1443,6 @@ desktop/views/pages/welcome.vue:
|
||||||
desktop/views/pages/drive.vue:
|
desktop/views/pages/drive.vue:
|
||||||
title: "Misskey Drive"
|
title: "Misskey Drive"
|
||||||
|
|
||||||
desktop/views/pages/home-customize.vue:
|
|
||||||
title: "ホームのカスタマイズ"
|
|
||||||
|
|
||||||
desktop/views/pages/note.vue:
|
desktop/views/pages/note.vue:
|
||||||
prev: "前の投稿"
|
prev: "前の投稿"
|
||||||
next: "次の投稿"
|
next: "次の投稿"
|
||||||
|
@ -1490,10 +1483,6 @@ desktop/views/pages/user/user.photos.vue:
|
||||||
loading: "読み込み中"
|
loading: "読み込み中"
|
||||||
no-photos: "写真はありません"
|
no-photos: "写真はありません"
|
||||||
|
|
||||||
desktop/views/pages/user/user.profile.vue:
|
|
||||||
follows-you: "フォローされています"
|
|
||||||
menu: "メニュー"
|
|
||||||
|
|
||||||
desktop/views/pages/user/user.header.vue:
|
desktop/views/pages/user/user.header.vue:
|
||||||
posts: "投稿"
|
posts: "投稿"
|
||||||
following: "フォロー"
|
following: "フォロー"
|
||||||
|
@ -1503,6 +1492,7 @@ desktop/views/pages/user/user.header.vue:
|
||||||
year: "年"
|
year: "年"
|
||||||
month: "月"
|
month: "月"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
follows-you: "フォローされています"
|
||||||
|
|
||||||
desktop/views/pages/user/user.timeline.vue:
|
desktop/views/pages/user/user.timeline.vue:
|
||||||
default: "投稿"
|
default: "投稿"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-activity">
|
<div>
|
||||||
<div ref="chart"></div>
|
<div ref="chart"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,7 +9,17 @@ import Vue from 'vue';
|
||||||
import ApexCharts from 'apexcharts';
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['user'],
|
props: {
|
||||||
|
user: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 21
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fetching: true,
|
fetching: true,
|
||||||
|
@ -21,7 +31,7 @@ export default Vue.extend({
|
||||||
this.$root.api('charts/user/notes', {
|
this.$root.api('charts/user/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
span: 'day',
|
span: 'day',
|
||||||
limit: 21
|
limit: this.limit
|
||||||
}).then(stats => {
|
}).then(stats => {
|
||||||
const normal = [];
|
const normal = [];
|
||||||
const reply = [];
|
const reply = [];
|
||||||
|
@ -32,7 +42,7 @@ export default Vue.extend({
|
||||||
const m = now.getMonth();
|
const m = now.getMonth();
|
||||||
const d = now.getDate();
|
const d = now.getDate();
|
||||||
|
|
||||||
for (let i = 0; i < 21; i++) {
|
for (let i = 0; i < this.limit; i++) {
|
||||||
const x = new Date(y, m, d - i);
|
const x = new Date(y, m, d - i);
|
||||||
normal.push([
|
normal.push([
|
||||||
x,
|
x,
|
||||||
|
@ -99,10 +109,3 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mk-activity
|
|
||||||
max-width 600px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
</style>
|
|
11
src/client/app/common/views/components/dummy.vue
Normal file
11
src/client/app/common/views/components/dummy.vue
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,8 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
|
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
|
||||||
:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
|
:class="{ wait, block, inline, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
|
:inline="inline"
|
||||||
>
|
>
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
|
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
|
||||||
|
@ -28,6 +29,11 @@ export default Vue.extend({
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
inline: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
mini: {
|
mini: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -128,6 +134,9 @@ export default Vue.extend({
|
||||||
border solid 1px var(--primary)
|
border solid 1px var(--primary)
|
||||||
border-radius 36px
|
border-radius 36px
|
||||||
|
|
||||||
|
&.inline
|
||||||
|
display inline-block
|
||||||
|
|
||||||
&.mini
|
&.mini
|
||||||
padding 0
|
padding 0
|
||||||
min-width 0
|
min-width 0
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import dummy from './dummy.vue';
|
||||||
import userName from './user-name.vue';
|
import userName from './user-name.vue';
|
||||||
import followButton from './follow-button.vue';
|
import followButton from './follow-button.vue';
|
||||||
import error from './error.vue';
|
import error from './error.vue';
|
||||||
|
@ -46,6 +47,7 @@ import formButton from './ui/form/button.vue';
|
||||||
import formRadio from './ui/form/radio.vue';
|
import formRadio from './ui/form/radio.vue';
|
||||||
|
|
||||||
Vue.component('mfm', misskeyFlavoredMarkdown);
|
Vue.component('mfm', misskeyFlavoredMarkdown);
|
||||||
|
Vue.component('mk-dummy', dummy);
|
||||||
Vue.component('mk-user-name', userName);
|
Vue.component('mk-user-name', userName);
|
||||||
Vue.component('mk-follow-button', followButton);
|
Vue.component('mk-follow-button', followButton);
|
||||||
Vue.component('mk-error', error);
|
Vue.component('mk-error', error);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import wSlideshow from './slideshow.vue';
|
||||||
import wTips from './tips.vue';
|
import wTips from './tips.vue';
|
||||||
import wNav from './nav.vue';
|
import wNav from './nav.vue';
|
||||||
import wHashtags from './hashtags.vue';
|
import wHashtags from './hashtags.vue';
|
||||||
|
import wInstance from './instance.vue';
|
||||||
|
|
||||||
Vue.component('mkw-analog-clock', wAnalogClock);
|
Vue.component('mkw-analog-clock', wAnalogClock);
|
||||||
Vue.component('mkw-nav', wNav);
|
Vue.component('mkw-nav', wNav);
|
||||||
|
@ -27,3 +28,4 @@ Vue.component('mkw-memo', wMemo);
|
||||||
Vue.component('mkw-rss', wRss);
|
Vue.component('mkw-rss', wRss);
|
||||||
Vue.component('mkw-version', wVersion);
|
Vue.component('mkw-version', wVersion);
|
||||||
Vue.component('mkw-hashtags', wHashtags);
|
Vue.component('mkw-hashtags', wHashtags);
|
||||||
|
Vue.component('mkw-instance', wInstance);
|
||||||
|
|
14
src/client/app/common/views/widgets/instance.vue
Normal file
14
src/client/app/common/views/widgets/instance.vue
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<template>
|
||||||
|
<div class="mkw-instance">
|
||||||
|
<mk-widget-container>
|
||||||
|
<mk-instance/>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
export default define({
|
||||||
|
name: 'instance'
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -12,19 +12,13 @@ import init from '../init';
|
||||||
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
||||||
import composeNotification from '../common/scripts/compose-notification';
|
import composeNotification from '../common/scripts/compose-notification';
|
||||||
|
|
||||||
import MkIndex from './views/pages/index.vue';
|
import MkHome from './views/home/home.vue';
|
||||||
import MkHome from './views/pages/home.vue';
|
import MkDeck from './views/deck/deck.vue';
|
||||||
import MkDeck from './views/pages/deck/deck.vue';
|
|
||||||
import MkUser from './views/pages/user/user.vue';
|
|
||||||
import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue';
|
import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue';
|
||||||
import MkFavorites from './views/pages/favorites.vue';
|
|
||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||||
import MkDrive from './views/pages/drive.vue';
|
import MkDrive from './views/pages/drive.vue';
|
||||||
import MkHomeCustomize from './views/pages/home-customize.vue';
|
|
||||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
|
import MkMessagingRoom from './views/pages/messaging-room.vue';
|
||||||
import MkNote from './views/pages/note.vue';
|
|
||||||
import MkSearch from './views/pages/search.vue';
|
import MkSearch from './views/pages/search.vue';
|
||||||
import MkTag from './views/pages/tag.vue';
|
|
||||||
import MkReversi from './views/pages/games/reversi.vue';
|
import MkReversi from './views/pages/games/reversi.vue';
|
||||||
import MkShare from './views/pages/share.vue';
|
import MkShare from './views/pages/share.vue';
|
||||||
import MkFollow from '../common/views/pages/follow.vue';
|
import MkFollow from '../common/views/pages/follow.vue';
|
||||||
|
@ -36,6 +30,7 @@ import PostFormWindow from './views/components/post-form-window.vue';
|
||||||
import RenoteFormWindow from './views/components/renote-form-window.vue';
|
import RenoteFormWindow from './views/components/renote-form-window.vue';
|
||||||
import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue';
|
import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue';
|
||||||
import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue';
|
import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue';
|
||||||
|
import MkHomeTimeline from './views/home/timeline.vue';
|
||||||
import Notification from './views/components/ui-notification.vue';
|
import Notification from './views/components/ui-notification.vue';
|
||||||
|
|
||||||
import { url } from '../config';
|
import { url } from '../config';
|
||||||
|
@ -44,7 +39,7 @@ import MiOS from '../mios';
|
||||||
/**
|
/**
|
||||||
* init
|
* init
|
||||||
*/
|
*/
|
||||||
init(async (launch) => {
|
init(async (launch, os) => {
|
||||||
Vue.mixin({
|
Vue.mixin({
|
||||||
methods: {
|
methods: {
|
||||||
$contextmenu(e, menu, opts?) {
|
$contextmenu(e, menu, opts?) {
|
||||||
|
@ -134,31 +129,37 @@ init(async (launch) => {
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/', name: 'index', component: MkIndex },
|
os.store.getters.isSignedIn && os.store.state.device.deckMode
|
||||||
{ path: '/home', name: 'home', component: MkHome },
|
? { path: '/', name: 'index', component: MkDeck, children: [
|
||||||
{ path: '/deck', name: 'deck', component: MkDeck },
|
{ path: '/@:user', name: 'user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default) },
|
||||||
{ path: '/i/customize-home', component: MkHomeCustomize },
|
{ path: '/notes/:note', name: 'note', component: () => import('./views/deck/deck.note-column.vue').then(m => m.default) },
|
||||||
{ path: '/i/favorites', component: MkFavorites },
|
{ path: '/tags/:tag', name: 'tag', component: () => import('./views/deck/deck.hashtag-column.vue').then(m => m.default) },
|
||||||
|
{ path: '/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: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) }
|
||||||
|
]},
|
||||||
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
||||||
{ path: '/i/drive', component: MkDrive },
|
{ path: '/i/drive', component: MkDrive },
|
||||||
{ path: '/i/drive/folder/:folder', component: MkDrive },
|
{ path: '/i/drive/folder/:folder', component: MkDrive },
|
||||||
{ path: '/i/settings', component: MkSettings },
|
{ path: '/i/settings', component: MkSettings },
|
||||||
{ path: '/selectdrive', component: MkSelectDrive },
|
{ path: '/selectdrive', component: MkSelectDrive },
|
||||||
{ path: '/search', component: MkSearch },
|
{ path: '/search', component: MkSearch },
|
||||||
{ path: '/tags/:tag', name: 'tag', component: MkTag },
|
|
||||||
{ path: '/share', component: MkShare },
|
{ path: '/share', component: MkShare },
|
||||||
{ path: '/games/reversi/:game?', component: MkReversi },
|
{ path: '/games/reversi/:game?', component: MkReversi },
|
||||||
{ path: '/@:user', name: 'user', component: MkUser },
|
|
||||||
{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
|
{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
|
||||||
{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
|
{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
|
||||||
{ path: '/notes/:note', name: 'note', component: MkNote },
|
|
||||||
{ path: '/authorize-follow', component: MkFollow },
|
{ path: '/authorize-follow', component: MkFollow },
|
||||||
{ path: '*', component: MkNotFound }
|
{ path: '*', component: MkNotFound }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Launch the app
|
// Launch the app
|
||||||
const [app, os] = launch(router);
|
const [app, _] = launch(router);
|
||||||
|
|
||||||
if (os.store.getters.isSignedIn) {
|
if (os.store.getters.isSignedIn) {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,396 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-home" :data-customize="customize">
|
|
||||||
<div class="customize" v-if="customize">
|
|
||||||
<router-link to="/"><fa icon="check"/>{{ $t('done') }}</router-link>
|
|
||||||
<div>
|
|
||||||
<div class="adder">
|
|
||||||
<p>{{ $t('add-widget') }}</p>
|
|
||||||
<select v-model="widgetAdderSelected">
|
|
||||||
<option value="profile">{{ $t('@.widgets.profile') }}</option>
|
|
||||||
<option value="analog-clock">{{ $t('@.widgets.analog-clock') }}</option>
|
|
||||||
<option value="calendar">{{ $t('@.widgets.calendar') }}</option>
|
|
||||||
<option value="timemachine">{{ $t('@.widgets.timemachine') }}</option>
|
|
||||||
<option value="activity">{{ $t('@.widgets.activity') }}</option>
|
|
||||||
<option value="rss">{{ $t('@.widgets.rss') }}</option>
|
|
||||||
<option value="trends">{{ $t('@.widgets.trends') }}</option>
|
|
||||||
<option value="photo-stream">{{ $t('@.widgets.photo-stream') }}</option>
|
|
||||||
<option value="slideshow">{{ $t('@.widgets.slideshow') }}</option>
|
|
||||||
<option value="version">{{ $t('@.widgets.version') }}</option>
|
|
||||||
<option value="broadcast">{{ $t('@.widgets.broadcast') }}</option>
|
|
||||||
<option value="notifications">{{ $t('@.widgets.notifications') }}</option>
|
|
||||||
<option value="users">{{ $t('@.widgets.users') }}</option>
|
|
||||||
<option value="polls">{{ $t('@.widgets.polls') }}</option>
|
|
||||||
<option value="post-form">{{ $t('@.widgets.post-form') }}</option>
|
|
||||||
<option value="messaging">{{ $t('@.widgets.messaging') }}</option>
|
|
||||||
<option value="memo">{{ $t('@.widgets.memo') }}</option>
|
|
||||||
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
|
||||||
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
|
||||||
<option value="server">{{ $t('@.widgets.server') }}</option>
|
|
||||||
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
|
||||||
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
|
||||||
</select>
|
|
||||||
<button @click="addWidget">{{ $t('add') }}</button>
|
|
||||||
</div>
|
|
||||||
<div class="trash">
|
|
||||||
<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
|
|
||||||
<p>{{ $t('@.trash') }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
|
|
||||||
<template v-if="customize">
|
|
||||||
<x-draggable v-for="place in ['left', 'right']"
|
|
||||||
:list="widgets[place]"
|
|
||||||
:class="place"
|
|
||||||
:data-place="place"
|
|
||||||
:options="{ group: 'x', animation: 150 }"
|
|
||||||
@sort="onWidgetSort"
|
|
||||||
:key="place"
|
|
||||||
>
|
|
||||||
<div v-for="widget in widgets[place]" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
|
|
||||||
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="desktop"/>
|
|
||||||
</div>
|
|
||||||
</x-draggable>
|
|
||||||
<div class="main">
|
|
||||||
<a @click="hint">{{ $t('@.customization-tips.title') }}</a>
|
|
||||||
<div>
|
|
||||||
<mk-post-form v-if="$store.state.settings.showPostFormOnTopOfTl"/>
|
|
||||||
<mk-timeline ref="tl" @loaded="onTlLoaded"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div v-for="place in ['left', 'right']" :class="place">
|
|
||||||
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp" platform="desktop"/>
|
|
||||||
</div>
|
|
||||||
<div class="main">
|
|
||||||
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
|
|
||||||
<mk-timeline class="tl" ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../i18n';
|
|
||||||
import * as XDraggable from 'vuedraggable';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
|
|
||||||
const defaultDesktopHomeWidgets = {
|
|
||||||
left: [
|
|
||||||
'profile',
|
|
||||||
'calendar',
|
|
||||||
'activity',
|
|
||||||
'rss',
|
|
||||||
'hashtags',
|
|
||||||
'photo-stream',
|
|
||||||
'version'
|
|
||||||
],
|
|
||||||
right: [
|
|
||||||
'broadcast',
|
|
||||||
'notifications',
|
|
||||||
'users',
|
|
||||||
'polls',
|
|
||||||
'server',
|
|
||||||
'nav',
|
|
||||||
'tips'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
//#region Construct home data
|
|
||||||
const _defaultDesktopHomeWidgets = [];
|
|
||||||
|
|
||||||
for (const widget of defaultDesktopHomeWidgets.left) {
|
|
||||||
_defaultDesktopHomeWidgets.push({
|
|
||||||
name: widget,
|
|
||||||
id: uuid(),
|
|
||||||
place: 'left',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const widget of defaultDesktopHomeWidgets.right) {
|
|
||||||
_defaultDesktopHomeWidgets.push({
|
|
||||||
name: widget,
|
|
||||||
id: uuid(),
|
|
||||||
place: 'right',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n('desktop/views/components/home.vue'),
|
|
||||||
components: {
|
|
||||||
XDraggable
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
customize: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'timeline'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
connection: null,
|
|
||||||
widgetAdderSelected: null,
|
|
||||||
trash: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
home(): any[] {
|
|
||||||
return this.$store.state.settings.home || [];
|
|
||||||
},
|
|
||||||
left(): any[] {
|
|
||||||
return this.home.filter(w => w.place == 'left');
|
|
||||||
},
|
|
||||||
right(): any[] {
|
|
||||||
return this.home.filter(w => w.place == 'right');
|
|
||||||
},
|
|
||||||
widgets(): any {
|
|
||||||
return {
|
|
||||||
left: this.left,
|
|
||||||
right: this.right
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
if (this.$store.state.settings.home == null) {
|
|
||||||
this.$root.api('i/update_home', {
|
|
||||||
home: _defaultDesktopHomeWidgets
|
|
||||||
}).then(() => {
|
|
||||||
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.connection = this.$root.stream.useSharedConnection('main');
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
hint() {
|
|
||||||
this.$root.dialog({
|
|
||||||
title: this.$t('@.customization-tips.title'),
|
|
||||||
text: this.$t('@.customization-tips.paragraph')
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onTlLoaded() {
|
|
||||||
this.$emit('loaded');
|
|
||||||
},
|
|
||||||
|
|
||||||
onWidgetContextmenu(widgetId) {
|
|
||||||
const w = (this.$refs[widgetId] as any)[0];
|
|
||||||
if (w.func) w.func();
|
|
||||||
},
|
|
||||||
|
|
||||||
onWidgetSort() {
|
|
||||||
this.saveHome();
|
|
||||||
},
|
|
||||||
|
|
||||||
onTrash(evt) {
|
|
||||||
this.saveHome();
|
|
||||||
},
|
|
||||||
|
|
||||||
addWidget() {
|
|
||||||
this.$store.dispatch('settings/addHomeWidget', {
|
|
||||||
name: this.widgetAdderSelected,
|
|
||||||
id: uuid(),
|
|
||||||
place: 'left',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
saveHome() {
|
|
||||||
const left = this.widgets.left;
|
|
||||||
const right = this.widgets.right;
|
|
||||||
this.$store.commit('settings/setHome', left.concat(right));
|
|
||||||
for (const w of left) w.place = 'left';
|
|
||||||
for (const w of right) w.place = 'right';
|
|
||||||
this.$root.api('i/update_home', {
|
|
||||||
home: this.home
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
warp(date) {
|
|
||||||
(this.$refs.tl as any).warp(date);
|
|
||||||
},
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
(this.$refs.tl as any).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mk-home
|
|
||||||
display block
|
|
||||||
|
|
||||||
&[data-customize]
|
|
||||||
padding-top 48px
|
|
||||||
background-image url('/assets/desktop/grid.svg')
|
|
||||||
|
|
||||||
> .main > .main
|
|
||||||
> a
|
|
||||||
display block
|
|
||||||
margin-bottom 8px
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
> div
|
|
||||||
cursor not-allowed !important
|
|
||||||
|
|
||||||
> *
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
&:not([data-customize])
|
|
||||||
> .main > *:empty
|
|
||||||
display none
|
|
||||||
|
|
||||||
> .customize
|
|
||||||
position fixed
|
|
||||||
z-index 1000
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
height 48px
|
|
||||||
color var(--text)
|
|
||||||
background var(--desktopHeaderBg)
|
|
||||||
box-shadow 0 1px 1px rgba(#000, 0.075)
|
|
||||||
|
|
||||||
> a
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
z-index 1001
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
padding 0 16px
|
|
||||||
line-height 48px
|
|
||||||
text-decoration none
|
|
||||||
color var(--primaryForeground)
|
|
||||||
background var(--primary)
|
|
||||||
transition background 0.1s ease
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background var(--primaryLighten10)
|
|
||||||
|
|
||||||
&:active
|
|
||||||
background var(--primaryDarken10)
|
|
||||||
transition background 0s ease
|
|
||||||
|
|
||||||
> [data-icon]
|
|
||||||
margin-right 8px
|
|
||||||
|
|
||||||
> div
|
|
||||||
display flex
|
|
||||||
margin 0 auto
|
|
||||||
max-width 1220px - 32px
|
|
||||||
|
|
||||||
> div
|
|
||||||
width 50%
|
|
||||||
|
|
||||||
&.adder
|
|
||||||
> p
|
|
||||||
display inline
|
|
||||||
line-height 48px
|
|
||||||
|
|
||||||
&.trash
|
|
||||||
border-left solid 1px var(--faceDivider)
|
|
||||||
|
|
||||||
> div
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
|
|
||||||
> p
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
line-height 48px
|
|
||||||
margin 0
|
|
||||||
text-align center
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
margin 0 auto
|
|
||||||
max-width 1240px
|
|
||||||
|
|
||||||
> *
|
|
||||||
.customize-container
|
|
||||||
cursor move
|
|
||||||
border-radius 6px
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
box-shadow 0 0 8px rgba(64, 120, 200, 0.3)
|
|
||||||
|
|
||||||
> *
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
padding 16px
|
|
||||||
width calc(100% - 280px * 2)
|
|
||||||
order 2
|
|
||||||
|
|
||||||
> .form
|
|
||||||
margin-bottom 16px
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
&.side
|
|
||||||
> .main
|
|
||||||
width calc(100% - 280px)
|
|
||||||
max-width 680px
|
|
||||||
|
|
||||||
> *:not(.main)
|
|
||||||
width 280px
|
|
||||||
padding 16px 0 16px 0
|
|
||||||
|
|
||||||
> *:not(:last-child)
|
|
||||||
margin-bottom 16px
|
|
||||||
|
|
||||||
> .left
|
|
||||||
padding-left 16px
|
|
||||||
order 1
|
|
||||||
|
|
||||||
> .right
|
|
||||||
padding-right 16px
|
|
||||||
order 3
|
|
||||||
|
|
||||||
&.side
|
|
||||||
@media (max-width 1000px)
|
|
||||||
> *:not(.main)
|
|
||||||
display none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
width 100%
|
|
||||||
max-width 700px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
&:not(.side)
|
|
||||||
@media (max-width 1200px)
|
|
||||||
> *:not(.main)
|
|
||||||
display none
|
|
||||||
|
|
||||||
> .main
|
|
||||||
width 100%
|
|
||||||
max-width 700px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -2,8 +2,6 @@ import Vue from 'vue';
|
||||||
|
|
||||||
import ui from './ui.vue';
|
import ui from './ui.vue';
|
||||||
import uiNotification from './ui-notification.vue';
|
import uiNotification from './ui-notification.vue';
|
||||||
import home from './home.vue';
|
|
||||||
import timeline from './timeline.vue';
|
|
||||||
import notes from './notes.vue';
|
import notes from './notes.vue';
|
||||||
import subNoteContent from './sub-note-content.vue';
|
import subNoteContent from './sub-note-content.vue';
|
||||||
import window from './window.vue';
|
import window from './window.vue';
|
||||||
|
@ -24,8 +22,6 @@ import widgetContainer from './widget-container.vue';
|
||||||
|
|
||||||
Vue.component('mk-ui', ui);
|
Vue.component('mk-ui', ui);
|
||||||
Vue.component('mk-ui-notification', uiNotification);
|
Vue.component('mk-ui-notification', uiNotification);
|
||||||
Vue.component('mk-home', home);
|
|
||||||
Vue.component('mk-timeline', timeline);
|
|
||||||
Vue.component('mk-notes', notes);
|
Vue.component('mk-notes', notes);
|
||||||
Vue.component('mk-sub-note-content', subNoteContent);
|
Vue.component('mk-sub-note-content', subNoteContent);
|
||||||
Vue.component('mk-window', window);
|
Vue.component('mk-window', window);
|
||||||
|
|
|
@ -31,9 +31,6 @@
|
||||||
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
|
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
|
||||||
<span slot="desc">{{ $t('auto-popout-desc') }}</span>
|
<span slot="desc">{{ $t('auto-popout-desc') }}</span>
|
||||||
</ui-switch>
|
</ui-switch>
|
||||||
<ui-switch v-model="deckNav">{{ $t('deck-nav') }}
|
|
||||||
<span slot="desc">{{ $t('deck-nav-desc') }}</span>
|
|
||||||
</ui-switch>
|
|
||||||
<ui-switch v-model="keepCw">{{ $t('keep-cw') }}
|
<ui-switch v-model="keepCw">{{ $t('keep-cw') }}
|
||||||
<span slot="desc">{{ $t('keep-cw-desc') }}</span>
|
<span slot="desc">{{ $t('keep-cw-desc') }}</span>
|
||||||
</ui-switch>
|
</ui-switch>
|
||||||
|
@ -89,9 +86,6 @@
|
||||||
<ui-radio v-model="navbar" value="left">{{ $t('navbar-position-left') }}</ui-radio>
|
<ui-radio v-model="navbar" value="left">{{ $t('navbar-position-left') }}</ui-radio>
|
||||||
<ui-radio v-model="navbar" value="right">{{ $t('navbar-position-right') }}</ui-radio>
|
<ui-radio v-model="navbar" value="right">{{ $t('navbar-position-right') }}</ui-radio>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
|
||||||
<ui-switch v-model="deckDefault">{{ $t('deck-default') }}</ui-switch>
|
|
||||||
</section>
|
|
||||||
<section>
|
<section>
|
||||||
<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</ui-switch>
|
<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</ui-switch>
|
||||||
<ui-switch v-model="useShadow">{{ $t('use-shadow') }}</ui-switch>
|
<ui-switch v-model="useShadow">{{ $t('use-shadow') }}</ui-switch>
|
||||||
|
@ -337,11 +331,6 @@ export default Vue.extend({
|
||||||
set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
deckNav: {
|
|
||||||
get() { return this.$store.state.settings.deckNav; },
|
|
||||||
set(value) { this.$store.commit('settings/set', { key: 'deckNav', value }); }
|
|
||||||
},
|
|
||||||
|
|
||||||
keepCw: {
|
keepCw: {
|
||||||
get() { return this.$store.state.settings.keepCw; },
|
get() { return this.$store.state.settings.keepCw; },
|
||||||
set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); }
|
set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); }
|
||||||
|
@ -367,11 +356,6 @@ export default Vue.extend({
|
||||||
set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
deckDefault: {
|
|
||||||
get() { return this.$store.state.device.deckDefault; },
|
|
||||||
set(value) { this.$store.commit('device/set', { key: 'deckDefault', value }); }
|
|
||||||
},
|
|
||||||
|
|
||||||
enableSounds: {
|
enableSounds: {
|
||||||
get() { return this.$store.state.device.enableSounds; },
|
get() { return this.$store.state.device.enableSounds; },
|
||||||
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
||||||
|
@ -534,8 +518,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
customizeHome() {
|
customizeHome() {
|
||||||
this.$router.push('/i/customize-home');
|
location.href = '/?customize';
|
||||||
this.$emit('done');
|
|
||||||
},
|
},
|
||||||
updateWallpaper() {
|
updateWallpaper() {
|
||||||
this.$chooseDriveFile({
|
this.$chooseDriveFile({
|
||||||
|
|
|
@ -44,13 +44,6 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<router-link to="/i/customize-home">
|
|
||||||
<i><fa icon="wrench"/></i>
|
|
||||||
<span>{{ $t('customize') }}</span>
|
|
||||||
<i><fa icon="angle-right"/></i>
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<router-link to="/i/settings">
|
<router-link to="/i/settings">
|
||||||
<i><fa icon="cog"/></i>
|
<i><fa icon="cog"/></i>
|
||||||
|
|
|
@ -2,20 +2,20 @@
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<ul>
|
<ul>
|
||||||
<template v-if="$store.getters.isSignedIn">
|
<template v-if="$store.getters.isSignedIn">
|
||||||
<template v-if="$store.state.device.deckDefault">
|
<template v-if="$store.state.device.deckMode">
|
||||||
<li class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
<li class="deck active" @click="goToTop">
|
||||||
<router-link to="/"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
<router-link to="/"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
|
<li class="home">
|
||||||
<router-link to="/home"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
<a @click="toggleDeckMode(false)"><fa icon="home"/><p>{{ $t('home') }}</p></a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<li class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
|
<li class="home active" @click="goToTop">
|
||||||
<router-link to="/"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
<router-link to="/"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
|
<li class="deck">
|
||||||
<router-link to="/deck"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
|
<a @click="toggleDeckMode(true)"><fa icon="columns"/><p>{{ $t('deck') }}</p></a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<li class="messaging">
|
<li class="messaging">
|
||||||
|
@ -70,6 +70,11 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleDeckMode(deck) {
|
||||||
|
this.$store.commit('device/set', { key: 'deckMode', value: deck });
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
|
||||||
onReversiInvited() {
|
onReversiInvited() {
|
||||||
this.hasGameInvitations = true;
|
this.hasGameInvitations = true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav" v-if="$store.getters.isSignedIn">
|
<div class="nav" v-if="$store.getters.isSignedIn">
|
||||||
<template v-if="$store.state.device.deckDefault">
|
<template v-if="$store.state.device.deckMode">
|
||||||
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
|
||||||
<router-link to="/"><fa icon="columns"/></router-link>
|
<router-link to="/"><fa icon="columns"/></router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,9 +27,9 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import Menu from '../../../../common/views/components/menu.vue';
|
import Menu from '../../../common/views/components/menu.vue';
|
||||||
import { countIf } from '../../../../../../prelude/array';
|
import { countIf } from '../../../../../prelude/array';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('deck'),
|
i18n: i18n('deck'),
|
||||||
|
@ -245,10 +245,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.$store.commit('device/set', {
|
this.$router.push('/');
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: null
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
goTop() {
|
goTop() {
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XDirect from './deck.direct.vue';
|
import XDirect from './deck.direct.vue';
|
||||||
|
|
88
src/client/app/desktop/views/deck/deck.favorites-column.vue
Normal file
88
src/client/app/desktop/views/deck/deck.favorites-column.vue
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<x-column>
|
||||||
|
<span slot="header">
|
||||||
|
<fa :icon="['fa', 'star']"/>{{ $t('favorites') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-notes ref="timeline" :more="existMore ? more : null"/>
|
||||||
|
</div>
|
||||||
|
</x-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import XColumn from './deck.column.vue';
|
||||||
|
import XNotes from './deck.notes.vue';
|
||||||
|
|
||||||
|
const fetchLimit = 10;
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XColumn,
|
||||||
|
XNotes,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
moreFetching: false,
|
||||||
|
existMore: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||||
|
this.$root.api('i/favorites', {
|
||||||
|
limit: fetchLimit + 1,
|
||||||
|
}).then(notes => {
|
||||||
|
if (notes.length == fetchLimit + 1) {
|
||||||
|
notes.pop();
|
||||||
|
this.existMore = true;
|
||||||
|
}
|
||||||
|
res(notes.map(x => x.note));
|
||||||
|
this.fetching = false;
|
||||||
|
this.$emit('loaded');
|
||||||
|
}, rej);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
more() {
|
||||||
|
this.moreFetching = true;
|
||||||
|
|
||||||
|
const promise = this.$root.api('i/favorites', {
|
||||||
|
limit: fetchLimit + 1,
|
||||||
|
untilId: (this.$refs.timeline as any).tail().id,
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.then(notes => {
|
||||||
|
if (notes.length == fetchLimit + 1) {
|
||||||
|
notes.pop();
|
||||||
|
} else {
|
||||||
|
this.existMore = false;
|
||||||
|
}
|
||||||
|
for (const n of notes) {
|
||||||
|
(this.$refs.timeline as any).append(n);
|
||||||
|
}
|
||||||
|
this.moreFetching = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.$refs.timeline.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
119
src/client/app/desktop/views/deck/deck.hashtag-column.vue
Normal file
119
src/client/app/desktop/views/deck/deck.hashtag-column.vue
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<template>
|
||||||
|
<x-column>
|
||||||
|
<span slot="header">
|
||||||
|
<fa icon="hashtag"/><span>{{ tag }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
|
||||||
|
<div ref="chart" class="chart"></div>
|
||||||
|
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
|
||||||
|
</div>
|
||||||
|
</x-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import XColumn from './deck.column.vue';
|
||||||
|
import XHashtagTl from './deck.hashtag-tl.vue';
|
||||||
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XColumn,
|
||||||
|
XHashtagTl
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
tag(): string {
|
||||||
|
return this.$route.params.tag;
|
||||||
|
},
|
||||||
|
|
||||||
|
tagTl(): any {
|
||||||
|
return {
|
||||||
|
query: [[this.tag]]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.$root.api('charts/hashtag', {
|
||||||
|
tag: this.tag,
|
||||||
|
span: 'hour',
|
||||||
|
limit: 24
|
||||||
|
}).then(stats => {
|
||||||
|
const local = [];
|
||||||
|
const remote = [];
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const y = now.getFullYear();
|
||||||
|
const m = now.getMonth();
|
||||||
|
const d = now.getDate();
|
||||||
|
const h = now.getHours();
|
||||||
|
|
||||||
|
for (let i = 0; i < 24; i++) {
|
||||||
|
const x = new Date(y, m, d, h - i);
|
||||||
|
local.push([x, stats.local.count[i]]);
|
||||||
|
remote.push([x, stats.remote.count[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chart = new ApexCharts(this.$refs.chart, {
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
height: 70,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
clipMarkers: false,
|
||||||
|
padding: {
|
||||||
|
top: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 16,
|
||||||
|
left: 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight',
|
||||||
|
width: 2
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: 'Local',
|
||||||
|
data: local
|
||||||
|
}, {
|
||||||
|
name: 'Remote',
|
||||||
|
data: remote
|
||||||
|
}],
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chart.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
|
||||||
|
background var(--deckColumnBg)
|
||||||
|
|
||||||
|
> .chart
|
||||||
|
margin-bottom 16px
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
> .tl
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
</style>
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XMentions from './deck.mentions.vue';
|
import XMentions from './deck.mentions.vue';
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XNotes from './deck.notes.vue';
|
import XNotes from './deck.notes.vue';
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n(),
|
i18n: i18n(),
|
||||||
|
@ -31,13 +31,6 @@ export default Vue.extend({
|
||||||
XNote
|
XNote
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
|
||||||
noteId: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
note: null,
|
note: null,
|
||||||
|
@ -45,11 +38,25 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$root.api('notes/show', { noteId: this.noteId }).then(note => {
|
this.fetch();
|
||||||
this.note = note;
|
},
|
||||||
this.fetching = false;
|
|
||||||
});
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
|
||||||
|
this.$root.api('notes/show', {
|
||||||
|
noteId: this.$route.params.note
|
||||||
|
}).then(note => {
|
||||||
|
this.note = note;
|
||||||
|
this.fetching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -38,10 +38,10 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import shouldMuteNote from '../../../../common/scripts/should-mute-note';
|
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||||
|
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
|
|
||||||
const displayLimit = 20;
|
const displayLimit = 20;
|
||||||
|
|
|
@ -96,8 +96,8 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import getNoteSummary from '../../../../../../misc/get-note-summary';
|
import getNoteSummary from '../../../../../misc/get-note-summary';
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XNotifications from './deck.notifications.vue';
|
import XNotifications from './deck.notifications.vue';
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XNotification from './deck.notification.vue';
|
import XNotification from './deck.notification.vue';
|
||||||
|
|
||||||
const displayLimit = 20;
|
const displayLimit = 20;
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XTl from './deck.tl.vue';
|
import XTl from './deck.tl.vue';
|
||||||
import XListTl from './deck.list-tl.vue';
|
import XListTl from './deck.list-tl.vue';
|
|
@ -93,13 +93,13 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import parseAcct from '../../../../../../misc/acct/parse';
|
import parseAcct from '../../../../../misc/acct/parse';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import XNotes from './deck.notes.vue';
|
import XNotes from './deck.notes.vue';
|
||||||
import XNote from '../../components/note.vue';
|
import XNote from '../components/note.vue';
|
||||||
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
import XUserMenu from '../../../common/views/components/user-menu.vue';
|
||||||
import { concat } from '../../../../../../prelude/array';
|
import { concat } from '../../../../../prelude/array';
|
||||||
import ApexCharts from 'apexcharts';
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
const fetchLimit = 10;
|
const fetchLimit = 10;
|
||||||
|
@ -112,13 +112,6 @@ export default Vue.extend({
|
||||||
XNote
|
XNote
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
|
||||||
acct: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: null,
|
user: null,
|
||||||
|
@ -144,119 +137,128 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$root.api('users/show', parseAcct(this.acct)).then(user => {
|
this.fetch();
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
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() {
|
initTl() {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
this.$root.api('users/notes', {
|
this.$root.api('users/notes', {
|
|
@ -9,11 +9,7 @@
|
||||||
</div>
|
</div>
|
||||||
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/>
|
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="temporaryColumn">
|
<router-view></router-view>
|
||||||
<x-user-column v-if="temporaryColumn.type == 'user'" :acct="temporaryColumn.acct" :key="temporaryColumn.acct"/>
|
|
||||||
<x-note-column v-else-if="temporaryColumn.type == 'note'" :note-id="temporaryColumn.noteId" :key="temporaryColumn.noteId"/>
|
|
||||||
<x-hashtag-column v-else-if="temporaryColumn.type == 'tag'" :tag="temporaryColumn.tag" :key="temporaryColumn.tag"/>
|
|
||||||
</template>
|
|
||||||
<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
|
<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
|
||||||
</div>
|
</div>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
|
@ -21,20 +17,17 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumnCore from './deck.column-core.vue';
|
import XColumnCore from './deck.column-core.vue';
|
||||||
import Menu from '../../../../common/views/components/menu.vue';
|
import Menu from '../../../common/views/components/menu.vue';
|
||||||
import MkUserListsWindow from '../../components/user-lists-window.vue';
|
import MkUserListsWindow from '../components/user-lists-window.vue';
|
||||||
|
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('deck'),
|
i18n: i18n('deck'),
|
||||||
components: {
|
components: {
|
||||||
XColumnCore,
|
XColumnCore
|
||||||
XUserColumn: () => import('./deck.user-column.vue').then(m => m.default),
|
|
||||||
XNoteColumn: () => import('./deck.note-column.vue').then(m => m.default),
|
|
||||||
XHashtagColumn: () => import('./deck.hashtag-column.vue').then(m => m.default)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -55,10 +48,6 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
temporaryColumn(): any {
|
|
||||||
return this.$store.state.device.deckTemporaryColumn;
|
|
||||||
},
|
|
||||||
|
|
||||||
keymap(): any {
|
keymap(): any {
|
||||||
return {
|
return {
|
||||||
't': this.focus
|
't': this.focus
|
||||||
|
@ -66,7 +55,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {/*
|
||||||
temporaryColumn() {
|
temporaryColumn() {
|
||||||
if (this.temporaryColumn != null) {
|
if (this.temporaryColumn != null) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
@ -76,7 +65,7 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
},
|
},
|
||||||
|
|
||||||
provide() {
|
provide() {
|
||||||
|
@ -86,8 +75,6 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$store.commit('navHook', this.onNav);
|
|
||||||
|
|
||||||
if (this.$store.state.settings.deck == null) {
|
if (this.$store.state.settings.deck == null) {
|
||||||
const deck = {
|
const deck = {
|
||||||
columns: [/*{
|
columns: [/*{
|
||||||
|
@ -133,8 +120,6 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$store.commit('navHook', null);
|
|
||||||
|
|
||||||
document.documentElement.style.overflow = 'auto';
|
document.documentElement.style.overflow = 'auto';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -143,39 +128,6 @@ export default Vue.extend({
|
||||||
return this.$refs[id][0];
|
return this.$refs[id][0];
|
||||||
},
|
},
|
||||||
|
|
||||||
onNav(to) {
|
|
||||||
if (!this.$store.state.settings.deckNav) return false;
|
|
||||||
|
|
||||||
if (to.name == 'user') {
|
|
||||||
this.$store.commit('device/set', {
|
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: {
|
|
||||||
type: 'user',
|
|
||||||
acct: to.params.user
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else if (to.name == 'note') {
|
|
||||||
this.$store.commit('device/set', {
|
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: {
|
|
||||||
type: 'note',
|
|
||||||
noteId: to.params.note
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else if (to.name == 'tag') {
|
|
||||||
this.$store.commit('device/set', {
|
|
||||||
key: 'deckTemporaryColumn',
|
|
||||||
value: {
|
|
||||||
type: 'tag',
|
|
||||||
tag: to.params.tag
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
add() {
|
add() {
|
||||||
this.$root.new(Menu, {
|
this.$root.new(Menu, {
|
||||||
source: this.$refs.add,
|
source: this.$refs.add,
|
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XColumn from './deck.column.vue';
|
import XColumn from './deck.column.vue';
|
||||||
import * as XDraggable from 'vuedraggable';
|
import * as XDraggable from 'vuedraggable';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
|
@ -1,16 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<div class="ecsvsegy" v-if="!fetching">
|
||||||
<main v-if="!fetching">
|
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
<template v-for="favorite in favorites">
|
||||||
<template v-for="favorite in favorites">
|
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
</template>
|
||||||
</template>
|
</sequential-entrance>
|
||||||
</sequential-entrance>
|
<div class="more" v-if="existMore">
|
||||||
<div class="more" v-if="existMore">
|
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||||
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
</mk-ui>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -72,10 +70,8 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
main
|
.ecsvsegy
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 16px
|
|
||||||
max-width 700px
|
|
||||||
|
|
||||||
> * > .post
|
> * > .post
|
||||||
margin-bottom 16px
|
margin-bottom 16px
|
400
src/client/app/desktop/views/home/home.vue
Normal file
400
src/client/app/desktop/views/home/home.vue
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
<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'
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
left(): any[] {
|
||||||
|
return this.home.filter(w => w.place == 'left');
|
||||||
|
},
|
||||||
|
right(): any[] {
|
||||||
|
return this.home.filter(w => w.place == 'right');
|
||||||
|
},
|
||||||
|
widgets(): any {
|
||||||
|
return {
|
||||||
|
left: this.left,
|
||||||
|
right: this.right
|
||||||
|
};
|
||||||
|
},
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
't': this.focus
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
const defaultDesktopHomeWidgets = {
|
||||||
|
left: [
|
||||||
|
'profile',
|
||||||
|
'calendar',
|
||||||
|
'activity',
|
||||||
|
'rss',
|
||||||
|
'hashtags',
|
||||||
|
'photo-stream',
|
||||||
|
'version'
|
||||||
|
],
|
||||||
|
right: [
|
||||||
|
'customize',
|
||||||
|
'broadcast',
|
||||||
|
'notifications',
|
||||||
|
'users',
|
||||||
|
'polls',
|
||||||
|
'server',
|
||||||
|
'nav',
|
||||||
|
'tips'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
//#region Construct home data
|
||||||
|
const _defaultDesktopHomeWidgets = [];
|
||||||
|
|
||||||
|
for (const widget of defaultDesktopHomeWidgets.left) {
|
||||||
|
_defaultDesktopHomeWidgets.push({
|
||||||
|
name: widget,
|
||||||
|
id: uuid(),
|
||||||
|
place: 'left',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const widget of defaultDesktopHomeWidgets.right) {
|
||||||
|
_defaultDesktopHomeWidgets.push({
|
||||||
|
name: widget,
|
||||||
|
id: uuid(),
|
||||||
|
place: 'right',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
if (this.$store.state.settings.home == null) {
|
||||||
|
this.$root.api('i/update_home', {
|
||||||
|
home: _defaultDesktopHomeWidgets
|
||||||
|
}).then(() => {
|
||||||
|
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.connection = this.$root.stream.useSharedConnection('main');
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.connection.dispose();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
hint() {
|
||||||
|
this.$root.dialog({
|
||||||
|
title: this.$t('@.customization-tips.title'),
|
||||||
|
text: this.$t('@.customization-tips.paragraph')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onTlLoaded() {
|
||||||
|
this.$emit('loaded');
|
||||||
|
},
|
||||||
|
|
||||||
|
onWidgetContextmenu(widgetId) {
|
||||||
|
const w = (this.$refs[widgetId] as any)[0];
|
||||||
|
if (w.func) w.func();
|
||||||
|
},
|
||||||
|
|
||||||
|
onWidgetSort() {
|
||||||
|
this.saveHome();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTrash(evt) {
|
||||||
|
this.saveHome();
|
||||||
|
},
|
||||||
|
|
||||||
|
addWidget() {
|
||||||
|
this.$store.dispatch('settings/addHomeWidget', {
|
||||||
|
name: this.widgetAdderSelected,
|
||||||
|
id: uuid(),
|
||||||
|
place: 'left',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
saveHome() {
|
||||||
|
const left = this.widgets.left;
|
||||||
|
const right = this.widgets.right;
|
||||||
|
this.$store.commit('settings/setHome', left.concat(right));
|
||||||
|
for (const w of left) w.place = 'left';
|
||||||
|
for (const w of right) w.place = 'right';
|
||||||
|
this.$root.api('i/update_home', {
|
||||||
|
home: this.home
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
|
location.href = '/';
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
(this.$refs.content as any).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.wqsofvpm
|
||||||
|
display block
|
||||||
|
|
||||||
|
&[data-customize]
|
||||||
|
padding-top 48px
|
||||||
|
background-image url('/assets/desktop/grid.svg')
|
||||||
|
|
||||||
|
> .main > .main
|
||||||
|
> a
|
||||||
|
display block
|
||||||
|
margin-bottom 8px
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
> div
|
||||||
|
cursor not-allowed !important
|
||||||
|
|
||||||
|
> *
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&:not([data-customize])
|
||||||
|
> .main > *:not(.main):empty
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .customize
|
||||||
|
position fixed
|
||||||
|
z-index 1000
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
height 48px
|
||||||
|
color var(--text)
|
||||||
|
background var(--desktopHeaderBg)
|
||||||
|
box-shadow 0 1px 1px rgba(#000, 0.075)
|
||||||
|
|
||||||
|
> a
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
z-index 1001
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
padding 0 16px
|
||||||
|
line-height 48px
|
||||||
|
text-decoration none
|
||||||
|
color var(--primaryForeground)
|
||||||
|
background var(--primary)
|
||||||
|
transition background 0.1s ease
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--primaryLighten10)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background var(--primaryDarken10)
|
||||||
|
transition background 0s ease
|
||||||
|
|
||||||
|
> [data-icon]
|
||||||
|
margin-right 8px
|
||||||
|
|
||||||
|
> div
|
||||||
|
display flex
|
||||||
|
margin 0 auto
|
||||||
|
max-width 1220px - 32px
|
||||||
|
|
||||||
|
> div
|
||||||
|
width 50%
|
||||||
|
|
||||||
|
&.adder
|
||||||
|
> p
|
||||||
|
display inline
|
||||||
|
line-height 48px
|
||||||
|
|
||||||
|
&.trash
|
||||||
|
border-left solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
> div
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
> p
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
line-height 48px
|
||||||
|
margin 0
|
||||||
|
text-align center
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
margin 0 auto
|
||||||
|
max-width 1240px
|
||||||
|
|
||||||
|
> *
|
||||||
|
.customize-container
|
||||||
|
cursor move
|
||||||
|
border-radius 6px
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
box-shadow 0 0 8px rgba(64, 120, 200, 0.3)
|
||||||
|
|
||||||
|
> *
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
padding 16px
|
||||||
|
width calc(100% - 280px * 2)
|
||||||
|
order 2
|
||||||
|
|
||||||
|
&.side
|
||||||
|
> .main
|
||||||
|
width calc(100% - 280px)
|
||||||
|
max-width 680px
|
||||||
|
|
||||||
|
> *:not(.main)
|
||||||
|
width 280px
|
||||||
|
padding 16px 0 16px 0
|
||||||
|
|
||||||
|
> *:not(:last-child)
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> .left
|
||||||
|
padding-left 16px
|
||||||
|
order 1
|
||||||
|
|
||||||
|
> .right
|
||||||
|
padding-right 16px
|
||||||
|
order 3
|
||||||
|
|
||||||
|
&.side
|
||||||
|
@media (max-width 1000px)
|
||||||
|
> *:not(.main)
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
width 100%
|
||||||
|
max-width 700px
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
&:not(.side)
|
||||||
|
@media (max-width 1200px)
|
||||||
|
> *:not(.main)
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .main
|
||||||
|
width 100%
|
||||||
|
max-width 700px
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,13 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<div v-if="!fetching" class="kcthdwmv">
|
||||||
<main v-if="!fetching">
|
<mk-note-detail :note="note"/>
|
||||||
<mk-note-detail :note="note"/>
|
<footer>
|
||||||
<footer>
|
<router-link v-if="note.next" :to="note.next"><fa icon="angle-left"/> {{ $t('next') }}</router-link>
|
||||||
<router-link v-if="note.next" :to="note.next"><fa icon="angle-left"/> {{ $t('next') }}</router-link>
|
<router-link v-if="note.prev" :to="note.prev">{{ $t('prev') }} <fa icon="angle-right"/></router-link>
|
||||||
<router-link v-if="note.prev" :to="note.prev">{{ $t('prev') }} <fa icon="angle-right"/></router-link>
|
</footer>
|
||||||
</footer>
|
</div>
|
||||||
</main>
|
|
||||||
</mk-ui>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -48,8 +46,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
main
|
.kcthdwmv
|
||||||
padding 16px
|
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
> footer
|
> footer
|
|
@ -1,11 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<div>
|
||||||
<header :class="$style.header">
|
|
||||||
<h1>#{{ $route.params.tag }}</h1>
|
|
||||||
</header>
|
|
||||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||||
</mk-ui>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -96,17 +93,11 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" module>
|
<style lang="stylus" module>
|
||||||
.header
|
|
||||||
width 100%
|
|
||||||
max-width 600px
|
|
||||||
margin 0 auto
|
|
||||||
color #555
|
|
||||||
|
|
||||||
.notes
|
.notes
|
||||||
width 600px
|
background var(--face)
|
||||||
margin 0 auto
|
box-shadow var(--shadow)
|
||||||
border solid 1px rgba(#000, 0.075)
|
border-radius var(--round)
|
||||||
border-radius 6px
|
overflow hidden
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.empty
|
.empty
|
|
@ -1,27 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-timeline">
|
<div class="mk-timeline">
|
||||||
<header>
|
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
|
||||||
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
|
<div class="main">
|
||||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
|
<header>
|
||||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
|
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
|
||||||
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
|
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
|
||||||
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
|
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
|
||||||
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
|
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
|
||||||
<div class="buttons">
|
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
|
||||||
<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>
|
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
|
||||||
<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>
|
<div class="buttons">
|
||||||
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
|
<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 @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></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>
|
||||||
</div>
|
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
|
||||||
</header>
|
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
|
||||||
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
|
</div>
|
||||||
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
|
</header>
|
||||||
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
|
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
|
||||||
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
|
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
|
||||||
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
|
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
|
||||||
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
|
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
|
||||||
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
|
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
|
||||||
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -30,7 +33,7 @@ import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import XCore from './timeline.core.vue';
|
import XCore from './timeline.core.vue';
|
||||||
import Menu from '../../../common/views/components/menu.vue';
|
import Menu from '../../../common/views/components/menu.vue';
|
||||||
import MkSettingsWindow from './settings-window.vue';
|
import MkSettingsWindow from '../components/settings-window.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/timeline.vue'),
|
i18n: i18n('desktop/views/components/timeline.vue'),
|
||||||
|
@ -184,81 +187,87 @@ export default Vue.extend({
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-timeline
|
.mk-timeline
|
||||||
background var(--face)
|
> .form
|
||||||
box-shadow var(--shadow)
|
margin-bottom 16px
|
||||||
border-radius var(--round)
|
box-shadow var(--shadow)
|
||||||
overflow hidden
|
border-radius var(--round)
|
||||||
|
|
||||||
> header
|
> .main
|
||||||
padding 0 8px
|
background var(--face)
|
||||||
z-index 10
|
box-shadow var(--shadow)
|
||||||
background var(--faceHeader)
|
border-radius var(--round)
|
||||||
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
|
overflow hidden
|
||||||
|
|
||||||
> .buttons
|
> header
|
||||||
position absolute
|
padding 0 8px
|
||||||
z-index 2
|
z-index 10
|
||||||
top 0
|
background var(--faceHeader)
|
||||||
right 0
|
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
|
||||||
padding-right 8px
|
|
||||||
|
|
||||||
> button
|
> .buttons
|
||||||
padding 0 8px
|
position absolute
|
||||||
font-size 0.9em
|
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
|
line-height 42px
|
||||||
color var(--faceTextButton)
|
font-size 12px
|
||||||
|
user-select none
|
||||||
> .badge
|
|
||||||
position absolute
|
|
||||||
top -4px
|
|
||||||
right 4px
|
|
||||||
font-size 10px
|
|
||||||
color var(--notificationIndicator)
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color var(--faceTextButtonHover)
|
|
||||||
|
|
||||||
&[data-active]
|
&[data-active]
|
||||||
color var(--primary)
|
color var(--primary)
|
||||||
cursor default
|
cursor default
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
&:before
|
&:before
|
||||||
content ""
|
content ""
|
||||||
display block
|
display block
|
||||||
position absolute
|
position absolute
|
||||||
bottom 0
|
bottom 0
|
||||||
left 0
|
left -8px
|
||||||
width 100%
|
width calc(100% + 16px)
|
||||||
height 2px
|
height 2px
|
||||||
background var(--primary)
|
background var(--primary)
|
||||||
|
|
||||||
> span
|
&:not([data-active])
|
||||||
display inline-block
|
color var(--desktopTimelineSrc)
|
||||||
padding 0 10px
|
cursor pointer
|
||||||
line-height 42px
|
|
||||||
font-size 12px
|
|
||||||
user-select none
|
|
||||||
|
|
||||||
&[data-active]
|
&:hover
|
||||||
color var(--primary)
|
color var(--desktopTimelineSrcHover)
|
||||||
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>
|
</style>
|
|
@ -15,6 +15,13 @@
|
||||||
</div>
|
</div>
|
||||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
<div class="actions" v-if="$store.getters.isSignedIn">
|
||||||
|
<template v-if="$store.state.i.id != user.id">
|
||||||
|
<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p>
|
||||||
|
<mk-follow-button :user="user" :inline="true" class="follow"/>
|
||||||
|
</template>
|
||||||
|
<ui-button @click="menu" ref="menu" :inline="true"><fa icon="ellipsis-h"/></ui-button>
|
||||||
|
</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,6 +52,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import * as age from 's-age';
|
import * as age from 's-age';
|
||||||
|
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/pages/user/user.header.vue'),
|
i18n: i18n('desktop/views/pages/user/user.header.vue'),
|
||||||
|
@ -99,6 +107,13 @@ export default Vue.extend({
|
||||||
this.$updateBanner().then(i => {
|
this.$updateBanner().then(i => {
|
||||||
this.user.bannerUrl = i.bannerUrl;
|
this.user.bannerUrl = i.bannerUrl;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
menu() {
|
||||||
|
this.$root.new(XUserMenu, {
|
||||||
|
source: this.$refs.menu.$el,
|
||||||
|
user: this.user
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -187,6 +202,10 @@ export default Vue.extend({
|
||||||
padding 16px 16px 16px 154px
|
padding 16px 16px 16px 154px
|
||||||
color var(--text)
|
color var(--text)
|
||||||
|
|
||||||
|
> .actions
|
||||||
|
> .follow
|
||||||
|
width 200px
|
||||||
|
|
||||||
> .fields
|
> .fields
|
||||||
margin-top 16px
|
margin-top 16px
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default Vue.extend({
|
||||||
> .img
|
> .img
|
||||||
flex 1 1 33%
|
flex 1 1 33%
|
||||||
width 33%
|
width 33%
|
||||||
height 80px
|
height 120px
|
||||||
background-position center center
|
background-position center center
|
||||||
background-size cover
|
background-size cover
|
||||||
background-clip content-box
|
background-clip content-box
|
109
src/client/app/desktop/views/home/user/user.vue
Normal file
109
src/client/app/desktop/views/home/user/user.vue
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
|
||||||
|
<div class="is-suspended" v-if="user.isSuspended"><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</div>
|
||||||
|
<div class="is-remote" v-if="user.host != null"><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></div>
|
||||||
|
<div class="main">
|
||||||
|
<x-header :user="user"/>
|
||||||
|
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
||||||
|
<x-integrations :user="user"/>
|
||||||
|
<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>-->
|
||||||
|
<div class="activity">
|
||||||
|
<mk-widget-container :show-header="true" :naked="false">
|
||||||
|
<template slot="header"><fa icon="chart-bar"/>{{ $t('activity') }}</template>
|
||||||
|
<x-activity :user="user" :limit="35" style="padding: 16px;"/>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
<x-photos :user="user"/>
|
||||||
|
<x-friends :user="user"/>
|
||||||
|
<x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
|
||||||
|
<x-timeline class="timeline" ref="tl" :user="user"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../../i18n';
|
||||||
|
import parseAcct from '../../../../../../misc/acct/parse';
|
||||||
|
import Progress from '../../../../common/scripts/loading';
|
||||||
|
import XHeader from './user.header.vue';
|
||||||
|
import XTimeline from './user.timeline.vue';
|
||||||
|
import XPhotos from './user.photos.vue';
|
||||||
|
import XFollowersYouKnow from './user.followers-you-know.vue';
|
||||||
|
import XFriends from './user.friends.vue';
|
||||||
|
import XIntegrations from './user.integrations.vue';
|
||||||
|
import XActivity from '../../../../common/views/components/activity.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
components: {
|
||||||
|
XHeader,
|
||||||
|
XTimeline,
|
||||||
|
XPhotos,
|
||||||
|
XFollowersYouKnow,
|
||||||
|
XFriends,
|
||||||
|
XIntegrations,
|
||||||
|
XActivity
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
user: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.fetching = true;
|
||||||
|
Progress.start();
|
||||||
|
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
||||||
|
this.user = user;
|
||||||
|
this.fetching = false;
|
||||||
|
Progress.done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
(this.$refs.tl as any).warp(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.xygkxeaeontfaokvqmiblezmhvhostak
|
||||||
|
width 100%
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
> .is-suspended
|
||||||
|
> .is-remote
|
||||||
|
margin-bottom 16px
|
||||||
|
padding 14px 16px
|
||||||
|
font-size 14px
|
||||||
|
box-shadow var(--shadow)
|
||||||
|
border-radius var(--round)
|
||||||
|
|
||||||
|
&.is-suspended
|
||||||
|
color var(--suspendedInfoFg)
|
||||||
|
background var(--suspendedInfoBg)
|
||||||
|
|
||||||
|
&.is-remote
|
||||||
|
color var(--remoteInfoFg)
|
||||||
|
background var(--remoteInfoBg)
|
||||||
|
|
||||||
|
> a
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
> .main
|
||||||
|
> *
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> .timeline
|
||||||
|
box-shadow var(--shadow)
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,112 +0,0 @@
|
||||||
<template>
|
|
||||||
<x-column>
|
|
||||||
<span slot="header">
|
|
||||||
<fa icon="hashtag"/><span>{{ tag }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
|
|
||||||
<div ref="chart" class="chart"></div>
|
|
||||||
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
|
|
||||||
</div>
|
|
||||||
</x-column>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import XColumn from './deck.column.vue';
|
|
||||||
import XHashtagTl from './deck.hashtag-tl.vue';
|
|
||||||
import ApexCharts from 'apexcharts';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
XColumn,
|
|
||||||
XHashtagTl
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
tag: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
tagTl(): any {
|
|
||||||
return {
|
|
||||||
query: [[this.tag]]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.$root.api('charts/hashtag', {
|
|
||||||
tag: this.tag,
|
|
||||||
span: 'hour',
|
|
||||||
limit: 24
|
|
||||||
}).then(stats => {
|
|
||||||
const local = [];
|
|
||||||
const remote = [];
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const y = now.getFullYear();
|
|
||||||
const m = now.getMonth();
|
|
||||||
const d = now.getDate();
|
|
||||||
const h = now.getHours();
|
|
||||||
|
|
||||||
for (let i = 0; i < 24; i++) {
|
|
||||||
const x = new Date(y, m, d, h - i);
|
|
||||||
local.push([x, stats.local.count[i]]);
|
|
||||||
remote.push([x, stats.remote.count[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chart = new ApexCharts(this.$refs.chart, {
|
|
||||||
chart: {
|
|
||||||
type: 'area',
|
|
||||||
height: 70,
|
|
||||||
sparkline: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
clipMarkers: false,
|
|
||||||
padding: {
|
|
||||||
top: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
left: 16
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
curve: 'straight',
|
|
||||||
width: 2
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: 'Local',
|
|
||||||
data: local
|
|
||||||
}, {
|
|
||||||
name: 'Remote',
|
|
||||||
data: remote
|
|
||||||
}],
|
|
||||||
xaxis: {
|
|
||||||
type: 'datetime',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chart.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
|
|
||||||
background var(--deckColumnBg)
|
|
||||||
|
|
||||||
> .chart
|
|
||||||
margin-bottom 16px
|
|
||||||
background var(--face)
|
|
||||||
|
|
||||||
> .tl
|
|
||||||
background var(--face)
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
<mk-home customize/>
|
|
||||||
</template>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<template>
|
|
||||||
<mk-ui>
|
|
||||||
<mk-home :mode="mode" @loaded="loaded" ref="home" v-hotkey.global="keymap"/>
|
|
||||||
</mk-ui>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Progress from '../../../common/scripts/loading';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
props: {
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'timeline'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
keymap(): any {
|
|
||||||
return {
|
|
||||||
't': this.focus
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.title = this.$root.instanceName;
|
|
||||||
|
|
||||||
Progress.start();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loaded() {
|
|
||||||
Progress.done();
|
|
||||||
},
|
|
||||||
focus() {
|
|
||||||
this.$refs.home.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,25 +0,0 @@
|
||||||
<template>
|
|
||||||
<component :is="page"></component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Home from './home.vue';
|
|
||||||
import Welcome from './welcome.vue';
|
|
||||||
import Deck from './deck/deck.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
Home,
|
|
||||||
Deck,
|
|
||||||
Welcome
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
page(): string {
|
|
||||||
if (!this.$store.getters.isSignedIn) return 'welcome';
|
|
||||||
return this.$store.state.device.deckDefault ? 'deck' : 'home';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="profile" v-if="$store.getters.isSignedIn">
|
|
||||||
<div class="friend-form" v-if="$store.state.i.id != user.id">
|
|
||||||
<mk-follow-button :user="user" block/>
|
|
||||||
<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="action-form">
|
|
||||||
<ui-button @click="menu" ref="menu">{{ $t('menu') }}</ui-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../../i18n';
|
|
||||||
import XUserMenu from '../../../../common/views/components/user-menu.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n('desktop/views/pages/user/user.profile.vue'),
|
|
||||||
props: ['user'],
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
menu() {
|
|
||||||
this.$root.new(XUserMenu, {
|
|
||||||
source: this.$refs.menu.$el,
|
|
||||||
user: this.user
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.profile
|
|
||||||
background var(--face)
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
> *:first-child
|
|
||||||
border-top none !important
|
|
||||||
|
|
||||||
> .friend-form
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
border-bottom solid 1px var(--faceDivider)
|
|
||||||
|
|
||||||
> .followed
|
|
||||||
margin 12px 0 0 0
|
|
||||||
padding 0
|
|
||||||
text-align center
|
|
||||||
line-height 24px
|
|
||||||
font-size 0.8em
|
|
||||||
color var(--text)
|
|
||||||
border-radius 4px
|
|
||||||
|
|
||||||
> .action-form
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
> *
|
|
||||||
width 100%
|
|
||||||
|
|
||||||
&:not(:last-child)
|
|
||||||
margin-bottom 12px
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,155 +0,0 @@
|
||||||
<template>
|
|
||||||
<mk-ui>
|
|
||||||
<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching">
|
|
||||||
<div class="is-suspended" v-if="user.isSuspended"><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</div>
|
|
||||||
<div class="is-remote" v-if="user.host != null"><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></div>
|
|
||||||
<main>
|
|
||||||
<div class="main">
|
|
||||||
<x-header :user="user"/>
|
|
||||||
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
|
||||||
<x-timeline class="timeline" ref="tl" :user="user"/>
|
|
||||||
</div>
|
|
||||||
<div class="side">
|
|
||||||
<div class="instance" v-if="!$store.getters.isSignedIn"><mk-instance/></div>
|
|
||||||
<x-profile :user="user"/>
|
|
||||||
<x-integrations :user="user"/>
|
|
||||||
<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>
|
|
||||||
<mk-activity :user="user"/>
|
|
||||||
<x-photos :user="user"/>
|
|
||||||
<x-friends :user="user"/>
|
|
||||||
<x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
|
|
||||||
<div class="nav"><mk-nav/></div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</mk-ui>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
import i18n from '../../../../i18n';
|
|
||||||
import parseAcct from '../../../../../../misc/acct/parse';
|
|
||||||
import Progress from '../../../../common/scripts/loading';
|
|
||||||
import XHeader from './user.header.vue';
|
|
||||||
import XTimeline from './user.timeline.vue';
|
|
||||||
import XProfile from './user.profile.vue';
|
|
||||||
import XPhotos from './user.photos.vue';
|
|
||||||
import XFollowersYouKnow from './user.followers-you-know.vue';
|
|
||||||
import XFriends from './user.friends.vue';
|
|
||||||
import XIntegrations from './user.integrations.vue';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
i18n: i18n(),
|
|
||||||
components: {
|
|
||||||
XHeader,
|
|
||||||
XTimeline,
|
|
||||||
XProfile,
|
|
||||||
XPhotos,
|
|
||||||
XFollowersYouKnow,
|
|
||||||
XFriends,
|
|
||||||
XIntegrations
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fetching: true,
|
|
||||||
user: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
$route: 'fetch'
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fetch();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch() {
|
|
||||||
this.fetching = true;
|
|
||||||
Progress.start();
|
|
||||||
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
|
|
||||||
this.user = user;
|
|
||||||
this.fetching = false;
|
|
||||||
Progress.done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
warp(date) {
|
|
||||||
(this.$refs.tl as any).warp(date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.xygkxeaeontfaokvqmiblezmhvhostak
|
|
||||||
max-width 980px
|
|
||||||
min-width 720px
|
|
||||||
padding 16px
|
|
||||||
margin 0 auto
|
|
||||||
|
|
||||||
> .is-suspended
|
|
||||||
> .is-remote
|
|
||||||
margin-bottom 16px
|
|
||||||
padding 14px 16px
|
|
||||||
font-size 14px
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
&.is-suspended
|
|
||||||
color var(--suspendedInfoFg)
|
|
||||||
background var(--suspendedInfoBg)
|
|
||||||
|
|
||||||
&.is-remote
|
|
||||||
color var(--remoteInfoFg)
|
|
||||||
background var(--remoteInfoBg)
|
|
||||||
|
|
||||||
> a
|
|
||||||
font-weight bold
|
|
||||||
|
|
||||||
> main
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
|
|
||||||
> .main
|
|
||||||
> .side
|
|
||||||
> *:not(:last-child)
|
|
||||||
margin-bottom 16px
|
|
||||||
|
|
||||||
> .main
|
|
||||||
flex 1
|
|
||||||
min-width 0 // SEE: http://kudakurage.hatenadiary.com/entry/2016/04/01/232722
|
|
||||||
margin-right 16px
|
|
||||||
|
|
||||||
> .timeline
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
|
|
||||||
> .side
|
|
||||||
width 275px
|
|
||||||
flex-shrink 0
|
|
||||||
|
|
||||||
> p
|
|
||||||
display block
|
|
||||||
margin 0
|
|
||||||
padding 0 12px
|
|
||||||
text-align center
|
|
||||||
font-size 0.8em
|
|
||||||
color var(--text)
|
|
||||||
|
|
||||||
> .instance
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
> .nav
|
|
||||||
padding 16px
|
|
||||||
font-size 12px
|
|
||||||
color var(--text)
|
|
||||||
background var(--face)
|
|
||||||
box-shadow var(--shadow)
|
|
||||||
border-radius var(--round)
|
|
||||||
|
|
||||||
a
|
|
||||||
color var(--text)99
|
|
||||||
|
|
||||||
i
|
|
||||||
color var(--text)
|
|
||||||
|
|
||||||
</style>
|
|
21
src/client/app/desktop/views/widgets/customize.vue
Normal file
21
src/client/app/desktop/views/widgets/customize.vue
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div class="mkw-customize">
|
||||||
|
<ui-button @click="customize()">{{ $t('@.customize-home') }}</ui-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
|
export default define({
|
||||||
|
name: 'customize',
|
||||||
|
}).extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
methods: {
|
||||||
|
customize(date) {
|
||||||
|
location.href = '/?customize';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -9,6 +9,7 @@ import wPolls from './polls.vue';
|
||||||
import wPostForm from './post-form.vue';
|
import wPostForm from './post-form.vue';
|
||||||
import wMessaging from './messaging.vue';
|
import wMessaging from './messaging.vue';
|
||||||
import wProfile from './profile.vue';
|
import wProfile from './profile.vue';
|
||||||
|
import wCustomize from './customize.vue';
|
||||||
|
|
||||||
Vue.component('mkw-notifications', wNotifications);
|
Vue.component('mkw-notifications', wNotifications);
|
||||||
Vue.component('mkw-timemachine', wTimemachine);
|
Vue.component('mkw-timemachine', wTimemachine);
|
||||||
|
@ -19,3 +20,4 @@ Vue.component('mkw-polls', wPolls);
|
||||||
Vue.component('mkw-post-form', wPostForm);
|
Vue.component('mkw-post-form', wPostForm);
|
||||||
Vue.component('mkw-messaging', wMessaging);
|
Vue.component('mkw-messaging', wMessaging);
|
||||||
Vue.component('mkw-profile', wProfile);
|
Vue.component('mkw-profile', wProfile);
|
||||||
|
Vue.component('mkw-customize', wCustomize);
|
||||||
|
|
|
@ -350,7 +350,7 @@ if (localStorage.getItem('should-refresh') == 'true') {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MiOSを初期化してコールバックする
|
// MiOSを初期化してコールバックする
|
||||||
export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void, sw = false) => {
|
export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS) => void, sw = false) => {
|
||||||
const os = new MiOS(sw);
|
const os = new MiOS(sw);
|
||||||
|
|
||||||
os.init(() => {
|
os.init(() => {
|
||||||
|
@ -436,11 +436,6 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// Navigation hook
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
next(os.store.state.navHook && os.store.state.navHook(to) ? false : undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (!document.hidden) {
|
if (!document.hidden) {
|
||||||
os.store.commit('clearBehindNotes');
|
os.store.commit('clearBehindNotes');
|
||||||
|
@ -507,6 +502,6 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
|
||||||
return [app, os] as [Vue, MiOS];
|
return [app, os] as [Vue, MiOS];
|
||||||
};
|
};
|
||||||
|
|
||||||
callback(launch);
|
callback(launch, os);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default Vue.extend({
|
||||||
XPhotos,
|
XPhotos,
|
||||||
XFriends,
|
XFriends,
|
||||||
XFollowersYouKnow,
|
XFollowersYouKnow,
|
||||||
XActivity: () => import('../../components/activity.vue').then(m => m.default)
|
XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
props: ['user']
|
props: ['user']
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default define({
|
||||||
}).extend({
|
}).extend({
|
||||||
i18n: i18n(),
|
i18n: i18n(),
|
||||||
components: {
|
components: {
|
||||||
XActivity: () => import('../components/activity.vue').then(m => m.default)
|
XActivity: () => import('../../../common/views/components/activity.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
func() {
|
func() {
|
||||||
|
|
|
@ -10,7 +10,6 @@ const defaultSettings = {
|
||||||
home: null,
|
home: null,
|
||||||
mobileHome: [],
|
mobileHome: [],
|
||||||
deck: null,
|
deck: null,
|
||||||
deckNav: true,
|
|
||||||
keepCw: false,
|
keepCw: false,
|
||||||
tagTimelines: [],
|
tagTimelines: [],
|
||||||
fetchOnScroll: true,
|
fetchOnScroll: true,
|
||||||
|
@ -67,8 +66,7 @@ const defaultDeviceSettings = {
|
||||||
deckColumnAlign: 'center',
|
deckColumnAlign: 'center',
|
||||||
deckColumnWidth: 'normal',
|
deckColumnWidth: 'normal',
|
||||||
mobileNotificationPosition: 'bottom',
|
mobileNotificationPosition: 'bottom',
|
||||||
deckTemporaryColumn: null,
|
deckMode: false,
|
||||||
deckDefault: false,
|
|
||||||
useOsDefaultEmojis: false,
|
useOsDefaultEmojis: false,
|
||||||
disableShowingAnimatedImages: false
|
disableShowingAnimatedImages: false
|
||||||
};
|
};
|
||||||
|
@ -82,7 +80,6 @@ export default (os: MiOS) => new Vuex.Store({
|
||||||
i: null,
|
i: null,
|
||||||
indicate: false,
|
indicate: false,
|
||||||
uiHeaderHeight: 0,
|
uiHeaderHeight: 0,
|
||||||
navHook: null,
|
|
||||||
behindNotes: []
|
behindNotes: []
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -107,10 +104,6 @@ export default (os: MiOS) => new Vuex.Store({
|
||||||
state.uiHeaderHeight = height;
|
state.uiHeaderHeight = height;
|
||||||
},
|
},
|
||||||
|
|
||||||
navHook(state, callback) {
|
|
||||||
state.navHook = callback;
|
|
||||||
},
|
|
||||||
|
|
||||||
pushBehindNote(state, note) {
|
pushBehindNote(state, note) {
|
||||||
if (note.userId === state.i.id) return;
|
if (note.userId === state.i.id) return;
|
||||||
if (state.behindNotes.some(n => n.id === note.id)) return;
|
if (state.behindNotes.some(n => n.id === note.id)) return;
|
||||||
|
|
Loading…
Reference in a new issue