diff --git a/src/api/user.js b/src/api/user.js
index 17e13195d..70929b162 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -207,7 +207,7 @@ export const postStatus = ({
idempotencyKey,
}) => {
const form = new FormData()
- const pollOptions = poll.options || []
+ const pollOptions = poll?.options || []
form.append('status', status)
form.append('source', 'Pleroma FE')
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
index 580cb183f..a71014078 100644
--- a/src/components/chat_message/chat_message.js
+++ b/src/components/chat_message/chat_message.js
@@ -1,8 +1,8 @@
-import { find } from 'lodash'
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
import Attachment from 'src/components/attachment/attachment.vue'
+import MentionLink from 'src/components/mention_link/mention_link.vue'
import ChatMessageDate from 'src/components/chat_message_date/chat_message_date.vue'
import Gallery from 'src/components/gallery/gallery.vue'
import LinkPreview from 'src/components/link-preview/link-preview.vue'
@@ -10,6 +10,7 @@ import Popover from 'src/components/popover/popover.vue'
import StatusActionButtons from 'src/components/status_action_buttons/status_action_buttons.vue'
import StatusBody from 'src/components/status_body/status_body.vue'
import StatusContent from 'src/components/status_content/status_content.vue'
+import StatusPopover from 'src/components/status_popover/status_popover.vue'
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
import UserPopover from 'src/components/user_popover/user_popover.vue'
@@ -22,9 +23,10 @@ import {
faCircleNotch,
faEllipsisH,
faTimes,
+ faReply,
} from '@fortawesome/free-solid-svg-icons'
-library.add(faTimes, faEllipsisH, faCircleNotch)
+library.add(faTimes, faEllipsisH, faCircleNotch, faReply)
const ChatMessage = {
name: 'ChatMessage',
@@ -35,6 +37,8 @@ const ChatMessage = {
'chatItem',
'previousItem',
'hoveredMessageChain',
+ 'focused',
+ 'repliedTo',
],
emits: ['hover', 'replyRequested'],
components: {
@@ -48,6 +52,8 @@ const ChatMessage = {
LinkPreview,
ChatMessageDate,
UserPopover,
+ StatusPopover,
+ MentionLink,
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
@@ -89,6 +95,25 @@ const ChatMessage = {
this.chatItem.data.in_reply_to_status_id
]
},
+ replyToName() {
+ if (this.message.in_reply_to_screen_name) {
+ return this.message.in_reply_to_screen_name
+ } else {
+ const user = this.$store.getters.findUser(
+ this.status.in_reply_to_user_id,
+ )
+ return user && user.screen_name_ui
+ }
+ },
+ replyProfileLink() {
+ if (this.isCustomReply) {
+ const user = this.$store.getters.findUser(
+ this.message.in_reply_to_user_id,
+ )
+ // FIXME Why user not found sometimes???
+ return user ? user.statusnet_profile_url : 'NOT_FOUND'
+ }
+ },
messageForStatusContent() {
return {
summary: '',
@@ -101,6 +126,14 @@ const ChatMessage = {
hasAttachment() {
return this.message.attachments.length > 0
},
+ classnames() {
+ return {
+ '-outgoing': this.isCurrentUser,
+ '-incoming': !this.isCurrentUser,
+ '-pending': this.message.pending,
+ '-focused': this.focused,
+ }
+ },
...mapPiniaState(useInterfaceStore, {
betterShadow: (store) => store.browserSupport.cssFilter,
}),
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss
index 34382c42d..0d5db41b2 100644
--- a/src/components/chat_message/chat_message.scss
+++ b/src/components/chat_message/chat_message.scss
@@ -17,7 +17,7 @@
position: absolute;
top: -0.8em;
right: 0.4rem;
- z-index: 10;
+ z-index: 1;
.quick-action-buttons {
justify-items: end;
@@ -39,23 +39,16 @@
}
.reply-to-header {
- white-space: nowrap;
- width: 100%;
display: flex;
- align-items: baseline;
- gap: 0.5em;
+ gap: 0.5rem;
+ align-items: center;
+ padding: 0.125em 0;
+ width: 100%;
+ }
- .reply-label {
- display: inline-block;
- line-height: 1;
- }
-
- .reply-body {
- line-height: 1;
- display: inline-block;
- overflow-x: hidden;
- text-overflow: ellipsis;
- }
+ .avatar-spacer {
+ flex: 0 0 2.2rem;
+ width: 2.2rem;
}
.popover {
@@ -73,8 +66,7 @@
}
.avatar-wrapper {
- margin-right: 0.72em;
- width: 32px;
+ margin-right: 0.5em;
}
.link-preview,
@@ -124,27 +116,70 @@
}
}
+ .message-bubble-wrapper {
+ display: flex;
+ }
+
.chat-message-inner {
display: flex;
flex-direction: column;
align-items: flex-start;
- max-width: 80%;
- min-width: 10em;
- width: 100%;
}
- .-outgoing {
+ .reply-indicator {
display: flex;
- flex-flow: row wrap;
- place-content: end flex-end;
+ place-items: center;
+ place-content: center;
+ padding: 0.25em;
+ margin: var(--roundness) 0;
+ background: var(--border);
+ border-radius: var(--roundness);
+ border: 1px solid var(--border);
+ }
- .chat-message-inner {
- align-items: flex-end;
+ .end-spacer {
+ flex: 1 1 0;
+ min-width: calc(2.2em + 0.5em + 2em);
+ }
+
+ &.-incoming {
+ .reply-indicator {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ }
+ }
+
+ .reply-to-popover {
+ white-space: nowrap;
+ }
+
+ .reply-label {
+ white-space: nowrap;
+ }
+
+ &.-outgoing {
+ &,
+ .message-bubble-wrapper,
+ .chat-message{
+ flex-direction: row-reverse;
}
.reply-to-header {
justify-content: end;
}
+
+ .reply-indicator {
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0;
+
+ .icon {
+ transform: scaleX(-1);
+ }
+ }
+
+ .chat-message-inner {
+ align-items: flex-end;
+ }
}
.chat-message-inner.with-media {
diff --git a/src/components/chat_message/chat_message.style.js b/src/components/chat_message/chat_message.style.js
index f7632bc6f..67f0ae6c1 100644
--- a/src/components/chat_message/chat_message.style.js
+++ b/src/components/chat_message/chat_message.style.js
@@ -4,6 +4,9 @@ export default {
variants: {
outgoing: '.outgoing',
},
+ states: {
+ focused: '.-focused',
+ },
validInnerComponents: ['Text', 'Icon', 'Border', 'PollGraph'],
defaultRules: [
{
@@ -18,5 +21,11 @@ export default {
background: '--bg, 5',
},
},
+ {
+ state: ['focused'],
+ directives: {
+ background: '--inheritedBackground, 10',
+ },
+ },
],
}
diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue
index 91ad9a4eb..234d20cb2 100644
--- a/src/components/chat_message/chat_message.vue
+++ b/src/components/chat_message/chat_message.vue
@@ -2,13 +2,64 @@
diff --git a/src/components/chat_message_list/chat_message_list.js b/src/components/chat_message_list/chat_message_list.js
index 4babea5a7..a51e6a0e4 100644
--- a/src/components/chat_message_list/chat_message_list.js
+++ b/src/components/chat_message_list/chat_message_list.js
@@ -14,6 +14,8 @@ const ChatMessageList = {
default: [],
},
headerDate: Boolean,
+ focusedId: String,
+ repliedId: String,
},
data() {
return {
diff --git a/src/components/chat_message_list/chat_message_list.vue b/src/components/chat_message_list/chat_message_list.vue
index cd1688a8c..5cdbf6871 100644
--- a/src/components/chat_message_list/chat_message_list.vue
+++ b/src/components/chat_message_list/chat_message_list.vue
@@ -6,6 +6,8 @@
:chat-item="chatItem"
:previous-item="getPreviousItem(index)"
:hovered-message-chain="chatItem.messageChainId === hoveredMessageChainId"
+ :focused="chatItem.id === focusedId"
+ :repliedTo="chatItem.id === repliedId"
@hover="onMessageHover"
@delete="onMessageDelete"
@reply-requested="onReplyRequested"
diff --git a/src/components/conversation/conversation.scss b/src/components/conversation/conversation.scss
index 8bd391703..9f9c30747 100644
--- a/src/components/conversation/conversation.scss
+++ b/src/components/conversation/conversation.scss
@@ -99,10 +99,10 @@
h4 {
margin: 0.5em 0;
- line-height: 1.5;
+ line-height: 1.1;
button {
- line-height: 1.5;
+ line-height: 1.1;
}
}
@@ -110,6 +110,9 @@
display: flex;
align-items: center;
gap: 0.5em;
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
+ padding: 0.5em;
}
}
@@ -119,5 +122,9 @@
flex-direction: column;
align-items: stretch;
bottom: 0;
+
+ .reply-form form {
+ margin-top: 0;
+ }
}
}
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index bad0277fd..19d63fbbe 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -202,6 +202,8 @@
>
explicitReplyStatus = e"
/>
@@ -211,9 +213,7 @@
class="chat-view-reply-form panel-footer -flexible-height"
>
-
+
{{ explicitReplyStatus ? $t('status.reply_to_selected') : $t('status.reply_to_last') }}
-
-
-
-
+
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
index 22311d348..5d0334a63 100644
--- a/src/components/poll/poll_form.js
+++ b/src/components/poll/poll_form.js
@@ -30,7 +30,10 @@ export default {
return pollFallback(this.modelValue, 'pollType')
},
set(newVal) {
- this.$emit('update:modelValue', { ...this.modelValue, pollType: newVal })
+ this.$emit('update:modelValue', {
+ ...this.modelValue,
+ pollType: newVal,
+ })
},
},
options: {
@@ -39,14 +42,17 @@ export default {
},
set(newVal) {
this.$emit('update:modelValue', { ...this.modelValue, options: newVal })
- }
+ },
},
expiryAmount: {
get() {
return pollFallback(this.modelValue, 'expiryAmount')
},
set(newVal) {
- this.$emit('update:modelValue', { ...this.modelValue, expiryAmount: newVal })
+ this.$emit('update:modelValue', {
+ ...this.modelValue,
+ expiryAmount: newVal,
+ })
},
},
expiryUnit: {
@@ -54,7 +60,10 @@ export default {
return pollFallback(this.modelValue, 'expiryUnit')
},
set(newVal) {
- this.$emit('update:modelValue', { ...this.modelValue, expiryUnit: newVal })
+ this.$emit('update:modelValue', {
+ ...this.modelValue,
+ expiryUnit: newVal,
+ })
},
},
pollLimits() {
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index c98454342..a34e9a3ec 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -1,4 +1,10 @@
-import { debounce, reject, uniqBy } from 'lodash'
+import {
+ debounce,
+ isEqual,
+ unescape as ldUnescape,
+ reject,
+ uniqBy,
+} from 'lodash'
import { mapActions, mapState } from 'pinia'
import { defineAsyncComponent } from 'vue'
@@ -60,14 +66,12 @@ const pxStringToNumber = (str) => {
return Number(str.substring(0, str.length - 2))
}
-const DEFAULT_NEWSTATUS = {
-}
-
const PostStatusForm = {
props: {
// Status editing stuff
statusId: String,
statusText: String,
+ statusSubject: String,
statusIsSensitive: {
type: Boolean,
required: false,
@@ -79,30 +83,34 @@ const PostStatusForm = {
statusMediaDescriptions: Object,
statusScope: String,
statusContentType: String,
+
// Replies/mentions
- replyTo: String,
- repliedUser: Object,
- mentionsLine: Boolean,
- mentionsLineReadOnly: Boolean,
- attentions: Array,
- subject: String,
- copyMessageScope: String,
- profileMention: String,
+ replyTo: String, // id of the post replying to
+ repliedUser: Object, // user object replying to
+ attentions: Array, // list of users mentioned
+ repliedScope: String, // scope of the post replying to
+ repliedSubject: String, // subject of post replying to
+ profileMention: Boolean, // is mentioning a user (used in profile page -> mention)
+
// Draft stuff
- hideDraft: Boolean,
- closeable: Boolean,
- draftId: String,
+ hideDraft: Boolean, // Disable drafts functionality
+ closeable: Boolean, // Whether form can be closed (i.e. in replies)
+ draftId: String, // ID of the draft to be used
+
// Chats stuff
maxHeight: Number,
placeholder: String,
- postHandler: Function,
- preserveFocus: Boolean,
- autoFocus: Boolean,
- fileLimit: Number,
+ postHandler: Function, // Used to override poster to use chats one instead of status one
+ preserveFocus: Boolean, // Keep focus on form after posting
+ autoFocus: Boolean, // Steal focus when form is opened
+ fileLimit: Number, // Chats only support 1 attachment :(
submitOnEnter: Boolean,
emojiPickerPlacement: String,
- optimisticPosting: Boolean,
+ optimisticPosting: Boolean, // Don't wait for confirmation that post is done
+
// Feature toggles for special cases (mostly chats)
+ mentionsLine: Boolean, // Use separate field for specifying mentions
+ mentionsLineReadOnly: Boolean, // Make the said field read-only (for chat conversation view)
disableSubject: Boolean,
disableScopeSelector: Boolean,
disableVisibilitySelector: Boolean,
@@ -126,20 +134,19 @@ const PostStatusForm = {
],
data() {
return {
- randomSeed: genRandomSeed(),
- dropFiles: [],
- uploadingFiles: false,
- error: null,
- posting: false,
- highlighted: 0,
initialized: false,
- // Data is initialized first, but we have no access to .computed
- // so we pre-fill with stuff meant for status editing and later
- // back-fill with defaults in .created()
+ randomSeed: genRandomSeed(),
+ // Posting stuff
+ idempotencyKey: '',
+ /* Data is initialized first, but we have no access to .computed yet
+ * which we need for some defaults (i.e. user configration)
+ * so we pre-fill with stuff meant for status editing and later
+ * back-fill with defaults in .created()
+ */
newStatus: {
status: this.statusText ?? null,
mentions: this.statusMentionLine ?? null,
- spoilerText: this.subject ?? null,
+ spoilerText: this.statusSubject ?? null,
quote: this.statusQuote ?? null,
files: this.statusFiles ?? null,
poll: this.statusPoll ?? null,
@@ -148,15 +155,25 @@ const PostStatusForm = {
visibility: this.statusVisibility ?? null,
contentType: this.statusContentType ?? null,
},
- caret: 0,
+
+ // Attachments
+ dropFiles: [],
+ uploadingFiles: false,
showDropIcon: 'hide',
dropStopTimeout: null,
+
+ // Preview
preview: null,
previewLoading: false,
- emojiInputShown: false,
- idempotencyKey: '',
+
+ // Draft
saveInhibited: true,
saveable: false,
+
+ // Misc States
+ emojiInputShown: false,
+ error: null,
+ posting: false,
}
},
components: {
@@ -178,8 +195,11 @@ const PostStatusForm = {
Popover,
},
created() {
+ this.updateIdempotencyKey()
+
// If we are starting a new post, do not associate it with old drafts
- const draft = !this.disableDraft && (this.draftId || this.statusType !== 'new')
+ const draft =
+ !this.disableDraft && (this.draftId || this.statusType !== 'new')
? this.getDraft(this.statusType, this.refId)
: null
@@ -189,43 +209,14 @@ const PostStatusForm = {
this.newStatus[key] = draft[key] ?? this.newStatus[key]
})
} else {
- const defaultNewStatus = {
- spoilerText: '',
- files: [],
- poll: null,
- quote: null,
- mediaDescriptions: {},
- }
-
- const scope =
- (this.copyMessageScope && this.userDefaultScopeCopy) ||
- this.copyMessageScope === 'direct'
- ? this.copyMessageScope
- : this.userDefaultScope
-
- const preset = this.$route.query.message
- let statusText = preset ?? ''
-
- if (this.mentionsLine) {
- defaultNewStatus.status = statusText
- defaultNewStatus.mentions = this.mentionsString.trim()
- } else {
- defaultNewStatus.status = this.mentionsString + statusText
- defaultNewStatus.mentions = ''
- }
-
- defaultNewStatus.nsfw = this.userDefaultSensitive
- defaultNewStatus.visibility = scope
- defaultNewStatus.contentType = this.userDefaultPostContentType
-
- Object.entries(defaultNewStatus).forEach(([key, value]) => {
+ Object.entries(this.defaultNewStatus).forEach(([key, value]) => {
this.newStatus[key] = this.newStatus[key] ?? value
})
}
+
this.initialized = true
},
mounted() {
- this.updateIdempotencyKey()
this.resize(this.$refs.textarea)
if (this.replyTo) {
@@ -249,7 +240,7 @@ const PostStatusForm = {
return !this.disablePreview && (!!this.preview || this.previewLoading)
},
- // Composition stuff
+ // Composing stuff
statusType() {
if (this.replyTo) {
return 'reply'
@@ -264,16 +255,17 @@ const PostStatusForm = {
refId() {
if (this.replyTo) {
return this.replyTo
- } else if (profileMention) {
+ } else if (this.profileMention && this.repliedUser?.id) {
return this.profileMention && this.repliedUser?.id
- } else if (statusId) {
+ } else if (this.statusId) {
return this.statusId
} else {
return null
}
},
mentionsString() {
- if (this.statusType !== 'reply' && this.statusType !== 'mention') return ''
+ if (this.statusType !== 'reply' && this.statusType !== 'mention')
+ return ''
let allAttentions = [...(this.attentions || [])]
allAttentions.unshift(this.repliedUser)
@@ -281,7 +273,9 @@ const PostStatusForm = {
allAttentions = uniqBy(allAttentions, 'id')
allAttentions = reject(allAttentions, { id: this.currentUser.id })
- const mentions = allAttentions.map((attention) => `@${attention.screen_name}`)
+ const mentions = allAttentions.map(
+ (attention) => `@${attention.screen_name}`,
+ )
return mentions.length > 0 ? mentions.join(' ') + ' ' : ''
},
@@ -290,6 +284,39 @@ const PostStatusForm = {
? this.mentionsString + this.newStatus.status
: this.newStatus.status
},
+ defaultNewStatus() {
+ const defaultNewStatus = {
+ files: [],
+ poll: null,
+ quote: null,
+ mediaDescriptions: {},
+ }
+
+ const scope =
+ (this.repliedScope && this.userDefaultScopeCopy) ||
+ this.repliedScope === 'direct'
+ ? this.repliedScope
+ : this.userDefaultScope
+
+ const preset = this.$route.query.message
+ const statusText = preset ?? ''
+
+ if (this.mentionsLine) {
+ defaultNewStatus.status = statusText
+ defaultNewStatus.mentions = this.mentionsString.trim()
+ } else {
+ defaultNewStatus.status = this.mentionsString + statusText
+ defaultNewStatus.mentions = ''
+ }
+
+ defaultNewStatus.spoilerText = this.repliedSubjectString ?? ''
+ defaultNewStatus.nsfw = this.userDefaultSensitive
+ defaultNewStatus.visibility = scope
+ defaultNewStatus.contentType = this.userDefaultPostContentType
+
+ return defaultNewStatus
+ },
+ // -Edit
isEdit() {
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
},
@@ -297,13 +324,26 @@ const PostStatusForm = {
isReply() {
return this.statusType === 'reply'
},
- inReplyStatusId() {
+ inReplyToStatusId() {
return !this.hasQuote ||
!this.newStatus.quote.thread ||
!this.newStatus.quote.id
? this.replyTo
: undefined
},
+ repliedSubjectString() {
+ if (!this.repliedSubject) return null
+ const decodedSummary = ldUnescape(this.repliedSubject)
+ const behavior = this.mergedConfig.subjectLineBehavior
+ const startsWithRe = decodedSummary.match(/^re[: ]/i)
+ if ((behavior !== 'noop' && startsWithRe) || behavior === 'masto') {
+ return decodedSummary
+ } else if (behavior === 'email') {
+ return 're: '.concat(decodedSummary)
+ } else if (behavior === 'noop') {
+ return ''
+ }
+ },
// -Poll
hasPoll() {
return this.newStatus.poll != null
@@ -406,7 +446,7 @@ const PostStatusForm = {
isOverLengthLimit() {
return this.hasStatusLengthLimit && this.charactersLeft < 0
},
- emptyStatus() {
+ isEmptyStatus() {
return (
this.newStatus.status.trim() === '' && this.newStatus.files.length === 0
)
@@ -416,6 +456,15 @@ const PostStatusForm = {
},
// Drafts
+ isDirty() {
+ return Object.entries(this.defaultNewStatus).some(
+ ([key, defaultValue]) => {
+ const actualValue = this.newStatus[key]
+ if (actualValue === null) return false
+ return !isEqual(actualValue, defaultValue)
+ },
+ )
+ },
shouldAutoSaveDraft() {
return useMergedConfigStore().mergedConfig.autoSaveDraft
},
@@ -525,11 +574,8 @@ const PostStatusForm = {
}),
},
watch: {
- newStatus: {
- deep: true,
- handler() {
- if (this.initialized) this.statusChanged()
- },
+ isDirty(newVal, oldVal) {
+ this.statusChanged()
},
saveable(val) {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes
@@ -541,45 +587,25 @@ const PostStatusForm = {
}
},
},
- beforeUnmount() {
- this.maybeAutoSaveDraft()
- this.removeBeforeUnloadListener()
- },
methods: {
- ...mapActions(useMediaViewerStore, ['increment']),
- statusChanged() {
- this.autoPreview()
- this.updateIdempotencyKey()
- this.debouncedMaybeAutoSaveDraft()
- this.saveable = true
- this.saveInhibited = false
- },
+ // Composing
onMentionsLineUpdate(e) {
if (this.mentionsLineReadOnly) return
this.newStatus.mentionsLine = e
},
- toggleQuoteForm() {
- if (!this.hasQuote) {
- this.newStatus.quote = {}
- this.newStatus.quote.thread = false
- this.newStatus.quote.id = null
- this.newStatus.quote.url = ''
- } else {
- this.newStatus.quote = null
- }
+ changeVis(visibility) {
+ this.newStatus.visibility = visibility
},
clearStatus() {
- const newStatus = this.newStatus
this.saveInhibited = true
this.newStatus.status = ''
- this.newStatus.mentionsLine = '',
- this.newStatus.spoilerText = '',
- this.newStatus.files = [],
- this.newStatus.poll = null,
- this.newStatus.quote = null,
- this.newStatus.mediaDescriptions = {},
-
- this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
+ ;(this.newStatus.mentionsLine = ''),
+ (this.newStatus.spoilerText = ''),
+ (this.newStatus.files = []),
+ (this.newStatus.poll = null),
+ (this.newStatus.quote = null),
+ (this.newStatus.mediaDescriptions = {}),
+ this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearQuoteForm()
if (this.preserveFocus) {
this.$nextTick(() => {
@@ -610,12 +636,12 @@ const PostStatusForm = {
if (
this.optimisticPosting &&
- (this.emptyStatus || this.isOverLengthLimit)
+ (this.isEmptyStatus || this.isOverLengthLimit)
) {
return
}
- if (this.emptyStatus) {
+ if (this.isEmptyStatus) {
this.error = this.$t('post_status.empty_status_error')
return
}
@@ -638,12 +664,12 @@ const PostStatusForm = {
const postingOptions = {
status: this.newStatusContent,
- spoilerText: newStatus.spoilerText || null,
+ spoilerText: newStatus.spoilerText ?? null,
visibility: newStatus.visibility,
sensitive: newStatus.nsfw,
media: newStatus.files,
store: this.$store,
- inReplyToStatusId: this.inReplyStatusId,
+ inReplyToStatusId: this.inReplyToStatusId,
quoteId: this.quoteId,
contentType: newStatus.contentType,
poll,
@@ -667,8 +693,10 @@ const PostStatusForm = {
this.posting = false
})
},
+
+ // Preview
previewStatus() {
- if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') {
+ if (this.isEmptyStatus && this.newStatus.spoilerText.trim() === '') {
this.preview = { error: this.$t('post_status.preview_empty') }
this.previewLoading = false
return
@@ -684,10 +712,11 @@ const PostStatusForm = {
sensitive: newStatus.nsfw,
media: [],
store: this.$store,
- inReplyToStatusId: this.inReplyStatusId,
+ inReplyToStatusId: this.inReplyToStatusId,
quoteId: this.quoteId,
contentType: newStatus.contentType,
- poll: {},
+ poll: null,
+ quote: null,
preview: true,
})
.then((data) => {
@@ -722,6 +751,21 @@ const PostStatusForm = {
this.previewStatus()
}
},
+
+ // Attachments
+ 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)))
+ },
addMediaFile(fileInfo) {
this.newStatus.files.push(fileInfo)
this.$emit('resize', { delayed: true })
@@ -795,6 +839,9 @@ const PostStatusForm = {
this.showDropIcon = 'show'
}
},
+
+ // Auto-sizable input field
+ // TODO separate into its own component pls
onEmojiInputInput() {
this.$nextTick(() => {
this.resize(this.$refs.textarea)
@@ -908,53 +955,39 @@ const PostStatusForm = {
scrollerRef.scrollTop = targetScroll
}
},
- clearError() {
- this.error = null
- },
- changeVis(visibility) {
- this.newStatus.visibility = visibility
- },
+
+ // Poll
togglePollForm() {
this.newStatus.poll = this.hasPoll ? null : {}
},
setPoll(poll) {
this.newStatus.poll = poll
},
+
+ // Quote
+ toggleQuoteForm() {
+ if (!this.hasQuote) {
+ this.newStatus.quote = {}
+ this.newStatus.quote.thread = false
+ this.newStatus.quote.id = null
+ this.newStatus.quote.url = ''
+ } else {
+ this.newStatus.quote = null
+ }
+ },
clearQuoteForm() {
if (this.$refs.quoteForm) {
this.$refs.quoteForm.clear()
}
},
- dismissScopeNotice() {
- useSyncConfigStore().setSimplePrefAndSave({
- path: 'hideScopeNotice',
- value: true,
- })
- },
- 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)))
- },
- handleEmojiInputShow(value) {
- this.emojiInputShown = value
- },
- updateIdempotencyKey() {
- this.idempotencyKey = Date.now().toString()
- },
- openProfileTab() {
- useInterfaceStore().openSettingsModalTab('profile')
- },
- propsToNative(props) {
- return propsToNative(props)
+
+ // Drafts
+ statusChanged() {
+ this.autoPreview()
+ this.updateIdempotencyKey()
+ this.debouncedMaybeAutoSaveDraft()
+ this.saveable = true
+ this.saveInhibited = false
},
saveDraft() {
if (!this.disableDraft && !this.saveInhibited) {
@@ -1036,6 +1069,34 @@ const PostStatusForm = {
window.removeEventListener('beforeunload', this._beforeUnloadListener)
}
},
+
+ // Misc
+ propsToNative(props) {
+ return propsToNative(props)
+ },
+ handleEmojiInputShow(value) {
+ this.emojiInputShown = value
+ },
+ updateIdempotencyKey() {
+ this.idempotencyKey = Date.now().toString()
+ },
+ openProfileTab() {
+ useInterfaceStore().openSettingsModalTab('profile')
+ },
+ dismissScopeNotice() {
+ useSyncConfigStore().setSimplePrefAndSave({
+ path: 'hideScopeNotice',
+ value: true,
+ })
+ },
+ clearError() {
+ this.error = null
+ },
+ ...mapActions(useMediaViewerStore, ['increment']),
+ },
+ beforeUnmount() {
+ this.maybeAutoSaveDraft()
+ this.removeBeforeUnloadListener()
},
}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index f8d34f73c..aef20e0cc 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -233,7 +233,7 @@
v-if="!disableVisibilitySelector"
:show-all="showAllScopes"
:user-default="userDefaultScope"
- :original-scope="copyMessageScope"
+ :original-scope="repliedScope"
:initial-scope="newStatus.visibility"
:on-scope-change="changeVis"
/>
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index 432250548..eb73e263c 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -479,7 +479,8 @@ export default {
>
{this.collapse
? pass2.map((x) => {
- if (!Array.isArray(x)) return x.replace(/\n/g, ' ')
+ if (typeof x === 'string') return x.replace(/\n/g, ' ')
+ if (!Array.isArray(x)) return x
return x.map((y) => (y.type === 'br' ? ' ' : y))
})
: pass2}
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 29493fb09..b9359b399 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -1,4 +1,4 @@
-import { unescape as ldUnescape, uniqBy } from 'lodash'
+import { uniqBy } from 'lodash'
import { defineAsyncComponent } from 'vue'
import AvatarList from 'src/components/avatar_list/avatar_list.vue'
@@ -377,19 +377,6 @@ const Status = {
return user && user.screen_name_ui
}
},
- replySubject() {
- if (!this.status.summary) return ''
- const decodedSummary = ldUnescape(this.status.summary)
- const behavior = this.mergedConfig.subjectLineBehavior
- const startsWithRe = decodedSummary.match(/^re[: ]/i)
- if ((behavior !== 'noop' && startsWithRe) || behavior === 'masto') {
- return decodedSummary
- } else if (behavior === 'email') {
- return 're: '.concat(decodedSummary)
- } else if (behavior === 'noop') {
- return ''
- }
- },
combinedFavsAndRepeatsUsers() {
// Use the status from the global status repository since favs and repeats are saved in it
const combinedUsers = [].concat(
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 59cd6aaca..3ddcf4e96 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -549,8 +549,8 @@
:reply-to="status.id"
:attentions="status.attentions"
:replied-user="status.user"
- :copy-message-scope="status.visibility"
- :subject="replySubject"
+ :replied-scope="status.visibility"
+ :replied-subject="status.summary"
@posted="closeReplyForm"
@draft-done="closeReplyForm"
@close-accepted="closeReplyForm"
diff --git a/src/modules/users.js b/src/modules/users.js
index 22d5be30d..8777f18da 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -45,10 +45,10 @@ import {
} from 'src/api/public.js'
import {
blockUser as apiBlockUser,
+ editUserNote as apiEditUserNote,
muteUser as apiMuteUser,
unblockUser as apiUnblockUser,
unmuteUser as apiUnmuteUser,
- editUserNote as apiEditUserNote,
fetchBlocks,
fetchDomainMutes,
fetchMutes,