Add Chats

This commit is contained in:
eugenijm 2020-05-07 16:10:53 +03:00
parent a0ddcbdf5b
commit aa2cf51c05
69 changed files with 2794 additions and 161 deletions

View file

@ -9,7 +9,7 @@ import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters } from 'vuex'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
@ -33,7 +33,22 @@ const PostStatusForm = {
'repliedUser',
'attentions',
'copyMessageScope',
'subject'
'subject',
'disableSubject',
'disableScopeSelector',
'disableNotice',
'disableLockWarning',
'disablePolls',
'disableSensitivityCheckbox',
'disableSubmit',
'placeholder',
'maxHeight',
'request',
'preserveFocus',
'autoFocus',
'fileLimit',
'submitOnEnter',
'emojiPickerPlacement'
],
components: {
MediaUpload,
@ -46,10 +61,13 @@ const PostStatusForm = {
},
mounted () {
this.resize(this.$refs.textarea)
const textLength = this.$refs.textarea.value.length
this.$refs.textarea.setSelectionRange(textLength, textLength)
if (this.replyTo) {
const textLength = this.$refs.textarea.value.length
this.$refs.textarea.setSelectionRange(textLength, textLength)
}
if (this.replyTo || this.autoFocus) {
this.$refs.textarea.focus()
}
},
@ -72,7 +90,7 @@ const PostStatusForm = {
return {
dropFiles: [],
submitDisabled: false,
uploadingFiles: false,
error: null,
posting: false,
highlighted: 0,
@ -91,7 +109,8 @@ const PostStatusForm = {
showDropIcon: 'hide',
dropStopTimeout: null,
preview: null,
previewLoading: false
previewLoading: false,
emojiInputShown: false
}
},
computed: {
@ -160,10 +179,11 @@ const PostStatusForm = {
},
pollsAvailable () {
return this.$store.state.instance.pollsAvailable &&
this.$store.state.instance.pollLimits.max_options >= 2
this.$store.state.instance.pollLimits.max_options >= 2 &&
this.disablePolls !== true
},
hideScopeNotice () {
return this.$store.getters.mergedConfig.hideScopeNotice
return this.disableNotice || this.$store.getters.mergedConfig.hideScopeNotice
},
pollContentError () {
return this.pollFormVisible &&
@ -176,7 +196,13 @@ const PostStatusForm = {
emptyStatus () {
return this.newStatus.status.trim() === '' && this.newStatus.files.length === 0
},
...mapGetters(['mergedConfig'])
uploadFileLimitReached () {
return this.newStatus.files.length >= this.fileLimit
},
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout
})
},
watch: {
'newStatus.contentType': function () {
@ -187,9 +213,19 @@ const PostStatusForm = {
}
},
methods: {
async postStatus (newStatus) {
async postStatus (event, newStatus, opts = {}) {
if (this.posting) { return }
if (this.submitDisabled) { return }
if (this.emojiInputShown) { return }
if (this.submitOnEnter) {
event.stopPropagation()
event.preventDefault()
}
if (opts.control && this.submitOnEnter) {
newStatus.status = `${newStatus.status}\n`
return
}
if (this.emptyStatus) {
this.error = this.$t('post_status.empty_status_error')
return
@ -211,7 +247,7 @@ const PostStatusForm = {
return
}
const data = await statusPoster.postStatus({
const postingOptions = {
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility,
@ -221,32 +257,40 @@ const PostStatusForm = {
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType,
poll
})
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
mediaDescriptions: {}
}
this.pollFormVisible = false
this.$refs.mediaUpload.clearFile()
this.clearPollForm()
this.$emit('posted')
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
} else {
this.error = data.error
}
this.posting = false
const request = this.request ? this.request : statusPoster.postStatus
request(postingOptions).then((data) => {
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
mediaDescriptions: {}
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
this.$emit('posted', data)
if (this.preserveFocus) {
this.$nextTick(() => {
this.$refs.textarea.focus()
})
}
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
} else {
this.error = data.error
}
this.posting = false
})
},
previewStatus () {
if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') {
@ -301,20 +345,26 @@ const PostStatusForm = {
},
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
// TODO: use fixed dimensions instead so relying on timeout
setTimeout(() => {
this.$emit('resize')
}, 150)
},
removeMediaFile (fileInfo) {
let index = this.newStatus.files.indexOf(fileInfo)
this.newStatus.files.splice(index, 1)
this.$emit('resize')
},
uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
},
disableSubmit () {
this.submitDisabled = true
startedUploadingFiles () {
this.uploadingFiles = true
},
enableSubmit () {
this.submitDisabled = false
finishedUploadingFiles () {
this.uploadingFiles = false
},
type (fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype)
@ -348,7 +398,7 @@ const PostStatusForm = {
this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
},
fileDrag (e) {
e.dataTransfer.dropEffect = 'copy'
e.dataTransfer.dropEffect = this.uploadFileLimitReached ? 'none' : 'copy'
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'show'
@ -367,6 +417,7 @@ const PostStatusForm = {
// Reset to default height for empty form, nothing else to do here.
if (target.value === '') {
target.style.height = null
this.$emit('resize', null)
this.$refs['emoji-input'].resize()
return
}
@ -419,8 +470,10 @@ const PostStatusForm = {
// BEGIN content size update
target.style.height = 'auto'
const newHeight = target.scrollHeight - vertPadding
const heightWithoutPadding = target.scrollHeight - vertPadding
const newHeight = this.maxHeight ? Math.min(heightWithoutPadding, this.maxHeight) : heightWithoutPadding
target.style.height = `${newHeight}px`
this.$emit('resize', newHeight)
// END content size update
// We check where the bottom border of form-bottom element is, this uses findOffset
@ -480,6 +533,9 @@ const PostStatusForm = {
setAllMediaDescriptions () {
const ids = this.newStatus.files.map(file => file.id)
return Promise.all(ids.map(id => this.setMediaDescription(id)))
},
handleEmojiInputShow (value) {
this.emojiInputShown = value
}
}
}

View file

@ -5,19 +5,20 @@
>
<form
autocomplete="off"
@submit.prevent="postStatus(newStatus)"
@submit.prevent
@dragover.prevent="fileDrag"
>
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator icon-upload"
class="drop-indicator"
:class="[uploadFileLimitReached ? 'icon-block' : 'icon-upload']"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
/>
<div class="form-group">
<i18n
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
path="post_status.account_not_locked_warning"
tag="p"
class="visibility-notice"
@ -108,7 +109,7 @@
/>
</div>
<EmojiInput
v-if="newStatus.spoilerText || alwaysShowSubject"
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
@ -126,6 +127,7 @@
ref="emoji-input"
v-model="newStatus.status"
:suggest="emojiUserSuggestor"
:placement="emojiPickerPlacement"
class="form-control main-input"
enable-emoji-picker
hide-emoji-button
@ -133,16 +135,19 @@
@input="onEmojiInputInput"
@sticker-uploaded="addMediaFile"
@sticker-upload-failed="uploadFailed"
@shown="handleEmojiInputShow"
>
<textarea
ref="textarea"
v-model="newStatus.status"
:placeholder="$t('post_status.default')"
:placeholder="placeholder || $t('post_status.default')"
rows="1"
:disabled="posting"
class="form-post-body"
@keydown.meta.enter="postStatus(newStatus)"
@keydown.ctrl.enter="postStatus(newStatus)"
:class="{ 'scrollable-form': !!maxHeight }"
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
@keydown.meta.enter="postStatus($event, newStatus, { control: true })"
@keydown.ctrl.enter="postStatus($event, newStatus)"
@input="resize"
@compositionupdate="resize"
@paste="paste"
@ -155,7 +160,10 @@
{{ charactersLeft }}
</p>
</EmojiInput>
<div class="visibility-tray">
<div
v-if="!disableScopeSelector"
class="visibility-tray"
>
<scope-selector
:show-all="showAllScopes"
:user-default="userDefaultScope"
@ -213,10 +221,11 @@
ref="mediaUpload"
class="media-upload-icon"
:drop-files="dropFiles"
@uploading="disableSubmit"
:disabled="uploadFileLimitReached"
@uploading="startedUploadingFiles"
@uploaded="addMediaFile"
@upload-failed="uploadFailed"
@all-uploaded="enableSubmit"
@all-uploaded="finishedUploadingFiles"
/>
<div
class="emoji-icon"
@ -253,11 +262,13 @@
>
{{ $t('general.submit') }}
</button>
<!-- touchstart is used to keep the OSK at the same position after a message send -->
<button
v-else
:disabled="submitDisabled"
type="submit"
:disabled="uploadingFiles || disableSubmit"
class="btn btn-default"
@touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)"
>
{{ $t('general.submit') }}
</button>
@ -297,7 +308,7 @@
</div>
</div>
<div
v-if="newStatus.files.length > 0"
v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox"
class="upload_settings"
>
<Checkbox v-model="newStatus.nsfw">
@ -331,6 +342,8 @@
}
.post-status-form {
position: relative;
.form-bottom {
display: flex;
justify-content: space-between;
@ -547,6 +560,10 @@
padding-bottom: 1.75em;
min-height: 1px;
box-sizing: content-box;
&.scrollable-form {
overflow-y: auto;
}
}
.main-input {
@ -609,4 +626,11 @@
border: 2px dashed var(--text, $fallback--text);
}
}
// todo: unify with attachment.vue (otherwise the uploaded images are not minified unless a status with an attachment was displayed before)
img.media-upload {
line-height: 0;
max-height: 200px;
max-width: 100%;
}
</style>