From 95b157ac3e40408ab0298f3e247ef36feb6123be Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 15 Feb 2019 06:31:03 +0900
Subject: [PATCH] Add featured page

---
 locales/ja-JP.yml                             |  1 +
 src/client/app/desktop/script.ts              |  2 +
 .../views/components/ui.header.nav.vue        | 21 ++++---
 .../views/deck/deck.featured-column.vue       | 59 +++++++++++++++++++
 .../app/desktop/views/home/featured.vue       | 54 +++++++++++++++++
 src/client/app/mobile/script.ts               |  2 +-
 .../app/mobile/views/components/ui.nav.vue    |  3 +
 .../app/mobile/views/pages/featured.vue       | 49 +++++++++++++++
 8 files changed, 182 insertions(+), 9 deletions(-)
 create mode 100644 src/client/app/desktop/views/deck/deck.featured-column.vue
 create mode 100644 src/client/app/desktop/views/home/featured.vue
 create mode 100644 src/client/app/mobile/views/pages/featured.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index d025f300a6..659ece2d14 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -29,6 +29,7 @@ common:
   enter-password: "パスワードを入力してください"
   2fa: "二段階認証"
   customize-home: "ホームをカスタマイズ"
+  featured-notes: "ハイライト"
 
   got-it: "わかった"
   customization-tips:
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index 26fe273394..2454520618 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -134,6 +134,7 @@ init(async (launch, os) => {
 					{ path: '/@:user', name: 'user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default) },
 					{ path: '/notes/:note', name: 'note', component: () => import('./views/deck/deck.note-column.vue').then(m => m.default) },
 					{ path: '/tags/:tag', name: 'tag', component: () => import('./views/deck/deck.hashtag-column.vue').then(m => m.default) },
+					{ path: '/featured', component: () => import('./views/deck/deck.featured-column.vue').then(m => m.default) },
 					{ path: '/i/favorites', component: () => import('./views/deck/deck.favorites-column.vue').then(m => m.default) }
 				]}
 				: { path: '/', component: MkHome, children: [
@@ -141,6 +142,7 @@ init(async (launch, os) => {
 					{ path: '/@:user', name: 'user', component: () => import('./views/home/user/user.vue').then(m => m.default) },
 					{ path: '/notes/:note', name: 'note', component: () => import('./views/home/note.vue').then(m => m.default) },
 					{ path: '/tags/:tag', name: 'tag', component: () => import('./views/home/tag.vue').then(m => m.default) },
+					{ path: '/featured', component: () => import('./views/home/featured.vue').then(m => m.default) },
 					{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) }
 				]},
 			{ path: '/i/messaging/:user', component: MkMessagingRoom },
diff --git a/src/client/app/desktop/views/components/ui.header.nav.vue b/src/client/app/desktop/views/components/ui.header.nav.vue
index 44659eb0d8..23fa67d1f1 100644
--- a/src/client/app/desktop/views/components/ui.header.nav.vue
+++ b/src/client/app/desktop/views/components/ui.header.nav.vue
@@ -25,14 +25,17 @@
 					<template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template>
 				</a>
 			</li>
-			<li class="game">
-				<a @click="game">
-					<fa icon="gamepad"/>
-					<p>{{ $t('game') }}</p>
-					<template v-if="hasGameInvitations"><fa icon="circle"/></template>
-				</a>
-			</li>
 		</template>
+		<li class="featured">
+			<router-link to="/featured"><fa :icon="faNewspaper"/><p>{{ $t('@.featured-notes') }}</p></router-link>
+		</li>
+		<li class="game">
+			<a @click="game">
+				<fa icon="gamepad"/>
+				<p>{{ $t('game') }}</p>
+				<template v-if="hasGameInvitations"><fa icon="circle"/></template>
+			</a>
+		</li>
 	</ul>
 </div>
 </template>
@@ -42,13 +45,15 @@ import Vue from 'vue';
 import i18n from '../../../i18n';
 import MkMessagingWindow from './messaging-window.vue';
 import MkGameWindow from './game-window.vue';
+import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
 
 export default Vue.extend({
 	i18n: i18n('desktop/views/components/ui.header.nav.vue'),
 	data() {
 		return {
 			hasGameInvitations: false,
-			connection: null
+			connection: null,
+			faNewspaper
 		};
 	},
 	computed: {
diff --git a/src/client/app/desktop/views/deck/deck.featured-column.vue b/src/client/app/desktop/views/deck/deck.featured-column.vue
new file mode 100644
index 0000000000..bd2e99ba48
--- /dev/null
+++ b/src/client/app/desktop/views/deck/deck.featured-column.vue
@@ -0,0 +1,59 @@
+<template>
+<x-column>
+	<span slot="header">
+		<fa :icon="faNewspaper"/>{{ $t('@.featured-notes') }}
+	</span>
+
+	<div>
+		<x-notes ref="timeline" :more="null"/>
+	</div>
+</x-column>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../i18n';
+import XColumn from './deck.column.vue';
+import XNotes from './deck.notes.vue';
+import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
+
+export default Vue.extend({
+	i18n: i18n(),
+
+	components: {
+		XColumn,
+		XNotes,
+	},
+
+	data() {
+		return {
+			fetching: true,
+			faNewspaper
+		};
+	},
+
+	mounted() {
+		this.fetch();
+	},
+
+	methods: {
+		fetch() {
+			this.fetching = true;
+
+			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
+				this.$root.api('notes/featured', {
+					limit: 15,
+				}).then(notes => {
+					res(notes);
+					this.fetching = false;
+					this.$emit('loaded');
+				}, rej);
+			}));
+		},
+
+		focus() {
+			this.$refs.timeline.focus();
+		}
+	}
+});
+</script>
diff --git a/src/client/app/desktop/views/home/featured.vue b/src/client/app/desktop/views/home/featured.vue
new file mode 100644
index 0000000000..efebfe4821
--- /dev/null
+++ b/src/client/app/desktop/views/home/featured.vue
@@ -0,0 +1,54 @@
+<template>
+<div class="glowckho" v-if="!fetching">
+	<sequential-entrance animation="entranceFromTop" delay="25">
+		<template v-for="note in notes">
+			<mk-note-detail class="post" :note="note" :key="note.id"/>
+		</template>
+	</sequential-entrance>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import Progress from '../../../common/scripts/loading';
+
+export default Vue.extend({
+	data() {
+		return {
+			fetching: true,
+			notes: [],
+		};
+	},
+	created() {
+		this.fetch();
+	},
+	methods: {
+		fetch() {
+			Progress.start();
+			this.fetching = true;
+
+			this.$root.api('notes/featured', {
+				limit: 10
+			}).then(notes => {
+				this.notes = notes;
+				this.fetching = false;
+
+				Progress.done();
+			});
+		},
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.glowckho
+	margin 0 auto
+
+	> * > .post
+		margin-bottom 16px
+
+	> .more
+		margin 32px 16px 16px 16px
+		text-align center
+
+</style>
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index bbbdc0ebb0..f912c0d53b 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -12,7 +12,6 @@ import init from '../init';
 
 import MkIndex from './views/pages/index.vue';
 import MkSignup from './views/pages/signup.vue';
-import MkUser from './views/pages/user.vue';
 import MkSelectDrive from './views/pages/selectdrive.vue';
 import MkDrive from './views/pages/drive.vue';
 import MkNotifications from './views/pages/notifications.vue';
@@ -134,6 +133,7 @@ init((launch) => {
 			{ path: '/selectdrive', component: MkSelectDrive },
 			{ path: '/search', component: MkSearch },
 			{ path: '/tags/:tag', component: MkTag },
+			{ path: '/featured', name: 'featured', component: () => import('./views/pages/featured.vue').then(m => m.default) },
 			{ path: '/share', component: MkShare },
 			{ path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
 			{ path: '/@:user', component: () => import('./views/pages/user.vue').then(m => m.default) },
diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue
index c59d624f77..af2e3e4c6f 100644
--- a/src/client/app/mobile/views/components/ui.nav.vue
+++ b/src/client/app/mobile/views/components/ui.nav.vue
@@ -19,6 +19,7 @@
 					<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 					<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 					<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
+					<li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li>
 					<li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 				</ul>
 				<ul>
@@ -50,6 +51,7 @@
 import Vue from 'vue';
 import i18n from '../../../i18n';
 import { lang } from '../../../config';
+import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
 
 export default Vue.extend({
 	i18n: i18n('mobile/views/components/ui.nav.vue'),
@@ -62,6 +64,7 @@ export default Vue.extend({
 			aboutUrl: `/docs/${lang}/about`,
 			announcements: [],
 			searching: false,
+			faNewspaper
 		};
 	},
 
diff --git a/src/client/app/mobile/views/pages/featured.vue b/src/client/app/mobile/views/pages/featured.vue
new file mode 100644
index 0000000000..f97fb3b542
--- /dev/null
+++ b/src/client/app/mobile/views/pages/featured.vue
@@ -0,0 +1,49 @@
+<template>
+<mk-ui>
+	<span slot="header"><span style="margin-right:4px;"><fa :icon="faNewspaper"/></span>{{ $t('@.featured-notes') }}</span>
+
+	<main>
+		<sequential-entrance animation="entranceFromTop" delay="25">
+			<template v-for="note in notes">
+				<mk-note-detail class="post" :note="note" :key="note.id"/>
+			</template>
+		</sequential-entrance>
+	</main>
+</mk-ui>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../i18n';
+import Progress from '../../../common/scripts/loading';
+import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
+
+export default Vue.extend({
+	i18n: i18n(''),
+	data() {
+		return {
+			fetching: true,
+			notes: [],
+			faNewspaper
+		};
+	},
+	created() {
+		this.fetch();
+	},
+	methods: {
+		fetch() {
+			Progress.start();
+			this.fetching = true;
+
+			this.$root.api('notes/featured', {
+				limit: 10
+			}).then(notes => {
+				this.notes = notes;
+				this.fetching = false;
+
+				Progress.done();
+			});
+		},
+	}
+});
+</script>