Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable30] fix: move useDocumentTitle logic to composable #13713

Merged
merged 2 commits into from
Nov 6, 2024
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
147 changes: 3 additions & 144 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import RightSidebar from './components/RightSidebar/RightSidebar.vue'
import SettingsDialog from './components/SettingsDialog/SettingsDialog.vue'

import { useActiveSession } from './composables/useActiveSession.js'
import { useDocumentVisibility } from './composables/useDocumentVisibility.ts'
import { useDocumentTitle } from './composables/useDocumentTitle.ts'
import { useHashCheck } from './composables/useHashCheck.js'
import { useIsInCall } from './composables/useIsInCall.js'
import { useSessionIssueHandler } from './composables/useSessionIssueHandler.js'
Expand Down Expand Up @@ -66,6 +66,7 @@ export default {
},

setup() {
useDocumentTitle()
// Add provided value to check if we're in the main app or plugin
provide('Talk:isMainApp', true)

Expand All @@ -74,7 +75,6 @@ export default {
isLeavingAfterSessionIssue: useSessionIssueHandler(),
isMobile: useIsMobile(),
isNextcloudTalkHashDirty: useHashCheck(),
isDocumentVisible: useDocumentVisibility(),
supportSessionState: useActiveSession(),
federationStore: useFederationStore(),
callViewStore: useCallViewStore(),
Expand All @@ -84,8 +84,6 @@ export default {

data() {
return {
savedLastMessageMap: {},
defaultPageTitle: false,
loading: false,
isRefreshingCurrentConversation: false,
recordingConsentGiven: false,
Expand All @@ -106,57 +104,6 @@ export default {
return !this.isLeavingAfterSessionIssue && this.isInCall
},

/**
* Keeps a list for all last message ids
*
* @return {object} Map with token => lastMessageId
*/
lastMessageMap() {
const conversationList = this.$store.getters.conversationsList
if (conversationList.length === 0) {
return {}
}

const lastMessage = {}
conversationList.forEach(conversation => {
lastMessage[conversation.token] = 0
if (conversation.lastMessage) {
const currentActorIsAuthor = conversation.lastMessage.actorType === this.$store.getters.getActorType()
&& conversation.lastMessage.actorId === this.$store.getters.getActorId()
if (currentActorIsAuthor) {
// Set a special value when the actor is the author so we can skip it.
// Can't use 0 though because hidden commands result in 0
// and they would hide other previously posted new messages
lastMessage[conversation.token] = -1
} else {
lastMessage[conversation.token] = Math.max(
conversation.lastMessage && conversation.lastMessage.id ? conversation.lastMessage.id : 0,
this.$store.getters.getLastKnownMessageId(conversation.token) ? this.$store.getters.getLastKnownMessageId(conversation.token) : 0,
)
}
}
})
return lastMessage
},

/**
* @return {boolean} Returns true, if
* - a conversation is newly added to lastMessageMap
* - a conversation has a different last message id then previously
*/
atLeastOneLastMessageIdChanged() {
let modified = false
Object.keys(this.lastMessageMap).forEach(token => {
if (!this.savedLastMessageMap[token] // Conversation is new
|| (this.savedLastMessageMap[token] !== this.lastMessageMap[token] // Last message changed
&& this.lastMessageMap[token] !== -1)) { // But is not from the current user
modified = true
}
})

return modified
},

/**
* The current conversation token
*
Expand Down Expand Up @@ -187,14 +134,6 @@ export default {
},

watch: {
atLeastOneLastMessageIdChanged() {
if (this.isDocumentVisible) {
return
}

this.setPageTitle(this.getConversationName(this.token), this.atLeastOneLastMessageIdChanged)
},

token(newValue, oldValue) {
const shouldShowSidebar = BrowserStorage.getItem('sidebarOpen') !== 'false'
// Collapse the sidebar if it's a one to one conversation
Expand All @@ -221,22 +160,6 @@ export default {
}
}
},

isDocumentVisible(value) {
if (value) {
// Remove the potential "*" marker for unread chat messages
let title = this.getConversationName(this.token)
if (window.document.title.indexOf(t('spreed', 'Duplicate session')) === 0) {
title = t('spreed', 'Duplicate session')
}
this.setPageTitle(title, false)
} else {
// Copy the last message map to the saved version,
// this will be our reference to check if any chat got a new
// message since the last visit
this.savedLastMessageMap = this.lastMessageMap
}
},
},

beforeCreate() {
Expand Down Expand Up @@ -398,11 +321,6 @@ export default {
* the store.
*/
EventBus.once('conversations-received', () => {
if (this.$route.name === 'conversation') {
// Adjust the page title once the conversation list is loaded
this.setPageTitle(this.getConversationName(this.token), false)
}

if (!getCurrentUser()) {
// Set the current actor/participant for guests
const conversation = this.$store.getters.conversation(this.token)
Expand Down Expand Up @@ -441,7 +359,7 @@ export default {
if (this.warnLeaving && !to.params?.skipLeaveWarning) {
OC.dialogs.confirmDestructive(
t('spreed', 'Navigating away from the page will leave the call in {conversation}', {
conversation: this.getConversationName(this.token),
conversation: this.currentConversation?.displayName ?? '',
}),
t('spreed', 'Leave call'),
{
Expand All @@ -463,19 +381,6 @@ export default {
}
})

Router.afterEach((to) => {
/**
* Change the page title only after the route was changed
*/
if (to.name === 'conversation') {
// Page title
const nextConversationName = this.getConversationName(to.params.token)
this.setPageTitle(nextConversationName)
} else if (to.name === 'notfound') {
this.setPageTitle('')
}
})

if (getCurrentUser()) {
console.debug('Setting current user')
this.$store.dispatch('setCurrentUser', getCurrentUser())
Expand Down Expand Up @@ -626,38 +531,6 @@ export default {
this.fetchSingleConversation(this.token)
},

/**
* Set the page title to the conversation name
*
* @param {string} title Prefix for the page title e.g. conversation name
* @param {boolean} showAsterix Prefix for the page title e.g. conversation name
*/
setPageTitle(title, showAsterix) {
if (this.defaultPageTitle === false) {
// On the first load we store the current page title "Talk - Nextcloud",
// so we can append it every time again
this.defaultPageTitle = window.document.title
// Coming from a "Duplicate session - Talk - …" page?
if (this.defaultPageTitle.indexOf(' - ' + t('spreed', 'Talk') + ' - ') !== -1) {
this.defaultPageTitle = this.defaultPageTitle.substring(this.defaultPageTitle.indexOf(' - ' + t('spreed', 'Talk') + ' - ') + 3)
}
// When a conversation is opened directly, the "Talk - " part is
// missing from the title
if (!IS_DESKTOP && this.defaultPageTitle.indexOf(t('spreed', 'Talk') + ' - ') !== 0) {
this.defaultPageTitle = t('spreed', 'Talk') + ' - ' + this.defaultPageTitle
}
}

let newTitle = this.defaultPageTitle
if (title !== '') {
newTitle = `${title} - ${newTitle}`
}
if (showAsterix && !newTitle.startsWith('* ')) {
newTitle = '* ' + newTitle
}
window.document.title = newTitle
},

onResize() {
this.windowHeight = window.innerHeight - document.getElementById('header').clientHeight
},
Expand All @@ -670,20 +543,6 @@ export default {
event.preventDefault()
},

/**
* Get a conversation's name.
*
* @param {string} token The conversation's token
* @return {string} The conversation's name
*/
getConversationName(token) {
if (!this.$store.getters.conversation(token)) {
return ''
}

return this.$store.getters.conversation(token).displayName
},

async fetchSingleConversation(token) {
if (this.isRefreshingCurrentConversation) {
return
Expand Down
162 changes: 162 additions & 0 deletions src/composables/useDocumentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { computed, ref, watch } from 'vue'
import type { ComputedRef } from 'vue'
import type { Route } from 'vue-router'

import { t } from '@nextcloud/l10n'

import { useDocumentVisibility } from './useDocumentVisibility.ts'
import { useStore } from './useStore.js'
import Router from '../router/router.js'
import { EventBus } from '../services/EventBus.ts'
import type { Conversation } from '../types/index.ts'

/**
* Composable to check whether the page is visible.
*/
export function useDocumentTitle() {
const store = useStore()
const isDocumentVisible = useDocumentVisibility()

const defaultPageTitle = ref<string>(getDefaultPageTitle())
const showAsterisk = ref(false)
const savedLastMessageMap = ref<Record<string, number>>({})

const conversationList = computed(() => store.getters.conversationsList)
const actorId = computed(() => store.getters.getActorId())
const actorType = computed(() => store.getters.getActorType())

watch(conversationList, (newValue) => {
if (isDocumentVisible.value || document.title.startsWith('* ')
|| !Object.keys(savedLastMessageMap.value).length) {
return
}

const newLastMessageMap = getLastMessageMap(newValue)
/**
* @return {boolean} Returns true, if
* - a conversation is newly added to lastMessageMap
* - a conversation has a different last message id then previously
*/
const shouldShowAsterisk = Object.keys(newLastMessageMap).some(token => {
return !savedLastMessageMap.value[token] // Conversation is new
|| (savedLastMessageMap.value[token] !== newLastMessageMap[token] // Last message changed
&& newLastMessageMap[token] !== -1) // But is not from the current user
})
if (shouldShowAsterisk) {
showAsterisk.value = true
setPageTitleFromRoute(Router.currentRoute)
}
})

watch(isDocumentVisible, () => {
if (isDocumentVisible.value) {
// Remove asterisk for unread chat messages
showAsterisk.value = false
setPageTitleFromRoute(Router.currentRoute)
} else {
// Copy the last message map to the saved version,
// this will be our reference to check if any chat got a new
// message since the last visit
savedLastMessageMap.value = getLastMessageMap(conversationList.value)
}
})

/**
* Adjust the page title to the conversation name once conversationsList is loaded
*/
EventBus.once('conversations-received', () => {
setPageTitleFromRoute(Router.currentRoute)
})

/**
* Change the page title after the route was changed
*/
Router.afterEach((to) => setPageTitleFromRoute(to))

/**
* Get a list for all last message ids
*
* @param conversationList array of conversations
*/
function getLastMessageMap(conversationList: Conversation[]): Record<string, number> {
if (conversationList.length === 0) {
return {}
}

return conversationList.reduce((acc: Record<string, number>, conversation: Conversation) => {
const { token, lastMessage } = conversation
// Default to 0 for messages without valid lastMessage
if (!lastMessage || Array.isArray(lastMessage)) {
acc[token] = 0
return acc
}

if (lastMessage.actorId === actorId.value && lastMessage.actorType === actorType.value) {
// Set a special value when the actor is the author so we can skip it.
// Can't use 0 though because hidden commands result in 0,
// and they would hide other previously posted new messages
acc[token] = -1
} else {
// @ts-expect-error: Property 'id' does not exist on type ChatProxyMessage
const lastMessageId = lastMessage.id ?? 0
const lastKnownMessageId = store.getters.getLastKnownMessageId(token) ?? 0
acc[token] = Math.max(lastMessageId, lastKnownMessageId)
}
return acc
}, {})
}

/**
*
* @param route current web route
*/
function setPageTitleFromRoute(route: Route) {
switch (route.name) {
case 'conversation':
setPageTitle(store.getters.conversation(route.params.token)?.displayName ?? '')
break
case 'duplicatesession':
setPageTitle(t('spreed', 'Duplicate session'))
break
default:
setPageTitle('')
}
}

/**
* Set the page title to the conversation name
*
* @param title Prefix for the page title e.g. conversation name
*/
function setPageTitle(title: string) {
const newTitle = title ? `${title} - ${defaultPageTitle.value}` : defaultPageTitle.value
document.title = (showAsterisk.value && !newTitle.startsWith('* '))
? '* ' + newTitle
: newTitle
}

/**
* Get the default page title of Talk page like "Talk - Nextcloud", to append it every time again
*/
function getDefaultPageTitle() {
// Do nothing on Desktop
if (IS_DESKTOP) {
return document.title
}
const appNamePrefix = t('spreed', 'Talk') + ' - '
// If coming from a "… - Talk - …" page, keep only "Talk - …" part
if (document.title.includes(' - ' + appNamePrefix)) {
return document.title.substring(document.title.indexOf(' - ' + appNamePrefix) + 3)
}
// When a conversation is opened directly, "Talk - " might be missing from the title
if (!document.title.startsWith(appNamePrefix)) {
return appNamePrefix + document.title
}
return document.title
}
}
Loading
Loading