From b3574eb2742701d1c25f6fa591bd43e14acd32c3 Mon Sep 17 00:00:00 2001
From: Jonas Sulzer <jonas@violoncello.ch>
Date: Wed, 2 Sep 2020 16:26:20 +0200
Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20composer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- button in appnavigation to create post -> open modal
- composer in timeline not sticky
- reply opens composer in modal
- use vuex to store composer reply and modal open state

Signed-off-by: Jonas Sulzer <jonas@violoncello.ch>
---
 src/App.vue                     | 30 +++++++++++++++++++
 src/components/Composer.vue     | 19 ++++--------
 src/components/TimelinePost.vue |  3 +-
 src/store/composer.js           | 51 +++++++++++++++++++++++++++++++++
 src/store/index.js              |  2 ++
 5 files changed, 91 insertions(+), 14 deletions(-)
 create mode 100644 src/store/composer.js

diff --git a/src/App.vue b/src/App.vue
index 78ac28d1..db008de4 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,10 +1,21 @@
 <template>
 	<Content v-if="!serverData.setup" app-name="social" :class="{public: serverData.public}">
 		<AppNavigation v-if="!serverData.public">
+			<AppNavigationNew
+				:text="t('social', 'New post')"
+				:disabled="false"
+				button-id="new-socialpost-button"
+				button-class="icon-add" 
+				@click="showNewPostModal" />
 			<AppNavigationItem v-for="item in menu.items" :key="item.key" :to="item.to"
 				:title="item.title" :icon="item.icon" :exact="true" />
 		</AppNavigation>
 		<AppContent>
+			<Modal v-if="newPostModal" @close="closeNewPostModal">
+				<div class="composer-in-modal">
+					<Composer />
+				</div>
+			</Modal>
 			<div v-if="serverData.isAdmin && !serverData.checks.success" class="setup social__wrapper">
 				<h3 v-if="!serverData.checks.checks.wellknown">
 					{{ t('social', '.well-known/webfinger isn\'t properly set up!') }}
@@ -85,6 +96,10 @@
 		text-decoration: underline;
 	}
 
+	.composer-in-modal {
+		width: 600px;
+		max-width: 100%;
+	}
 </style>
 
 <script>
@@ -92,8 +107,11 @@ import Content from '@nextcloud/vue/dist/Components/Content'
 import AppContent from '@nextcloud/vue/dist/Components/AppContent'
 import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
 import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
+import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew'
+import Modal from '@nextcloud/vue/dist/Components/Modal'
 
 import axios from '@nextcloud/axios'
+import Composer from './components/Composer.vue'
 import Search from './components/Search.vue'
 import currentuserMixin from './mixins/currentUserMixin'
 import { loadState } from '@nextcloud/initial-state'
@@ -106,6 +124,9 @@ export default {
 		AppContent,
 		AppNavigation,
 		AppNavigationItem,
+		AppNavigationNew,
+		Modal,
+		Composer,
 		Search
 	},
 	mixins: [currentuserMixin],
@@ -118,6 +139,9 @@ export default {
 		}
 	},
 	computed: {
+		newPostModal() {
+			return this.$store.getters.getComposerModalState
+		},
 		timeline: function() {
 			return this.$store.getters.getTimeline
 		},
@@ -242,6 +266,12 @@ export default {
 			if (data.source === 'timeline.direct' && timeline === 'direct') {
 				this.$store.dispatch('addToTimeline', [data.payload])
 			}
+		},
+		showNewPostModal() {
+			this.$store.commit('openComposerModal')
+		},
+		closeNewPostModal() {
+			this.$store.commit('closeComposerModal')
 		}
 	}
 }
diff --git a/src/components/Composer.vue b/src/components/Composer.vue
index f14a93a3..8bd0a7ad 100644
--- a/src/components/Composer.vue
+++ b/src/components/Composer.vue
@@ -36,10 +36,10 @@
 		</div>
 		<div v-if="replyTo" class="reply-to">
 			<p>
