From b97a03383990d54573bd5e68393a1ad11e33608b Mon Sep 17 00:00:00 2001 From: jared Date: Mon, 25 Mar 2019 22:38:15 -0400 Subject: [PATCH] #255 - add emoji input component --- src/components/emoji-input/emoji-input.js | 106 ++++++++++++++++++ src/components/emoji-input/emoji-input.vue | 99 ++++++++++++++++ .../post_status_form/post_status_form.js | 4 +- .../post_status_form/post_status_form.vue | 5 +- 4 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/components/emoji-input/emoji-input.js create mode 100644 src/components/emoji-input/emoji-input.vue diff --git a/src/components/emoji-input/emoji-input.js b/src/components/emoji-input/emoji-input.js new file mode 100644 index 00000000..56414358 --- /dev/null +++ b/src/components/emoji-input/emoji-input.js @@ -0,0 +1,106 @@ +import Completion from '../../services/completion/completion.js' +import { take, filter, map } from 'lodash' + +const EmojiInput = { + props: [ + 'value', + 'placeholder', + 'type' + ], + data () { + return { + highlighted: 0, + caret: 0 + } + }, + computed: { + suggestions () { + const firstchar = this.textAtCaret.charAt(0) + if (firstchar === ':') { + if (this.textAtCaret === ':') { return } + const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.startsWith(this.textAtCaret.slice(1))) + if (matchedEmoji.length <= 0) { + return false + } + return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({ + shortcode: `:${shortcode}:`, + utf: utf || '', + // eslint-disable-next-line camelcase + img: utf ? '' : this.$store.state.instance.server + image_url, + highlighted: index === this.highlighted + })) + } else { + return false + } + }, + textAtCaret () { + return (this.wordAtCaret || {}).word || '' + }, + wordAtCaret () { + const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} + return word + }, + emoji () { + return this.$store.state.instance.emoji || [] + }, + customEmoji () { + return this.$store.state.instance.customEmoji || [] + } + }, + methods: { + replace (replacement) { + const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) + this.$emit('input', newValue) + this.caret = 0 + }, + replaceEmoji (e) { + const len = this.suggestions.length || 0 + if (this.textAtCaret === ':' || e.ctrlKey) { return } + if (len > 0) { + e.preventDefault() + const emoji = this.suggestions[this.highlighted] + const replacement = emoji.utf || (emoji.shortcode + ' ') + const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) + this.$emit('input', newValue) + this.caret = 0 + this.highlighted = 0 + } + }, + cycleBackward (e) { + const len = this.suggestions.length || 0 + if (len > 0) { + e.preventDefault() + this.highlighted -= 1 + if (this.highlighted < 0) { + this.highlighted = this.suggestions.length - 1 + } + } else { + this.highlighted = 0 + } + }, + cycleForward (e) { + const len = this.suggestions.length || 0 + if (len > 0) { + if (e.shiftKey) { return } + e.preventDefault() + this.highlighted += 1 + if (this.highlighted >= len) { + this.highlighted = 0 + } + } else { + this.highlighted = 0 + } + }, + onKeydown (e) { + e.stopPropagation() + }, + onInput (e) { + this.$emit('input', e.target.value) + }, + setCaret ({target: {selectionStart}}) { + this.caret = selectionStart + } + } +} + +export default EmojiInput diff --git a/src/components/emoji-input/emoji-input.vue b/src/components/emoji-input/emoji-input.vue new file mode 100644 index 00000000..95606305 --- /dev/null +++ b/src/components/emoji-input/emoji-input.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index c5f30ca6..229aefb7 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -1,5 +1,6 @@ import statusPoster from '../../services/status_poster/status_poster.service.js' import MediaUpload from '../media_upload/media_upload.vue' +import EmojiInput from '../emoji-input/emoji-input.vue' import fileTypeService from '../../services/file_type/file_type.service.js' import Completion from '../../services/completion/completion.js' import { take, filter, reject, map, uniqBy } from 'lodash' @@ -28,7 +29,8 @@ const PostStatusForm = { 'subject' ], components: { - MediaUpload + MediaUpload, + EmojiInput }, mounted () { this.resize(this.$refs.textarea) diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 612f87c1..9d449b74 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -10,12 +10,13 @@ {{ $t('post_status.account_not_locked_warning_link') }}

{{ $t('post_status.direct_warning') }}

- + class="form-cw" + />