diff options
Diffstat (limited to 'src/components/emoji-input')
| -rw-r--r-- | src/components/emoji-input/emoji-input.js | 99 | ||||
| -rw-r--r-- | src/components/emoji-input/emoji-input.vue | 36 | ||||
| -rw-r--r-- | src/components/emoji-input/suggestor.js | 38 |
3 files changed, 112 insertions, 61 deletions
diff --git a/src/components/emoji-input/emoji-input.js b/src/components/emoji-input/emoji-input.js index a5bb6eaf..466341c0 100644 --- a/src/components/emoji-input/emoji-input.js +++ b/src/components/emoji-input/emoji-input.js @@ -1,15 +1,17 @@ import Completion from '../../services/completion/completion.js' -import { take, filter, map } from 'lodash' +import { take } from 'lodash' const EmojiInput = { props: [ - 'value', 'placeholder', + 'suggest', + 'value', 'type', 'classname' ], data () { return { + input: undefined, highlighted: 0, caret: 0 } @@ -17,35 +19,46 @@ const EmojiInput = { 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 { + if (this.textAtCaret === firstchar) { return } + const matchedSuggestions = this.suggest(this.textAtCaret) + if (matchedSuggestions.length <= 0) { return false } + return take(matchedSuggestions, 5).map(({shortcode, image_url, replacement}, index) => ({ + shortcode, + replacement, + // eslint-disable-next-line camelcase + img: !image_url ? '' : this.$store.state.instance.server + image_url, + highlighted: index === this.highlighted + })) }, textAtCaret () { return (this.wordAtCaret || {}).word || '' }, wordAtCaret () { - const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} - return word - }, - emoji () { - return this.$store.state.instance.emoji || [] + if (this.value && this.caret) { + const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} + return word + } }, - customEmoji () { - return this.$store.state.instance.customEmoji || [] + }, + mounted () { + const slots = this.$slots.default + if (slots.length === 0) return + const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag)) + if (!input) return + this.input = input + input.elm.addEventListener('keyup', this.setCaret) + input.elm.addEventListener('paste', this.setCaret) + input.elm.addEventListener('focus', this.setCaret) + input.elm.addEventListener('keydown', this.onKeyDown) + }, + unmounted () { + if (this.input) { + this.input.elm.removeEventListener('keyup', this.setCaret) + this.input.elm.removeEventListener('paste', this.setCaret) + this.input.elm.removeEventListener('focus', this.setCaret) + this.input.elm.removeEventListener('keydown', this.onKeyDown) } }, methods: { @@ -54,23 +67,21 @@ const EmojiInput = { this.$emit('input', newValue) this.caret = 0 }, - replaceEmoji (e) { + replaceText () { const len = this.suggestions.length || 0 - if (this.textAtCaret === ':' || e.ctrlKey) { return } + if (this.textAtCaret.length === 1) { return } if (len > 0) { - e.preventDefault() - const emoji = this.suggestions[this.highlighted] - const replacement = emoji.utf || (emoji.shortcode + ' ') + const suggestion = this.suggestions[this.highlighted] + const replacement = suggestion.replacement const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) this.$emit('input', newValue) this.caret = 0 this.highlighted = 0 } }, - cycleBackward (e) { + cycleBackward () { const len = this.suggestions.length || 0 if (len > 0) { - e.preventDefault() this.highlighted -= 1 if (this.highlighted < 0) { this.highlighted = this.suggestions.length - 1 @@ -79,11 +90,9 @@ const EmojiInput = { this.highlighted = 0 } }, - cycleForward (e) { + cycleForward () { 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 @@ -92,13 +101,33 @@ const EmojiInput = { this.highlighted = 0 } }, - onKeydown (e) { + onKeyDown (e) { + this.setCaret(e) e.stopPropagation() + + const { ctrlKey, shiftKey, key } = e + if (key === 'Tab') { + if (shiftKey) { + this.cycleBackward() + } else { + this.cycleForward() + } + } + if (key === 'ArrowUp') { + this.cycleBackward() + } else if (key === 'ArrowDown') { + this.cycleForward() + } + if (key === 'Enter') { + if (!ctrlKey) { + this.replaceText() + } + } }, onInput (e) { this.$emit('input', e.target.value) }, - setCaret ({target: {selectionStart}}) { + setCaret ({ target: { selectionStart, value } }) { this.caret = selectionStart } } diff --git a/src/components/emoji-input/emoji-input.vue b/src/components/emoji-input/emoji-input.vue index 338b77cd..eec33d1a 100644 --- a/src/components/emoji-input/emoji-input.vue +++ b/src/components/emoji-input/emoji-input.vue @@ -1,9 +1,7 @@ <template> <div class="emoji-input"> - <input - v-if="type !== 'textarea'" + <slot :class="classname" - :type="type" :value="value" :placeholder="placeholder" @input="onInput" @@ -15,36 +13,22 @@ @keydown.shift.tab="cycleBackward" @keydown.tab="cycleForward" @keydown.enter="replaceEmoji" - /> - <textarea - v-else - :class="classname" - :value="value" - :placeholder="placeholder" - @input="onInput" - @click="setCaret" - @keyup="setCaret" - @keydown="onKeydown" - @keydown.down="cycleForward" - @keydown.up="cycleBackward" - @keydown.shift.tab="cycleBackward" - @keydown.tab="cycleForward" - @keydown.enter="replaceEmoji" - ></textarea> + > + </slot> <div class="autocomplete-panel" v-if="suggestions"> <div class="autocomplete-panel-body"> <div - v-for="(emoji, index) in suggestions" + v-for="(suggestion, index) in suggestions" :key="index" - @click="replace(emoji.utf || (emoji.shortcode + ' '))" + @click="replace(suggestion.replacement)" class="autocomplete-item" - :class="{ highlighted: emoji.highlighted }" + :class="{ highlighted: suggestion.highlighted }" > - <span v-if="emoji.img"> - <img :src="emoji.img" /> + <span v-if="suggestion.img"> + <img :src="suggestion.img" /> </span> - <span v-else>{{emoji.utf}}</span> - <span>{{emoji.shortcode}}</span> + <span v-else>{{suggestion.replacement}}</span> + <span>{{suggestion.shortcode}}</span> </div> </div> </div> diff --git a/src/components/emoji-input/suggestor.js b/src/components/emoji-input/suggestor.js new file mode 100644 index 00000000..f1a0d0da --- /dev/null +++ b/src/components/emoji-input/suggestor.js @@ -0,0 +1,38 @@ +export default function suggest (data) { + return input => { + const trimmed = input.trim() + const firstChar = trimmed[0] + console.log(`'${trimmed}'`, firstChar, firstChar === ':') + if (firstChar === ':' && data.emoji) { + return suggestEmoji(data.emoji)(trimmed) + } + if (firstChar === '@' && data.users) { + return suggestUsers(data.users)(trimmed) + } + return [] + } +} + +function suggestEmoji (emojis) { + return input => { + const shortcode = input.toLowerCase().substr(1) + console.log(shortcode) + return emojis.filter(emoji => emoji.shortcode.toLowerCase().startsWith(shortcode)) + } +} + +function suggestUsers (users) { + return input => { + const shortcode = input.toLowerCase().substr(1) + return users.filter( + user => + user.screen_name.toLowerCase().startsWith('@' + shortcode) || + user.name.toLowerCase().startsWith(shortcode) + ).map(({ screen_name, name, profile_image_url_original }) => ({ + shortcode: screen_name, + detail: name, + image_url: profile_image_url_original, + replacement: '@' + screen_name + })) + } +} |
