From 8872b4802e2b0dff25eaaf884884d740ed016c98 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 8 Jun 2019 16:23:58 +0300 Subject: [PATCH] standartized autocomplete panel suggesions format, fixed some bugs --- src/App.scss | 4 - src/boot/after_store.js | 11 +-- src/components/emoji-input/emoji-input.js | 87 +++++++++++++++---- src/components/emoji-input/emoji-input.vue | 64 +++++++------- src/components/emoji-input/suggestor.js | 20 +++-- .../post_status_form/post_status_form.js | 37 -------- .../post_status_form/post_status_form.vue | 18 ++-- 7 files changed, 129 insertions(+), 112 deletions(-) diff --git a/src/App.scss b/src/App.scss index 52a786ad..fd724979 100644 --- a/src/App.scss +++ b/src/App.scss @@ -809,14 +809,10 @@ nav { .autocomplete { &-panel { - position: relative; - &-body { margin: 0 0.5em 0 0.5em; border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - position: absolute; - z-index: 1; box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); // this doesn't match original but i don't care, making it uniform. box-shadow: var(--popupShadow); diff --git a/src/boot/after_store.js b/src/boot/after_store.js index ea5a3f58..c0faa0a2 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -154,9 +154,9 @@ const getStaticEmoji = async ({ store }) => { const values = await res.json() const emoji = Object.keys(values).map((key) => { return { - shortcode: key, - image_url: false, - 'replacement': values[key] + displayText: key, + imageUrl: false, + replacement: values[key] } }) store.dispatch('setInstanceOption', { name: 'emoji', value: emoji }) @@ -178,9 +178,10 @@ const getCustomEmoji = async ({ store }) => { const result = await res.json() const values = Array.isArray(result) ? Object.assign({}, ...result) : result const emoji = Object.keys(values).map((key) => { + const imageUrl = values[key].image_url return { - shortcode: key, - image_url: values[key].image_url || values[key], + displayText: key, + imageUrl: imageUrl ? store.state.instance.server + imageUrl : values[key], replacement: `:${key}: ` } }) diff --git a/src/components/emoji-input/emoji-input.js b/src/components/emoji-input/emoji-input.js index 466341c0..fb60d998 100644 --- a/src/components/emoji-input/emoji-input.js +++ b/src/components/emoji-input/emoji-input.js @@ -13,7 +13,19 @@ const EmojiInput = { return { input: undefined, highlighted: 0, - caret: 0 + caret: 0, + focused: false, + popupOptions: { + placement: 'bottom-start', + trigger: 'hover', + // See: https://github.com/RobinCK/vue-popper/issues/63 + 'delay-on-mouse-over': 9999999, + 'delay-on-mouse-out': 9999999, + modifiers: { + arrow: { enabled: true }, + offset: { offset: '0, 5px' } + } + } } }, computed: { @@ -24,13 +36,17 @@ const EmojiInput = { 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 - })) + return take(matchedSuggestions, 5) + .map(({ displayText, imageUrl, replacement }, index) => ({ + displayText, + replacement, + // eslint-disable-next-line camelcase + img: imageUrl || '', + highlighted: index === this.highlighted + })) + }, + showPopup () { + return this.focused && this.suggestions && this.suggestions.length > 0 }, textAtCaret () { return (this.wordAtCaret || {}).word || '' @@ -40,25 +56,29 @@ const EmojiInput = { const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} return word } - }, + } }, mounted () { const slots = this.$slots.default - if (slots.length === 0) return + if (!slots || 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) + this.resize() + input.elm.addEventListener('blur', this.onBlur) + input.elm.addEventListener('focus', this.onFocus) + input.elm.addEventListener('paste', this.onPaste) + input.elm.addEventListener('keyup', this.onKeyUp) 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) + const { input } = this + if (input) { + input.elm.removeEventListener('blur', this.onBlur) + input.elm.removeEventListener('focus', this.onFocus) + input.elm.removeEventListener('paste', this.onPaste) + input.elm.removeEventListener('keyup', this.onKeyUp) + input.elm.removeEventListener('keydown', this.onKeyDown) } }, methods: { @@ -101,26 +121,49 @@ const EmojiInput = { this.highlighted = 0 } }, + onBlur (e) { + this.focused = false + this.setCaret(e) + this.resize(e) + }, + onFocus (e) { + this.focused = true + this.setCaret(e) + this.resize(e) + }, + onKeyUp (e) { + this.setCaret(e) + this.resize(e) + }, + onPaste (e) { + this.setCaret(e) + this.resize(e) + }, onKeyDown (e) { this.setCaret(e) - e.stopPropagation() + this.resize(e) const { ctrlKey, shiftKey, key } = e if (key === 'Tab') { if (shiftKey) { this.cycleBackward() + e.preventDefault() } else { this.cycleForward() + e.preventDefault() } } if (key === 'ArrowUp') { this.cycleBackward() + e.preventDefault() } else if (key === 'ArrowDown') { this.cycleForward() + e.preventDefault() } if (key === 'Enter') { if (!ctrlKey) { this.replaceText() + e.preventDefault() } } }, @@ -129,6 +172,12 @@ const EmojiInput = { }, setCaret ({ target: { selectionStart, value } }) { this.caret = selectionStart + }, + resize () { + const { panel } = this.$refs + if (!panel) return + const { offsetHeight, offsetTop } = this.input.elm + this.$refs.panel.style.top = (offsetTop + offsetHeight) + 'px' } } } diff --git a/src/components/emoji-input/emoji-input.vue b/src/components/emoji-input/emoji-input.vue index eec33d1a..0ca74322 100644 --- a/src/components/emoji-input/emoji-input.vue +++ b/src/components/emoji-input/emoji-input.vue @@ -1,38 +1,25 @@ @@ -41,8 +28,21 @@ @import '../../_variables.scss'; .emoji-input { - .form-control { - width: 100%; + display: flex; + flex-direction: column; + + &.hide { + display: none + } + + .autocomplete-panel { + position: absolute; + z-index: 9; + margin-top: 2px; + } + + input, textarea { + flex: 1; } } diff --git a/src/components/emoji-input/suggestor.js b/src/components/emoji-input/suggestor.js index f1a0d0da..54fd7f29 100644 --- a/src/components/emoji-input/suggestor.js +++ b/src/components/emoji-input/suggestor.js @@ -15,24 +15,26 @@ export default function suggest (data) { function suggestEmoji (emojis) { return input => { - const shortcode = input.toLowerCase().substr(1) - console.log(shortcode) - return emojis.filter(emoji => emoji.shortcode.toLowerCase().startsWith(shortcode)) + const noPrefix = input.toLowerCase().substr(1) + return emojis + .filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix)) } } function suggestUsers (users) { return input => { - const shortcode = input.toLowerCase().substr(1) + const noPrefix = input.toLowerCase().substr(1) return users.filter( user => - user.screen_name.toLowerCase().startsWith('@' + shortcode) || - user.name.toLowerCase().startsWith(shortcode) + user.screen_name.toLowerCase().startsWith(noPrefix) || + user.name.toLowerCase().startsWith(noPrefix) + /* eslint-disable camelcase */ ).map(({ screen_name, name, profile_image_url_original }) => ({ - shortcode: screen_name, - detail: name, - image_url: profile_image_url_original, + displayText: screen_name, + detailText: name, + imageUrl: profile_image_url_original, replacement: '@' + screen_name })) + /* eslint-enable camelcase */ } } diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 1516dd43..008b821a 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -83,43 +83,6 @@ const PostStatusForm = { } }, computed: { - candidates () { - const firstchar = this.textAtCaret.charAt(0) - if (firstchar === '@') { - const query = this.textAtCaret.slice(1).toUpperCase() - const matchedUsers = filter(this.users, (user) => { - return user.screen_name.toUpperCase().startsWith(query) || - user.name && user.name.toUpperCase().startsWith(query) - }) - if (matchedUsers.length <= 0) { - return false - } - // eslint-disable-next-line camelcase - return map(take(matchedUsers, 5), ({screen_name, name, profile_image_url_original}, index) => ({ - // eslint-disable-next-line camelcase - screen_name: `@${screen_name}`, - name: name, - img: profile_image_url_original, - highlighted: index === this.highlighted - })) - } else 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) => ({ - screen_name: `:${shortcode}:`, - name: '', - utf: utf || '', - // eslint-disable-next-line camelcase - img: utf ? '' : this.$store.state.instance.server + image_url, - highlighted: index === this.highlighted - })) - } else { - return false - } - }, users () { return this.$store.state.users.users }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 507b14bf..a8a34265 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -32,24 +32,29 @@ {{ $t('post_status.direct_warning_to_all') }}

- + @@ -251,7 +257,7 @@ min-height: 1px; } - form textarea.form-control { + .form-post-body { line-height:16px; resize: none; overflow: hidden; @@ -260,7 +266,7 @@ box-sizing: content-box; } - form textarea.form-control:focus { + .form-post-body:focus { min-height: 48px; }