diff options
author | Julius Härtl <jus@bitgrid.net> | 2018-10-23 15:38:03 +0200 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2018-10-23 22:12:28 +0200 |
commit | 29e34e8fea5c957c30e54847a3d3ed0cf6b7e5eb (patch) | |
tree | 8418539e87e28915d7d20264caf783a8fd57213c /src | |
parent | 35cc3588080279c0fea116100a5f1016c8b6212c (diff) |
Add basic routing and profile info page
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'src')
-rw-r--r-- | src/App.vue | 145 | ||||
-rw-r--r-- | src/components/ProfileInfo.vue | 113 | ||||
-rw-r--r-- | src/components/TimelineEntry.vue | 14 | ||||
-rw-r--r-- | src/main.js | 20 | ||||
-rw-r--r-- | src/router.js | 58 | ||||
-rw-r--r-- | src/store/account.js | 48 | ||||
-rw-r--r-- | src/store/index.js | 12 | ||||
-rw-r--r-- | src/store/settings.js | 38 | ||||
-rw-r--r-- | src/views/Profile.vue | 155 | ||||
-rw-r--r-- | src/views/Timeline.vue | 225 |
10 files changed, 711 insertions, 117 deletions
diff --git a/src/App.vue b/src/App.vue index 1f6baeb3..805193f7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,131 +1,50 @@ <template> - <div id="content" class="app-social"> - <div id="app-navigation"> + <div class="app-social"> + <div id="app-navigation" v-if="!serverData.public"> <app-navigation :menu="menu"></app-navigation> </div> <div id="app-content"> - <div class="social__container"> - <div class="social__welcome" v-if="!infoHidden"> - <a class="close icon-close" @click="hideInfo()" href="#"><span class="hidden-visually">Close</span></a> - <h3>🎉{{ t('social', 'Nextcloud becomes part of the federated social networks!') }}</h3> - <p> - {{ t('social', 'We have automatically created a social account for you. Your social id is the same as the federated cloud id:') }} - <span class="social-id">{{ socialId }}</span> - </p> - </div> - <div class="social__timeline"> - <div class="new-post" data-id=""> - <div class="new-post-author"> - <div class="avatar currentUser" data-username="admin"><img src="/index.php/avatar/admin/32?v=1" alt=""></div> - </div> - <form class="new-post-form"> - <div class="author currentUser"> - {{ currentUser.displayName }} - <span class="social-id">{{ socialId }}</span> - </div> - <div contenteditable="true" class="message" placeholder="Share a thought…"></div> - <input class="submit icon-confirm has-tooltip" type="submit" value="" title="" data-original-title="Post"> - <div class="submitLoading icon-loading-small hidden"></div> - </form> - </div> - <timeline-entry v-for="entry in timeline" :key="entry.id" :item="entry"></timeline-entry> - </div> - </div> + <router-view :key="$route.fullPath"></router-view> </div> </div> </template> <style scoped> - .social__welcome { - max-width: 700px; - margin: 15px auto; - padding: 15px; - border-radius: 10px; - background-color: var(--color-background-dark); - } - - .social__welcome h3 { - margin-top: 0; - } - - .social__welcome .icon-close { - float:right; - } - - .social__welcome .social-id { - font-weight: bold; - } - - .social__timeline { - max-width: 700px; - margin: 15px auto; - } - - .new-post { - display: flex; - padding: 10px; - background-color: var(--color-main-background); - position: sticky; - top: 47px; - z-index: 100; - margin-bottom: 10px; - } - .new-post-author { - padding: 5px; - } - .author .social-id { - opacity: .5; - } - .new-post-form { - flex-grow: 1; - position: relative; - } - .message { + .app-social { width: 100%; } - [contenteditable=true]:empty:before{ - content: attr(placeholder); - display: block; /* For Firefox */ - opacity: .5; - } - input[type=submit] { - width: 44px; - height: 44px; - margin: 0; - padding: 13px; - background-color: transparent; - border: none; - opacity: 0.3; - position: absolute; - bottom: 0; - right: 0; - } - - #app-content { - position: relative; - } - </style> <script> import { PopoverMenu, - AppNavigation + AppNavigation, + Multiselect, + Avatar } from 'nextcloud-vue'; import TimelineEntry from './components/TimelineEntry'; + import ProfileInfo from './components/ProfileInfo'; export default { name: 'App', components: { - PopoverMenu, AppNavigation, TimelineEntry + PopoverMenu, AppNavigation, TimelineEntry, Multiselect, Avatar, + ProfileInfo }, data: function () { return { infoHidden: false, + state: [], }; }, beforeMount: function() { + // importing server data into the store + const serverDataElmt = document.getElementById('serverData'); + if (serverDataElmt !== null) { + this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server)); + } + let example = { message: 'Want to #DropDropbox? #DeleteGoogle? #decentralize? We got you covered, easy as a piece of 🥞\n' + '\n' + @@ -136,10 +55,19 @@ timestamp: '1 day ago' }; let data = []; - for (let i=0; i<20; i++) { + for (let i=0; i<3; i++) { example.id = Math.floor((Math.random() * 100)); data.push(example); } + data.push({ + message: 'Want to #DropDropbox? #DeleteGoogle? #decentralize? We got you covered, easy as a piece of 🥞\n' + + '\n' + + 'Get started right now: https://nextcloud.com/signup', + author: 'Admin☁️💻', + authorId: 'admin', + authorAvatar: OC.linkTo('social', 'img/nextcloud.png'), + timestamp: '1 day ago' + }) this.$store.commit('addToTimeline', data); }, methods: { @@ -148,6 +76,9 @@ } }, computed: { + url: function() { + return OC.linkTo('social', 'img/nextcloud.png'); + }, currentUser: function() { return OC.getCurrentUser(); }, @@ -157,21 +88,29 @@ timeline: function() { return this.$store.getters.getTimeline; }, + serverData: function() { + return this.$store.getters.getServerData; + }, menu: function () { let defaultCategories = [ { id: 'social-timeline', classes: [], - href: '#', icon: 'icon-category-monitoring', text: t('social', 'Timeline'), + router: { + name: 'timeline', + }, }, { - id: 'social-your-posts', + id: 'social-account', classes: [], - href: '#', icon: 'icon-user', - text: t('social', 'Your posts'), + text: t('social', 'Your account'), + router: { + name: 'profile', + params: {account: this.currentUser.uid } + }, }, { id: 'social-friends', diff --git a/src/components/ProfileInfo.vue b/src/components/ProfileInfo.vue new file mode 100644 index 00000000..7b5b2ab7 --- /dev/null +++ b/src/components/ProfileInfo.vue @@ -0,0 +1,113 @@ +<!-- + - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @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/>. + - + --> + +<template> + <div class="user-profile" v-if="uid && accountInfo"> + <div class="user-profile--info"> + <avatar :user="profileInfo.uid" :displayName="displayName" :size="128" /> + <h2 v-if="accountInfo.displayname">{{accountInfo.displayname.value}}</h2> + <p>{{accountInfo.cloudId}}</p> + <p v-if="accountInfo.website">Website: <a :href="accountInfo.website.value">{{accountInfo.website.value}}</a></p> + + <button v-if="!serverData.public" class="primary" @click="follow">Follow this user</button> + </div> + + + <ul class="user-profile--sections"> + <li id="social-timeline" class=""> + <a href="#" class="icon-category-monitoring">{{ accountInfo.posts }} posts</a> + </li> + <li id="social-timeline" class=""> + <a href="#" class="icon-category-social">{{ accountInfo.following }} following</a> + </li> + <li id="social-timeline" class=""> + <a href="#" class="icon-category-social">{{ accountInfo.followers }} followers</a> + </li> + </ul> + </div> +</template> +<style scoped> + .user-profile { + display: flex; + width: 100%; + text-align: center; + padding-top: 20px; + align-items: flex-end; + margin-bottom: 20px; + } + h2 { + margin-bottom: 5px; + } + + .user-profile--info { + width: 40%; + } + .user-profile--sections { + width: 60%; + display: flex; + margin-bottom: 30px; + } + .user-profile--sections li { + flex-grow: 1; + } + .user-profile--sections li a { + padding-left: 24px; + background-position: 0px center; + height: 40px; + } +</style> +<script> + + import { Avatar } from 'nextcloud-vue' + + export default { + name: 'ProfileInfo', + props: ['uid'], + components: { + Avatar + }, + methods: { + follow() { + console.log('TODO: implement following users'); + } + }, + computed: { + displayName() { + console.log(this.accountInfo); + return this.accountInfo.displayname.value || ''; + }, + serverData: function() { + return this.$store.getters.getServerData; + }, + accountInfo: function() { + return this.$store.getters.getAccount(this.uid); + }, + profileInfo() { + return { + uid: this.uid, + server: OC.getHost() + } + } + } + } + +</script> diff --git a/src/components/TimelineEntry.vue b/src/components/TimelineEntry.vue index 50c1609e..899861d1 100644 --- a/src/components/TimelineEntry.vue +++ b/src/components/TimelineEntry.vue @@ -1,11 +1,15 @@ <template> <div class="timeline-entry"> <div class="entry-content"> - <div class="post-avatar"><img alt="" width="32" height="32" :src="item.authorAvatar"></div> + <div class="post-avatar"> + <avatar :size="32" :url="item.authorAvatar" /> + </div> <div class="post-content"> <div class="post-author-wrapper"> - <span class="post-author">{{ item.author }}</span> - <span class="post-author-id">{{ item.authorId }}</span> + <router-link :to="{ name: 'profile', params: { account: item.authorId }}"> + <span class="post-author">{{ item.author }}</span> + <span class="post-author-id">{{ item.authorId }}</span> + </router-link> </div> <div class="post-message" v-html="formatedMessage"> </div> @@ -16,11 +20,13 @@ </template> <script> + import { Avatar } from 'nextcloud-vue' + export default { name: 'TimelineEntry', props: ['item'], components: { - + Avatar }, data: function () { return { diff --git a/src/main.js b/src/main.js index 892e527b..5f9d65a0 100644 --- a/src/main.js +++ b/src/main.js @@ -19,12 +19,25 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ +import "@babel/polyfill"; import Vue from 'vue' -import App from './App' +import {sync} from 'vuex-router-sync'; +import App from './App' import store from './store'; +import router from './router'; + +sync(store, router); + +// CSP config for webpack dynamic chunk loading +// eslint-disable-next-line +__webpack_nonce__ = btoa(OC.requestToken) +// Correct the root of the app for chunk loading +// OC.linkTo matches the apps folders +// eslint-disable-next-line +__webpack_public_path__ = OC.linkTo('social', 'js/') Vue.prototype.t = t Vue.prototype.n = n @@ -33,6 +46,7 @@ Vue.prototype.OCA = OCA /* eslint-disable-next-line no-new */ new Vue({ - render: h => h(App), + router: router, + render: h => h(App), store: store -}).$mount('#content') +}).$mount('#vue-content') diff --git a/src/router.js b/src/router.js new file mode 100644 index 00000000..4758068d --- /dev/null +++ b/src/router.js @@ -0,0 +1,58 @@ +/** + * @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author Julius Härtl <jus@bitgrid.net> + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @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/>. + * + */ +import Vue from 'vue'; +import Router from 'vue-router'; + +// Dynamic loading +const Timeline = () => import('./views/Timeline'); +const Profile = () => import('./views/Profile'); +Vue.use(Router); + +export default new Router({ + mode: 'history', + // if index.php is in the url AND we got this far, then it's working: + // let's keep using index.php in the url + base: OC.generateUrl(''), + linkActiveClass: 'active', + routes: [ + { + path: '/:index(index.php/)?apps/social/', + component: Timeline, + props: true, + name: 'timeline' + }, + { + path: '/:index(index.php/)?apps/social/account/:account', + component: Profile, + props: true, + name: 'profile', + }, + { + path: '/:index(index.php/)?apps/social/:account', + component: Profile, + props: true, + name: 'public', + } + ] +}); diff --git a/src/store/account.js b/src/store/account.js new file mode 100644 index 00000000..e1adaac0 --- /dev/null +++ b/src/store/account.js @@ -0,0 +1,48 @@ +/* + * @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @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/>. + * + */ + +import axios from 'nextcloud-axios'; +import Vue from 'vue'; + +const state = { + accounts: {} +}; +const mutations = { + addAccount(state, {uid, data}) { + Vue.set(state.accounts, uid, data); + } +}; +const getters = { + getAccount(state) { + return (uid) => state.accounts[uid]; + } +}; +const actions = { + fetchAccountInfo(context, uid) { + axios.get(OC.generateUrl('apps/social/local/account/' + uid)).then((response) => { + console.log(response.data); + context.commit('addAccount', {uid: uid, data:response.data}); + }); + } +}; + +export default {state, mutations, getters, actions}; diff --git a/src/store/index.js b/src/store/index.js index 061ad400..72cc73de 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -24,20 +24,18 @@ import Vue from 'vue'; import Vuex from 'vuex'; import timeline from './timeline'; +import account from './account'; +import settings from './settings'; Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production'; -const mutations = { - -}; - export default new Vuex.Store({ modules: { - timeline + timeline, + account, + settings }, strict: debug, - - mutations }); diff --git a/src/store/settings.js b/src/store/settings.js new file mode 100644 index 00000000..10fcbe5b --- /dev/null +++ b/src/store/settings.js @@ -0,0 +1,38 @@ +/* + * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @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 = { + serverData: {} +}; +const mutations = { + setServerData(state, data) { + state.serverData = data; + } +}; +const getters = { + getServerData(state) { + return state.serverData; + } +}; +const actions = {}; + +export default {state, mutations, getters, actions}; diff --git a/src/views/Profile.vue b/src/views/Profile.vue new file mode 100644 index 00000000..27215af7 --- /dev/null +++ b/src/views/Profile.vue @@ -0,0 +1,155 @@ +<!-- + - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - + - @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/>. + - + --> + +<template> + <div class="social__wrapper"> + <profile-info :uid="uid"></profile-info> + <div class="social__container"> + <div class="social__timeline"> + <timeline-entry v-for="entry in timeline" :item="entry"></timeline-entry> + </div> + </div> + </div> +</template> + +<style scoped> + .social__wrapper { + max-width: 700px; + margin: 15px auto; + } + + .social__welcome { + max-width: 700px; + margin: 15px auto; + padding: 15px; + border-radius: 10px; + background-color: var(--color-background-dark); + } + + .social__welcome h3 { + margin-top: 0; + } + + .social__welcome .icon-close { + float:right; + } + + .social__welcome .social-id { + font-weight: bold; + } + + .social__timeline { + max-width: 700px; + margin: 15px auto; + } + + .new-post { + display: flex; + padding: 10px; + background-color: var(--color-main-background); + position: sticky; + top: 47px; + z-index: 100; + margin-bottom: 10px; + } + .new-post-author { + padding: 5px; + } + .author .social-id { + opacity: .5; + } + .new-post-form { + flex-grow: 1; + position: relative; + } + .message { + width: 100%; + } + [contenteditable=true]:empty:before{ + content: attr(placeholder); + display: block; /* For Firefox */ + opacity: .5; + } + input[type=submit] { + width: 44px; + height: 44px; + margin: 0; + padding: 13px; + background-color: transparent; + border: none; + opacity: 0.3; + position: absolute; + bottom: 0; + right: 0; + } + + #app-content { + position: relative; + } + +</style> + + +<script> + import { + PopoverMenu, + AppNavigation, + Multiselect, + Avatar + } from 'nextcloud-vue'; + import TimelineEntry from './../components/TimelineEntry'; + import ProfileInfo from './../components/ProfileInfo'; + + export default { + name: 'Timeline', + components: { + PopoverMenu, AppNavigation, TimelineEntry, Multiselect, Avatar, + ProfileInfo + }, + beforeMount() { + this.uid = this.$route.params.account; + this.$store.dispatch('fetchAccountInfo', this.uid); + }, + data: function () { + return { + state: [], + uid: null, + }; + }, + methods: { + }, + computed: { + serverData: function() { + return this.$store.getters.getServerData; + }, + currentUser: function() { + return OC.getCurrentUser(); + }, + socialId: function() { + return '@' + OC.getCurrentUser().uid + '@' + OC.getHost(); + }, + timeline: function() { + return this.$store.getters.getTimeline; + } + } + } +</script> diff --git a/src/views/Timeline.vue b/src/views/Timeline.vue new file mode 100644 index 00000000..503e34bf --- /dev/null +++ b/src/views/Timeline.vue @@ -0,0 +1,225 @@ +<template> + <div class="social__wrapper"> + <div class="social__container"> + <div class="social__welcome" v-if="!infoHidden"> + <a class="close icon-close" @click="hideInfo()" href="#"><span class="hidden-visually">Close</span></a> + <h3>🎉{{ t('social', 'Nextcloud becomes part of the federated social networks!') }}</h3> + <p> + {{ t('social', 'We have automatically created a social account for you. Your social id is the same as the federated cloud id:') }} + <span class="social-id">{{ socialId }}</span> + </p> + </div> + <div class="social__timeline"> + <div class="new-post" data-id=""> + <div class="new-post-author"> + <avatar :user="currentUser.uid" :displayName="currentUser.displayName" :size="32" /> + </div> + <form class="new-post-form"> + <div class="author currentUser"> + {{ currentUser.displayName }} + <span class="social-id">{{ socialId }}</span> + </div> + <div contenteditable="true" class="message" placeholder="Share a thought…"></div> + <input class="submit icon-confirm has-tooltip" type="submit" value="" title="" data-original-title="Post"> + <div class="submitLoading icon-loading-small hidden"></div> + </form> + </div> + <timeline-entry v-for="entry in timeline" :item="entry"></timeline-entry> + </div> + </div> + </div> +</template> + +<style scoped> + .social__wrapper { + display: flex; + } + + .social__container { + flex-grow: 1; + } + .social__profile { + max-width: 500px; + flex-grow: 1; + border-right: 1px solid var(--color-background-dark); + text-align: center; + padding-top: 20px; + } + .social__welcome { + max-width: 700px; + margin: 15px auto; + padding: 15px; + border-radius: 10px; + background-color: var(--color-background-dark); + } + + .social__welcome h3 { + margin-top: 0; + } + + .social__welcome .icon-close { + float:right; + } + + .social__welcome .social-id { + font-weight: bold; + } + + .social__timeline { + max-width: 700px; + margin: 15px auto; + } + + .new-post { + display: flex; + padding: 10px; + background-color: var(--color-main-background); + position: sticky; + top: 47px; + z-index: 100; + margin-bottom: 10px; + } + .new-post-author { + padding: 5px; + } + .author .social-id { + opacity: .5; + } + .new-post-form { + flex-grow: 1; + position: relative; + } + .message { + width: 100%; + } + [contenteditable=true]:empty:before{ + content: attr(placeholder); + display: block; /* For Firefox */ + opacity: .5; + } + input[type=submit] { + width: 44px; + height: 44px; + margin: 0; + padding: 13px; + background-color: transparent; + border: none; + opacity: 0.3; + position: absolute; + bottom: 0; + right: 0; + } + + #app-content { + position: relative; + } + +</style> + + +<script> + import { + PopoverMenu, + AppNavigation, + Multiselect, + Avatar + } from 'nextcloud-vue'; + import TimelineEntry from './../components/TimelineEntry'; + + export default { + name: 'Timeline', + components: { + PopoverMenu, AppNavigation, TimelineEntry, Multiselect, Avatar + }, + data: function () { + return { + infoHidden: false, + state: [], + }; + }, + beforeMount: function() { + let example = { + message: 'Want to #DropDropbox? #DeleteGoogle? #decentralize? We got you covered, easy as a piece of 🥞\n' + + '\n' + + 'Get started right now: https://nextcloud.com/signup', + author: 'Nextcloud 📱☁️💻', + authorId: '@nextcloud@mastodon.xyz', + authorAvatar: OC.linkTo('social', 'img/nextcloud.png'), + timestamp: '1 day ago' + }; + let data = []; + for (let i=0; i<20; i++) { + let item = Object.assign({}, example); + item.id = i; + data.push(item); |