import { Dropdown, Dialog, dialog } from "mdui" import { $ } from "mdui/jq" import Client from "../../api/Client.ts" import Data_Message from "../../api/client_data/Message.ts" import DataCaches from "../../api/DataCaches.ts" import Avatar from "../Avatar.tsx" import copyToClipboard from "../copyToClipboard.ts" import useAsyncEffect from "../useAsyncEffect.ts" import useEventListener from "../useEventListener.ts" import React from "react" import isMobileUI from "../isMobileUI.ts" import User from "../../api/client_data/User.ts" import getUrlForFileByHash from "../../getUrlForFileByHash.ts" import escapeHTML from "../../escapeHtml.ts" function prettyFlatParsedMessage(html: string) { const elements = new DOMParser().parseFromString(html, 'text/html').body.children // 纯文本直接处理 if (elements.length == 0) return `${escapeHTML(html)}` let ls: Element[] = [] let ret = '' // 第一个元素时, 不会被聚合在一起 let lastElementType = '' const textElementTags = [ 'chat-text', 'chat-mention', 'chat-quote', ] function checkContinuousElement(tagName: string) { /* console.log('shangyige ', lastElementType) console.log("dangqian", tagName) console.log("上一个元素的类型和当前不一致?", lastElementType != tagName) console.log("上一个元素的类型和这个元素的类型都属于文本类型", (textElementTags.indexOf(lastElementType) != -1 && textElementTags.indexOf(tagName) != -1)) */ // 如果上一个元素的类型和当前不一致, 或者上一个元素的类型和这个元素的类型都属于文本类型 (亦或者到最后一步时) 执行 if ((lastElementType != tagName || (textElementTags.indexOf(lastElementType) != -1 && textElementTags.indexOf(tagName) != -1)) || tagName == 'LAST_CHICKEN') { /* console.log(tagName, '进入') */ // 如果上一个元素类型为文本类型, 且当前不是文本类型时, 用文本包裹 if (textElementTags.indexOf(lastElementType) != -1) { // 当前的文本类型不应该和上一个分离, 滚出去 if (textElementTags.indexOf(tagName) != -1) return /* console.log(tagName, '文字和被') */ // 由于 chat-mention 不是用内部元素实现的, 因此在这个元素的生成中必须放置占位字符串 // 尽管显示上占位字符串不会显示, 但是在这里依然是会被处理的, 因为本身还是 innerHTML // 当文本非空时, 将文字合并在一起 if (ls.map((v) => v.innerHTML).join('').trim() != '') ret += `${ls.map((v) => v.outerHTML).join('')}` } else // 非文本类型元素, 各自成块 ret += ls.map((v) => v.outerHTML).join('') ls = [] } } for (const e of elements) { // 当出现非文本元素时, 将文本聚合在一起 // 如果是其他类型, 虽然也执行聚合, 但是不会有外层包裹 /* console.log("当前", e, "内容", e.innerHTML) */ checkContinuousElement(e.nodeName.toLowerCase()) ls.push(e) lastElementType = e.nodeName.toLowerCase() } // 最后将剩余的转换 checkContinuousElement('LAST_CHICKEN') return ret } interface Args extends React.HTMLAttributes { userId: string noUserDisplay?: boolean rawData: string renderHTML: string message: Data_Message openUserInfoDialog: (user: User | string) => void } export default function Message({ userId, rawData, renderHTML, message, openUserInfoDialog, noUserDisplay, ...props }: Args) { const isAtRight = Client.myUserProfile?.id == userId const [nickName, setNickName] = React.useState("") const [avatarUrl, setAvatarUrl] = React.useState("") useAsyncEffect(async () => { const user = await DataCaches.getUserProfile(userId) setNickName(user.nickname) setAvatarUrl(getUrlForFileByHash(user?.avatar_file_hash)) }, [userId]) const dropDownRef = React.useRef(null) useEventListener(dropDownRef, 'closed', () => { setDropDownOpen(false) }) const [isDropDownOpen, setDropDownOpen] = React.useState(false) /* const [isUsingFullDisplay, setIsUsingFullDisplay] = React.useState(false) */ /* React.useEffect(() => { const text = $(dropDownRef.current as HTMLElement).find('#msg').text().trim() setIsUsingFullDisplay(text == '' || ( rawData.split("tws:\/\/file\?hash=").length == 2 && /\<\/chat\-(file|image|video)\>(\<\/span\>)?$/.test(renderHTML.trim()) )) }, [renderHTML]) */ return (
{ if (isMobileUI()) return e.preventDefault() setDropDownOpen(!isDropDownOpen) }} onClick={(e) => { if (!isMobileUI()) return e.preventDefault() setDropDownOpen(!isDropDownOpen) }} style={{ width: "100%", display: "flex", justifyContent: isAtRight ? "flex-end" : "flex-start", flexDirection: "column" }} {...props}>
{ // 发送者昵称(左) isAtRight && {nickName} } { // 发送者头像 } { e.stopPropagation() openUserInfoDialog(userId) }} /> { // 发送者昵称(右) !isAtRight && {nickName} }
{ e.stopPropagation() setDropDownOpen(false) }}> copyToClipboard($(dropDownRef.current as HTMLElement).find('#msg').text().trim())}>复制文字 copyToClipboard(rawData)}>复制原文 dialog({ headline: "原始数据", body: `${Object.keys(message) // @ts-ignore 懒 .map((k) => `${k} = ${message[k]}`) .join('

')}`, closeOnEsc: true, closeOnOverlayClick: true, actions: [ { text: "关闭", onClick: () => { return true }, } ] }).addEventListener('click', (e) => e.stopPropagation())}>原始数据
) }