aboutsummaryrefslogtreecommitdiff
path: root/src/components/post_status_form
diff options
context:
space:
mode:
authorHJ <30-hj@users.noreply.git.pleroma.social>2020-07-10 09:04:45 +0000
committerHJ <30-hj@users.noreply.git.pleroma.social>2020-07-10 09:04:45 +0000
commitc1a20079bef51dc38cb9826cee5bb2fbfe2cf68b (patch)
treedeed4aa330bcc8b983855df3a24c23ddb4bfeeb8 /src/components/post_status_form
parentd2f0e4e7d515afe4b15d4e6a0e52d9fee2349c4a (diff)
parentb756c83e8d275c0f9d210c15a319d36dca56d3c8 (diff)
Merge branch 'direct-conversations' into 'develop'
Chats Closes #201 See merge request pleroma/pleroma-fe!1019
Diffstat (limited to 'src/components/post_status_form')
-rw-r--r--src/components/post_status_form/post_status_form.js134
-rw-r--r--src/components/post_status_form/post_status_form.vue69
2 files changed, 147 insertions, 56 deletions
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 18f02eba..b0d94555 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -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,23 @@ const PostStatusForm = {
'repliedUser',
'attentions',
'copyMessageScope',
- 'subject'
+ 'subject',
+ 'disableSubject',
+ 'disableScopeSelector',
+ 'disableNotice',
+ 'disableLockWarning',
+ 'disablePolls',
+ 'disableSensitivityCheckbox',
+ 'disableSubmit',
+ 'disablePreview',
+ 'placeholder',
+ 'maxHeight',
+ 'postHandler',
+ 'preserveFocus',
+ 'autoFocus',
+ 'fileLimit',
+ 'submitOnEnter',
+ 'emojiPickerPlacement'
],
components: {
MediaUpload,
@@ -46,10 +62,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 +91,7 @@ const PostStatusForm = {
return {
dropFiles: [],
- submitDisabled: false,
+ uploadingFiles: false,
error: null,
posting: false,
highlighted: 0,
@@ -91,7 +110,8 @@ const PostStatusForm = {
showDropIcon: 'hide',
dropStopTimeout: null,
preview: null,
- previewLoading: false
+ previewLoading: false,
+ emojiInputShown: false
}
},
computed: {
@@ -160,10 +180,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 &&
@@ -171,12 +192,18 @@ const PostStatusForm = {
this.newStatus.poll.error
},
showPreview () {
- return !!this.preview || this.previewLoading
+ return !this.disablePreview && (!!this.preview || this.previewLoading)
},
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 +214,15 @@ 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 (this.emptyStatus) {
this.error = this.$t('post_status.empty_status_error')
return
@@ -211,7 +244,7 @@ const PostStatusForm = {
return
}
- const data = await statusPoster.postStatus({
+ const postingOptions = {
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility,
@@ -221,32 +254,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 postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus
+
+ postHandler(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 +342,23 @@ const PostStatusForm = {
},
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
+ this.$emit('resize', { delayed: true })
},
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.$emit('resize')
+ this.uploadingFiles = false
},
type (fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype)
@@ -348,7 +392,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 +411,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')
this.$refs['emoji-input'].resize()
return
}
@@ -419,8 +464,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 +527,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
}
}
}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 626584ed..3dcf1f79 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -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"
@@ -69,7 +70,10 @@
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p>
- <div class="preview-heading faint">
+ <div
+ v-if="!disablePreview"
+ class="preview-heading faint"
+ >
<a
class="preview-toggle faint"
@click.stop.prevent="togglePreview"
@@ -108,7 +112,7 @@
/>
</div>
<EmojiInput
- v-if="newStatus.spoilerText || alwaysShowSubject"
+ v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
@@ -126,23 +130,28 @@
ref="emoji-input"
v-model="newStatus.status"
:suggest="emojiUserSuggestor"
+ :placement="emojiPickerPlacement"
class="form-control main-input"
enable-emoji-picker
hide-emoji-button
+ :newline-on-ctrl-enter="submitOnEnter"
enable-sticker-picker
@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)"
+ @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
@input="resize"
@compositionupdate="resize"
@paste="paste"
@@ -155,7 +164,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 +225,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 +266,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 +312,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 +346,8 @@
}
.post-status-form {
+ position: relative;
+
.form-bottom {
display: flex;
justify-content: space-between;
@@ -422,6 +439,19 @@
color: var(--lightText, $fallback--lightText);
}
}
+
+ &.disabled {
+ i {
+ cursor: not-allowed;
+ color: $fallback--icon;
+ color: var(--btnDisabledText, $fallback--icon);
+
+ &:hover {
+ color: $fallback--icon;
+ color: var(--btnDisabledText, $fallback--icon);
+ }
+ }
+ }
}
// Order is not necessary but a good indicator
@@ -547,6 +577,10 @@
padding-bottom: 1.75em;
min-height: 1px;
box-sizing: content-box;
+
+ &.scrollable-form {
+ overflow-y: auto;
+ }
}
.main-input {
@@ -609,4 +643,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, .media-upload-container > video {
+ line-height: 0;
+ max-height: 200px;
+ max-width: 100%;
+}
</style>