diff options
Diffstat (limited to 'src/components/post_status_form')
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 119 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 80 |
2 files changed, 145 insertions, 54 deletions
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 40bbf6d4..9b2a9c90 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -1,14 +1,14 @@ import statusPoster from '../../services/status_poster/status_poster.service.js' import MediaUpload from '../media_upload/media_upload.vue' import ScopeSelector from '../scope_selector/scope_selector.vue' -import EmojiInput from '../emoji-input/emoji-input.vue' +import EmojiInput from '../emoji_input/emoji_input.vue' import PollForm from '../poll/poll_form.vue' -import StickerPicker from '../sticker_picker/sticker_picker.vue' import fileTypeService from '../../services/file_type/file_type.service.js' +import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { reject, map, uniqBy } from 'lodash' -import suggestor from '../emoji-input/suggestor.js' +import suggestor from '../emoji_input/suggestor.js' -const buildMentionsString = ({ user, attentions }, currentUser) => { +const buildMentionsString = ({ user, attentions = [] }, currentUser) => { let allAttentions = [...attentions] allAttentions.unshift(user) @@ -35,7 +35,6 @@ const PostStatusForm = { MediaUpload, EmojiInput, PollForm, - StickerPicker, ScopeSelector }, mounted () { @@ -84,8 +83,7 @@ const PostStatusForm = { contentType }, caret: 0, - pollFormVisible: false, - stickerPickerVisible: false + pollFormVisible: false } }, computed: { @@ -161,12 +159,6 @@ const PostStatusForm = { safeDMEnabled () { return this.$store.state.instance.safeDM }, - stickersAvailable () { - if (this.$store.state.instance.stickers) { - return this.$store.state.instance.stickers.length > 0 - } - return 0 - }, pollsAvailable () { return this.$store.state.instance.pollsAvailable && this.$store.state.instance.pollLimits.max_options >= 2 @@ -222,7 +214,6 @@ const PostStatusForm = { poll: {} } this.pollFormVisible = false - this.stickerPickerVisible = false this.$refs.mediaUpload.clearFile() this.clearPollForm() this.$emit('posted') @@ -239,7 +230,6 @@ const PostStatusForm = { addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) this.enableSubmit() - this.stickerPickerVisible = false }, removeMediaFile (fileInfo) { let index = this.newStatus.files.indexOf(fileInfo) @@ -260,6 +250,7 @@ const PostStatusForm = { return fileTypeService.fileType(fileInfo.mimetype) }, paste (e) { + this.resize(e) if (e.clipboardData.files.length > 0) { // prevent pasting of file as text e.preventDefault() @@ -278,20 +269,96 @@ const PostStatusForm = { fileDrag (e) { e.dataTransfer.dropEffect = 'copy' }, + onEmojiInputInput (e) { + this.$nextTick(() => { + this.resize(this.$refs['textarea']) + }) + }, resize (e) { const target = e.target || e if (!(target instanceof window.Element)) { return } + + // Reset to default height for empty form, nothing else to do here. + if (target.value === '') { + target.style.height = null + this.$refs['emoji-input'].resize() + return + } + + const rootRef = this.$refs['root'] + /* Scroller is either `window` (replies in TL), sidebar (main post form, + * replies in notifs) or mobile post form. Note that getting and setting + * scroll is different for `Window` and `Element`s + */ + const scrollerRef = this.$el.closest('.sidebar-scroller') || + this.$el.closest('.post-form-modal-view') || + window + + // Getting info about padding we have to account for, removing 'px' part const topPaddingStr = window.getComputedStyle(target)['padding-top'] const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom'] - // Remove "px" at the end of the values - const vertPadding = Number(topPaddingStr.substr(0, topPaddingStr.length - 2)) + - Number(bottomPaddingStr.substr(0, bottomPaddingStr.length - 2)) - // Auto is needed to make textbox shrink when removing lines + const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2)) + const bottomPadding = Number(bottomPaddingStr.substring(0, bottomPaddingStr.length - 2)) + const vertPadding = topPadding + bottomPadding + + const oldHeightStr = target.style.height || '' + const oldHeight = Number(oldHeightStr.substring(0, oldHeightStr.length - 2)) + + /* Explanation: + * + * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight + * scrollHeight returns element's scrollable content height, i.e. visible + * element + overscrolled parts of it. We use it to determine when text + * inside the textarea exceeded its height, so we can set height to prevent + * overscroll, i.e. make textarea grow with the text. HOWEVER, since we + * explicitly set new height, scrollHeight won't go below that, so we can't + * SHRINK the textarea when there's extra space. To workaround that we set + * height to 'auto' which makes textarea tiny again, so that scrollHeight + * will match text height again. HOWEVER, shrinking textarea can screw with + * the scroll since there might be not enough padding around root to even + * warrant a scroll, so it will jump to 0 and refuse to move anywhere, + * so we check current scroll position before shrinking and then restore it + * with needed delta. + */ + + // this part has to be BEFORE the content size update + const currentScroll = scrollerRef === window + ? scrollerRef.scrollY + : scrollerRef.scrollTop + const scrollerHeight = scrollerRef === window + ? scrollerRef.innerHeight + : scrollerRef.offsetHeight + const scrollerBottomBorder = currentScroll + scrollerHeight + + // BEGIN content size update target.style.height = 'auto' - target.style.height = `${target.scrollHeight - vertPadding}px` - if (target.value === '') { - target.style.height = null + const newHeight = target.scrollHeight - vertPadding + target.style.height = `${newHeight}px` + // END content size update + + // We check where the bottom border of root element is, this uses findOffset + // to find offset relative to scrollable container (scroller) + const rootBottomBorder = rootRef.offsetHeight + findOffset(rootRef, scrollerRef).top + + const textareaSizeChangeDelta = newHeight - oldHeight || 0 + const isBottomObstructed = scrollerBottomBorder < rootBottomBorder + const rootChangeDelta = rootBottomBorder - scrollerBottomBorder + const totalDelta = textareaSizeChangeDelta + + (isBottomObstructed ? rootChangeDelta : 0) + + const targetScroll = currentScroll + totalDelta + + if (scrollerRef === window) { + scrollerRef.scroll(0, targetScroll) + } else { + scrollerRef.scrollTop = targetScroll } + + this.$refs['emoji-input'].resize() + }, + showEmojiPicker () { + this.$refs['textarea'].focus() + this.$refs['emoji-input'].triggerShowPicker() }, clearError () { this.error = null @@ -299,14 +366,6 @@ const PostStatusForm = { changeVis (visibility) { this.newStatus.visibility = visibility }, - toggleStickerPicker () { - this.stickerPickerVisible = !this.stickerPickerVisible - }, - clearStickerPicker () { - if (this.$refs.stickerPicker) { - this.$refs.stickerPicker.clear() - } - }, togglePollForm () { this.pollFormVisible = !this.pollFormVisible }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index d29d47e4..4916d988 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -1,5 +1,8 @@ <template> - <div class="post-status-form"> + <div + ref="root" + class="post-status-form" + > <form autocomplete="off" @submit.prevent="postStatus(newStatus)" @@ -61,6 +64,7 @@ <EmojiInput v-if="newStatus.spoilerText || alwaysShowSubject" v-model="newStatus.spoilerText" + enable-emoji-picker :suggest="emojiSuggestor" class="form-control" > @@ -73,9 +77,16 @@ > </EmojiInput> <EmojiInput + ref="emoji-input" v-model="newStatus.status" :suggest="emojiUserSuggestor" class="form-control main-input" + enable-emoji-picker + hide-emoji-button + enable-sticker-picker + @input="onEmojiInputInput" + @sticker-uploaded="addMediaFile" + @sticker-upload-failed="uploadFailed" > <textarea ref="textarea" @@ -89,6 +100,7 @@ @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" + @compositionupdate="resize" @paste="paste" /> <p @@ -152,30 +164,29 @@ <div class="form-bottom-left"> <media-upload ref="mediaUpload" + class="media-upload-icon" :drop-files="dropFiles" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" /> <div - v-if="stickersAvailable" - class="sticker-icon" + class="emoji-icon" > <i - :title="$t('stickers.add_sticker')" - class="icon-picture btn btn-default" - :class="{ selected: stickerPickerVisible }" - @click="toggleStickerPicker" + :title="$t('emoji.add_emoji')" + class="icon-smile btn btn-default" + @click="showEmojiPicker" /> </div> <div v-if="pollsAvailable" class="poll-icon" + :class="{ selected: pollFormVisible }" > <i :title="$t('polls.add_poll')" class="icon-chart-bar btn btn-default" - :class="pollFormVisible && 'selected'" @click="togglePollForm" /> </div> @@ -258,11 +269,6 @@ <label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label> </div> </form> - <sticker-picker - v-if="stickerPickerVisible" - ref="stickerPicker" - @uploaded="addMediaFile" - /> </div> </template> @@ -299,6 +305,7 @@ .post-status-form { .form-bottom { display: flex; + justify-content: space-between; padding: 0.5em; height: 32px; @@ -316,6 +323,9 @@ .form-bottom-left { display: flex; flex: 1; + padding-right: 7px; + margin-right: 7px; + max-width: 10em; } .text-format { @@ -325,19 +335,38 @@ } } - .poll-icon, .sticker-icon { + .media-upload-icon, .poll-icon, .emoji-icon { font-size: 26px; flex: 1; - .selected { - color: $fallback--lightText; - color: var(--lightText, $fallback--lightText); + i { + display: block; + width: 100%; + } + + &.selected, &:hover { + // needs to be specific to override icon default color + i, label { + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } } } - .sticker-icon { - flex: 0; - min-width: 50px; + // Order is not necessary but a good indicator + .media-upload-icon { + order: 1; + text-align: left; + } + + .emoji-icon { + order: 2; + text-align: center; + } + + .poll-icon { + order: 3; + text-align: right; } .icon-chart-bar { @@ -369,6 +398,13 @@ } } + .status-input-wrapper { + display: flex; + position: relative; + width: 100%; + flex-direction: column; + } + .attachments { padding: 0 0.5em; @@ -444,10 +480,6 @@ box-sizing: content-box; } - .form-post-body:focus { - min-height: 48px; - } - .main-input { position: relative; } |
