import { message } from 'ant-design-vue/es' import type { MessageArgsProps } from 'ant-design-vue/es' import type { VueNode } from 'ant-design-vue/es/_util/type' import type { VNode } from 'vue' import { isPrimitiveValue } from 'nocodb-sdk' import NcAlert, { type NcAlertProps } from '../components/nc/Alert.vue' import { getI18n } from '~/plugins/a.i18n' interface NcAlertMessageProps extends Pick< NcAlertProps, | 'showIcon' | 'closable' | 'align' | 'copyText' | 'copyBtnTooltip' | 'messageClass' | 'descriptionClass' | 'duration' | 'showDuration' > {} /** * `NcMessageObjectProps` defines the properties allowed in `ncMessage`, * extending `NcAlertProps` while omitting fields that are irrelevant for messages. */ export interface NcMessageObjectProps extends NcAlertMessageProps, Omit { title?: string content?: string | (() => VueNode) | VueNode /** * Custom action slot content to be rendered inside the alert. * It can be either a `VueNode` or a function returning a `VueNode`. */ action?: VNode | (() => VNode) showDefaultMessage?: boolean showCopyBtn?: boolean /** * For internal use only */ renderAsNcAlert?: boolean } /** * `NcMessageProps` can either be a string (message text) or an object of type `NcMessageObjectProps`. */ export type NcMessageProps = NcMessageObjectProps | VueNode /** * Use `copyText` & `copyBtnTooltip` to set the copy text & tooltip for the copy button if params is primitive value */ export interface NcMessageExtraProps extends Pick {} const defaultNcMessageExtraProps = { showDefaultMessage: false, showCopyBtn: true, } /** * Default values for `NcMessageObjectProps`. */ const initialValue = { title: '', content: '', showIcon: true, closable: true, align: 'top', class: '', messageClass: '', descriptionClass: '', renderAsNcAlert: true, ...defaultNcMessageExtraProps, } as NcMessageObjectProps const initialToastTypeValue = { closable: false, showCopyBtn: false, showDuration: false, showIcon: false, duration: 2, } as NcMessageObjectProps /** * Generates a unique key for each message instance. * @returns A unique string key for the message. */ const generateMessageKey = (params: NcMessageProps) => { if (ncIsString(params) || !params.key) { return `ncMessage_${Date.now()}_${Math.random()}` } return params.key } function isNcMessageObjectProps(params: any): params is NcMessageObjectProps { return !ncIsEmptyObject(params) } /** * Processes `NcMessageProps` and merges them with default values. * - If a string is provided, it sets it as the description while applying a default content based on type. * - If neither `content` nor `description` exist, the content is set to the default localized text. * - Uses the spread operator to ensure proper merging of values. * * @param type - The type of message (`success`, `error`, `info`, `warning`). * @param params - The message parameters, either a string or an object. * @param ncMessageExtraProps - The extra props * @returns A full `NcMessageObjectProps` object with defaults applied. */ const getMessageProps = ( type: NcAlertProps['type'], params: NcMessageProps, ncMessageExtraProps: NcMessageExtraProps = defaultNcMessageExtraProps, ): NcMessageObjectProps => { const updatedParams = { ...initialValue, ...(type === 'toast' ? initialToastTypeValue : {}) } let content = '' if (type === 'toast') { if (isPrimitiveValue(params)) { return { ...updatedParams, content: params, renderAsNcAlert: true, } } else { return { ...updatedParams, ...params, renderAsNcAlert: true, } } } else if (isPrimitiveValue(params)) { content = params?.toString() ?? '' // If params is a string, use it as the description and apply a default message based on type return { ...updatedParams, title: ncMessageExtraProps.showDefaultMessage || !content ? getI18n().global.t(`objects.ncMessage.${type}`) : '', content, copyText: ncMessageExtraProps.showCopyBtn ? ncMessageExtraProps.copyText ?? content ?? '' : '', } } else if (!isNcMessageObjectProps(params)) { content = params return { ...updatedParams, title: ncMessageExtraProps.showDefaultMessage || !content ? getI18n().global.t(`objects.ncMessage.${type}`) : '', content, renderAsNcAlert: false, } } else { const showDefaultMessage = ncMessageExtraProps.showDefaultMessage ?? params.showDefaultMessage /** * default value of `ncMessageExtraProps.showCopyBtn` & `params.showCopyBtn` is true, so if one is false then we should not show */ const showCopyBtn = ncMessageExtraProps.showCopyBtn && params.showCopyBtn let copyText = '' if (showCopyBtn) { if (params?.copyText || ncMessageExtraProps.copyText) { copyText = params?.copyText || ncMessageExtraProps.copyText } else if (isPrimitiveValue(params.content)) { copyText = params.content?.toString() ?? params.title ?? '' } } // If neither title nor content exist, set message to the default localized text const showDefaultTitle = !updatedParams.title && !updatedParams.content && showDefaultMessage // Merge provided object properties into the default values using the spread operator return { ...updatedParams, ...params, copyText, ...(showDefaultTitle ? { title: getI18n().global.t(`objects.ncMessage.${type}`) } : {}), renderAsNcAlert: isPrimitiveValue(params.content), } } } /** * Displays a message using Ant Design's `message.open`, rendering an `NcAlert` inside. * Note: we have to render our `NcAlert` only if content is primitive value * @param type - The type of message (`success`, `error`, `info`, `warning`). * @param params - The message content or properties. * @param duration - Optional duration in seconds before auto-dismissal. * */ const showMessage = ( type: NcAlertProps['type'], params: NcMessageProps, duration?: number, ncMessageExtraProps?: NcMessageExtraProps, ) => { const props = getMessageProps(type, params, ncMessageExtraProps) const { onClick, onClose, content, title, getPopupContainer, style, appContext, prefixCls = '', rootPrefixCls = '', renderAsNcAlert, showCopyBtn: _showCopyBtn, showDefaultMessage: _showDefaultMessage, ...ncAlertProps } = props // If title & content is blank then no need to show blank toast message if (!title && !content) return const key = generateMessageKey(params) let ncDuration = duration ?? ncAlertProps.duration // If showDuration is false and duration is not provided, then set duration to 3 seconds if (!ncAlertProps.showDuration && !ncDuration) { ncDuration = 3 } return message.open({ key, content: renderAsNcAlert ? () => h( NcAlert, { ...ncAlertProps, message: title, description: content, type, isNotification: true, onClose: () => { onClose?.() message.destroy(key) }, duration: ncDuration, }, { action: ncIsFunction(ncAlertProps.action) ? ncAlertProps.action : ncAlertProps.action ? () => ncAlertProps.action : undefined, icon: ncIsFunction(ncAlertProps.icon) ? ncAlertProps.icon : ncAlertProps.icon ? () => ncAlertProps.icon : undefined, }, ) : content, type: !renderAsNcAlert && type !== 'toast' ? type : undefined, duration: ncDuration, prefixCls, rootPrefixCls, getPopupContainer, onClose() { onClose?.() }, style, appContext, onClick(event) { onClick?.(event) }, }) } /** * `ncMessage` provides methods to display different types of messages. * * ## Usage Examples: * * ### Display a default success message (title based on type) * ```ts * ncMessage.success(); * ncMessage.error(); * ncMessage.info(); * ncMessage.warn(); * ``` * * ### Display a success message with a custom title only * ```ts * ncMessage.success({ * title: 'Table created successfully', * }); * ``` * * ### Display a success message with a custom description only * ```ts * ncMessage.success({ * content: 'Lorem ipsum dolor sit amet, consectetur adipiscing.', * }); * ``` * * ### Display a default title based on type with a custom description * ```ts * ncMessage.success('Lorem ipsum dolor sit amet, consectetur adipiscing', undefined, { showDefaultMessage: false }); * ncMessage.error('Lorem ipsum dolor sit amet, consectetur adipiscing', undefined, { showDefaultMessage: false }); * ncMessage.info('Lorem ipsum dolor sit amet, consectetur adipiscing', undefined, { showDefaultMessage: false }); * ncMessage.warn('Lorem ipsum dolor sit amet, consectetur adipiscing', undefined, { showDefaultMessage: false }); * ``` * * ### Fully customized success message * ```ts * ncMessage.success({ * title: 'Table created successfully', * content: 'Lorem ipsum dolor sit amet, consectetur adipiscing', * copyText: 'Lorem ipsum dolor sit amet, consectetur adipiscing', * showIcon: true, * closable: true, * showDuration: false, * align: 'center', * action: h( * resolveComponent('NcButton'), * { * onClick: () => { * console.log('clicked') * }, * size: 'small', * type: 'secondary', * }, * () => 'Okay', * ), * icon: () => h(resolveComponent('GeneralIcon'), { icon: 'settings', class: 'text-2xl text-red-500' }), * }); * ``` */ const ncMessage = { success: (params: NcMessageProps = '', duration?: number, ncMessageExtraProps?: NcMessageExtraProps) => { return showMessage('success', params, duration, ncMessageExtraProps) }, error: (params: NcMessageProps = '', duration?: number, ncMessageExtraProps?: NcMessageExtraProps) => { return showMessage('error', params, duration, ncMessageExtraProps) }, info: (params: NcMessageProps = '', duration?: number, ncMessageExtraProps?: NcMessageExtraProps) => { return showMessage('info', params, duration, ncMessageExtraProps) }, warn: (params: NcMessageProps = '', duration?: number, ncMessageExtraProps?: NcMessageExtraProps) => { return showMessage('warning', params, duration, ncMessageExtraProps) }, warning: (params: NcMessageProps = '', duration?: number, ncMessageExtraProps?: NcMessageExtraProps) => { return showMessage('warning', params, duration, ncMessageExtraProps) }, toast: ( params: | (Omit & { content: string | number | null | undefined }) | string | number | null | undefined = '', duration?: number, ncMessageExtraProps?: NcMessageExtraProps, ) => { return showMessage('toast', params, duration, ncMessageExtraProps) }, } export { ncMessage }