UNPKG

@blocklet/ui-react

Version:

Some useful front-end web components that can be used in Blocklets.

247 lines (221 loc) 7.74 kB
import isEmpty from 'lodash/isEmpty'; import DOMPurify from 'dompurify'; import { Link, toTextList, getLink as getLinkUtil } from '@abtnode/util/lib/notification-preview/highlight'; import { isSameAddr } from '@abtnode/util/lib/notification-preview/func'; import { getBlockletSDK } from '@blocklet/js-sdk'; /** * 通知对象的活动目标接口 */ interface ActivityTarget { type?: string; id?: string; } /** * 通知对象的活动元数据接口 */ interface ActivityMeta { id?: string; } /** * 活动对象接口 */ interface Activity { type?: string; actor?: string; target?: ActivityTarget; meta?: ActivityMeta; } /** * 通知对象接口 */ interface Notification { activity?: Activity; actorInfo?: any; severity?: string; title?: string; description?: string; items?: Notification[]; } /** * 合并相邻的通知数据 * 合并条件: * 1. 数据必须是相邻的 * 2. activity.type 必须相同且不为 null 或 undefined * 3. 如果存在target对象,activity.target.type和activity.target.id都必须相同 * 4. 如果相邻数据的 activity.type 相同但没有 activity.target 对象,则需要合并 * * @param {Notification[]} notifications - 需要处理的通知数据 * @returns {Notification[]} - 合并后的通知数组 */ export const mergeAdjacentNotifications = (notifications: Notification[]): Notification[] => { if (!notifications || !notifications.length) { return []; } const result: Notification[] = []; let currentGroup: Notification | null = null; let groupItems: Notification[] = []; notifications.forEach((notification: Notification) => { // 如果没有activity或activity.type为null/undefined或者这是第一条记录 if (!notification.activity || !notification.activity.type || !currentGroup) { // 处理前一个分组(如果存在) if (currentGroup) { // 如果只有一个通知,直接添加原通知 if (groupItems.length === 1) { result.push(groupItems[0]); } else { // 将items添加到第一个通知对象,并将其添加到结果中 currentGroup.items = groupItems; result.push(currentGroup); } } // 处理当前通知 if (notification.activity && notification.activity.type) { // 将第一个通知对象作为组的基础,不创建新对象 currentGroup = notification; groupItems = [notification]; } else { // 无activity或activity.type的记录直接添加到结果中 result.push(notification); // 重置当前组 currentGroup = null; groupItems = []; } return; } // 获取当前组和当前通知的activity信息 const currentActivity = groupItems[0].activity; const currentType = currentActivity?.type; const currentTargetType = currentActivity?.target?.type; const currentTargetId = currentActivity?.target?.id; const newType = notification.activity.type; const newTargetType = notification.activity.target?.type; const newTargetId = notification.activity.target?.id; // 判断是否需要合并 const shouldMerge = // activity.type 必须相同且不为null或undefined currentType && newType && currentType === newType && // 如果都没有target,可以合并 ((!currentActivity?.target && !notification.activity.target) || // 如果都有target,则target.type和target.id都必须相同 (currentActivity?.target && notification.activity.target && currentTargetType === newTargetType && currentTargetId === newTargetId)); if (shouldMerge) { // 合并到当前组 groupItems.push(notification); } else { // 不满足合并条件,处理当前组 // 如果只有一个通知,直接添加原通知 if (groupItems.length === 1) { result.push(groupItems[0]); } else { // 将items添加到第一个通知对象,并将其添加到结果中 // 使用Object.assign创建浅拷贝,避免修改原对象 const groupToAdd = Object.assign({}, currentGroup, { items: groupItems }); result.push(groupToAdd); } // 开始新的分组 currentGroup = notification; groupItems = [notification]; } }); // 处理最后一个分组(如果有) if (currentGroup) { // 如果只有一个通知,直接添加原通知 if (groupItems.length === 1) { result.push(groupItems[0]); } else { // 将items添加到第一个通知对象,并将其添加到结果中 // 使用Object.assign创建浅拷贝,避免修改原对象 const groupToAdd = Object.assign({}, currentGroup, { items: groupItems }); result.push(groupToAdd); } } return result; }; /** * 判断通知是否包含activity * @param {Notification} notification - 通知对象 * @returns {boolean} - 是否包含activity */ export const isIncludeActivity = (notification: Notification): boolean => { return !isEmpty(notification.activity) && !!notification.activity?.type && !!notification.activity?.actor; }; /** * 是否可以自动已读 */ export const canAutoRead = (notification: Notification | null | undefined): boolean => { const { severity } = notification || {}; return !!severity && ['normal', 'success', 'info'].includes(severity); }; /** * 获取 activity 的链接 * 链接的来源有两种 * 1. activity.meta.id * 2. activity.target.id * @param {Activity} activity - 活动对象 * @returns {Object | null} - 活动的链接 */ export const getActivityLink = ( activity: Activity | null | undefined ): { metaLink?: string | null; targetLink?: string | null } | null => { if (!activity) { return null; } const { meta, target } = activity; return { metaLink: meta?.id, targetLink: target?.id, }; }; const getUserProfileUrl = async (did: string, locale: string) => { try { const client = getBlockletSDK(); const profileUrl = await client.user.getProfileUrl({ did, locale }); return profileUrl; } catch (error) { console.error(error); return undefined; } }; const getLink = async (item: any, locale: string) => { const { type, did } = item; if (isSameAddr(type, 'did')) { const profileUrl = await getUserProfileUrl(did, locale); return profileUrl || getLinkUtil(item); } return getLinkUtil(item); }; export const toClickableSpan = async (str: string, locale: string, isHighLight = true) => { const textList = toTextList(str); const result = await Promise.all( textList.map(async (item: any) => { if (item instanceof Link) { if (isHighLight) { const url = await getLink(item, locale); const { type, chainId, did } = item; // HACK: 邮件中无法支持 dapp 的展示,缺少 dapp 链接,只能作为加粗展示 if (isSameAddr(type, 'dapp')) { return `<em class="dapp" data-type="${type}" data-chain-id="${chainId}" data-did="${did}">${item.text}</em>`; } if (url) { return `<a target="_blank" rel="noopener noreferrer" class="link" href="${url}">${item.text}</a>`; } // 默认展示为加粗 return `<em class="common" data-type="${type}" data-chain-id="${chainId}" data-did="${did}">${item.text}</em>`; } return item.text; } return item; }) ).then((results) => results.join('')); return result; }; export const sanitize = (innerHtml: string) => { return DOMPurify.sanitize(innerHtml, { ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'id', 'style'], }); };