@@ -32,11 +32,8 @@ import {
3232 type ElementNode ,
3333 type LexicalNode ,
3434 type SerializedLexicalNode ,
35- TextNode ,
36- type EditorConfig ,
3735 type EditorState ,
3836 type NodeKey ,
39- type SerializedTextNode ,
4037 type Spread ,
4138} from "lexical" ;
4239import {
@@ -103,7 +100,7 @@ type SerializedComposerMentionNode = Spread<
103100 type : "composer-mention" ;
104101 version : 1 ;
105102 } ,
106- SerializedTextNode
103+ SerializedLexicalNode
107104> ;
108105
109106type SerializedComposerSkillNode = Spread <
@@ -132,7 +129,40 @@ const ComposerTerminalContextActionsContext = createContext<{
132129 onRemoveTerminalContext : ( ) => { } ,
133130} ) ;
134131
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 > {
136166 __path : string ;
137167
138168 static override getType ( ) : string {
@@ -144,12 +174,12 @@ class ComposerMentionNode extends TextNode {
144174 }
145175
146176 static override importJSON ( serializedNode : SerializedComposerMentionNode ) : ComposerMentionNode {
147- return $createComposerMentionNode ( serializedNode . path ) ;
177+ return $createComposerMentionNode ( serializedNode . path ) . updateFromJSON ( serializedNode ) ;
148178 }
149179
150180 constructor ( path : string , key ?: NodeKey ) {
181+ super ( key ) ;
151182 const normalizedPath = path . startsWith ( "@" ) ? path . slice ( 1 ) : path ;
152- super ( `@${ normalizedPath } ` , key ) ;
153183 this . __path = normalizedPath ;
154184 }
155185
@@ -162,41 +192,26 @@ class ComposerMentionNode extends TextNode {
162192 } ;
163193 }
164194
165- override createDOM ( _config : EditorConfig ) : HTMLElement {
195+ override createDOM ( ) : HTMLElement {
166196 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" ;
171198 return dom ;
172199 }
173200
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 {
187202 return false ;
188203 }
189204
190- override canInsertTextAfter ( ) : false {
191- return false ;
205+ override getTextContent ( ) : string {
206+ return `@ ${ this . __path } ` ;
192207 }
193208
194- override isTextEntity ( ) : true {
209+ override isInline ( ) : true {
195210 return true ;
196211 }
197212
198- override isToken ( ) : true {
199- return true ;
213+ override decorate ( ) : ReactElement {
214+ return < ComposerMentionDecorator path = { this . __path } /> ;
200215 }
201216}
202217
@@ -434,28 +449,6 @@ function resolvedThemeFromDocument(): "light" | "dark" {
434449 return document . documentElement . classList . contains ( "dark" ) ? "dark" : "light" ;
435450}
436451
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- container . title = pathValue ;
442- container . setAttribute ( "aria-label" , pathValue ) ;
443-
444- const theme = resolvedThemeFromDocument ( ) ;
445- const icon = document . createElement ( "img" ) ;
446- icon . alt = "" ;
447- icon . ariaHidden = "true" ;
448- icon . className = COMPOSER_INLINE_CHIP_ICON_CLASS_NAME ;
449- icon . loading = "lazy" ;
450- icon . src = getVscodeIconUrlForEntry ( pathValue , inferEntryKindFromPath ( pathValue ) , theme ) ;
451-
452- const label = document . createElement ( "span" ) ;
453- label . className = COMPOSER_INLINE_CHIP_LABEL_CLASS_NAME ;
454- label . textContent = basenameOfPath ( pathValue ) ;
455-
456- container . append ( icon , label ) ;
457- }
458-
459452function terminalContextSignature ( contexts : ReadonlyArray < TerminalContextDraft > ) : string {
460453 return contexts
461454 . map ( ( context ) =>
@@ -597,12 +590,9 @@ function getAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: number): numb
597590 }
598591
599592 if ( $isTextNode ( node ) ) {
600- if ( node instanceof ComposerMentionNode ) {
601- return getAbsoluteOffsetForInlineTokenPoint ( node , offset , pointOffset ) ;
602- }
603593 return offset + Math . min ( pointOffset , node . getTextContentSize ( ) ) ;
604594 }
605- if ( node instanceof ComposerSkillNode || node instanceof ComposerTerminalContextNode ) {
595+ if ( isComposerInlineTokenNode ( node ) ) {
606596 return getAbsoluteOffsetForInlineTokenPoint ( node , offset , pointOffset ) ;
607597 }
608598
@@ -644,12 +634,9 @@ function getExpandedAbsoluteOffsetForPoint(node: LexicalNode, pointOffset: numbe
644634 }
645635
646636 if ( $isTextNode ( node ) ) {
647- if ( node instanceof ComposerMentionNode ) {
648- return getExpandedAbsoluteOffsetForInlineTokenPoint ( node , offset , pointOffset ) ;
649- }
650637 return offset + Math . min ( pointOffset , node . getTextContentSize ( ) ) ;
651638 }
652- if ( node instanceof ComposerSkillNode || node instanceof ComposerTerminalContextNode ) {
639+ if ( isComposerInlineTokenNode ( node ) ) {
653640 return getExpandedAbsoluteOffsetForInlineTokenPoint ( node , offset , pointOffset ) ;
654641 }
655642
@@ -675,10 +662,7 @@ function findSelectionPointAtOffset(
675662 node : LexicalNode ,
676663 remainingRef : { value : number } ,
677664) : { key : string ; offset : number ; type : "text" | "element" } | null {
678- if ( node instanceof ComposerMentionNode || node instanceof ComposerSkillNode ) {
679- return findSelectionPointForInlineToken ( node , remainingRef ) ;
680- }
681- if ( node instanceof ComposerTerminalContextNode ) {
665+ if ( isComposerInlineTokenNode ( node ) ) {
682666 return findSelectionPointForInlineToken ( node , remainingRef ) ;
683667 }
684668
0 commit comments