Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/MessagesList/MessagesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,7 @@ export default {
position: relative;
flex: 1 0;
padding-top: 20px;
overflow-y: auto;
overflow-y: scroll;
overflow-x: hidden;
border-bottom: 1px solid var(--color-border);
transition: $transition;
Expand Down
59 changes: 51 additions & 8 deletions src/components/NewMessage/NewMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@

<!-- Input area -->
<div class="new-message-form__input">
<NewMessageAbsenceInfo v-if="userAbsence"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also check the dates. For example, I can set my OoO proactively from tomorrow and I am still working today. AbsenceInfo should be shown starting from tomorrow and not from now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then OoO should be set up from tommorow by BG job, I don't think it has to be double-checked on Talk side

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if it should be from the server side to only render the active OoO or any OoO that exists because dates were given in the response.

Should we use them to show when they will be back for example ? like on Webex

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the next iteration maybe

:user-absence="userAbsence"
:display-name="conversation.displayName" />

<div class="new-message-form__emoji-picker">
<NcEmojiPicker v-if="!disabled"
:container="container"
Expand Down Expand Up @@ -167,6 +171,7 @@ import NcEmojiPicker from '@nextcloud/vue/dist/Components/NcEmojiPicker.js'
import NcRichContenteditable from '@nextcloud/vue/dist/Components/NcRichContenteditable.js'

import Quote from '../Quote.vue'
import NewMessageAbsenceInfo from './NewMessageAbsenceInfo.vue'
import NewMessageAttachments from './NewMessageAttachments.vue'
import NewMessageAudioRecorder from './NewMessageAudioRecorder.vue'
import NewMessageNewFileDialog from './NewMessageNewFileDialog.vue'
Expand All @@ -177,6 +182,7 @@ import { CONVERSATION, PARTICIPANT, PRIVACY } from '../../constants.js'
import { EventBus } from '../../services/EventBus.js'
import { shareFile } from '../../services/filesSharingServices.js'
import { searchPossibleMentions } from '../../services/mentionsService.js'
import { useChatExtrasStore } from '../../stores/chatExtras.js'
import { useSettingsStore } from '../../stores/settings.js'
import { fetchClipboardContent } from '../../utils/clipboard.js'
import { isDarkTheme } from '../../utils/isDarkTheme.js'
Expand All @@ -201,6 +207,7 @@ export default {
NcButton,
NcEmojiPicker,
NcRichContenteditable,
NewMessageAbsenceInfo,
NewMessageAttachments,
NewMessageAudioRecorder,
NewMessageNewFileDialog,
Expand Down Expand Up @@ -261,9 +268,11 @@ export default {
expose: ['focusInput'],

setup() {
const chatExtrasStore = useChatExtrasStore()
const settingsStore = useSettingsStore()

return {
chatExtrasStore,
settingsStore,
supportTypingStatus,
}
Expand Down Expand Up @@ -297,6 +306,10 @@ export default {
return this.conversation.readOnly === CONVERSATION.STATE.READ_ONLY
},

isOneToOneConversation() {
return this.conversation.type === CONVERSATION.TYPE.ONE_TO_ONE
},

noChatPermission() {
return (this.conversation.permissions & PARTICIPANT.PERMISSIONS.CHAT) === 0
},
Expand Down Expand Up @@ -373,10 +386,15 @@ export default {
showAudioRecorder() {
return !this.hasText && this.canUploadFiles && !this.broadcast && !this.upload
},

showTypingStatus() {
return this.hasTypingIndicator && this.supportTypingStatus
&& this.settingsStore.typingStatusPrivacy === PRIVACY.PUBLIC
},

userAbsence() {
return this.chatExtrasStore.absence[this.token]
},
},

watch: {
Expand All @@ -388,13 +406,18 @@ export default {
this.$store.dispatch('setCurrentMessageInput', { token: this.token, text: newValue })
},

token(token) {
if (token) {
this.text = this.$store.getters.currentMessageInput(token)
} else {
this.text = ''
token: {
immediate: true,
handler(token) {
if (token) {
this.text = this.$store.getters.currentMessageInput(token)
} else {
this.text = ''
}
this.clearTypingInterval()

this.checkAbsenceStatus()
}
this.clearTypingInterval()
},
},

Expand Down Expand Up @@ -797,6 +820,26 @@ export default {
isMobile() {
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent)
},

async checkAbsenceStatus() {
if (!this.isOneToOneConversation) {
return
}

// TODO replace with status message id 'vacationing'
if (this.conversation.status === 'dnd') {
// Fetch actual absence status from server
await this.chatExtrasStore.getUserAbsence({
token: this.token,
userId: this.conversation.name,
})
} else {
// Remove stored absence status
this.chatExtrasStore.resetUserAbsence({
token: this.token,
})
}
}
},
}
</script>
Expand All @@ -817,7 +860,7 @@ export default {

<style lang="scss" scoped>
.wrapper {
padding: 12px 0;
padding: 12px 12px 12px 0;
min-height: 69px;
}

Expand Down Expand Up @@ -855,7 +898,7 @@ export default {
}

&__quote {
margin: 0 16px 12px 24px;
margin: 0 16px 12px;
background-color: var(--color-background-hover);
padding: 8px;
border-radius: var(--border-radius-large);
Expand Down
160 changes: 160 additions & 0 deletions src/components/NewMessage/NewMessageAbsenceInfo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<!--
- @copyright Copyright (c) 2023 Maksim Sukharev <antreesy.web@gmail.com>
-
- @author Maksim Sukharev <antreesy.web@gmail.com>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<!-- eslint-disable vue/singleline-html-element-content-newline -->
<template>
<NcNoteCard type="info" class="absence-reminder">
<div class="absence-reminder__content">
<AvatarWrapper :id="userAbsence.userId"
:name="displayName"
:size="AVATAR.SIZE.EXTRA_SMALL"
source="users"
disable-menu
disable-tooltip />
<h4 class="absence-reminder__caption">{{ userAbsenceCaption }}</h4>
<NcButton v-if="userAbsenceMessage"
class="absence-reminder__button"
type="tertiary"
@click="toggleCollapsed">
<template #icon>
<ChevronDown class="icon" :class="{'icon--reverted': !collapsed}" :size="20" />
</template>
</NcButton>
</div>
<p class="absence-reminder__message" :class="{'absence-reminder__message--collapsed': collapsed}">{{ userAbsenceMessage }}</p>
</NcNoteCard>
</template>

<script>
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'

import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue'

import { AVATAR } from '../../constants.js'

export default {
name: 'NewMessageAbsenceInfo',

components: {
AvatarWrapper,
ChevronDown,
NcButton,
NcNoteCard,
},

props: {
userAbsence: {
type: Object,
required: true,
},

displayName: {
type: String,
required: true,
},
},

setup() {
return { AVATAR }
},

data() {
return {
collapsed: true,
}
},

computed: {
userAbsenceCaption() {
return t('spreed', '{user} is out of office and might not respond.', { user: this.displayName })
},

userAbsenceMessage() {
return this.userAbsence.message || this.userAbsence.status
},
},

methods: {
toggleCollapsed() {
this.collapsed = !this.collapsed
},
},
}
</script>

<style lang="scss" scoped>
@import '../../assets/variables';

.absence-reminder {
margin: 0 16px 12px;
padding: 10px 10px 10px 6px;
border-radius: var(--border-radius-large);

// FIXME upstream: allow to hide or replace NoteCard default icon
& :deep(.notecard__icon) {
display: none;
}

& > :deep(div) {
width: 100%;
}

&__content {
display: flex;
align-items: center;
gap: 4px;
width: 100%;
}

&__caption {
font-weight: bold;
}

&__message {
padding-left: 26px;
white-space: pre-line;
word-wrap: break-word;

&--collapsed {
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
}

&__button {
margin-left: auto;

& .icon {
transition: $transition;

&--reverted {
transform: rotate(180deg);
}
}
}
}
</style>
1 change: 0 additions & 1 deletion src/components/TopBar/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ export default {

<style lang="scss" scoped>
.top-bar {
right: 12px; /* needed so we can still use the scrollbar */
display: flex;
z-index: 10;
justify-content: flex-end;
Expand Down
10 changes: 10 additions & 0 deletions src/services/participantsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ const setTyping = (typing) => {
signalingSetTyping(typing)
}

/**
* Get absence information for a user (in a given 1-1 conversation).
*
* @param {string} userId user id
*/
const getUserAbsence = async (userId) => {
return axios.get(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}', { userId }))
}

export {
joinConversation,
rejoinConversation,
Expand All @@ -261,4 +270,5 @@ export {
setPermissions,
setSessionState,
setTyping,
getUserAbsence,
}
Loading