プロンプトトリガーの複数文字対応
Lexxyのプロンプト機能で、従来の単一文字トリガー(@、#など)に加え、複数文字列(by:、group:など)をトリガーとして使用できるようになりました。
背景
これまでLexxyのプロンプト機能は、単一文字(例:@でメンション、#でタグ)をトリガーとして扱う実装になっていました。しかし、Basecamp 5のSearch機能では、より意味的な複数文字のトリガー(例:by:で作成者検索、group:でグループ検索)を使用したいという要件がありました。
技術的な変更
主な変更はLexicalPromptElementクラスのトリガー検出ロジックです。
変更前:単一文字の検出
if ($isTextNode(node) && offset > 0) {
const fullText = node.getTextContent()
const charBeforeCursor = fullText[offset - 1]
if (charBeforeCursor === this.trigger) {
const isAtStart = offset === 1
const charBeforeTrigger = offset > 1 ? fullText[offset - 2] : null
const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n"
if (isAtStart || isPrecededBySpaceOrNewline) {
unregister()
this.#showPopover()
}
}
}
変更後:複数文字列の検出
if ($isTextNode(node)) {
const fullText = node.getTextContent()
const triggerLength = this.trigger.length
if (offset >= triggerLength) {
const textBeforeCursor = fullText.slice(offset - triggerLength, offset)
if (textBeforeCursor === this.trigger) {
const isAtStart = offset === triggerLength
const charBeforeTrigger = offset > triggerLength ? fullText[offset - triggerLength - 1] : null
const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n"
if (isAtStart || isPrecededBySpaceOrNewline) {
unregister()
this.#showPopover()
}
}
}
}
主要な変更点
-
文字列長の考慮:
triggerLengthを導入し、トリガーが複数文字の場合に対応 -
部分文字列抽出:
fullText[offset - 1]からfullText.slice(offset - triggerLength, offset)に変更し、複数文字を抽出 -
オフセット計算: すべてのオフセット計算で
triggerLengthを考慮するように修正
実装例として、テストコードでは以下のように複数文字トリガーが使用されています:
<lexxy-prompt trigger="group:" src="<%= groups_path %>" name="mention">
</lexxy-prompt>
<lexxy-prompt trigger="person:" src="<%= people_path %>" name="mention" supports-space-in-searches>
</lexxy-prompt>
設計判断
この実装では、既存のトリガー検出ロジックの構造を維持しつつ、文字列長を動的に扱うことで後方互換性を保っています。単一文字トリガー(trigger="@")も引き続き動作し、triggerLength === 1として処理されます。
また、トリガーの前に空白または改行が必要という制約は維持されています。これにより、hellogroup:のような意図しないトリガーを防ぎ、hello group:のように明示的な区切りがある場合のみプロンプトが表示されます。
さらに、registerUpdateListenerの引数を({ editorState })に変更し、エディタの状態を直接読み取るようにしています。これは、Lexicalエディタのベストプラクティスに沿った変更と考えられます。