diff options
| author | Shpuld Shpuldson <shpuld@gmail.com> | 2017-06-19 11:32:40 +0300 |
|---|---|---|
| committer | Shpuld Shpuldson <shpuld@gmail.com> | 2017-06-19 11:32:40 +0300 |
| commit | 3785a863cb27af18fe91403189eff16f662dc2d0 (patch) | |
| tree | ff1ee977f74e9516fec4ca0691b017ce6f7867ff /src/components/post_status_form | |
| parent | 143aa3b990c0e0fac98c4a097d68e9f7518f1940 (diff) | |
| parent | 8e5d17a659b157f095ad3850ac3cdd2e537ac38b (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.js | 110 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 82 |
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; + } } } |