-				<span>In reply to</span>
+				<span>{{ t('social', 'In reply to') }}</span>
 				<actor-avatar :actor="replyTo.actor_info" :size="16" />
 				<strong>{{ replyTo.actor_info.account }}</strong>
-				<a class="icon-close" @click="replyTo=null" />
+				<a class="icon-close" @click="$store.commit('removeComposerReply')" />
 			</p>
 			<div class="reply-to-preview">
 				{{ replyTo.content }}
@@ -111,10 +111,6 @@
 	.new-post {
 		padding: 10px;
 		background-color: var(--color-main-background);
-		position: sticky;
-		top: 47px;
-		z-index: 100;
-		margin-bottom: 10px;
 	}
 
 	.new-post-author {
@@ -422,7 +418,6 @@ export default {
 			postAttachments: [],	// The toot's attachments
 			canType: true,
 			search: '',
-			replyTo: null,
 			tributeOptions: {
 				spaceSelectsMatch: true,
 				collection: [
@@ -517,6 +512,9 @@ export default {
 		}
 	},
 	computed: {
+		replyTo() {
+			return this.$store.getters.getReply
+		},
 		currentVisibilityIconClass() {
 			return this.visibilityIconClass(this.type)
 		},
@@ -607,11 +605,6 @@ export default {
 			]
 		}
 	},
-	mounted() {
-		this.$root.$on('composer-reply', (data) => {
-			this.replyTo = data
-		})
-	},
 	methods: {
 		AddAttachment() {
 			// TODO: handle (or prevent) mulitple files
@@ -815,7 +808,7 @@ export default {
 			this.loading = true
 			this.$store.dispatch('post', postData).then((response) => {
 				this.loading = false
-				this.replyTo = null
+				this.$store.commit('removeComposerReply')
 				this.post = ''
 				this.$refs.composerInput.innerText = this.post
 				this.postAttachments = []
diff --git a/src/components/TimelinePost.vue b/src/components/TimelinePost.vue
index d3ff2318..19a6a99c 100644
--- a/src/components/TimelinePost.vue
+++ b/src/components/TimelinePost.vue
@@ -144,7 +144,8 @@ export default {
 			return actorInfo.name !== '' ? actorInfo.name : actorInfo.preferredUsername
 		},
 		reply() {
-			this.$root.$emit('composer-reply', this.item)
+			this.$store.commit('setComposerReply', this.item)
+			this.$store.commit('openComposerModal')
 		},
 		boost() {
 			let params = {
diff --git a/src/store/composer.js b/src/store/composer.js
new file mode 100644
index 00000000..67f65e8e
--- /dev/null
+++ b/src/store/composer.js
@@ -0,0 +1,51 @@
+/*
+ * @copyright Copyright (c) 2030 Jonas Sulzer <jonas@violoncello.ch>
+ *
+ * @author Jonas Sulzer <jonas@violoncello.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const state = {
+	openInModal: false,
+	reply: null
+}
+const mutations = {
+	openComposerModal(state) {
+		state.openInModal = true
+	},
+	closeComposerModal(state) {
+		state.openInModal = false
+	},
+	setComposerReply(state, data) {
+		state.reply = data
+	},
+	removeComposerReply(state) {
+		state.reply = null
+	}
+}
+const getters = {
+	getComposerModalState(state) {
+		return state.openInModal
+	},
+	getReply(state) {
+		return state.reply
+	}
+}
+const actions = {}
+
+export default { state, mutations, getters, actions }
diff --git a/src/store/index.js b/src/store/index.js
index 4076bff7..716de29d 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -24,6 +24,7 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
 import timeline from './timeline'
+import composer from './composer'
 import account from './account'
 import settings from './settings'
 
@@ -34,6 +35,7 @@ const debug = process.env.NODE_ENV !== 'production'
 export default new Vuex.Store({
 	modules: {
 		timeline,
+		composer,
 		account,
 		settings
 	},