diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx index fe446595..eb48a1ab 100644 --- a/src/views/messages-view.jsx +++ b/src/views/messages-view.jsx @@ -288,6 +288,45 @@ class MessagesView extends React.Component { this.componentSetup({}, {}); + // Ch.7: jump to a message found via chat-history search/media grid (content-library-view.jsx's + // History) -- the componentDidUpdate check a few lines below only fires on a *change* to + // jumpToSeq while this component stays mounted, but tinode-web.jsx never renders MessagesView + // and a Ch.7 side panel at the same time (`!chapterSevenActive` gates it) -- every jump from + // History's search results/image grid/video grid therefore unmounts the Ch.7 view and mounts a + // *brand new* MessagesView instance with jumpToSeq already set on its very first render, which + // componentDidUpdate never sees (it doesn't run for the initial mount). Confirmed live: the + // seq stayed in the URL indefinitely (jumpToMessage's own `removeUrlParam` call never fired) + // and the conversation opened at the bottom instead of scrolling to the found message -- + // 100% reproducible for every Ch.7 "定位到消息来源" entry point, not an intermittent timing issue. + if (this.props.jumpToSeq) { + const seq = parseInt(this.props.jumpToSeq, 10); + const topicObj = this.props.tinode && this.props.topic ? this.props.tinode.getTopic(this.props.topic) : null; + const attemptJump = () => this.jumpToMessage(seq); + // A first-ever mount also means `componentSetup()` just above only *started* subscribing + // this topic (subscribe() -> topic.subscribe() is async, fire-and-forget from here) -- + // jumpToMessage's own topic.getMeta() call requires an already-established subscription, + // and firing immediately races ahead of it: confirmed live, this raced getMeta() call came + // back as a real `{"code":403,"text":"permission denied"}` control message (not a local + // guard, an actual rejected server round trip) while the real subscribe's own request was + // still in flight one id apart. Poll the already-exposed isSubscribed() flag rather than + // guessing a fixed delay or reworking subscribe()'s fire-and-forget call sites to expose + // their promise; capped at 5s so a genuinely stuck subscribe still falls through to + // jumpToMessage's own existing error handling instead of waiting forever. + if (topicObj && !topicObj.isSubscribed()) { + let tries = 0; + const waitForSubscription = () => { + if (topicObj.isSubscribed() || ++tries > 25) { + attemptJump(); + } else { + window.setTimeout(waitForSubscription, 200); + } + }; + waitForSubscription(); + } else { + attemptJump(); + } + } + getBootstrap().then(data => { this.setState({messagePolicy: data.message_policy || null}); }).catch(_ => {});