2016-10-30 15:53:58 +00:00
|
|
|
import statusPoster from '../../services/status_poster/status_poster.service.js'
|
2016-11-06 18:30:35 +00:00
|
|
|
import MediaUpload from '../media_upload/media_upload.vue'
|
2019-03-03 13:15:41 +00:00
|
|
|
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
2019-08-12 10:18:37 +00:00
|
|
|
import EmojiInput from '../emoji_input/emoji_input.vue'
|
2019-06-18 20:28:31 +00:00
|
|
|
import PollForm from '../poll/poll_form.vue'
|
2020-06-29 11:48:22 +00:00
|
|
|
import Attachment from '../attachment/attachment.vue'
|
2020-06-28 09:16:41 +00:00
|
|
|
import StatusContent from '../status_content/status_content.vue'
|
2016-11-25 17:21:25 +00:00
|
|
|
import fileTypeService from '../../services/file_type/file_type.service.js'
|
2019-09-24 21:17:04 +00:00
|
|
|
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
2020-06-28 12:14:01 +00:00
|
|
|
import { reject, map, uniqBy, debounce } from 'lodash'
|
2019-08-12 10:18:37 +00:00
|
|
|
import suggestor from '../emoji_input/suggestor.js'
|
2020-05-07 13:10:53 +00:00
|
|
|
import { mapGetters, mapState } from 'vuex'
|
2019-10-07 17:43:23 +00:00
|
|
|
import Checkbox from '../checkbox/checkbox.vue'
|
2016-11-03 16:17:32 +00:00
|
|
|
|
2020-10-19 16:38:49 +00:00
|
|
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
|
|
import {
|
|
|
|
faChevronDown,
|
|
|
|
faSmileBeam,
|
|
|
|
faPollH,
|
|
|
|
faUpload,
|
2020-10-20 18:18:23 +00:00
|
|
|
faBan,
|
2020-10-20 21:01:28 +00:00
|
|
|
faTimes,
|
|
|
|
faCircleNotch
|
2020-10-19 16:38:49 +00:00
|
|
|
} from '@fortawesome/free-solid-svg-icons'
|
|
|
|
|
|
|
|
library.add(
|
|
|
|
faChevronDown,
|
|
|
|
faSmileBeam,
|
|
|
|
faPollH,
|
|
|
|
faUpload,
|
2020-10-20 18:18:23 +00:00
|
|
|
faBan,
|
2020-10-20 21:01:28 +00:00
|
|
|
faTimes,
|
|
|
|
faCircleNotch
|
2020-10-19 16:38:49 +00:00
|
|
|
)
|
|
|
|
|
2019-09-19 18:38:55 +00:00
|
|
|
const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
|
2016-11-03 16:17:32 +00:00
|
|
|
let allAttentions = [...attentions]
|
|
|
|
|
|
|
|
allAttentions.unshift(user)
|
|
|
|
|
|
|
|
allAttentions = uniqBy(allAttentions, 'id')
|
2019-06-09 18:35:49 +00:00
|
|
|
allAttentions = reject(allAttentions, { id: currentUser.id })
|
2016-11-03 16:17:32 +00:00
|
|
|
|
|
|
|
let mentions = map(allAttentions, (attention) => {
|
|
|
|
return `@${attention.screen_name}`
|
|
|
|
})
|
|
|
|
|
2019-01-31 00:50:19 +00:00
|
|
|
return mentions.length > 0 ? mentions.join(' ') + ' ' : ''
|
2016-11-03 16:17:32 +00:00
|
|
|
}
|
2016-10-30 15:53:58 +00:00
|
|
|
|
2020-07-15 13:36:06 +00:00
|
|
|
// Converts a string with px to a number like '2px' -> 2
|
|
|
|
const pxStringToNumber = (str) => {
|
|
|
|
return Number(str.substring(0, str.length - 2))
|
|
|
|
}
|
|
|
|
|
2016-10-30 15:53:58 +00:00
|
|
|
const PostStatusForm = {
|
2016-11-03 15:59:27 +00:00
|
|
|
props: [
|
2016-11-03 16:17:32 +00:00
|
|
|
'replyTo',
|
|
|
|
'repliedUser',
|
2018-06-12 17:28:48 +00:00
|
|
|
'attentions',
|
2018-09-25 12:16:26 +00:00
|
|
|
'copyMessageScope',
|
2020-05-07 13:10:53 +00:00
|
|
|
'subject',
|
|
|
|
'disableSubject',
|
|
|
|
'disableScopeSelector',
|
|
|
|
'disableNotice',
|
|
|
|
'disableLockWarning',
|
|
|
|
'disablePolls',
|
|
|
|
'disableSensitivityCheckbox',
|
|
|
|
'disableSubmit',
|
2020-07-06 13:55:29 +00:00
|
|
|
'disablePreview',
|
2020-05-07 13:10:53 +00:00
|
|
|
'placeholder',
|
|
|
|
'maxHeight',
|
2020-06-21 14:13:29 +00:00
|
|
|
'postHandler',
|
2020-05-07 13:10:53 +00:00
|
|
|
'preserveFocus',
|
|
|
|
'autoFocus',
|
|
|
|
'fileLimit',
|
|
|
|
'submitOnEnter',
|
2020-10-29 10:33:06 +00:00
|
|
|
'emojiPickerPlacement',
|
|
|
|
'optimisticPosting'
|
2016-11-03 15:59:27 +00:00
|
|
|
],
|
2016-11-06 18:30:35 +00:00
|
|
|
components: {
|
2019-03-03 13:15:41 +00:00
|
|
|
MediaUpload,
|
2019-04-08 16:02:50 +00:00
|
|
|
EmojiInput,
|
2019-06-18 20:28:31 +00:00
|
|
|
PollForm,
|
2019-10-07 17:43:23 +00:00
|
|
|
ScopeSelector,
|
2020-06-29 11:48:22 +00:00
|
|
|
Checkbox,
|
2020-07-07 07:01:37 +00:00
|
|
|
Attachment,
|
2020-06-28 09:16:41 +00:00
|
|
|
StatusContent
|
2016-11-06 18:30:35 +00:00
|
|
|
},
|
2018-04-15 16:05:16 +00:00
|
|
|
mounted () {
|
2020-07-15 13:19:57 +00:00
|
|
|
this.updateIdempotencyKey()
|
2019-02-14 19:15:59 +00:00
|
|
|
this.resize(this.$refs.textarea)
|
|
|
|
|
2018-08-05 19:17:11 +00:00
|
|
|
if (this.replyTo) {
|
2020-05-07 13:10:53 +00:00
|
|
|
const textLength = this.$refs.textarea.value.length
|
|
|
|
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.replyTo || this.autoFocus) {
|
2018-08-05 19:17:11 +00:00
|
|
|
this.$refs.textarea.focus()
|
|
|
|
}
|
2018-04-15 16:05:16 +00:00
|
|
|
},
|
2016-11-03 15:59:27 +00:00
|
|
|
data () {
|
2018-04-29 14:44:08 +00:00
|
|
|
const preset = this.$route.query.message
|
|
|
|
let statusText = preset || ''
|
2016-11-03 16:17:32 +00:00
|
|
|
|
2019-09-29 19:33:15 +00:00
|
|
|
const { scopeCopy } = this.$store.getters.mergedConfig
|
2018-12-18 20:31:10 +00:00
|
|
|
|
2016-11-03 16:17:32 +00:00
|
|
|
if (this.replyTo) {
|
|
|
|
const currentUser = this.$store.state.users.currentUser
|
|
|
|
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
|
|
|
|
}
|
|
|
|
|
2019-06-09 18:35:49 +00:00
|
|
|
const scope = ((this.copyMessageScope && scopeCopy) || this.copyMessageScope === 'direct')
|
|
|
|
? this.copyMessageScope
|
|
|
|
: this.$store.state.users.currentUser.default_scope
|
2018-09-25 12:16:26 +00:00
|
|
|
|
2021-02-23 08:00:23 +00:00
|
|
|
const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
|
2019-02-21 16:16:11 +00:00
|
|
|
|
2016-10-30 15:53:58 +00:00
|
|
|
return {
|
2017-02-21 13:13:19 +00:00
|
|
|
dropFiles: [],
|
2020-05-07 13:10:53 +00:00
|
|
|
uploadingFiles: false,
|
2017-08-20 10:16:41 +00:00
|
|
|
error: null,
|
2017-08-21 12:35:14 +00:00
|
|
|
posting: false,
|
2019-02-14 19:15:59 +00:00
|
|
|
highlighted: 0,
|
2016-11-03 16:17:32 +00:00
|
|
|
newStatus: {
|
2018-11-07 16:20:33 +00:00
|
|
|
spoilerText: this.subject || '',
|
2016-11-06 18:30:35 +00:00
|
|
|
status: statusText,
|
2021-02-23 08:00:23 +00:00
|
|
|
nsfw: !!sensitiveByDefault,
|
2018-06-08 13:25:48 +00:00
|
|
|
files: [],
|
2019-06-18 20:28:31 +00:00
|
|
|
poll: {},
|
2019-02-18 05:03:26 +00:00
|
|
|
mediaDescriptions: {},
|
2019-02-21 16:16:11 +00:00
|
|
|
visibility: scope,
|
|
|
|
contentType
|
2019-02-14 19:15:59 +00:00
|
|
|
},
|
2019-06-18 20:28:31 +00:00
|
|
|
caret: 0,
|
2020-06-10 08:01:38 +00:00
|
|
|
pollFormVisible: false,
|
2020-06-10 09:41:02 +00:00
|
|
|
showDropIcon: 'hide',
|
2020-06-28 09:16:41 +00:00
|
|
|
dropStopTimeout: null,
|
|
|
|
preview: null,
|
2020-05-07 13:10:53 +00:00
|
|
|
previewLoading: false,
|
2020-07-15 13:19:57 +00:00
|
|
|
emojiInputShown: false,
|
|
|
|
idempotencyKey: ''
|
2016-10-30 15:53:58 +00:00
|
|
|
}
|
|
|
|
},
|
2016-11-30 12:39:17 +00:00
|
|
|
computed: {
|
2019-02-14 19:15:59 +00:00
|
|
|
users () {
|
|
|
|
return this.$store.state.users.users
|
|
|
|
},
|
2019-03-03 13:15:41 +00:00
|
|
|
userDefaultScope () {
|
|
|
|
return this.$store.state.users.currentUser.default_scope
|
|
|
|
},
|
|
|
|
showAllScopes () {
|
2019-10-09 18:32:32 +00:00
|
|
|
return !this.mergedConfig.minimalScopesMode
|
2019-03-03 13:15:41 +00:00
|
|
|
},
|
2019-06-06 21:17:49 +00:00
|
|
|
emojiUserSuggestor () {
|
|
|
|
return suggestor({
|
|
|
|
emoji: [
|
|
|
|
...this.$store.state.instance.emoji,
|
|
|
|
...this.$store.state.instance.customEmoji
|
|
|
|
],
|
2020-11-18 16:43:24 +00:00
|
|
|
store: this.$store
|
2019-06-06 21:17:49 +00:00
|
|
|
})
|
|
|
|
},
|
|
|
|
emojiSuggestor () {
|
2019-06-09 18:35:49 +00:00
|
|
|
return suggestor({
|
|
|
|
emoji: [
|
|
|
|
...this.$store.state.instance.emoji,
|
|
|
|
...this.$store.state.instance.customEmoji
|
|
|
|
]
|
|
|
|
})
|
2019-06-06 21:17:49 +00:00
|
|
|
},
|
2019-02-14 19:15:59 +00:00
|
|
|
emoji () {
|
|
|
|
return this.$store.state.instance.emoji || []
|
|
|
|
},
|
|
|
|
customEmoji () {
|
|
|
|
return this.$store.state.instance.customEmoji || []
|
|
|
|
},
|
2018-02-09 14:51:04 +00:00
|
|
|
statusLength () {
|
|
|
|
return this.newStatus.status.length
|
|
|
|
},
|
2018-11-07 16:20:33 +00:00
|
|
|
spoilerTextLength () {
|
|
|
|
return this.newStatus.spoilerText.length
|
|
|
|
},
|
2018-02-09 14:51:04 +00:00
|
|
|
statusLengthLimit () {
|
2018-09-09 19:31:34 +00:00
|
|
|
return this.$store.state.instance.textlimit
|
2018-02-09 14:51:04 +00:00
|
|
|
},
|
|
|
|
hasStatusLengthLimit () {
|
|
|
|
return this.statusLengthLimit > 0
|
|
|
|
},
|
|
|
|
charactersLeft () {
|
2018-11-07 16:20:33 +00:00
|
|
|
return this.statusLengthLimit - (this.statusLength + this.spoilerTextLength)
|
2018-02-11 14:07:29 +00:00
|
|
|
},
|
|
|
|
isOverLengthLimit () {
|
2018-11-07 16:20:33 +00:00
|
|
|
return this.hasStatusLengthLimit && (this.charactersLeft < 0)
|
2018-06-08 13:25:48 +00:00
|
|
|
},
|
2019-03-30 10:41:42 +00:00
|
|
|
minimalScopesMode () {
|
|
|
|
return this.$store.state.instance.minimalScopesMode
|
2018-08-31 14:00:41 +00:00
|
|
|
},
|
2018-12-03 03:47:35 +00:00
|
|
|
alwaysShowSubject () {
|
2019-09-29 19:33:15 +00:00
|
|
|
return this.mergedConfig.alwaysShowSubjectInput
|
2018-12-03 03:47:35 +00:00
|
|
|
},
|
2019-03-07 04:13:04 +00:00
|
|
|
postFormats () {
|
|
|
|
return this.$store.state.instance.postFormats || []
|
2019-04-02 14:26:14 +00:00
|
|
|
},
|
2019-04-02 15:19:45 +00:00
|
|
|
safeDMEnabled () {
|
|
|
|
return this.$store.state.instance.safeDM
|
2019-02-14 19:15:59 +00:00
|
|
|
},
|
2019-06-18 20:28:31 +00:00
|
|
|
pollsAvailable () {
|
|
|
|
return this.$store.state.instance.pollsAvailable &&
|
2020-05-07 13:10:53 +00:00
|
|
|
this.$store.state.instance.pollLimits.max_options >= 2 &&
|
|
|
|
this.disablePolls !== true
|
2019-03-08 18:53:46 +00:00
|
|
|
},
|
2019-05-07 16:13:45 +00:00
|
|
|
hideScopeNotice () {
|
2020-05-07 13:10:53 +00:00
|
|
|
return this.disableNotice || this.$store.getters.mergedConfig.hideScopeNotice
|
2017-03-15 16:06:48 +00:00
|
|
|
},
|
2019-06-18 20:28:31 +00:00
|
|
|
pollContentError () {
|
|
|
|
return this.pollFormVisible &&
|
|
|
|
this.newStatus.poll &&
|
|
|
|
this.newStatus.poll.error
|
2019-09-29 19:33:15 +00:00
|
|
|
},
|
2020-06-28 09:16:41 +00:00
|
|
|
showPreview () {
|
2020-07-07 15:30:05 +00:00
|
|
|
return !this.disablePreview && (!!this.preview || this.previewLoading)
|
2020-06-28 09:16:41 +00:00
|
|
|
},
|
2020-06-28 12:43:08 +00:00
|
|
|
emptyStatus () {
|
2020-07-06 07:45:47 +00:00
|
|
|
return this.newStatus.status.trim() === '' && this.newStatus.files.length === 0
|
2020-06-28 12:43:08 +00:00
|
|
|
},
|
2020-05-07 13:10:53 +00:00
|
|
|
uploadFileLimitReached () {
|
|
|
|
return this.newStatus.files.length >= this.fileLimit
|
|
|
|
},
|
|
|
|
...mapGetters(['mergedConfig']),
|
|
|
|
...mapState({
|
|
|
|
mobileLayout: state => state.interface.mobileLayout
|
|
|
|
})
|
2016-11-30 12:39:17 +00:00
|
|
|
},
|
2020-06-28 09:16:41 +00:00
|
|
|
watch: {
|
2020-07-16 07:18:18 +00:00
|
|
|
'newStatus': {
|
|
|
|
deep: true,
|
|
|
|
handler () {
|
|
|
|
this.statusChanged()
|
|
|
|
}
|
2020-06-28 09:16:41 +00:00
|
|
|
}
|
|
|
|
},
|
2016-10-30 15:53:58 +00:00
|
|
|
methods: {
|
2020-07-16 07:18:18 +00:00
|
|
|
statusChanged () {
|
|
|
|
this.autoPreview()
|
|
|
|
this.updateIdempotencyKey()
|
|
|
|
},
|
2020-07-15 13:19:57 +00:00
|
|
|
clearStatus () {
|
|
|
|
const newStatus = this.newStatus
|
|
|
|
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()
|
|
|
|
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()
|
|
|
|
},
|
2020-05-07 13:10:53 +00:00
|
|
|
async postStatus (event, newStatus, opts = {}) {
|
2020-10-29 10:33:06 +00:00
|
|
|
if (this.posting && !this.optimisticPosting) { return }
|
2020-07-10 10:48:37 +00:00
|
|
|
if (this.disableSubmit) { return }
|
2020-05-07 13:10:53 +00:00
|
|
|
if (this.emojiInputShown) { return }
|
|
|
|
if (this.submitOnEnter) {
|
|
|
|
event.stopPropagation()
|
|
|
|
event.preventDefault()
|
|
|
|
}
|
|
|
|
|
2020-10-29 10:33:06 +00:00
|
|
|
if (this.optimisticPosting && (this.emptyStatus || this.isOverLengthLimit)) { return }
|
|
|
|
|
2020-06-28 12:43:08 +00:00
|
|
|
if (this.emptyStatus) {
|
2020-07-06 07:53:03 +00:00
|
|
|
this.error = this.$t('post_status.empty_status_error')
|
2020-06-28 12:43:08 +00:00
|
|
|
return
|
2017-08-24 13:16:06 +00:00
|
|
|
}
|
|
|
|
|
2019-06-18 20:28:31 +00:00
|
|
|
const poll = this.pollFormVisible ? this.newStatus.poll : {}
|
|
|
|
if (this.pollContentError) {
|
|
|
|
this.error = this.pollContentError
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-21 12:35:14 +00:00
|
|
|
this.posting = true
|
2020-07-07 06:07:20 +00:00
|
|
|
|
2020-07-07 07:01:37 +00:00
|
|
|
try {
|
|
|
|
await this.setAllMediaDescriptions()
|
|
|
|
} catch (e) {
|
|
|
|
this.error = this.$t('post_status.media_description_error')
|
|
|
|
this.posting = false
|
|
|
|
return
|
|
|
|
}
|
2020-07-07 06:07:20 +00:00
|
|
|
|
2020-05-07 13:10:53 +00:00
|
|
|
const postingOptions = {
|
2016-10-30 15:53:58 +00:00
|
|
|
status: newStatus.status,
|
2018-06-07 21:31:43 +00:00
|
|
|
spoilerText: newStatus.spoilerText || null,
|
2018-06-07 09:03:50 +00:00
|
|
|
visibility: newStatus.visibility,
|
2018-08-25 21:18:43 +00:00
|
|
|
sensitive: newStatus.nsfw,
|
2016-11-06 18:30:35 +00:00
|
|
|
media: newStatus.files,
|
2016-11-03 15:59:27 +00:00
|
|
|
store: this.$store,
|
2018-08-31 00:42:42 +00:00
|
|
|
inReplyToStatusId: this.replyTo,
|
2019-06-18 20:28:31 +00:00
|
|
|
contentType: newStatus.contentType,
|
2020-07-15 13:19:57 +00:00
|
|
|
poll,
|
|
|
|
idempotencyKey: this.idempotencyKey
|
2020-07-07 06:07:20 +00:00
|
|
|
}
|
|
|
|
|
2020-06-21 14:13:29 +00:00
|
|
|
const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus
|
2020-05-07 13:10:53 +00:00
|
|
|
|
2020-06-21 14:13:29 +00:00
|
|
|
postHandler(postingOptions).then((data) => {
|
2020-05-07 13:10:53 +00:00
|
|
|
if (!data.error) {
|
2020-07-15 13:19:57 +00:00
|
|
|
this.clearStatus()
|
2020-05-07 13:10:53 +00:00
|
|
|
this.$emit('posted', data)
|
|
|
|
} else {
|
|
|
|
this.error = data.error
|
|
|
|
}
|
|
|
|
this.posting = false
|
|
|
|
})
|
2016-11-06 18:30:35 +00:00
|
|
|
},
|
2020-06-28 13:40:39 +00:00
|
|
|
previewStatus () {
|
2020-07-06 07:45:47 +00:00
|
|
|
if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') {
|
2020-07-06 07:53:03 +00:00
|
|
|
this.preview = { error: this.$t('post_status.preview_empty') }
|
2020-06-28 12:43:08 +00:00
|
|
|
this.previewLoading = false
|
|
|
|
return
|
|
|
|
}
|
2020-06-28 13:40:39 +00:00
|
|
|
const newStatus = this.newStatus
|
2020-06-28 09:16:41 +00:00
|
|
|
this.previewLoading = true
|
|
|
|
statusPoster.postStatus({
|
|
|
|
status: newStatus.status,
|
|
|
|
spoilerText: newStatus.spoilerText || null,
|
|
|
|
visibility: newStatus.visibility,
|
|
|
|
sensitive: newStatus.nsfw,
|
2020-06-28 12:43:08 +00:00
|
|
|
media: [],
|
2020-06-28 09:16:41 +00:00
|
|
|
store: this.$store,
|
|
|
|
inReplyToStatusId: this.replyTo,
|
|
|
|
contentType: newStatus.contentType,
|
|
|
|
poll: {},
|
|
|
|
preview: true
|
|
|
|
}).then((data) => {
|
|
|
|
// Don't apply preview if not loading, because it means
|
|
|
|
// user has closed the preview manually.
|
|
|
|
if (!this.previewLoading) return
|
|
|
|
if (!data.error) {
|
|
|
|
this.preview = data
|
|
|
|
} else {
|
|
|
|
this.preview = { error: data.error }
|
|
|
|
}
|
|
|
|
}).catch((error) => {
|
|
|
|
this.preview = { error }
|
|
|
|
}).finally(() => {
|
|
|
|
this.previewLoading = false
|
|
|
|
})
|
|
|
|
},
|
2020-07-06 07:45:47 +00:00
|
|
|
debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500),
|
2020-06-28 12:14:01 +00:00
|
|
|
autoPreview () {
|
2020-06-28 12:43:08 +00:00
|
|
|
if (!this.preview) return
|
2020-06-28 12:14:01 +00:00
|
|
|
this.previewLoading = true
|
2020-06-28 13:40:39 +00:00
|
|
|
this.debouncePreviewStatus()
|
2020-06-28 12:14:01 +00:00
|
|
|
},
|
2020-06-28 09:16:41 +00:00
|
|
|
closePreview () {
|
|
|
|
this.preview = null
|
|
|
|
this.previewLoading = false
|
|
|
|
},
|
2020-06-28 13:40:39 +00:00
|
|
|
togglePreview () {
|
|
|
|
if (this.showPreview) {
|
|
|
|
this.closePreview()
|
|
|
|
} else {
|
|
|
|
this.previewStatus()
|
|
|
|
}
|
|
|
|
},
|
2016-11-06 18:30:35 +00:00
|
|
|
addMediaFile (fileInfo) {
|
|
|
|
this.newStatus.files.push(fileInfo)
|
2020-06-21 14:13:29 +00:00
|
|
|
this.$emit('resize', { delayed: true })
|
2016-11-24 22:07:21 +00:00
|
|
|
},
|
2016-11-26 02:00:06 +00:00
|
|
|
removeMediaFile (fileInfo) {
|
|
|
|
let index = this.newStatus.files.indexOf(fileInfo)
|
|
|
|
this.newStatus.files.splice(index, 1)
|
2020-05-07 13:10:53 +00:00
|
|
|
this.$emit('resize')
|
2016-11-26 02:00:06 +00:00
|
|
|
},
|
2018-12-08 21:36:54 +00:00
|
|
|
uploadFailed (errString, templateArgs) {
|
2018-12-08 21:39:58 +00:00
|
|
|
templateArgs = templateArgs || {}
|
2018-12-12 13:38:01 +00:00
|
|
|
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
|
2018-12-08 15:23:21 +00:00
|
|
|
},
|
2020-05-07 13:10:53 +00:00
|
|
|
startedUploadingFiles () {
|
|
|
|
this.uploadingFiles = true
|
2016-11-24 22:07:21 +00:00
|
|
|
},
|
2020-05-07 13:10:53 +00:00
|
|
|
finishedUploadingFiles () {
|
2020-06-21 14:13:29 +00:00
|
|
|
this.$emit('resize')
|
2020-05-07 13:10:53 +00:00
|
|
|
this.uploadingFiles = false
|
2016-11-25 17:21:25 +00:00
|
|
|
},
|
|
|
|
type (fileInfo) {
|
|
|
|
return fileTypeService.fileType(fileInfo.mimetype)
|
2017-02-21 13:13:19 +00:00
|
|
|
},
|
2017-11-28 20:31:40 +00:00
|
|
|
paste (e) {
|
2020-06-28 12:14:01 +00:00
|
|
|
this.autoPreview()
|
2019-09-23 21:06:53 +00:00
|
|
|
this.resize(e)
|
2017-11-28 20:31:40 +00:00
|
|
|
if (e.clipboardData.files.length > 0) {
|
2019-03-24 03:45:24 +00:00
|
|
|
// prevent pasting of file as text
|
|
|
|
e.preventDefault()
|
2017-11-28 20:31:40 +00:00
|
|
|
// Strangely, files property gets emptied after event propagation
|
|
|
|
// Trying to wrap it in array doesn't work. Plus I doubt it's possible
|
|
|
|
// to hold more than one file in clipboard.
|
|
|
|
this.dropFiles = [e.clipboardData.files[0]]
|
|
|
|
}
|
|
|
|
},
|
2017-02-21 13:13:19 +00:00
|
|
|
fileDrop (e) {
|
2020-06-10 08:01:38 +00:00
|
|
|
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
|
2019-06-09 18:35:49 +00:00
|
|
|
e.preventDefault() // allow dropping text like before
|
2017-02-21 13:13:19 +00:00
|
|
|
this.dropFiles = e.dataTransfer.files
|
2020-06-10 08:01:38 +00:00
|
|
|
clearTimeout(this.dropStopTimeout)
|
2020-06-10 09:41:02 +00:00
|
|
|
this.showDropIcon = 'hide'
|
2017-02-21 13:13:19 +00:00
|
|
|
}
|
2017-02-22 12:53:05 +00:00
|
|
|
},
|
2020-06-10 08:01:38 +00:00
|
|
|
fileDragStop (e) {
|
|
|
|
// The false-setting is done with delay because just using leave-events
|
|
|
|
// directly caused unwanted flickering, this is not perfect either but
|
|
|
|
// much less noticable.
|
|
|
|
clearTimeout(this.dropStopTimeout)
|
2020-06-10 09:41:02 +00:00
|
|
|
this.showDropIcon = 'fade'
|
|
|
|
this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
|
2020-06-10 08:01:38 +00:00
|
|
|
},
|
2017-02-22 12:53:05 +00:00
|
|
|
fileDrag (e) {
|
2020-05-07 13:10:53 +00:00
|
|
|
e.dataTransfer.dropEffect = this.uploadFileLimitReached ? 'none' : 'copy'
|
2020-06-10 08:01:38 +00:00
|
|
|
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
|
|
|
|
clearTimeout(this.dropStopTimeout)
|
2020-06-10 09:41:02 +00:00
|
|
|
this.showDropIcon = 'show'
|
2020-06-10 08:01:38 +00:00
|
|
|
}
|
2017-05-31 11:02:54 +00:00
|
|
|
},
|
2019-09-23 19:12:25 +00:00
|
|
|
onEmojiInputInput (e) {
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.resize(this.$refs['textarea'])
|
|
|
|
})
|
|
|
|
},
|
2019-02-14 19:15:59 +00:00
|
|
|
resize (e) {
|
|
|
|
const target = e.target || e
|
|
|
|
if (!(target instanceof window.Element)) { return }
|
2019-09-25 16:30:55 +00:00
|
|
|
|
|
|
|
// Reset to default height for empty form, nothing else to do here.
|
2019-09-24 21:17:04 +00:00
|
|
|
if (target.value === '') {
|
|
|
|
target.style.height = null
|
2020-06-21 14:13:29 +00:00
|
|
|
this.$emit('resize')
|
2019-09-24 21:17:04 +00:00
|
|
|
this.$refs['emoji-input'].resize()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-22 20:53:23 +00:00
|
|
|
const formRef = this.$refs['form']
|
|
|
|
const bottomRef = this.$refs['bottom']
|
2019-09-25 16:30:55 +00:00
|
|
|
/* 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
|
|
|
|
*/
|
2019-10-22 20:53:23 +00:00
|
|
|
const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']
|
2020-07-15 13:36:06 +00:00
|
|
|
const bottomBottomPadding = pxStringToNumber(bottomBottomPaddingStr)
|
2019-10-22 20:53:23 +00:00
|
|
|
|
2019-09-25 16:30:55 +00:00
|
|
|
const scrollerRef = this.$el.closest('.sidebar-scroller') ||
|
2019-09-24 21:17:04 +00:00
|
|
|
this.$el.closest('.post-form-modal-view') ||
|
|
|
|
window
|
|
|
|
|
2019-09-25 16:30:55 +00:00
|
|
|
// Getting info about padding we have to account for, removing 'px' part
|
2019-06-22 13:41:02 +00:00
|
|
|
const topPaddingStr = window.getComputedStyle(target)['padding-top']
|
|
|
|
const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom']
|
2020-07-15 13:36:06 +00:00
|
|
|
const topPadding = pxStringToNumber(topPaddingStr)
|
|
|
|
const bottomPadding = pxStringToNumber(bottomPaddingStr)
|
2019-09-24 21:17:04 +00:00
|
|
|
const vertPadding = topPadding + bottomPadding
|
2019-09-25 16:30:55 +00:00
|
|
|
|
2020-07-15 13:36:06 +00:00
|
|
|
const oldHeight = pxStringToNumber(target.style.height)
|
|
|
|
|
2019-09-25 16:30:55 +00:00
|
|
|
/* 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
|
2019-10-22 20:53:23 +00:00
|
|
|
* the scroll since there might be not enough padding around form-bottom to even
|
2019-09-25 16:32:30 +00:00
|
|
|
* warrant a scroll, so it will jump to 0 and refuse to move anywhere,
|
2019-09-25 16:30:55 +00:00
|
|
|
* 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
|
2019-09-24 21:17:04 +00:00
|
|
|
|
2019-09-25 16:30:55 +00:00
|
|
|
// BEGIN content size update
|
2019-02-14 19:15:59 +00:00
|
|
|
target.style.height = 'auto'
|
2020-07-15 13:36:06 +00:00
|
|
|
const heightWithoutPadding = Math.floor(target.scrollHeight - vertPadding)
|
|
|
|
let newHeight = this.maxHeight ? Math.min(heightWithoutPadding, this.maxHeight) : heightWithoutPadding
|
|
|
|
// This is a bit of a hack to combat target.scrollHeight being different on every other input
|
|
|
|
// on some browsers for whatever reason. Don't change the height if difference is 1px or less.
|
|
|
|
if (Math.abs(newHeight - oldHeight) <= 1) {
|
|
|
|
newHeight = oldHeight
|
|
|
|
}
|
2019-09-25 16:30:55 +00:00
|
|
|
target.style.height = `${newHeight}px`
|
2020-05-07 13:10:53 +00:00
|
|
|
this.$emit('resize', newHeight)
|
2019-09-25 16:30:55 +00:00
|
|
|
// END content size update
|
2019-09-24 21:17:04 +00:00
|
|
|
|
2019-10-22 20:53:23 +00:00
|
|
|
// We check where the bottom border of form-bottom element is, this uses findOffset
|
2019-09-25 16:30:55 +00:00
|
|
|
// to find offset relative to scrollable container (scroller)
|
2019-10-22 20:53:23 +00:00
|
|
|
const bottomBottomBorder = bottomRef.offsetHeight + findOffset(bottomRef, scrollerRef).top + bottomBottomPadding
|
2019-09-24 21:17:04 +00:00
|
|
|
|
2019-10-22 20:53:23 +00:00
|
|
|
const isBottomObstructed = scrollerBottomBorder < bottomBottomBorder
|
|
|
|
const isFormBiggerThanScroller = scrollerHeight < formRef.offsetHeight
|
|
|
|
const bottomChangeDelta = bottomBottomBorder - scrollerBottomBorder
|
2019-10-08 17:53:55 +00:00
|
|
|
// The intention is basically this;
|
2019-10-22 20:53:23 +00:00
|
|
|
// Keep form-bottom always visible so that submit button is in view EXCEPT
|
|
|
|
// if form element bigger than scroller and caret isn't at the end, so that
|
2019-10-08 17:53:55 +00:00
|
|
|
// if you scroll up and edit middle of text you won't get scrolled back to bottom
|
|
|
|
const shouldScrollToBottom = isBottomObstructed &&
|
2019-10-22 20:53:23 +00:00
|
|
|
!(isFormBiggerThanScroller &&
|
2019-10-08 17:53:55 +00:00
|
|
|
this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
|
2019-10-22 20:53:23 +00:00
|
|
|
const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
|
2020-11-09 13:02:48 +00:00
|
|
|
const targetScroll = Math.round(currentScroll + totalDelta)
|
2019-09-24 21:17:04 +00:00
|
|
|
|
2020-11-09 13:02:48 +00:00
|
|
|
if (scrollerRef === window) {
|
|
|
|
scrollerRef.scroll(0, targetScroll)
|
|
|
|
} else {
|
|
|
|
scrollerRef.scrollTop = targetScroll
|
2019-02-14 19:15:59 +00:00
|
|
|
}
|
2019-09-24 21:17:04 +00:00
|
|
|
|
2019-09-23 19:12:25 +00:00
|
|
|
this.$refs['emoji-input'].resize()
|
2019-02-14 19:15:59 +00:00
|
|
|
},
|
2019-09-08 12:51:17 +00:00
|
|
|
showEmojiPicker () {
|
2019-08-12 17:01:38 +00:00
|
|
|
this.$refs['textarea'].focus()
|
|
|
|
this.$refs['emoji-input'].triggerShowPicker()
|
|
|
|
},
|
2017-08-24 13:16:06 +00:00
|
|
|
clearError () {
|
|
|
|
this.error = null
|
2018-06-07 09:03:50 +00:00
|
|
|
},
|
|
|
|
changeVis (visibility) {
|
|
|
|
this.newStatus.visibility = visibility
|
2019-05-07 16:13:45 +00:00
|
|
|
},
|
2019-06-18 20:28:31 +00:00
|
|
|
togglePollForm () {
|
|
|
|
this.pollFormVisible = !this.pollFormVisible
|
|
|
|
},
|
|
|
|
setPoll (poll) {
|
|
|
|
this.newStatus.poll = poll
|
|
|
|
},
|
|
|
|
clearPollForm () {
|
|
|
|
if (this.$refs.pollForm) {
|
|
|
|
this.$refs.pollForm.clear()
|
|
|
|
}
|
|
|
|
},
|
2019-05-07 16:13:45 +00:00
|
|
|
dismissScopeNotice () {
|
|
|
|
this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
|
2020-07-07 06:07:20 +00:00
|
|
|
},
|
|
|
|
setMediaDescription (id) {
|
|
|
|
const description = this.newStatus.mediaDescriptions[id]
|
|
|
|
if (!description || description.trim() === '') return
|
|
|
|
return statusPoster.setMediaDescription({ store: this.$store, id, description })
|
|
|
|
},
|
|
|
|
setAllMediaDescriptions () {
|
|
|
|
const ids = this.newStatus.files.map(file => file.id)
|
|
|
|
return Promise.all(ids.map(id => this.setMediaDescription(id)))
|
2020-05-07 13:10:53 +00:00
|
|
|
},
|
|
|
|
handleEmojiInputShow (value) {
|
|
|
|
this.emojiInputShown = value
|
2020-07-15 13:19:57 +00:00
|
|
|
},
|
|
|
|
updateIdempotencyKey () {
|
|
|
|
this.idempotencyKey = Date.now().toString()
|
2020-09-03 12:45:13 +00:00
|
|
|
},
|
|
|
|
openProfileTab () {
|
|
|
|
this.$store.dispatch('openSettingsModalTab', 'profile')
|
2016-10-30 15:53:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default PostStatusForm
|