Files
habitica/website/client/src/components/groups/chat.vue
T
2025-06-11 19:20:11 -05:00

291 lines
7.3 KiB
Vue

<template>
<div class="row chat-row">
<div class="col-12">
<h3
class="float-left label"
:class="{accepted: communityGuidelinesAccepted }"
>
{{ label }}
</h3>
<div
v-markdown="$t('markdownFormattingHelp')"
class="float-right"
></div>
<div
v-if="communityGuidelinesAccepted"
class="row"
>
<textarea
ref="user-entry"
v-model="newMessage"
dir="auto"
:placeholder="placeholder"
:class="{'user-entry': newMessage}"
:maxlength="MAX_MESSAGE_LENGTH"
@keydown="autoCompleteMixinUpdateCarretPosition"
@keyup.ctrl.enter="sendMessageShortcut()"
@keydown.tab="autoCompleteMixinHandleTab($event)"
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
@keydown.esc="autoCompleteMixinHandleEscape($event)"
@paste="disableMessageSendShortcut()"
></textarea>
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
<autocomplete
ref="autocomplete"
:text="newMessage"
:textbox="textbox"
:coords="mixinData.autoComplete.coords"
:caret-position="mixinData.autoComplete.caretPosition"
:chat="group.chat"
@select="selectedAutocomplete"
/>
</div>
<community-guidelines />
<div class="row chat-actions">
<div class="col-6 chat-receive-actions">
<button
v-once
class="btn btn-secondary float-left fetch"
@click="fetchRecentMessages()"
>
{{ $t('fetchRecentMessages') }}
</button>
<button
v-once
class="btn btn-secondary float-left"
@click="reverseChat()"
>
{{ $t('reverseChat') }}
</button>
</div>
<div class="col-6 chat-send-actions">
<button
class="btn btn-primary send-chat float-right"
:disabled="!communityGuidelinesAccepted"
:class="{ disabled: !communityGuidelinesAccepted }"
@click="sendMessage()"
>
{{ $t('send') }}
</button>
</div>
</div>
<slot name="additionRow"></slot>
<div class="row">
<div class="hr col-12"></div>
<chat-messages
:chat.sync="group.chat"
:group-type="group.type"
:group-id="group._id"
:group-name="group.name"
/>
</div>
</div>
</div>
</template>
<script>
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessages from '../chat/chatMessages';
import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
export default {
directives: {
markdown: markdownDirective,
},
components: {
autocomplete,
communityGuidelines,
chatMessages,
},
mixins: [externalLinks, autoCompleteHelperMixin],
props: ['label', 'group', 'placeholder'],
data () {
return {
newMessage: '',
sending: false,
chat: {
submitDisable: false,
submitTimeout: null,
},
textbox: null,
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
};
},
computed: {
...mapState({ user: 'user.data' }),
currentLength () {
return this.newMessage.length;
},
communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted;
},
},
mounted () {
this.textbox = this.$refs['user-entry'];
this.handleExternalLinks();
},
updated () {
this.handleExternalLinks();
},
methods: {
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
this.sendMessage();
}
},
async sendMessage () {
if (this.sending) return;
this.sending = true;
let response;
try {
response = await this.$store.dispatch('chat:postChat', {
group: this.group,
message: this.newMessage,
});
} catch (e) {
// catch exception to allow function to continue
}
if (response) {
this.group.chat.unshift(response.message);
this.newMessage = '';
}
this.sending = false;
// @TODO: I would like to not reload everytime we send. Why are we reloading?
// The response has all the necessary data...
const chat = await this.$store.dispatch('chat:getChat', { groupId: this.group._id });
this.group.chat = chat;
},
disableMessageSendShortcut () {
// Some users were experiencing accidental sending of messages after pasting
// So, after pasting, disable the shortcut for a second.
this.chat.submitDisable = true;
if (this.chat.submitTimeout) {
// If someone pastes during the disabled period, prevent early re-enable
clearTimeout(this.chat.submitTimeout);
this.chat.submitTimeout = null;
}
this.chat.submitTimeout = window.setTimeout(() => {
this.chat.submitTimeout = null;
this.chat.submitDisable = false;
}, 500);
},
selectedAutocomplete (newText, newCaret) {
this.newMessage = newText;
// Wait for v-modal to update
this.$nextTick(() => {
this.textbox.setSelectionRange(newCaret, newCaret);
this.textbox.focus();
});
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
reverseChat () {
this.group.chat.reverse();
},
},
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.autoCompleteMixinResetCoordsPosition();
next();
},
};
</script>
<style scoped lang="scss">
@import '@/assets/scss/colors.scss';
@import '@/assets/scss/variables.scss';
.chat-actions {
margin-top: 1em;
.chat-receive-actions {
padding-left: 0;
button {
margin-bottom: 1em;
&:not(:last-child) {
margin-right: 1em;
}
}
}
.chat-send-actions {
padding-right: 0;
}
}
.chat-row {
position: relative;
.label:not(.accepted) {
color: #a5a1ac;
}
.row {
margin-left: 0;
margin-right: 0;
clear: both;
}
textarea {
min-height: 150px;
width: 100%;
background-color: $white;
border: solid 1px $gray-400;
font-style: italic;
line-height: 1.43;
color: $gray-300;
padding: 10px 12px;
}
.user-entry {
font-style: normal;
color: $black;
}
.hr {
width: 100%;
height: 20px;
border-bottom: 1px solid $gray-500;
text-align: center;
margin: 2em 0;
}
.hr-middle {
font-size: 16px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.5;
text-align: center;
color: $gray-200;
background-color: $gray-700;
padding: .2em;
margin-top: .2em;
display: inline-block;
width: 100px;
}
}
</style>