Add composer component with emoji picker and basic @-mentions

Signed-off-by: Julius Härtl <jus@bitgrid.net>
pull/18/head
Julius Härtl 2018-11-12 21:12:28 +01:00
rodzic 3a3cf046b9
commit 62c68bb069
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4C614C6ED2CDE6DF
3 zmienionych plików z 268 dodań i 63 usunięć

Wyświetl plik

@ -0,0 +1,231 @@
<!--
- @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="new-post" data-id="">
<div class="new-post-author">
<avatar :user="currentUser.uid" :display-name="currentUser.displayName" :size="32" />
</div>
<form class="new-post-form">
<div class="author currentUser">
{{ currentUser.displayName }}
<span class="social-id">{{ socialId }}</span>
</div>
<vue-tribute :options="tributeOptions">
<div contenteditable="true" ref="composerInput" class="message" placeholder="Share a thought…" @input="updateInput" v-model="post"></div>
</vue-tribute>
<input class="submit icon-confirm has-tooltip" type="submit" value=""
title="" data-original-title="Post">
<div class="submitLoading icon-loading-small hidden" />
</form>
<div class="options">
<emoji-picker @emoji="insert" :search="search">
<div slot="emoji-invoker" slot-scope="{ events }" v-on="events">
<button type="button" v-tooltip="'Insert emoji'">😄</button>
</div>
<div slot="emoji-picker" slot-scope="{ emojis, insert, display }" class="emoji-picker popovermenu">
<div>
<div>
<input type="text" v-model="search">
</div>
<div>
<div v-for="(emojiGroup, category) in emojis" :key="category">
<h5>{{ category }}</h5>
<div>
<span class="emoji" v-for="(emoji, emojiName) in emojiGroup" :key="emojiName" @click="insert(emoji)" :title="emojiName">{{ emoji }}</span>
</div>
</div>
</div>
</div>
</div>
</emoji-picker>
</div>
</div>
</template>
<style scoped>
.new-post {
display: flex;
flex-wrap: wrap;
padding: 10px;
background-color: var(--color-main-background);
position: sticky;
top: 47px;
z-index: 100;
margin-bottom: 10px;
}
.new-post-author {
padding: 5px;
}
.new-post-form {
flex-grow: 1;
position: relative;
}
.message {
width: 100%;
}
.author .social-id {
opacity: .5;
}
[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;
}
.options {
display: flex;
align-items: flex-end;
width: 100%;
flex-direction: column;
}
.emoji-picker.popovermenu {
display: block;
padding: 5px;
width: 200px;
height: 200px;
}
.emoji-picker > div {
overflow: hidden;
overflow-y: scroll;
height: 190px;
}
.emoji-picker input {
width: 100%;
}
.emoji-picker .emoji {
padding: 3px;
}
</style>
<style>
/* Tribute-specific styles
* TODO: properly scope component css
*/
.tribute-container {
position: absolute;
top: 0;
left: 0;
height: auto;
max-height: 300px;
max-width: 500px;
overflow: auto;
display: block;
z-index: 999999;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(#000, 0.13);
}
.tribute-container ul {
margin: 0;
margin-top: 2px;
padding: 0;
list-style: none;
background: #fff;
border-radius: 4px;
border: 1px solid rgba(#000, 0.13);
background-clip: padding-box;
overflow: hidden;
}
.tribute-container li {
color: #3f5efb;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
}
.tribute-container li.highlight,
.tribute-container li:hover {
background: #3f5efb;
color: #fff;
}
.tribute-container li span {
font-weight: bold;
}
.tribute-container li.no-match {
cursor: default;
}
.tribute-container .menu-highlighted {
font-weight: bold;
}
</style>
<script>
import { Avatar } from 'nextcloud-vue'
import EmojiPicker from 'vue-emoji-picker'
import VueTribute from 'vue-tribute'
import { VTooltip } from 'v-tooltip'
import CurrentUserMixin from './../mixins/currentUserMixin'
export default {
name: 'Composer',
components: {
Avatar, EmojiPicker, VueTribute
},
directives: {
tooltip: VTooltip
},
mixins: [CurrentUserMixin],
props: {
},
computed: {
},
methods: {
insert(emoji) {
this.post += emoji;
this.$refs.composerInput.innerText = this.post;
},
updateInput (event) {
this.post = this.$refs.composerInput.innerText;
}
},
data() {
return {
post: '',
search: '',
tributeOptions: {
values: [
{key: 'Phil Heartman', value: 'pheartman'},
{key: 'Gordon Ramsey', value: 'gramsey'}
]
}
}
},
}
</script>

Wyświetl plik

@ -0,0 +1,32 @@
/*
* @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/>.
*
*/
export default {
computed: {
currentUser: function() {
return OC.getCurrentUser()
},
socialId: function() {
return '@' + OC.getCurrentUser().uid + '@' + OC.getHost()
},
}
};

Wyświetl plik

@ -12,21 +12,7 @@
</div>
</transition>
<div class="social__timeline">
<div class="new-post" data-id="">
<div class="new-post-author">
<avatar :user="currentUser.uid" :display-name="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…" />
<input class="submit icon-confirm has-tooltip" type="submit" value=""
title="" data-original-title="Post">
<div class="submitLoading icon-loading-small hidden" />
</form>
</div>
<composer></composer>
<timeline-entry v-for="entry in timeline" :item="entry" :key="entry.id" />
<infinite-loading ref="infiniteLoading" @infinite="infiniteHandler">
<div slot="spinner"><div class="icon-loading" /></div>
@ -90,46 +76,6 @@
margin: 15px auto;
}
.new-post {
display: flex;
padding: 10px;
background-color: var(--color-main-background-translucent);
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;
}
@ -154,10 +100,11 @@ import {
PopoverMenu,
AppNavigation,
Multiselect,
Avatar
} from 'nextcloud-vue'
import InfiniteLoading from 'vue-infinite-loading'
import TimelineEntry from './../components/TimelineEntry'
import Composer from './../components/Composer'
import CurrentUserMixin from './../mixins/currentUserMixin'
export default {
name: 'Timeline',
@ -166,9 +113,10 @@ export default {
AppNavigation,
TimelineEntry,
Multiselect,
Avatar,
Composer,
InfiniteLoading
},
mixins: [CurrentUserMixin],
data: function() {
return {
infoHidden: false,
@ -179,12 +127,6 @@ export default {
url: function() {
return OC.linkTo('social', 'img/nextcloud.png')
},
currentUser: function() {
return OC.getCurrentUser()
},
socialId: function() {
return '@' + OC.getCurrentUser().uid + '@' + OC.getHost()
},
timeline: function() {
return this.$store.getters.getTimeline
},