Skip to content

Commit

Permalink
feat(NcAvatar): implement custom javascript hook action for the conta…
Browse files Browse the repository at this point in the history
…cts menu

Signed-off-by: Richard Steinmetz <[email protected]>
  • Loading branch information
st3iny committed Dec 17, 2024
1 parent 2b2d3ca commit e4d7568
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 1 deletion.
27 changes: 26 additions & 1 deletion src/components/NcAvatar/NcAvatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ export default {
<component :is="item.ncActionComponent"
v-for="(item, key) in menu"
:key="key"
v-bind="item.ncActionComponentProps">
v-bind="item.ncActionComponentProps"
v-on="item.ncActionComponentHandlers">
<template v-if="item.iconSvg" #icon>
<NcIconSvgWrapper :svg="item.iconSvg" />
</template>
Expand Down Expand Up @@ -232,16 +233,19 @@ import NcActions from '../NcActions/index.js'
import NcActionLink from '../NcActionLink/index.js'
import NcActionRouter from '../NcActionRouter/index.js'
import NcActionText from '../NcActionText/index.js'
import NcActionButton from '../NcActionButton/index.js'
import NcButton from '../NcButton/index.js'
import NcIconSvgWrapper from '../NcIconSvgWrapper/index.js'
import NcLoadingIcon from '../NcLoadingIcon/index.js'
import NcUserStatusIcon from '../NcUserStatusIcon/index.js'
import usernameToColor from '../../functions/usernameToColor/index.js'
import { getEnabledContactsMenuActions } from '../../functions/contactsMenu/index.ts'
import { getAvatarUrl } from '../../utils/getAvatarUrl.ts'
import { getUserStatusText } from '../../utils/UserStatus.ts'
import { userStatus } from '../../mixins/index.js'
import { t } from '../../l10n.js'
import { getRoute } from '../../components/NcRichText/autolink.js'
import logger from '../../utils/logger.js'

import axios from '@nextcloud/axios'
import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'
Expand Down Expand Up @@ -419,6 +423,7 @@ export default {
isAvatarLoaded: false,
isMenuLoaded: false,
contactsMenuLoading: false,
contactsMenuData: {},
contactsMenuActions: [],
contactsMenuOpenState: false,
}
Expand Down Expand Up @@ -566,6 +571,25 @@ export default {
}
})

for (const action of getEnabledContactsMenuActions(this.contactsMenuData)) {
try {
actions.push({
ncActionComponent: NcActionButton,
ncActionComponentProps: {},
ncActionComponentHandlers: {
click: () => action.callback(this.contactsMenuData),
},
text: action.displayName(this.contactsMenuData),
iconSvg: action.iconSvg(this.contactsMenuData),
})
} catch (error) {
logger.error(`Failed to render ContactsMenu action ${action.id}`, {
error,
action,
})
}
}

/**
* @param {string} html The HTML to escape
*/
Expand Down Expand Up @@ -663,6 +687,7 @@ export default {
try {
const user = encodeURIComponent(this.user)
const { data } = await axios.post(generateUrl('contactsmenu/findOne'), `shareType=0&shareWith=${user}`)
this.contactsMenuData = data
this.contactsMenuActions = data.topAction ? [data.topAction].concat(data.actions) : data.actions
} catch (e) {
this.contactsMenuOpenState = false
Expand Down
64 changes: 64 additions & 0 deletions src/functions/contactsMenu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import logger from '../../utils/logger.js'

// Taken from \OC\Contacts\ContactsMenu\Entry::jsonSerialize
export interface ContactsMenuEntry {
id: number|string|null,
fullName: string,
avatar: string|null,
topAction: object|null,
actions: object[],
lastMessage: '',
emailAddresses: string[],
profileTitle: string|null,
profileUrl: string|null,
status: string|null,
statusMessage: null|string,
statusMessageTimestamp: null|number,
statusIcon: null|string,
isUser: boolean,
uid: null|string,
}

export interface ContactsMenuAction {
id: string,
displayName: (entry: ContactsMenuEntry) => string,
enabled: (entry: ContactsMenuEntry) => boolean,
iconSvg: (entry: ContactsMenuEntry) => string,
callback: (entry: ContactsMenuEntry) => void,
}

/**
* Register a contacts and avatar menu action that will invoke the given callback on click.
*
* @param {ContactsMenuAction} action The action to register
*/
export function registerContactsMenuAction(action: ContactsMenuAction): void {
window._nc_contacts_menu_hooks ??= {}

if (window._nc_contacts_menu_hooks[action.id]) {
logger.error(`ContactsMenu action for id ${action.id} has already been registered`, {
action,
})
return
}

window._nc_contacts_menu_hooks[action.id] = action
}

/**
* Get all registered and enabled contacts menu actions for the given menu entry.
*
* @param {ContactsMenuEntry} entry The contacts menu entry object as returned by the backend
*/
export function getEnabledContactsMenuActions(entry: ContactsMenuEntry): ContactsMenuAction[] {
if (!window._nc_contacts_menu_hooks) {
return []
}

return Object.values(window._nc_contacts_menu_hooks).filter((action) => action.enabled(entry))
}
1 change: 1 addition & 0 deletions src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from './dialog/index.ts'
export * from './emoji/index.ts'
export * from './reference/index.js'
export * from './isDarkTheme/index.ts'
export * from './contactsMenu/index.ts'
export { default as usernameToColor } from './usernameToColor/index.js'
8 changes: 8 additions & 0 deletions src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { ContactsMenuAction } from './functions/contactsMenu/index.ts'

declare const PRODUCTION: boolean

declare const SCOPE_VERSION: string
Expand All @@ -14,3 +16,9 @@ declare module '*?raw' {
const content: string
export default content
}

declare global {
interface Window {
_nc_contacts_menu_hooks: { [id: string]: ContactsMenuAction },
}
}

0 comments on commit e4d7568

Please sign in to comment.