From 5d73a1dd060288830bbac592c15fce3a174fa12b Mon Sep 17 00:00:00 2001
From: HJ <spam@hjkos.com>
Date: Thu, 14 Feb 2019 19:15:59 +0000
Subject: [PATCH] Revert "Merge branch 'fix/no-autocomplete-in-non-post-forms'
 into 'develop'"

This reverts merge request !551
---
 .../autocomplete_input/autocomplete_input.js  | 150 ------------------
 .../autocomplete_input/autocomplete_input.vue | 104 ------------
 .../post_status_form/post_status_form.js      | 129 ++++++++++++++-
 .../post_status_form/post_status_form.vue     |  84 ++++++++--
 src/components/user_settings/user_settings.js |   4 +-
 .../user_settings/user_settings.vue           |   4 +-
 6 files changed, 199 insertions(+), 276 deletions(-)
 delete mode 100644 src/components/autocomplete_input/autocomplete_input.js
 delete mode 100644 src/components/autocomplete_input/autocomplete_input.vue

diff --git a/src/components/autocomplete_input/autocomplete_input.js b/src/components/autocomplete_input/autocomplete_input.js
deleted file mode 100644
index 2a959fd1..00000000
--- 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 56233535..00000000
--- a/src/components/autocomplete_input/autocomplete_input.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<template>
-  <div style="display: flex; flex-direction: column;">
-    <textarea
-      v-if="multiline"
-      ref="textarea"
-      rows="1"
-      :value="text" :class="classObj" :id="id" :placeholder="placeholder"
-      @input="text = $event.target.value, $emit('input', $event.target.value), autoResize && resize($event)" 
-      @click="setCaret"
-      @keyup="setCaret"
-      @keydown.down="cycleForward"
-      @keydown.up="cycleBackward"
-      @keydown.shift.tab="cycleBackward"
-      @keydown.tab="cycleForward"
-      @keydown.enter="replaceCandidate"
-      @drop="drop && drop()"
-      @dragover.prevent="dragoverPrevent && dragoverPrevent()"
-      @paste="paste && paste()"
-      @keydown.meta.enter="keydownMetaEnter && keydownMetaEnter()"
-      @keyup.ctrl.enter="keyupCtrlEnter && keyupCtrlEnter()">
-    </textarea>
-    <input
-      v-else
-      ref="textarea"
-      :value="text" :class="classObj" :id="id" :placeholder="placeholder"
-      @input="text = $event.target.value, $emit('input', $event.target.value), autoResize && resize($event)" 
-      @click="setCaret"
-      @keyup="setCaret"
-      @keydown.down="cycleForward"
-      @keydown.up="cycleBackward"
-      @keydown.shift.tab="cycleBackward"
-      @keydown.tab="cycleForward"
-      @keydown.enter="replaceCandidate"
-      @drop="drop && drop()"
-      @dragover.prevent="dragoverPrevent && dragoverPrevent()"
-      @paste="paste && paste()"
-      @keydown.meta.enter="keydownMetaEnter && keydownMetaEnter()"
-      @keyup.ctrl.enter="keyupCtrlEnter && keyupCtrlEnter()"/>
-    <div style="position:relative;" v-if="candidates">
-      <div class="autocomplete-panel">
-        <div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))">
-          <div class="autocomplete" :class="{ highlighted: candidate.highlighted }">
-            <span v-if="candidate.img"><img :src="candidate.img"></img></span>
-            <span v-else>{{candidate.utf}}</span>
-            <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script src="./autocomplete_input.js"></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.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);
-  }
-}
-</style>
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 8e30264d..ab379c23 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 ef3a7901..6ed5d92e 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">
-      <auto-complete-input v-model="newStatus.status"
-        :classObj="{ 'form-control': true }"
-        :placeholder="$t('post_status.default')"
-        :autoResize="true"
-        :multiline="true"
-        :drop="fileDrop"
-        :dragoverPrevent="fileDrag"
-        :paste="paste"
-        :keydownMetaEnter="postStatusCopy"
-        :keyupCtrlEnter="postStatusCopy"/>
+      <textarea
+        ref="textarea"
+        @click="setCaret"
+        @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control"
+        @keydown.down="cycleForward"
+        @keydown.up="cycleBackward"
+        @keydown.shift.tab="cycleBackward"
+        @keydown.tab="cycleForward"
+        @keydown.enter="replaceCandidate"
+        @keydown.meta.enter="postStatus(newStatus)"
+        @keyup.ctrl.enter="postStatus(newStatus)"
+        @drop="fileDrop"
+        @dragover.prevent="fileDrag"
+        @input="resize"
+        @paste="paste">
+      </textarea>
       <div class="visibility-tray">
         <span class="text-format" v-if="formattingOptionsEnabled">
           <label for="post-content-type" class="select">
@@ -46,6 +52,17 @@
         </div>
       </div>
     </div>
+    <div style="position:relative;" v-if="candidates">
+        <div class="autocomplete-panel">
+          <div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))">
+            <div class="autocomplete" :class="{ highlighted: candidate.highlighted }">
+              <span v-if="candidate.img"><img :src="candidate.img"></img></span>
+              <span v-else>{{candidate.utf}}</span>
+              <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span>
+            </div>
+          </div>
+        </div>
+      </div>
       <div class='form-bottom'>
         <media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
 
@@ -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);
+    }
+  }
 }
 </style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index fa389c3b..d20bf308 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 ad7c17bd..d2381da2 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -9,9 +9,9 @@
           <div class="setting-item" >
             <h2>{{$t('settings.name_bio')}}</h2>
             <p>{{$t('settings.name')}}</p>
-            <auto-complete-input :classObj="{ 'name-changer': true }" :id="'username'" v-model="newName"/>
+            <input class='name-changer' id='username' v-model="newName"></input>
             <p>{{$t('settings.bio')}}</p>
-            <auto-complete-input :classObj="{ bio: true }" v-model="newBio" :multiline="true"/>
+            <textarea class="bio" v-model="newBio"></textarea>
             <p>
               <input type="checkbox" v-model="newLocked" id="account-locked">
               <label for="account-locked">{{$t('settings.lock_account_description')}}</label>