aboutsummaryrefslogtreecommitdiff
path: root/src/components/post_status_form
diff options
context:
space:
mode:
authorShpuld Shpuldson <shpuld@gmail.com>2017-06-19 11:32:40 +0300
committerShpuld Shpuldson <shpuld@gmail.com>2017-06-19 11:32:40 +0300
commit3785a863cb27af18fe91403189eff16f662dc2d0 (patch)
treeff1ee977f74e9516fec4ca0691b017ce6f7867ff /src/components/post_status_form
parent143aa3b990c0e0fac98c4a097d68e9f7518f1940 (diff)
parent8e5d17a659b157f095ad3850ac3cdd2e537ac38b (diff)
Update branch and fix conflicts.
Diffstat (limited to 'src/components/post_status_form')
-rw-r--r--src/components/post_status_form/post_status_form.js110
-rw-r--r--src/components/post_status_form/post_status_form.vue82
2 files changed, 117 insertions, 75 deletions
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 01aeeb68..a8b4d39c 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -1,10 +1,8 @@
import statusPoster from '../../services/status_poster/status_poster.service.js'
import MediaUpload from '../media_upload/media_upload.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
-import Tribute from '../../../node_modules/tributejs/src/Tribute.js'
-require('../../../node_modules/tributejs/scss/tribute.scss')
-
-import { merge, reject, map, uniqBy } from 'lodash'
+import Completion from '../../services/completion/completion.js'
+import { take, filter, reject, map, uniqBy } from 'lodash'
const buildMentionsString = ({user, attentions}, currentUser) => {
let allAttentions = [...attentions]
@@ -21,51 +19,6 @@ const buildMentionsString = ({user, attentions}, currentUser) => {
return mentions.join(' ') + ' '
}
-const defaultCollection = {
- // symbol that starts the lookup
- trigger: '@',
-
- // element to target for @mentions
- iframe: null,
-
- // class added in the flyout menu for active item
- selectClass: 'highlight',
-
- // function called on select that returns the content to insert
- selectTemplate: function (item) {
- return '@' + item.original.screen_name
- },
-
- // template for displaying item in menu
- menuItemTemplate: function (item) {
- return `<img src="${item.original.profile_image_url}"></img> <div class='name'>${item.string}</div>`
- },
-
- // template for when no match is found (optional),
- // If no template is provided, menu is hidden.
- noMatchTemplate: null,
-
- // specify an alternative parent container for the menu
- menuContainer: document.body,
-
- // column to search against in the object (accepts function or string)
- lookup: ({name, screen_name}) => `${name} (@${screen_name})`, // eslint-disable-line camelcase
-
- // column that contains the content to insert by default
- fillAttr: 'screen_name',
-
- // REQUIRED: array of objects to match
- values: [],
-
- // specify whether a space is required before the trigger character
- requireLeadingSpace: true,
-
- // specify whether a space is allowed in the middle of mentions
- allowSpaces: false
-}
-
-const tribute = new Tribute({ collection: [] })
-
const PostStatusForm = {
props: [
'replyTo',
@@ -89,30 +42,48 @@ const PostStatusForm = {
newStatus: {
status: statusText,
files: []
- }
+ },
+ caret: 0
}
},
computed: {
+ candidates () {
+ if (this.textAtCaret.charAt(0) === '@') {
+ const matchedUsers = filter(this.users, (user) => (user.name + user.screen_name).match(this.textAtCaret.slice(1)))
+ if (matchedUsers.length <= 0) {
+ return false
+ }
+ // eslint-disable-next-line camelcase
+ return map(take(matchedUsers, 5), ({screen_name, name, profile_image_url_original}) => ({
+ screen_name: screen_name,
+ name: name,
+ img: profile_image_url_original
+ }))
+ } else {
+ return false
+ }
+ },
+ textAtCaret () {
+ return (this.wordAtCaret || {}).word || ''
+ },
+ wordAtCaret () {
+ const word = Completion.wordAtPosition(this.newStatus.status, this.caret - 1) || {}
+ return word
+ },
users () {
return this.$store.state.users.users
- },
- completions () {
- let users = this.users
- users = merge({values: users}, defaultCollection)
- return [users]
}
},
- watch: {
- completions () {
- tribute.collection = this.completions
- }
- },
- mounted () {
- const textarea = this.$el.querySelector('textarea')
- tribute.collection = this.completions
- tribute.attach(textarea)
- },
methods: {
+ replace (replacement) {
+ this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
+ const el = this.$el.querySelector('textarea')
+ el.focus()
+ this.caret = 0
+ },
+ setCaret ({target: {selectionStart}}) {
+ this.caret = selectionStart
+ },
postStatus (newStatus) {
statusPoster.postStatus({
status: newStatus.status,
@@ -125,6 +96,8 @@ const PostStatusForm = {
files: []
}
this.$emit('posted')
+ let el = this.$el.querySelector('textarea')
+ el.style.height = '16px'
},
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
@@ -151,6 +124,13 @@ const PostStatusForm = {
},
fileDrag (e) {
e.dataTransfer.dropEffect = 'copy'
+ },
+ resize (e) {
+ e.target.style.height = 'auto'
+ e.target.style.height = `${e.target.scrollHeight - 10}px`
+ if (e.target.value === '') {
+ e.target.style.height = '16px'
+ }
}
}
}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index e7143b62..a17d6479 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -1,8 +1,23 @@
<template>
<div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)">
- <div class="form-group" >
- <textarea v-model="newStatus.status" placeholder="Just landed in L.A." rows="3" class="form-control" @keyup.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag"></textarea>
+ <div class="form-group base03-border" >
+ <textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" placeholder="Just landed in L.A." rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize"></textarea>
+ </div>
+ <div style="position:relative;" v-if="candidates">
+ <div class="autocomplete-panel base05-background">
+ <div v-for="candidate in candidates" @click="replace('@' + candidate.screen_name + ' ')" class="autocomplete base01">
+ <img :src="candidate.img"></img>
+ <span>
+ @{{candidate.screen_name}}
+ <small class="base02">{{candidate.name}}</small>
+ </span>
+ </div>
+ </div>
+ </div>
+ <div class='form-bottom'>
+ <media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload>
+ <button :disabled="submitDisabled" type="submit" class="btn btn-default base05 base01-background">Submit</button>
</div>
<div class="attachments">
<div class="attachment" v-for="file in newStatus.files">
@@ -13,10 +28,6 @@
<a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a>
</div>
</div>
- <div class='form-bottom'>
- <media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload>
- <button :disabled="submitDisabled" type="submit" class="btn btn-default base05 base01-background">Submit</button>
- </div>
</form>
</div>
</template>
@@ -44,14 +55,20 @@
.form-bottom {
display: flex;
padding: 0.5em;
+ height: 32px;
button {
- flex: 2;
+ width: 10em;
}
}
.attachments {
- padding: 0.5em;
+ padding: 0 0.5em;
+
+ .attachment {
+ position: relative;
+ margin: 0.5em 0.8em 0.2em 0;
+ }
i {
position: absolute;
@@ -91,11 +108,56 @@
form textarea {
border: solid;
border-width: 1px;
- border-color: silver;
+ border-color: inherit;
border-radius: 5px;
line-height:16px;
padding: 5px;
- resize: vertical;
+ resize: none;
+ overflow: hidden;
+ }
+
+ form textarea:focus {
+ min-height: 48px;
+ }
+
+ .btn {
+ cursor: pointer;
+ }
+
+ .btn[disabled] {
+ cursor: not-allowed;
+ }
+
+ .icon-cancel {
+ cursor: pointer;
+ }
+
+ .autocomplete-panel {
+ margin: 0 0.5em 0 0.5em;
+ border-radius: 5px;
+ position: absolute;
+ z-index: 1;
+ box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
+ min-width: 75%;
+ }
+
+ .autocomplete {
+ cursor: pointer;
+ padding: 0.2em 0.4em 0.2em 0.4em;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.4);
+ display: flex;
+ img {
+ width: 24px;
+ height: 24px;
+ border-radius: 2px;
+ }
+ span {
+ line-height: 24px;
+ margin: 0 0.1em 0 0.2em;
+ }
+ small {
+ font-style: italic;
+ }
}
}