Unified · Split
| @@ -32,11 +32,8 @@ import { · 31 unmodified lines | ||
| 32 | 32 | type ElementNode, |
| 33 | 33 | type LexicalNode, |
| 34 | 34 | type SerializedLexicalNode, |
| 35 | − TextNode, | |
| 36 | − type EditorConfig, | |
| 37 | 35 | type EditorState, |
| 38 | 36 | type NodeKey, |
| 39 | − type SerializedTextNode, | |
| 40 | 37 | type Spread, |
| 41 | 38 | } from "lexical"; |
| 42 | 39 | import { |
| @@ -103,7 +100,7 @@ type SerializedComposerMentionNode = Spread< · 60 unmodified lines | ||
| 103 | 100 | type: "composer-mention"; |
| 104 | 101 | version: 1; |
| 105 | 102 | }, |
| 106 | − SerializedTextNode | |
| 103 | + SerializedLexicalNode | |
| 107 | 104 | >; |
| 108 | 105 | |
| 109 | 106 | type SerializedComposerSkillNode = Spread< |
| @@ -132,7 +129,40 @@ const ComposerTerminalContextActionsContext = createContext<{ · 22 unmodified lines | ||
| 132 | 129 | onRemoveTerminalContext: () => {}, |
| 133 | 130 | }); |
| 134 | 131 | |
| 135 | −class ComposerMentionNode extends TextNode { | |
| 132 | +function ComposerMentionDecorator(props: { path: string }) { | |
| 133 | + const theme = resolvedThemeFromDocument(); | |
| 134 | + const chip = ( | |
| 135 | + <span | |
| 136 | + className={COMPOSER_INLINE_CHIP_CLASS_NAME} | |
| 137 | + contentEditable={false} | |
| 138 | + spellCheck={false} | |
| 139 | + data-composer-mention-chip="true" | |
| 140 | + > | |
| 141 | + <img | |
| 142 | + alt="" | |
| 143 | + aria-hidden="true" | |
| 144 | + className={COMPOSER_INLINE_CHIP_ICON_CLASS_NAME} | |
| 145 | + loading="lazy" | |
| 146 | + src={getVscodeIconUrlForEntry(props.path, inferEntryKindFromPath(props.path), theme)} | |
| 147 | + /> | |
| 148 | + <span className={COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME}>{basenameOfPath(props.path)}</span> | |
| 149 | + </span> | |
| 150 | + ); | |
| 151 | + | |
| 152 | + return ( | |
| 153 | + <Tooltip> | |
| 154 | + <TooltipTrigger render={chip} /> | |
| 155 | + <TooltipPopup | |
| 156 | + side="top" | |
| 157 | + className="max-w-[30rem] whitespace-normal leading-tight wrap-anywhere" | |
| 158 | + > | |
| 159 | + {props.path} | |
| 160 | + </TooltipPopup> | |
| 161 | + </Tooltip> | |
| 162 | + ); | |
| 163 | +} | |
| 164 | + | |
| 165 | +class ComposerMentionNode extends DecoratorNode<ReactElement> { | |
| 136 | 166 | __path: string; |
| 137 | 167 | |
| 138 | 168 | static override getType(): string { |
| @@ -144,12 +174,12 @@ class ComposerMentionNode extends TextNode { · 5 unmodified lines | ||
| 144 | 174 | } |
| 145 | 175 | |
| 146 | 176 | static override importJSON(serializedNode: SerializedComposerMentionNode): ComposerMentionNode { |
| 147 | − return $createComposerMentionNode(serializedNode.path); | |
| 177 | + return $createComposerMentionNode(serializedNode.path).updateFromJSON(serializedNode); | |
| 148 | 178 | } |
| 149 | 179 | |
| 150 | 180 | constructor(path: string, key?: NodeKey) { |
| 181 | + super(key); | |
| 151 | 182 | const normalizedPath = path.startsWith("@") ? path.slice(1) : path; |
| 152 | − super(`@${normalizedPath}`, key); | |
| 153 | 183 | this.__path = normalizedPath; |
| 154 | 184 | } |
| 155 | 185 | |
| @@ -162,41 +192,26 @@ class ComposerMentionNode extends TextNode { · 6 unmodified lines | ||
| 162 | 192 | }; |
| 163 | 193 | } |
| 164 | 194 | |
| 165 | − override createDOM(_config: EditorConfig): HTMLElement { | |
| 195 | + override createDOM(): HTMLElement { | |
| 166 | 196 | const dom = document.createElement("span"); |
| 167 | − dom.className = COMPOSER_INLINE_CHIP_CLASS_NAME; | |
| 168 | − dom.contentEditable = "false"; | |
| 169 | − dom.setAttribute("spellcheck", "false"); | |
| 170 | − renderMentionChipDom(dom, this.__path); | |
| 197 | + dom.className = "inline-flex align-middle leading-none"; | |
| 171 | 198 | return dom; |
| 172 | 199 | } |
| 173 | 200 | |
| 174 | − override updateDOM( | |
| 175 | − prevNode: ComposerMentionNode, | |
| 176 | − dom: HTMLElement, | |
| 177 | − _config: EditorConfig, | |
| 178 | − ): boolean { | |
| 179 | − dom.contentEditable = "false"; | |
| 180 | − if (prevNode.__text !== this.__text || prevNode.__path !== this.__path) { | |
| 181 | − renderMentionChipDom(dom, this.__path); | |
| 182 | − } | |
| 183 | − return false; | |
| 184 | − } | |
| 185 | − | |
| 186 | − override canInsertTextBefore(): false { | |
| 201 | + override updateDOM(): false { | |
| 187 | 202 | return false; |
| 188 | 203 | } |
| 189 | 204 | |
| 190 | − override canInsertTextAfter(): false { | |
| 191 | − return false; | |
| 205 | + override getTextContent(): string { | |
| 206 | + return `@${this.__path}`; | |
| 192 | 207 | } |
| 193 | 208 | |
| 194 | − override isTextEntity(): true { | |
| 209 | + override isInline(): true { | |
| 195 | 210 | return true; |
| 196 | 211 | } |
| 197 | 212 | |
| 198 | − override isToken(): true { | |
| 199 | − return true; | |
| 213 | + override decorate(): ReactElement { | |
| 214 | + return <ComposerMentionDecorator path={this.__path} />; | |
| 200 | 215 | } |
| 201 | 216 | } |
| 202 | 217 | |
| @@ -434,26 +449,6 @@ function resolvedThemeFromDocument(): "light" | "dark" { · 231 unmodified lines | ||
| 434 | 449 | return document.documentElement.classList.contains("dark") ? "dark" : "light"; |
| 435 | 450 | } |
| 436 | 451 | |
| 437 | −function renderMentionChipDom(container: HTMLElement, pathValue: string): void { | |
| 438 | − container.textContent = ""; | |
| 439 | − container.style.setProperty("user-select", "none"); | |
| 440 | − container.style.setProperty("-webkit-user-select", "none"); | |
| 441 | − | |
| 442 | − const theme = resolvedThemeFromDocument(); | |
| 443 | − const icon = document.createElement("img"); | |
| 444 | − icon.alt = ""; | |
| 445 | − icon.ariaHidden = "true"; | |
| 446 | − icon.className = COMPOSER_INLINE_CHIP_ICON_CLASS_NAME; | |
| 447 | − icon.loading = "lazy"; | |
| 448 | − icon.src = getVscodeIconUrlForEntry(pathValue, inferEntryKindFromPath(pathValue), theme); | |
| 449 | − | |
| 450 | − const label = document.createElement("span"); | |
| 451 | − label.className = COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME; | |
| 452 | − label.textContent = basenameOfPath(pathValue); | |
| 453 | − | |
| 454 | − container.append(icon, label); | |
| 455 | −} | |
| 456 | − | |
| 457 | 452 | function terminalContextSignature(contexts: ReadonlyArray<TerminalContextDraft>): string { |
| 458 | 453 | return contexts |
| 459 | 454 | .map((context) => |
| @@ -595,12 +590,9 @@ function getAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: number): numb · 135 unmodified lines | ||
| 595 | 590 | } |
| 596 | 591 | |
| 597 | 592 | if ($isTextNode(node)) { |
| 598 | − if (node instanceof ComposerMentionNode) { | |
| 599 | − return getAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); | |
| 600 | − } | |
| 601 | 593 | return offset + Math.min(pointOffset, node.getTextContentSize()); |
| 602 | 594 | } |
| 603 | − if (node instanceof ComposerSkillNode || node instanceof ComposerTerminalContextNode) { | |
| 595 | + if (isComposerInlineTokenNode(node)) { | |
| 604 | 596 | return getAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); |
| 605 | 597 | } |
| 606 | 598 | |
| @@ -642,12 +634,9 @@ function getExpandedAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: numbe · 35 unmodified lines | ||
| 642 | 634 | } |
| 643 | 635 | |
| 644 | 636 | if ($isTextNode(node)) { |
| 645 | − if (node instanceof ComposerMentionNode) { | |
| 646 | − return getExpandedAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); | |
| 647 | − } | |
| 648 | 637 | return offset + Math.min(pointOffset, node.getTextContentSize()); |
| 649 | 638 | } |
| 650 | − if (node instanceof ComposerSkillNode || node instanceof ComposerTerminalContextNode) { | |
| 639 | + if (isComposerInlineTokenNode(node)) { | |
| 651 | 640 | return getExpandedAbsoluteOffsetForInlineTokenPoint(node, offset, pointOffset); |
| 652 | 641 | } |
| 653 | 642 | |
| @@ -673,10 +662,7 @@ function findSelectionPointAtOffset( · 19 unmodified lines | ||
| 673 | 662 | node: LexicalNode, |
| 674 | 663 | remainingRef: { value: number }, |
| 675 | 664 | ): { key: string; offset: number; type: "text" | "element" } | null { |
| 676 | − if (node instanceof ComposerMentionNode || node instanceof ComposerSkillNode) { | |
| 677 | − return findSelectionPointForInlineToken(node, remainingRef); | |
| 678 | − } | |
| 679 | − if (node instanceof ComposerTerminalContextNode) { | |
| 665 | + if (isComposerInlineTokenNode(node)) { | |
| 680 | 666 | return findSelectionPointForInlineToken(node, remainingRef); |
| 681 | 667 | } |
| 682 | 668 | |