diff --git a/client-protocol/ChatAttachment.ts b/client-protocol/ChatAttachment.ts
new file mode 100644
index 0000000..c895723
--- /dev/null
+++ b/client-protocol/ChatAttachment.ts
@@ -0,0 +1,90 @@
+import { ApiCallbackMessage } from 'lingchair-internal-shared'
+import BaseClientObject from './BaseClientObject.ts'
+import CallbackError from './CallbackError.ts'
+import LingChairClient from './LingChairClient.ts'
+
+export default class ChatAttachment extends BaseClientObject {
+ declare file_hash: string
+ declare file_name: string
+ constructor(client: LingChairClient, {
+ file_hash,
+ file_name
+ }: {
+ file_hash: string,
+ file_name: string
+ }) {
+ super(client)
+ this.file_name = file_name
+ this.file_hash = file_hash
+ }
+ async blob() {
+ try {
+ return await this.blobOrThrow()
+ } catch (_) {
+ return null
+ }
+ }
+ fetch(init?: RequestInit) {
+ const url = this.client.getUrlForFileByHash(this.file_hash)
+ return fetch(url!, init)
+ }
+ async blobOrThrow() {
+ const re = await this.fetch()
+ const blob = await re.blob()
+ if (!re.ok) throw new CallbackError({
+ msg: await blob.text(),
+ code: re.status,
+ } as ApiCallbackMessage)
+ return blob
+ }
+ async getMimeType() {
+ try {
+ return await this.getMimeTypeOrThrow()
+ } catch (_) {
+ return null
+ }
+ }
+ async getMimeTypeOrThrow() {
+ const re = await this.fetch({
+ method: 'HEAD'
+ })
+ if (re.ok) {
+ const t = re.headers.get('content-type')
+ if (t)
+ return t
+ throw new Error("Unable to get Content-Type")
+ }
+ throw new CallbackError({
+ msg: await re.text(),
+ code: re.status,
+ } as ApiCallbackMessage)
+ }
+ async getLength() {
+ try {
+ return await this.getLengthOrThrow()
+ } catch (_) {
+ return null
+ }
+ }
+ async getLengthOrThrow() {
+ const re = await this.fetch({
+ method: 'HEAD'
+ })
+ if (re.ok) {
+ const contentLength = re.headers.get('content-length')
+ if (contentLength)
+ return parseInt(contentLength)
+ throw new Error("Unable to get Content-Length")
+ }
+ throw new CallbackError({
+ msg: await re.text(),
+ code: re.status,
+ } as ApiCallbackMessage)
+ }
+ getFileHash() {
+ return this.file_hash
+ }
+ getFileName() {
+ return this.file_name
+ }
+}
\ No newline at end of file
diff --git a/client-protocol/Message.ts b/client-protocol/Message.ts
index 2012f24..32122ea 100644
--- a/client-protocol/Message.ts
+++ b/client-protocol/Message.ts
@@ -3,8 +3,7 @@ import MessageBean from "./bean/MessageBean.ts"
import LingChairClient from "./LingChairClient.ts"
import Chat from "./Chat.ts"
import User from "./User.ts"
-import CallbackError from "./CallbackError.ts"
-import ApiCallbackMessage from "./ApiCallbackMessage.ts"
+import ChatAttachment from "./ChatAttachment.ts"
import * as marked from 'marked'
@@ -37,93 +36,18 @@ export class ChatMention extends BaseClientObject {
}
}
-type FileType = 'Video' | 'Image' | 'File'
-type MentionType = 'ChatMention' | 'UserMention'
+type ChatMentionType = 'ChatMention' | 'UserMention'
+type ChatFileType = 'Video' | 'Image' | 'File'
-export class ChatAttachment extends BaseClientObject {
- declare file_hash: string
- declare file_name: string
- constructor(client: LingChairClient, {
- file_hash,
- file_name
- }: {
- file_hash: string,
- file_name: string
- }) {
- super(client)
- this.file_name = file_name
- this.file_hash = file_hash
- }
- async blob() {
- try {
- return await this.blobOrThrow()
- } catch (_) {
- return null
- }
- }
- fetch(init?: RequestInit) {
- const url = this.client.getUrlForFileByHash(this.file_hash)
- return fetch(url!, init)
- }
- async blobOrThrow() {
- const re = await this.fetch()
- const blob = await re.blob()
- if (!re.ok) throw new CallbackError({
- msg: await blob.text(),
- code: re.status,
- } as ApiCallbackMessage)
- return blob
- }
- async getMimeType() {
- try {
- return await this.getMimeTypeOrThrow()
- } catch (_) {
- return null
- }
- }
- async getMimeTypeOrThrow() {
- const re = await this.fetch({
- method: 'HEAD'
- })
- if (re.ok) {
- const t = re.headers.get('content-type')
- if (t)
- return t
- throw new Error("Unable to get Content-Type")
- }
- throw new CallbackError({
- msg: await re.text(),
- code: re.status,
- } as ApiCallbackMessage)
- }
- async getLength() {
- try {
- return await this.getLengthOrThrow()
- } catch (_) {
- return null
- }
- }
- async getLengthOrThrow() {
- const re = await this.fetch({
- method: 'HEAD'
- })
- if (re.ok) {
- const contentLength = re.headers.get('content-length')
- if (contentLength)
- return parseInt(contentLength)
- throw new Error("Unable to get Content-Length")
- }
- throw new CallbackError({
- msg: await re.text(),
- code: re.status,
- } as ApiCallbackMessage)
- }
- getFileHash() {
- return this.file_hash
- }
- getFileName() {
- return this.file_name
- }
+type ChatParserTransformers = {
+ attachment?: ({ text, fileType, attachment }: { text: string, fileType: ChatFileType, attachment: ChatAttachment }) => string,
+ mention?: ({ text, mentionType, mention }: { text: string, mentionType: ChatMentionType, mention: ChatMention }) => string,
+}
+
+export type {
+ ChatMentionType,
+ ChatFileType,
+ ChatParserTransformers,
}
export default class Message extends BaseClientObject {
@@ -152,10 +76,7 @@ export default class Message extends BaseClientObject {
parseWithTransformers({
attachment,
mention,
- }: {
- attachment?: ({ text, fileType, attachment }: { text: string, fileType: FileType, attachment: ChatAttachment }) => string,
- mention?: ({ text, mentionType, mention }: { text: string, mentionType: MentionType, mention: ChatMention }) => string,
- }) {
+ }: ChatParserTransformers) {
return new marked.Marked({
async: false,
extensions: [
@@ -178,8 +99,8 @@ export default class Message extends BaseClientObject {
{
name: 'image',
renderer: ({ text, href }) => {
- const mentionType = /^(UserMention|ChatMention)=.*/.exec(text)?.[1] as MentionType
- const fileType = (/^(Video|File|Image)=.*/.exec(text)?.[1] || 'Image') as FileType
+ const mentionType = /^(UserMention|ChatMention)=.*/.exec(text)?.[1] as ChatMentionType
+ const fileType = (/^(Video|File|Image)=.*/.exec(text)?.[1] || 'Image') as ChatFileType
if (fileType != null && /tws:\/\/file\?hash=[A-Za-z0-9]+$/.test(href)) {
const file_hash = /^tws:\/\/file\?hash=(.*)/.exec(href)?.[1]!
diff --git a/client-protocol/main.ts b/client-protocol/main.ts
index 3bc5867..8a2c63e 100644
--- a/client-protocol/main.ts
+++ b/client-protocol/main.ts
@@ -7,10 +7,16 @@ import GroupSettingsBean from "./bean/GroupSettingsBean.ts"
import JoinRequestBean from "./bean/JoinRequestBean.ts"
import MessageBean from "./bean/MessageBean.ts"
import RecentChatBean from "./bean/RecentChatBean.ts"
-import Message, { ChatAttachment, ChatMention } from "./Message.ts"
+import Message, {
+ ChatMention,
+ ChatParserTransformers,
+ ChatMentionType,
+ ChatFileType,
+} from "./Message.ts"
import LingChairClient from "./LingChairClient.ts"
import CallbackError from "./CallbackError.ts"
+import ChatAttachment from "./ChatAttachment.ts"
export {
LingChairClient,
@@ -19,6 +25,7 @@ export {
Chat,
User,
UserMySelf,
+
Message,
ChatAttachment,
ChatMention,
@@ -29,4 +36,10 @@ export {
RecentChatBean,
JoinRequestBean,
}
-export type { GroupSettingsBean }
+export type {
+ ChatParserTransformers,
+ ChatMentionType,
+ ChatFileType,
+
+ GroupSettingsBean,
+}
diff --git a/client-protocol/type/ChatParserTransformers.ts b/client-protocol/type/ChatParserTransformers.ts
new file mode 100644
index 0000000..23dbc6f
--- /dev/null
+++ b/client-protocol/type/ChatParserTransformers.ts
@@ -0,0 +1,5 @@
+import ChatAttachment from '../ChatAttachment.ts'
+import { ChatMention } from '../Message.ts'
+import ChatFileType from './ChatFileType.ts'
+
+
diff --git a/client/ui/chat-fragment/ChatMessage.tsx b/client/ui/chat-fragment/ChatMessage.tsx
index c4d9104..bd10316 100644
--- a/client/ui/chat-fragment/ChatMessage.tsx
+++ b/client/ui/chat-fragment/ChatMessage.tsx
@@ -1,4 +1,4 @@
-import { Message } from "lingchair-client-protocol"
+import { ChatParserTransformers, Message } from "lingchair-client-protocol"
import isMobileUI from "../../utils/isMobileUI"
import useAsyncEffect from "../../utils/useAsyncEffect"
import ClientCache from "../../ClientCache"
@@ -94,6 +94,25 @@ const sanitizeConfig = {
],
}
+const transformers: ChatParserTransformers = {
+ attachment({ fileType, attachment }) {
+ const url = getClient().getUrlForFileByHash(attachment.getFileHash())
+ return ({
+ Image: ``,
+ Video: ``,
+ File: ``,
+ })?.[fileType]
+ },
+ mention({ mentionType, mention }) {
+ switch (mentionType) {
+ case "UserMention":
+ return `[对话提及]`
+ case "ChatMention":
+ return `[对话提及]`
+ }
+ },
+}
+
export default function ChatMessage({ message, noUserDisplay, avatarMenuItems, messageMenuItems }: { message: Message, noUserDisplay?: boolean, avatarMenuItems?: globalThis.React.JSX.IntrinsicElements['mdui-menu-item'][], messageMenuItems?: globalThis.React.JSX.IntrinsicElements['mdui-menu-item'][] }) {
const AppState = React.useContext(AppStateContext)
@@ -122,24 +141,7 @@ export default function ChatMessage({ message, noUserDisplay, avatarMenuItems, m
const messageInnerRef = React.useRef(null)
React.useEffect(() => {
- messageInnerRef.current!.innerHTML = prettyFlatParsedMessage(DOMPurify.sanitize(message.parseWithTransformers({
- attachment({ fileType, attachment }) {
- const url = getClient().getUrlForFileByHash(attachment.getFileHash())
- return ({
- Image: ``,
- Video: ``,
- File: ``,
- })?.[fileType]
- },
- mention({ mentionType, mention }) {
- switch (mentionType) {
- case "UserMention":
- return `[对话提及]`
- case "ChatMention":
- return `[对话提及]`
- }
- },
- }), sanitizeConfig))
+ messageInnerRef.current!.innerHTML = prettyFlatParsedMessage(DOMPurify.sanitize(message.parseWithTransformers(transformers), sanitizeConfig))
// 没有办法的办法 (笑)
// 姐姐, 谁让您不是 React 组件呢
@@ -170,7 +172,7 @@ export default function ChatMessage({ message, noUserDisplay, avatarMenuItems, m
flexDirection: "column"
}}>
{
-