修复回复/编辑消息触发风控弹窗时,"正在回复"预览条在弹窗结果出来前就被清空
现象:对一条历史消息点"回复",输入含链接的文本点发送,风控弹窗(安全提醒/警示级) 刚弹出的同时,composer 里"正在回复 XX"的预览条就已经消失了;如果这时候点"取消发送", 这条回复就被永久放弃——原来引用的消息内容和回复关系不再存在,也没有任何提示,唯一的 恢复办法是回到那条历史消息重新点一次"回复"。编辑消息(修改一条已发消息的内容后再次 包含链接)是同一根因的同款问题,只是编辑可以靠重新点"编辑"找回原文,没有回复那么彻底 不可逆。 根因:sendMessage() 原来在根据 this.state.reply / this.props.forwardMessage 组装好 msg/head 之后,同步、无条件地立刻调用了 this.handleCancelReply()(清空 reply/编辑 状态),这一步发生在下面的 URL 风控检查(POST /security/urls/check)之前——但风控 弹窗要等用户点"继续发送"或"取消发送"才会真正决定要不要走 doSend(),这中间的等待期里 UI 状态已经被提前清空了。 修法:把 handleCancelReply() 从三处提前调用点移到 doSend() 内部、真正调用 this.props.sendMessage(...) 之前。这样它只在真的要发送时才执行——不含链接的普通消息 里 doSend() 本来就是同步立即执行,时机与原来完全一样;命中风控的消息里,"继续发送" 时才会触发(此时清空回复状态是正确的,消息确实发出去了),"取消发送"时 doSend() 根本不会被调用,回复/编辑的预览条和引用内容都会完整保留,可以直接修改文本重试或 自己手动取消。转发场景不受影响:转发消息不会触发这条风控检查(originalText 恒为 null),移动调用时机对它是纯粹的时序等价替换。 验证:真实浏览器操作,对一条消息点"回复",输入含 https://example.com/a?token=abc123 的文本发送——警示弹窗弹出的同时预览条仍然可见;点"取消发送"后弹窗消失、预览条依旧 完整保留、composer 文本不变;再次点发送、这次点"我已知晓,继续发送",消息正确以 带引用卡片的回复形式发出,预览条在发送成功后才清空。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cdca509183
commit
a964d51ba5
@@ -1525,7 +1525,6 @@ class MessagesView extends React.Component {
|
|||||||
// We are forwarding a message.
|
// We are forwarding a message.
|
||||||
msg = this.props.forwardMessage.msg;
|
msg = this.props.forwardMessage.msg;
|
||||||
head = this.props.forwardMessage.head;
|
head = this.props.forwardMessage.head;
|
||||||
this.handleCancelReply();
|
|
||||||
} else if (this.state.reply) {
|
} else if (this.state.reply) {
|
||||||
if (this.state.reply.editing) {
|
if (this.state.reply.editing) {
|
||||||
if (msg == this.state.contentToEdit) {
|
if (msg == this.state.contentToEdit) {
|
||||||
@@ -1545,7 +1544,6 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
msg = Drafty.append(Drafty.sanitizeEntities(this.state.reply.content), msg);
|
msg = Drafty.append(Drafty.sanitizeEntities(this.state.reply.content), msg);
|
||||||
}
|
}
|
||||||
this.handleCancelReply();
|
|
||||||
} else if (typeof msg == 'string' && this.mentionedUsers && this.mentionedUsers.length) {
|
} else if (typeof msg == 'string' && this.mentionedUsers && this.mentionedUsers.length) {
|
||||||
// 4.2-4.3 @提醒:Drafty.parse 本来就会把纯文本里的"@word"自动识别成 MN 实体,但
|
// 4.2-4.3 @提醒:Drafty.parse 本来就会把纯文本里的"@word"自动识别成 MN 实体,但
|
||||||
// data.val 存的是匹配到的原始文字(tinode.dev.js 的 ENTITY_TYPES 表,pack: val =>
|
// data.val 存的是匹配到的原始文字(tinode.dev.js 的 ENTITY_TYPES 表,pack: val =>
|
||||||
@@ -1584,6 +1582,19 @@ class MessagesView extends React.Component {
|
|||||||
// the same backend version=0-skips-the-lock issue explained in handleComposerChange above.
|
// the same backend version=0-skips-the-lock issue explained in handleComposerChange above.
|
||||||
const draftVersionAtSend = this.state.draftVersion || 0;
|
const draftVersionAtSend = this.state.draftVersion || 0;
|
||||||
const doSend = () => {
|
const doSend = () => {
|
||||||
|
// Was called eagerly above (right after building `msg`/`head` from `this.state.reply`/
|
||||||
|
// `this.props.forwardMessage`), synchronously and unconditionally -- including on a message
|
||||||
|
// that turns out to need the URL risk dialog below, which only resolves *after* this function
|
||||||
|
// already returned. That meant clicking send on a reply/edit containing a link made the
|
||||||
|
// "回复"/编辑 preview bar vanish immediately, before the user had even seen the risk prompt;
|
||||||
|
// clicking "取消发送" then left the reply/edit permanently abandoned -- the quoted message was
|
||||||
|
// gone from the composer with no way back except manually replying again, even though the user
|
||||||
|
// only meant to cancel sending the flagged text, not the reply itself. Moving the call here
|
||||||
|
// means it only fires at the point a send is actually going to happen: immediately for the
|
||||||
|
// common non-URL case (this function runs synchronously right away, same timing as before),
|
||||||
|
// or deferred until "继续发送" for a flagged one -- and never at all if the dialog is cancelled,
|
||||||
|
// so the reply/edit state and its preview bar correctly survive a cancelled risk prompt.
|
||||||
|
this.handleCancelReply();
|
||||||
const result = this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
|
const result = this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
|
||||||
// Only a plain-text compose (not a reply/edit/forward, all of which pass a Drafty object or
|
// Only a plain-text compose (not a reply/edit/forward, all of which pass a Drafty object or
|
||||||
// reuse contentToEdit rather than the persisted draft) has a server-side draft worth clearing.
|
// reuse contentToEdit rather than the persisted draft) has a server-side draft worth clearing.
|
||||||
|
|||||||
Reference in New Issue
Block a user