botframework Webchat React 中的建议列表问题

Issues with suggestion list in botframework Webchat React

我刚刚使用 react.js 在我的 bot-framework 网络聊天 (v-4) 中添加了 autosuggestion/autocomplete 功能。但是有些问题我需要解决;

1.) 在收到建议时,我想在生成的建议列表中将我在网络聊天中输入的字词设为粗体。我这样做了,但我现在面临的问题是它只能将第一个字母设为粗体(如图所示)我想将其设为粗体,即使它在句子中也是如此。

2.) 当我 select 从建议列表中选择一个选项时,它必须被关闭。它会关闭其他选项,但 selected 选项不会。(如图所示)。我也想关闭它。

3.) 我想为建议列表中的 selecting 选项制作 up/down 箭头。

请在下面的链接中找到图片,

对于你的第一个问题,可能有两种方法可以做到这一点。要以 React 的方式做到这一点,您可以使用 indexOf 在建议中查找用户文本的索引,然后将文本拆分为多个 React 元素,其中一个以粗体显示。如果您想像现在一样使用 replace 那么这可能是使用 dangerouslySetInnerHTML:

的好机会
<div className="SuggestionParent" id="Suggestion1">
  {this.state.suggestions.map(suggestion => (
    <div className="Suggestion" onClick={this.handleSuggestionClick} >
      <div dangerouslySetInnerHTML={this.getSuggestionHtml(suggestion)} />
    </div>
  ))}
</div>

"dangerous" 警告是因为您需要确保您不允许用户提供任何可以进入内部 HTML 的潜在值,否则他们可能会注入脚本标签。只要您的建议是从固定数据库中提取的并且数据是安全的,那么您就可以了。否则,您将不得不清理 HTML,在这种情况下,根本不使用 dangerouslySetInnerHTML 可能会更容易。如果我们确实设置了内部 HTML,那么我们可以使用 replace 直接将 HTML 标记应用于字符串:

getSuggestionHtml(suggestion) {
  const lowerCaseSuggestion = suggestion.toLowerCase();
  return {
    __html: lowerCaseSuggestion.includes(this.state.suggestionTypedText) ? lowerCaseSuggestion
      .replace(this.state.suggestionTypedText, `<b>${this.state.suggestionTypedText}</b>`) : lowerCaseSuggestion
  };
}

关于你的第二个问题,你说你已经解决了。我可以看到您正在使用布尔开关暂时关闭您对 WEB_CHAT/SET_SEND_BOX 操作的响应方式。

对于你的第三个问题,在确定你的 UI 将如何工作时,你必须考虑很多设计注意事项,比如 "What happens if the user mouses over the suggestions while they're using the arrow keys?" 和 "Should the highlighted suggestion be previewed in the send box before the user presses enter?" 我希望找到一个预先存在的 React 自动完成组件,您可以使用它而不是构建自己的组件,因为它已经解决了所有这些潜在的陷阱。不幸的是,两个著名的 React 自动完成包 (here and here) 都有两个相同的问题:

  1. 它们目前没有得到维护
  2. 目标输入包含在组件中,因此您无需将组件连接到预先存在的输入。

但是,它们都是开源的,因此我们可以仿照它们构建我们自己的自动完成功能。我将引导您完成基本功能,您可以根据需要对其进行扩展。

键盘事件通常在 React 中使用 onKeyDown 属性 处理。我已将其放置在包含网络聊天和您的建议的元素上 parent:

<div className={ROOT_CSS} onKeyDown={this.handleKeyDown.bind(this)}>
  <div className={WEB_CHAT_CSS + ''}>
    <ReactWebChat

这将处理所有按键操作,因此您需要一种方法来路由到正确键的函数。您可以使用 switch 语句,但 react-autocomplete 的源代码使用查找对象,我认为这很聪明。

keyDownHandlers = {
  ArrowDown(event) {
    this.moveHighlight(event, 1);
  },
  ArrowUp(event) {
    this.moveHighlight(event, -1);
  },
  Enter(event) {
    const {suggestions} = this.state;
    if (!suggestions.length) {
      // menu is closed so there is no selection to accept -> do nothing
      return
    }
    event.preventDefault()
    this.applySuggestion(suggestions[this.state.highlightedIndex]);
  },
}

handleKeyDown(event) {
  if (this.keyDownHandlers[event.key])
  this.keyDownHandlers[event.key].call(this, event)
}

我已将向上和向下箭头的功能集中到一个函数中:moveHighlight。您将需要在您的状态中定义一个新的 属性 以跟踪键盘选择了哪个建议。我从 react-autocomplete 中保留名称 highlightedIndex

moveHighlight(event, direction) {
  event.preventDefault();
  const { highlightedIndex, suggestions } = this.state;
  if (!suggestions.length) return;
  let newIndex = (highlightedIndex + direction + suggestions.length) % suggestions.length;
  if (newIndex !== highlightedIndex) {
    this.setState({
      highlightedIndex: newIndex,
    });
  }
}

对于应用建议的回车键,您需要集中您的功能,以便它的工作方式与鼠标单击相同。

async handleSuggestionClick(event) {
  await this.applySuggestion(event.currentTarget.textContent);
}

async applySuggestion(newValue) {
  await this.setState({ typingChecking: "false", suggestions: [], highlightedIndex: 0 });
  this.state.suggestionCallback.dispatch({
    type: 'WEB_CHAT/SET_SEND_BOX',
    payload: {
      text: newValue,
    }
  });
  await this.setState({ typingChecking: "true" });
}

最后,确保 highlightedIndex 属性 用于以不同方式呈现突出显示的索引。

getSuggestionCss(index) {
  return index === this.state.highlightedIndex ? HIGHLIGHTED_CSS : SUGGESTION_CSS;
}

. . .

<div className="SuggestionParent" id="Suggestion1">
  {this.state.suggestions.map((suggestion, index) => (
    <div className={this.getSuggestionCss(index)} key={index} onClick={this.handleSuggestionClick} >
      <div dangerouslySetInnerHTML={this.getSuggestionHtml(suggestion)} />
    </div>
  ))}
</div>