forked from AkkomaGang/akkoma-fe
Merge branch 'fix/no-autocomplete-in-non-post-forms' into 'develop'
#255 - implement autocomplete in non post forms See merge request pleroma/pleroma-fe!551
This commit is contained in:
commit
2bc1cc9ff9
6 changed files with 276 additions and 199 deletions
150
src/components/autocomplete_input/autocomplete_input.js
Normal file
150
src/components/autocomplete_input/autocomplete_input.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
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
|
104
src/components/autocomplete_input/autocomplete_input.vue
Normal file
104
src/components/autocomplete_input/autocomplete_input.vue
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<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>
|
|
@ -1,8 +1,8 @@
|
||||||
import statusPoster from '../../services/status_poster/status_poster.service.js'
|
import statusPoster from '../../services/status_poster/status_poster.service.js'
|
||||||
import MediaUpload from '../media_upload/media_upload.vue'
|
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 fileTypeService from '../../services/file_type/file_type.service.js'
|
||||||
import Completion from '../../services/completion/completion.js'
|
import { reject, map, uniqBy } from 'lodash'
|
||||||
import { take, filter, reject, map, uniqBy } from 'lodash'
|
|
||||||
|
|
||||||
const buildMentionsString = ({user, attentions}, currentUser) => {
|
const buildMentionsString = ({user, attentions}, currentUser) => {
|
||||||
let allAttentions = [...attentions]
|
let allAttentions = [...attentions]
|
||||||
|
@ -28,13 +28,10 @@ const PostStatusForm = {
|
||||||
'subject'
|
'subject'
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
MediaUpload
|
MediaUpload,
|
||||||
|
AutoCompleteInput
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.resize(this.$refs.textarea)
|
|
||||||
const textLength = this.$refs.textarea.value.length
|
|
||||||
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
|
||||||
|
|
||||||
if (this.replyTo) {
|
if (this.replyTo) {
|
||||||
this.$refs.textarea.focus()
|
this.$refs.textarea.focus()
|
||||||
}
|
}
|
||||||
|
@ -61,15 +58,13 @@ const PostStatusForm = {
|
||||||
submitDisabled: false,
|
submitDisabled: false,
|
||||||
error: null,
|
error: null,
|
||||||
posting: false,
|
posting: false,
|
||||||
highlighted: 0,
|
|
||||||
newStatus: {
|
newStatus: {
|
||||||
spoilerText: this.subject || '',
|
spoilerText: this.subject || '',
|
||||||
status: statusText,
|
status: statusText,
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
files: [],
|
files: [],
|
||||||
visibility: scope
|
visibility: scope
|
||||||
},
|
}
|
||||||
caret: 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -81,59 +76,6 @@ const PostStatusForm = {
|
||||||
direct: { selected: this.newStatus.visibility === 'direct' }
|
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 () {
|
statusLength () {
|
||||||
return this.newStatus.status.length
|
return this.newStatus.status.length
|
||||||
},
|
},
|
||||||
|
@ -174,53 +116,8 @@ const PostStatusForm = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
replace (replacement) {
|
postStatusCopy () {
|
||||||
this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
|
this.postStatus(this.newStatus)
|
||||||
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) {
|
postStatus (newStatus) {
|
||||||
if (this.posting) { return }
|
if (this.posting) { return }
|
||||||
|
@ -305,18 +202,6 @@ const PostStatusForm = {
|
||||||
fileDrag (e) {
|
fileDrag (e) {
|
||||||
e.dataTransfer.dropEffect = 'copy'
|
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 () {
|
clearError () {
|
||||||
this.error = null
|
this.error = null
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,22 +16,16 @@
|
||||||
:placeholder="$t('post_status.content_warning')"
|
:placeholder="$t('post_status.content_warning')"
|
||||||
v-model="newStatus.spoilerText"
|
v-model="newStatus.spoilerText"
|
||||||
class="form-cw">
|
class="form-cw">
|
||||||
<textarea
|
<auto-complete-input v-model="newStatus.status"
|
||||||
ref="textarea"
|
:classObj="{ 'form-control': true }"
|
||||||
@click="setCaret"
|
:placeholder="$t('post_status.default')"
|
||||||
@keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control"
|
:autoResize="true"
|
||||||
@keydown.down="cycleForward"
|
:multiline="true"
|
||||||
@keydown.up="cycleBackward"
|
:drop="fileDrop"
|
||||||
@keydown.shift.tab="cycleBackward"
|
:dragoverPrevent="fileDrag"
|
||||||
@keydown.tab="cycleForward"
|
:paste="paste"
|
||||||
@keydown.enter="replaceCandidate"
|
:keydownMetaEnter="postStatusCopy"
|
||||||
@keydown.meta.enter="postStatus(newStatus)"
|
:keyupCtrlEnter="postStatusCopy"/>
|
||||||
@keyup.ctrl.enter="postStatus(newStatus)"
|
|
||||||
@drop="fileDrop"
|
|
||||||
@dragover.prevent="fileDrag"
|
|
||||||
@input="resize"
|
|
||||||
@paste="paste">
|
|
||||||
</textarea>
|
|
||||||
<div class="visibility-tray">
|
<div class="visibility-tray">
|
||||||
<span class="text-format" v-if="formattingOptionsEnabled">
|
<span class="text-format" v-if="formattingOptionsEnabled">
|
||||||
<label for="post-content-type" class="select">
|
<label for="post-content-type" class="select">
|
||||||
|
@ -52,17 +46,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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'>
|
<div class='form-bottom'>
|
||||||
<media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
|
<media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
|
||||||
|
|
||||||
|
@ -250,52 +233,5 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 4;
|
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>
|
</style>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { unescape } from 'lodash'
|
||||||
|
|
||||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
||||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
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'
|
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
||||||
|
|
||||||
const UserSettings = {
|
const UserSettings = {
|
||||||
|
@ -41,7 +42,8 @@ const UserSettings = {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
StyleSwitcher,
|
StyleSwitcher,
|
||||||
TabSwitcher
|
TabSwitcher,
|
||||||
|
AutoCompleteInput
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
<div class="setting-item" >
|
<div class="setting-item" >
|
||||||
<h2>{{$t('settings.name_bio')}}</h2>
|
<h2>{{$t('settings.name_bio')}}</h2>
|
||||||
<p>{{$t('settings.name')}}</p>
|
<p>{{$t('settings.name')}}</p>
|
||||||
<input class='name-changer' id='username' v-model="newName"></input>
|
<auto-complete-input :classObj="{ 'name-changer': true }" :id="'username'" v-model="newName"/>
|
||||||
<p>{{$t('settings.bio')}}</p>
|
<p>{{$t('settings.bio')}}</p>
|
||||||
<textarea class="bio" v-model="newBio"></textarea>
|
<auto-complete-input :classObj="{ bio: true }" v-model="newBio" :multiline="true"/>
|
||||||
<p>
|
<p>
|
||||||
<input type="checkbox" v-model="newLocked" id="account-locked">
|
<input type="checkbox" v-model="newLocked" id="account-locked">
|
||||||
<label for="account-locked">{{$t('settings.lock_account_description')}}</label>
|
<label for="account-locked">{{$t('settings.lock_account_description')}}</label>
|
||||||
|
|
Loading…
Reference in a new issue