remove chat mode from thread display settings, instead use dedicated page for chat convos
This commit is contained in:
parent
7b4fef365e
commit
c6ac897802
13 changed files with 303 additions and 249 deletions
|
|
@ -65,6 +65,15 @@ export default (store) => {
|
|||
component: ConversationPage,
|
||||
meta: { dontScroll: true },
|
||||
},
|
||||
{
|
||||
name: 'conversation2',
|
||||
path: '/conversation/:statusId',
|
||||
component: defineAsyncComponent(
|
||||
() => import('src/components/chat_view/chat_view.vue'),
|
||||
),
|
||||
props: true,
|
||||
beforeEnter: validateAuthenticatedRoute,
|
||||
},
|
||||
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
|
||||
{
|
||||
name: 'remote-user-profile-acct',
|
||||
|
|
|
|||
|
|
@ -75,14 +75,19 @@ const ChatMessage = {
|
|||
// ChatMessage only has account_id while Status has full user data
|
||||
return !!this.message.user
|
||||
},
|
||||
author() {
|
||||
const accountId = this.isStatus
|
||||
authorId() {
|
||||
return this.isStatus
|
||||
? this.message.user.id
|
||||
: this.message.account_id
|
||||
|
||||
return this.$store.getters.findUser(accountId)
|
||||
},
|
||||
author() {
|
||||
return this.$store.getters.findUser(this.authorId)
|
||||
},
|
||||
isCurrentUser() {
|
||||
// mini-hack/optimizaiton:
|
||||
// - current user would always be in memory so if user is missing it's obviously not us
|
||||
// - if anon views page then "us" pretty much doesn't exist
|
||||
if (!this.author || !this.currentUser) return false
|
||||
return this.author.id === this.currentUser.id
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -74,9 +74,10 @@
|
|||
>
|
||||
<UserPopover
|
||||
v-if="chatItem.isHead"
|
||||
:user-id="author.id"
|
||||
:user-id="authorId"
|
||||
>
|
||||
<UserAvatar
|
||||
v-if="author"
|
||||
:compact="true"
|
||||
:user="author"
|
||||
/>
|
||||
|
|
@ -202,7 +203,7 @@
|
|||
{{ ' ' }}
|
||||
<router-link
|
||||
class="timeago faint"
|
||||
:to="{ name: 'conversation', params: { id: message.id } }"
|
||||
:to="{ name: 'conversation2', params: { statusId: message.id } }"
|
||||
>
|
||||
<Timeago
|
||||
:time="message.created_at"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { maxBy, minBy, sortBy, throttle } from 'lodash'
|
||||
import { get, maxBy, minBy, sortBy, throttle } from 'lodash'
|
||||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { nextTick } from 'vue'
|
||||
import { mapState } from 'vuex'
|
||||
|
|
@ -28,6 +28,7 @@ import {
|
|||
sendChatMessage,
|
||||
} from 'src/api/chats.js'
|
||||
import { WSConnectionStatus } from 'src/api/websocket.js'
|
||||
import { fetchConversation, fetchStatus } from 'src/api/public.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
|
|
@ -52,6 +53,7 @@ const Chat = {
|
|||
PostStatusForm,
|
||||
},
|
||||
props: {
|
||||
statusId: String,
|
||||
testMode: Boolean,
|
||||
},
|
||||
data() {
|
||||
|
|
@ -65,6 +67,9 @@ const Chat = {
|
|||
minId: undefined,
|
||||
maxId: undefined,
|
||||
|
||||
// Conversation stuff
|
||||
explicitReplyStatus: null,
|
||||
|
||||
// Unread stuff
|
||||
newMessageCount: 0,
|
||||
lastReadMessageId: null,
|
||||
|
|
@ -108,6 +113,17 @@ const Chat = {
|
|||
)
|
||||
},
|
||||
computed: {
|
||||
conversationId() {
|
||||
const status = this.$store.state.statuses.allStatusesObject[this.statusId]
|
||||
return get(
|
||||
status,
|
||||
'retweeted_status.statusnet_conversation_id',
|
||||
get(status, 'statusnet_conversation_id'),
|
||||
)
|
||||
},
|
||||
isConversation() {
|
||||
return this.conversationId !== null
|
||||
},
|
||||
recipient() {
|
||||
return this.chat?.account
|
||||
},
|
||||
|
|
@ -123,7 +139,18 @@ const Chat = {
|
|||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
// Conversation stuff
|
||||
lastStatus() {
|
||||
return this.messages[this.messages.length - 1]
|
||||
},
|
||||
replyStatus() {
|
||||
return this.explicitReplyStatus ?? this.lastStatus
|
||||
},
|
||||
|
||||
// Global Stuff
|
||||
streamingEnabled() {
|
||||
if (this.isConversation) return false // Unsupported
|
||||
return (
|
||||
this.mergedConfig.useStreamingApi &&
|
||||
this.mastoUserSocketStatus === WSConnectionStatus.JOINED
|
||||
|
|
@ -142,13 +169,17 @@ const Chat = {
|
|||
messages() {
|
||||
// We don't want to scroll to the bottom on a new message when the user is viewing older messages.
|
||||
// Therefore we need to know whether the scroll position was at the bottom before the DOM update.
|
||||
const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET)
|
||||
const bottomedOutBeforeUpdate = isBottomedOut(BOTTOMED_OUT_OFFSET)
|
||||
this.$nextTick(() => {
|
||||
if (bottomedOutBeforeUpdate) {
|
||||
this.scrollDown()
|
||||
}
|
||||
})
|
||||
},
|
||||
async replyStatus(newVal) {
|
||||
await nextTick() // wait for changes to propagate to postStatusForm
|
||||
this.$refs.postStatusForm.update()
|
||||
},
|
||||
$route: function () {
|
||||
this.startFetching()
|
||||
},
|
||||
|
|
@ -159,53 +190,9 @@ const Chat = {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
onFilesDropped() {
|
||||
this.$nextTick(() => {
|
||||
this.handleResize()
|
||||
})
|
||||
},
|
||||
handleVisibilityChange() {
|
||||
this.$nextTick(() => {
|
||||
if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
|
||||
this.scrollDown({ forceRead: true })
|
||||
}
|
||||
})
|
||||
},
|
||||
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
|
||||
handleResize(opts = {}) {
|
||||
const { delayed = false } = opts
|
||||
|
||||
if (delayed) {
|
||||
setTimeout(() => {
|
||||
this.handleResize({ ...opts, delayed: false })
|
||||
}, SAFE_RESIZE_TIME_OFFSET)
|
||||
return
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const { offsetHeight = undefined } = getScrollPosition()
|
||||
const diff = offsetHeight - this.lastScrollPosition.offsetHeight
|
||||
if (diff !== 0 && !this.bottomedOut()) {
|
||||
this.$nextTick(() => {
|
||||
window.scrollBy({ top: -Math.trunc(diff) })
|
||||
})
|
||||
}
|
||||
this.lastScrollPosition = getScrollPosition()
|
||||
})
|
||||
},
|
||||
scrollDown(options = {}) {
|
||||
const { behavior = 'auto', forceRead = false } = options
|
||||
this.$nextTick(() => {
|
||||
window.scrollTo({
|
||||
top: document.documentElement.scrollHeight,
|
||||
behavior,
|
||||
})
|
||||
})
|
||||
if (forceRead) {
|
||||
this.readChat()
|
||||
}
|
||||
},
|
||||
// Actions
|
||||
async readChat() {
|
||||
if (this.conversationId) return // Unsupported
|
||||
if (!this.maxId || document.hidden) {
|
||||
return
|
||||
}
|
||||
|
|
@ -225,11 +212,17 @@ const Chat = {
|
|||
this.lastReadMessageId = this.maxId
|
||||
this.newMessageCount = 0
|
||||
},
|
||||
bottomedOut(offset) {
|
||||
return isBottomedOut(offset)
|
||||
},
|
||||
reachedTop() {
|
||||
return window.scrollY <= 0
|
||||
scrollDown(options = {}) {
|
||||
const { behavior = 'auto', forceRead = false } = options
|
||||
this.$nextTick(() => {
|
||||
window.scrollTo({
|
||||
top: document.documentElement.scrollHeight,
|
||||
behavior,
|
||||
})
|
||||
})
|
||||
if (forceRead) {
|
||||
this.readChat()
|
||||
}
|
||||
},
|
||||
cullOlder() {
|
||||
const maxIndex = this.messages.length
|
||||
|
|
@ -248,44 +241,6 @@ const Chat = {
|
|||
|
||||
this.messages = this.messages.slice(minIndex, maxIndex)
|
||||
},
|
||||
cullOlderCheck() {
|
||||
window.setTimeout(() => {
|
||||
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
||||
this.cullOlder()
|
||||
}
|
||||
}, 5000)
|
||||
},
|
||||
handleScroll: throttle(function () {
|
||||
if (!this.chat) {
|
||||
return
|
||||
}
|
||||
this.lastScrollPosition = getScrollPosition()
|
||||
|
||||
if (this.reachedTop()) {
|
||||
this.fetchChat({ maxId: this.minId })
|
||||
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
||||
this.jumpToBottomButtonVisible = false
|
||||
this.cullOlderCheck()
|
||||
if (this.newMessageCount > 0) {
|
||||
// Use a delay before marking as read to prevent situation where new messages
|
||||
// arrive just as you're leaving the view and messages that you didn't actually
|
||||
// get to see get marked as read.
|
||||
window.setTimeout(() => {
|
||||
// Don't mark as read if the element doesn't exist, user has left chat view
|
||||
if (this.$el) this.readChat()
|
||||
}, MARK_AS_READ_DELAY)
|
||||
}
|
||||
} else {
|
||||
this.jumpToBottomButtonVisible = true
|
||||
}
|
||||
}, 200),
|
||||
handleScrollUp(positionBeforeLoading) {
|
||||
const positionAfterLoading = getScrollPosition()
|
||||
|
||||
window.scrollTo({
|
||||
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
this.messages = this.messages.filter((m) => m.error)
|
||||
this.messagesIndex = this.messages.reduce(
|
||||
|
|
@ -305,12 +260,31 @@ const Chat = {
|
|||
return
|
||||
}
|
||||
|
||||
const { data: messages } = await chatMessages({
|
||||
id: this.chat.id,
|
||||
maxId,
|
||||
sinceId: fetchLatest ? this.maxId : null,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
let messages
|
||||
if (this.isConversation) {
|
||||
const [
|
||||
{ data: status },
|
||||
{ data: { ancestors, descendants } }
|
||||
] = await Promise.all([
|
||||
fetchStatus({
|
||||
id: this.statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
}),
|
||||
fetchConversation({
|
||||
id: this.statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
])
|
||||
messages = [...ancestors, status, ...descendants]
|
||||
} else {
|
||||
const { data } = await chatMessages({
|
||||
id: this.chat.id,
|
||||
maxId,
|
||||
sinceId: fetchLatest ? this.maxId : null,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
messages = data
|
||||
}
|
||||
|
||||
// Clear the current chat in case we're recovering from a ws connection loss.
|
||||
if (isFirstFetch) {
|
||||
|
|
@ -338,20 +312,22 @@ const Chat = {
|
|||
}
|
||||
},
|
||||
async startFetching() {
|
||||
try {
|
||||
const { data } = await getOrCreateChat({
|
||||
accountId: this.recipientId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
this.$store.commit('addNewUsers', [data.account])
|
||||
data.account = this.$store.getters.findUser(data.account.id)
|
||||
this.chat = data
|
||||
} catch (e) {
|
||||
console.error('Error creating or getting a chat', e)
|
||||
this.errorLoadingChat = true
|
||||
if (!this.isConversation) {
|
||||
try {
|
||||
const { data } = await getOrCreateChat({
|
||||
accountId: this.recipientId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
this.$store.commit('addNewUsers', [data.account])
|
||||
data.account = this.$store.getters.findUser(data.account.id)
|
||||
this.chat = data
|
||||
} catch (e) {
|
||||
console.error('Error creating or getting a chat', e)
|
||||
this.errorLoadingChat = true
|
||||
}
|
||||
}
|
||||
|
||||
if (this.chat) {
|
||||
if (this.isConversation || this.chat) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollDown({ forceRead: true })
|
||||
})
|
||||
|
|
@ -365,33 +341,12 @@ const Chat = {
|
|||
)
|
||||
this.fetchChat({ isFirstFetch: true })
|
||||
},
|
||||
async deleteChatMessage({ chatId, messageId }) {
|
||||
if (!this.testMode)
|
||||
await deleteChatMessage({
|
||||
chatId,
|
||||
messageId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
||||
this.messages = this.messages.filter((m) => m.id !== messageId)
|
||||
delete this.messagesIndex[messageId]
|
||||
|
||||
if (this.maxId === messageId) {
|
||||
const lastMessage = maxBy(this.messages, 'id')
|
||||
this.maxId = lastMessage.id
|
||||
}
|
||||
|
||||
if (this.minId === messageId) {
|
||||
const firstMessage = minBy(this.messages, 'id')
|
||||
this.minId = firstMessage.id
|
||||
}
|
||||
},
|
||||
addMessages({ messages: newMessages }) {
|
||||
for (let i = 0; i < newMessages.length; i++) {
|
||||
const message = newMessages[i]
|
||||
|
||||
// Sanity check
|
||||
if (message.chat_id !== this.chat.id) {
|
||||
if (!this.isConversation && (message.chat_id !== this.chat.id)) {
|
||||
console.warn(
|
||||
`Chat message doesn't belong to current chat (id: ${this.chat.id})!!`,
|
||||
message,
|
||||
|
|
@ -428,14 +383,11 @@ const Chat = {
|
|||
}
|
||||
}
|
||||
},
|
||||
handleAttachmentPosting() {
|
||||
this.$nextTick(() => {
|
||||
this.handleResize()
|
||||
// When the posting form size changes because of a media attachment, we need an extra resize
|
||||
// to account for the potential delay in the DOM update.
|
||||
this.scrollDown({ forceRead: true })
|
||||
})
|
||||
goBack() {
|
||||
this.$router.back()
|
||||
},
|
||||
|
||||
// Optimistic posting (chats only)
|
||||
async sendMessage({ status, media, idempotencyKey }) {
|
||||
const params = {
|
||||
id: this.chat.id,
|
||||
|
|
@ -515,12 +467,122 @@ const Chat = {
|
|||
fakeMessage.pending = false
|
||||
}
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push({
|
||||
name: 'chats',
|
||||
params: { username: this.currentUser.screen_name },
|
||||
|
||||
// Checks
|
||||
hasReachedTop() {
|
||||
return window.scrollY <= 0
|
||||
},
|
||||
cullOlderCheck() {
|
||||
if (this.conversationId) return
|
||||
window.setTimeout(() => {
|
||||
if (isBottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
||||
this.cullOlder()
|
||||
}
|
||||
}, 5000)
|
||||
},
|
||||
|
||||
// Event handlers
|
||||
onPosted(data) {
|
||||
this.explicitReplyStatus = null
|
||||
this.$router.push({ name: 'conversation2', params: { statusId: data.id } })
|
||||
},
|
||||
handleVisibilityChange() {
|
||||
this.$nextTick(() => {
|
||||
if (!document.hidden && isBottomedOut(BOTTOMED_OUT_OFFSET)) {
|
||||
this.scrollDown({ forceRead: true })
|
||||
}
|
||||
})
|
||||
},
|
||||
onFilesDropped() {
|
||||
this.$nextTick(() => {
|
||||
this.handleResize()
|
||||
})
|
||||
},
|
||||
handleResize(opts = {}) {
|
||||
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
|
||||
const { delayed = false } = opts
|
||||
|
||||
if (delayed) {
|
||||
setTimeout(() => {
|
||||
this.handleResize({ ...opts, delayed: false })
|
||||
}, SAFE_RESIZE_TIME_OFFSET)
|
||||
return
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const { offsetHeight = undefined } = getScrollPosition()
|
||||
const diff = offsetHeight - this.lastScrollPosition.offsetHeight
|
||||
if (diff !== 0 && !isBottomedOut()) {
|
||||
this.$nextTick(() => {
|
||||
window.scrollBy({ top: -Math.trunc(diff) })
|
||||
})
|
||||
}
|
||||
this.lastScrollPosition = getScrollPosition()
|
||||
})
|
||||
},
|
||||
handleScroll: throttle(function () {
|
||||
if (!this.chat) {
|
||||
return
|
||||
}
|
||||
this.lastScrollPosition = getScrollPosition()
|
||||
|
||||
if (this.hasReachedTop()) {
|
||||
this.fetchChat({ maxId: this.minId })
|
||||
} else if (isBottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
||||
this.jumpToBottomButtonVisible = false
|
||||
this.cullOlderCheck()
|
||||
if (this.newMessageCount > 0) {
|
||||
// Use a delay before marking as read to prevent situation where new messages
|
||||
// arrive just as you're leaving the view and messages that you didn't actually
|
||||
// get to see get marked as read.
|
||||
window.setTimeout(() => {
|
||||
// Don't mark as read if the element doesn't exist, user has left chat view
|
||||
if (this.$el) this.readChat()
|
||||
}, MARK_AS_READ_DELAY)
|
||||
}
|
||||
} else {
|
||||
this.jumpToBottomButtonVisible = true
|
||||
}
|
||||
}, 200),
|
||||
handleScrollUp(positionBeforeLoading) {
|
||||
const positionAfterLoading = getScrollPosition()
|
||||
|
||||
window.scrollTo({
|
||||
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
|
||||
})
|
||||
},
|
||||
handleAttachmentPosting() {
|
||||
this.$nextTick(() => {
|
||||
this.handleResize()
|
||||
// When the posting form size changes because of a media attachment, we need an extra resize
|
||||
// to account for the potential delay in the DOM update.
|
||||
this.scrollDown({ forceRead: true })
|
||||
})
|
||||
},
|
||||
|
||||
// Ugly
|
||||
// TODO move to ChatMessage
|
||||
async deleteChatMessage({ chatId, messageId }) {
|
||||
if (!this.testMode)
|
||||
await deleteChatMessage({
|
||||
chatId,
|
||||
messageId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
||||
this.messages = this.messages.filter((m) => m.id !== messageId)
|
||||
delete this.messagesIndex[messageId]
|
||||
|
||||
if (this.maxId === messageId) {
|
||||
const lastMessage = maxBy(this.messages, 'id')
|
||||
this.maxId = lastMessage.id
|
||||
}
|
||||
|
||||
if (this.minId === messageId) {
|
||||
const firstMessage = minBy(this.messages, 'id')
|
||||
this.minId = firstMessage.id
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,4 +95,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reply-to-text {
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
padding-top: 0.5em;
|
||||
margin-bottom: -0.5em;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,17 @@
|
|||
/>
|
||||
</button>
|
||||
<div class="title text-center">
|
||||
<template v-if="isConversation">
|
||||
<RichContent
|
||||
v-if="messages[0]?.summary"
|
||||
:html="messages[0].summary"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ $t('timeline.conversation') }}
|
||||
</template>
|
||||
</template>
|
||||
<ChatTitle
|
||||
v-else
|
||||
:user="recipient"
|
||||
:with-avatar="true"
|
||||
/>
|
||||
|
|
@ -29,7 +39,10 @@
|
|||
header-date
|
||||
:messages="messages"
|
||||
:pending-messages="pendingMessages"
|
||||
:replied-id="replyStatus?.id"
|
||||
:focused-id="statusId"
|
||||
@message-delete="deleteChatMessage"
|
||||
@reply-requested="e => explicitReplyStatus = e"
|
||||
/>
|
||||
<div
|
||||
ref="footer"
|
||||
|
|
@ -50,27 +63,52 @@
|
|||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="auto-reply-to-section">
|
||||
<div class="reply-to-text">
|
||||
{{ explicitReplyStatus ? $t('status.reply_to_selected') : $t('status.reply_to_last') }}
|
||||
<button
|
||||
v-if="explicitReplyStatus"
|
||||
class="button-default"
|
||||
@click="explicitReplyStatus = null"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
{{ $t('general.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PostStatusForm
|
||||
:disable-subject="true"
|
||||
:disable-scope-selector="true"
|
||||
:disable-notice="true"
|
||||
:disable-lock-warning="true"
|
||||
:disable-polls="true"
|
||||
:disable-quotes="true"
|
||||
:disable-sensitivity-checkbox="true"
|
||||
:disable-submit="errorLoadingChat || !chat"
|
||||
:disable-preview="true"
|
||||
:disable-draft="true"
|
||||
:optimistic-posting="true"
|
||||
:post-handler="sendMessage"
|
||||
ref="postStatusForm"
|
||||
:reply-to="replyStatus?.id"
|
||||
:mentions-line="isConversation"
|
||||
mentions-line-read-only
|
||||
:attentions="replyStatus?.attentions"
|
||||
:replied-user="replyStatus?.user"
|
||||
:replied-subject="replyStatus?.summary"
|
||||
:replied-scope="replyStatus?.visibility"
|
||||
|
||||
disable-quotes
|
||||
disable-notice
|
||||
disable-lock-warning
|
||||
:disable-subject="!isConversation"
|
||||
:disable-scope-selector="!isConversation"
|
||||
:disable-polls="!isConversation"
|
||||
:disable-sensitivity-checkbox="!isConversation"
|
||||
:disable-preview="!isConversation"
|
||||
:disable-draft="!isConversation"
|
||||
|
||||
:disable-submit="isConversation ? false : (errorLoadingChat || !chat)"
|
||||
:optimistic-posting="!isConversation"
|
||||
|
||||
:submit-on-enter="!mobileLayout"
|
||||
:preserve-focus="!mobileLayout"
|
||||
:auto-focus="!mobileLayout"
|
||||
:placeholder="formPlaceholder"
|
||||
:file-limit="1"
|
||||
max-height="160"
|
||||
:file-limit="isConversation ? null : 1"
|
||||
:max-height="160"
|
||||
emoji-picker-placement="top"
|
||||
:post-handler="isConversation ? null : sendMessage"
|
||||
@resize="handleResize"
|
||||
@posted="onPosted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ const conversation = {
|
|||
inlineDivePosition: null,
|
||||
loadStatusError: null,
|
||||
unsuspendibleIds: new Set(),
|
||||
explicitReplyStatus: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
@ -130,12 +129,6 @@ const conversation = {
|
|||
const maxDepth = this.mergedConfig.maxDepthInThread - 2
|
||||
return maxDepth >= 1 ? maxDepth : 1
|
||||
},
|
||||
lastStatus() {
|
||||
return this.conversation[this.conversation.length - 1]
|
||||
},
|
||||
replyStatus() {
|
||||
return this.explicitReplyStatus ?? this.lastStatus
|
||||
},
|
||||
streamingEnabled() {
|
||||
return (
|
||||
this.mergedConfig.useStreamingApi &&
|
||||
|
|
@ -152,10 +145,7 @@ const conversation = {
|
|||
return this.displayStyle === 'tree'
|
||||
},
|
||||
isLinearView() {
|
||||
return this.displayStyle === 'linear'
|
||||
},
|
||||
isChatView() {
|
||||
return this.displayStyle === 'chat'
|
||||
return this.displayStyle !== 'tree'
|
||||
},
|
||||
shouldFadeAncestors() {
|
||||
return this.mergedConfig.conversationTreeFadeAncestors
|
||||
|
|
@ -636,7 +626,6 @@ const conversation = {
|
|||
}
|
||||
},
|
||||
onPosted(data) {
|
||||
this.explicitReplyStatus = null
|
||||
if (this.isPage) {
|
||||
this.$router.push({ name: 'conversation', params: { id: data.id } })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="isLinearView || (isChatView && !isExpanded)"
|
||||
v-else-if="isLinearView"
|
||||
class="thread-body"
|
||||
>
|
||||
<article>
|
||||
|
|
@ -202,52 +202,6 @@
|
|||
/>
|
||||
</article>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="isChatView"
|
||||
class="chat-view"
|
||||
>
|
||||
<ChatMessageList
|
||||
:messages="conversation"
|
||||
:replied-id="replyStatus?.id"
|
||||
:focused-id="maybeFocused"
|
||||
@reply-requested="e => explicitReplyStatus = e"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="isChatView && isExpanded && replyStatus"
|
||||
class="chat-view-reply-form panel-footer -flexible-height"
|
||||
>
|
||||
<div class="auto-reply-to-section">
|
||||
<center class="reply-to-text">
|
||||
{{ explicitReplyStatus ? $t('status.reply_to_selected') : $t('status.reply_to_last') }}
|
||||
<button
|
||||
v-if="explicitReplyStatus"
|
||||
class="button-default"
|
||||
@click="explicitReplyStatus = null"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
{{ $t('general.cancel') }}
|
||||
</button>
|
||||
</center>
|
||||
</div>
|
||||
<PostStatusForm
|
||||
class="reply-form"
|
||||
:reply-to="replyStatus.id"
|
||||
:attentions="replyStatus.attentions"
|
||||
:replied-user="replyStatus.user"
|
||||
:replied-sibject="replyStatus.summary"
|
||||
:replied-scope="replyStatus.visibility"
|
||||
:submit-on-enter="!mobileLayout"
|
||||
:preserve-focus="!mobileLayout"
|
||||
:auto-focus="!mobileLayout"
|
||||
disable-quotes
|
||||
disable-lock-warning
|
||||
disable-notice
|
||||
mentions-line
|
||||
mentions-line-read-only
|
||||
@posted="onPosted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -588,6 +588,12 @@ const PostStatusForm = {
|
|||
},
|
||||
methods: {
|
||||
// Composing
|
||||
update() {
|
||||
Object.entries(this.defaultNewStatus).forEach(([key, value]) => {
|
||||
if (key === 'status') return
|
||||
this.newStatus[key] = value
|
||||
})
|
||||
},
|
||||
onMentionsLineUpdate(e) {
|
||||
if (this.mentionsLineReadOnly) return
|
||||
// TODO
|
||||
|
|
|
|||
|
|
@ -231,11 +231,12 @@
|
|||
>
|
||||
<scope-selector
|
||||
v-if="!disableVisibilitySelector"
|
||||
ref="scopeSelector"
|
||||
:show-all="showAllScopes"
|
||||
:user-default="userDefaultScope"
|
||||
:original-scope="repliedScope"
|
||||
:original-scope="newStatus.visibility"
|
||||
:initial-scope="newStatus.visibility"
|
||||
:on-scope-change="changeVis"
|
||||
@change="changeVis"
|
||||
/>
|
||||
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -57,24 +57,6 @@
|
|||
/> {{ $t('settings.conversation_display_linear_quick') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="menu-item dropdown-item -icon-double">
|
||||
<button
|
||||
class="main-button"
|
||||
:aria-checked="conversationDisplay === 'chat'"
|
||||
role="menuitemradio"
|
||||
@click="conversationDisplay = 'chat'"
|
||||
>
|
||||
<span
|
||||
class="input menu-checkbox -radio"
|
||||
:class="{ 'menu-checkbox-checked': conversationDisplay === 'chat' }"
|
||||
:aria-hidden="true"
|
||||
/><FAIcon
|
||||
icon="message"
|
||||
:aria-hidden="true"
|
||||
fixed-width
|
||||
/> {{ $t('settings.conversation_display_chat_quick') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="separator"
|
||||
|
|
|
|||
|
|
@ -26,16 +26,13 @@ const ScopeSelector = {
|
|||
required: false,
|
||||
type: String,
|
||||
},
|
||||
onScopeChange: {
|
||||
required: true,
|
||||
type: Function,
|
||||
},
|
||||
unstyled: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['change'],
|
||||
data() {
|
||||
return {
|
||||
currentScope: this.initialScope,
|
||||
|
|
@ -84,9 +81,14 @@ const ScopeSelector = {
|
|||
},
|
||||
changeVis(scope) {
|
||||
this.currentScope = scope
|
||||
this.onScopeChange && this.onScopeChange(scope)
|
||||
},
|
||||
this.$emit('change', scope)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
originalScope(newVal) {
|
||||
this.currentScope = newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ScopeSelector
|
||||
|
|
|
|||
|
|
@ -801,8 +801,6 @@
|
|||
"tree_fade_ancestors": "Display ancestors of the current status in faint text",
|
||||
"conversation_display_linear": "Linear-style",
|
||||
"conversation_display_linear_quick": "Linear view",
|
||||
"conversation_display_chat": "Chat-style",
|
||||
"conversation_display_chat_quick": "Chat view",
|
||||
"conversation_other_replies_button": "Show the \"other replies\" button",
|
||||
"conversation_other_replies_button_below": "Below statuses",
|
||||
"conversation_other_replies_button_inside": "Inside statuses",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue