diff --git a/src/components/autocomplete_input/autocomplete_input.js b/src/components/autocomplete_input/autocomplete_input.js deleted file mode 100644 index 2a959fd15c..0000000000 --- a/src/components/autocomplete_input/autocomplete_input.js +++ /dev/null @@ -1,150 +0,0 @@ -import Completion from '../../services/completion/completion.js' -import { take, filter, map } from 'lodash' - -const AutoCompleteInput = { - props: [ - 'id', - 'classObj', - 'value', - 'placeholder', - 'autoResize', - 'multiline', - 'drop', - 'dragoverPrevent', - 'paste', - 'keydownMetaEnter', - 'keyupCtrlEnter' - ], - components: {}, - mounted () { - this.autoResize && this.resize(this.$refs.textarea) - const textLength = this.$refs.textarea.value.length - this.$refs.textarea.setSelectionRange(textLength, textLength) - }, - data () { - return { - caret: 0, - highlighted: 0, - text: this.value - } - }, - computed: { - users () { - return this.$store.state.users.users - }, - emoji () { - return this.$store.state.instance.emoji || [] - }, - customEmoji () { - return this.$store.state.instance.customEmoji || [] - }, - textAtCaret () { - return (this.wordAtCaret || {}).word || '' - }, - wordAtCaret () { - const word = Completion.wordAtPosition(this.text, this.caret - 1) || {} - return word - }, - 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 - } - } - }, - methods: { - setCaret ({target: {selectionStart}}) { - this.caret = selectionStart - }, - cycleBackward (e) { - const len = this.candidates.length || 0 - if (len > 0) { - e.preventDefault() - this.highlighted -= 1 - if (this.highlighted < 0) { - this.highlighted = this.candidates.length - 1 - } - } else { - this.highlighted = 0 - } - }, - cycleForward (e) { - const len = this.candidates.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 - } - }, - replace (replacement) { - this.text = Completion.replaceWord(this.text, this.wordAtCaret, replacement) - const el = this.$el.querySelector('textarea') - el.focus() - this.caret = 0 - }, - replaceCandidate (e) { - const len = this.candidates.length || 0 - if (this.textAtCaret === ':' || e.ctrlKey) { return } - if (len > 0) { - e.preventDefault() - const candidate = this.candidates[this.highlighted] - const replacement = candidate.utf || (candidate.screen_name + ' ') - this.text = Completion.replaceWord(this.text, this.wordAtCaret, replacement) - const el = this.$el.querySelector('textarea') || this.$el.querySelector('input') - el.focus() - this.caret = 0 - this.highlighted = 0 - } - }, - resize (e) { - const target = e.target || e - if (!(target instanceof window.Element)) { return } - const vertPadding = Number(window.getComputedStyle(target)['padding-top'].substr(0, 1)) + - Number(window.getComputedStyle(target)['padding-bottom'].substr(0, 1)) - // Auto is needed to make textbox shrink when removing lines - target.style.height = 'auto' - target.style.height = `${target.scrollHeight - vertPadding}px` - if (target.value === '') { - target.style.height = null - } - } - } -} - -export default AutoCompleteInput diff --git a/src/components/autocomplete_input/autocomplete_input.vue b/src/components/autocomplete_input/autocomplete_input.vue deleted file mode 100644 index 56233535a1..0000000000 --- a/src/components/autocomplete_input/autocomplete_input.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - - - diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 8e30264d61..ab379c233e 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -1,8 +1,8 @@ import statusPoster from '../../services/status_poster/status_poster.service.js' import MediaUpload from '../media_upload/media_upload.vue' -import AutoCompleteInput from '../autocomplete_input/autocomplete_input.vue' import fileTypeService from '../../services/file_type/file_type.service.js' -import { 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] @@ -28,10 +28,13 @@ const PostStatusForm = { 'subject' ], components: { - MediaUpload, - AutoCompleteInput + MediaUpload }, mounted () { + this.resize(this.$refs.textarea) + const textLength = this.$refs.textarea.value.length + this.$refs.textarea.setSelectionRange(textLength, textLength) + if (this.replyTo) { this.$refs.textarea.focus() } @@ -58,13 +61,15 @@ const PostStatusForm = { submitDisabled: false, error: null, posting: false, + highlighted: 0, newStatus: { spoilerText: this.subject || '', status: statusText, nsfw: false, files: [], visibility: scope - } + }, + caret: 0 } }, computed: { @@ -76,6 +81,59 @@ const PostStatusForm = { direct: { selected: this.newStatus.visibility === 'direct' } } }, + 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 + } + }, + 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 + }, + emoji () { + return this.$store.state.instance.emoji || [] + }, + customEmoji () { + return this.$store.state.instance.customEmoji || [] + }, statusLength () { return this.newStatus.status.length }, @@ -116,8 +174,53 @@ const PostStatusForm = { } }, methods: { - postStatusCopy () { - this.postStatus(this.newStatus) + replace (replacement) { + this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement) + const el = this.$el.querySelector('textarea') + el.focus() + this.caret = 0 + }, + replaceCandidate (e) { + const len = this.candidates.length || 0 + if (this.textAtCaret === ':' || e.ctrlKey) { return } + if (len > 0) { + e.preventDefault() + const candidate = this.candidates[this.highlighted] + const replacement = candidate.utf || (candidate.screen_name + ' ') + this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement) + const el = this.$el.querySelector('textarea') + el.focus() + this.caret = 0 + this.highlighted = 0 + } + }, + cycleBackward (e) { + const len = this.candidates.length || 0 + if (len > 0) { + e.preventDefault() + this.highlighted -= 1 + if (this.highlighted < 0) { + this.highlighted = this.candidates.length - 1 + } + } else { + this.highlighted = 0 + } + }, + cycleForward (e) { + const len = this.candidates.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 + } + }, + setCaret ({target: {selectionStart}}) { + this.caret = selectionStart }, postStatus (newStatus) { if (this.posting) { return } @@ -202,6 +305,18 @@ const PostStatusForm = { fileDrag (e) { e.dataTransfer.dropEffect = 'copy' }, + resize (e) { + const target = e.target || e + if (!(target instanceof window.Element)) { return } + const vertPadding = Number(window.getComputedStyle(target)['padding-top'].substr(0, 1)) + + Number(window.getComputedStyle(target)['padding-bottom'].substr(0, 1)) + // Auto is needed to make textbox shrink when removing lines + target.style.height = 'auto' + target.style.height = `${target.scrollHeight - vertPadding}px` + if (target.value === '') { + target.style.height = null + } + }, clearError () { this.error = null }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index ef3a790184..6ed5d92e0c 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -16,16 +16,22 @@ :placeholder="$t('post_status.content_warning')" v-model="newStatus.spoilerText" class="form-cw"> - +
+
+
+
+
+ + {{candidate.utf}} + {{candidate.screen_name}}{{candidate.name}} +
+
+
+
@@ -233,5 +250,52 @@ cursor: pointer; z-index: 4; } + + .autocomplete-panel { + 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); + min-width: 75%; + background: $fallback--bg; + background: var(--bg, $fallback--bg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } + + .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: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + object-fit: contain; + } + + span { + line-height: 24px; + margin: 0 0.1em 0 0.2em; + } + + small { + margin-left: .5em; + color: $fallback--faint; + color: var(--faint, $fallback--faint); + } + + &.highlighted { + background-color: $fallback--fg; + background-color: var(--lightBg, $fallback--fg); + } + } } diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index fa389c3be0..d20bf30877 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -2,7 +2,6 @@ import { unescape } from 'lodash' import TabSwitcher from '../tab_switcher/tab_switcher.js' import StyleSwitcher from '../style_switcher/style_switcher.vue' -import AutoCompleteInput from '../autocomplete_input/autocomplete_input.vue' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' const UserSettings = { @@ -42,8 +41,7 @@ const UserSettings = { }, components: { StyleSwitcher, - TabSwitcher, - AutoCompleteInput + TabSwitcher }, computed: { user () { diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index ad7c17bd0d..d2381da226 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -9,9 +9,9 @@

{{$t('settings.name_bio')}}

{{$t('settings.name')}}

- +

{{$t('settings.bio')}}

- +