Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3510121
typeahead-utils.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Referenced Files
None
Subscribers
None
typeahead-utils.js
View Options
// @flow
import
*
as
React
from
'react'
;
import
{
oldValidUsernameRegexString
}
from
'lib/shared/account-utils'
;
import
{
stringForUserExplicit
}
from
'lib/shared/user-utils'
;
import
type
{
RelativeMemberInfo
}
from
'lib/types/thread-types'
;
import
{
typeaheadStyle
}
from
'../chat/chat-constants'
;
import
css
from
'../chat/typeahead-tooltip.css'
;
import
Button
from
'../components/button.react'
;
const
webTypeaheadRegex
:
RegExp
=
new
RegExp
(
`(?<textPrefix>(?:^(?:.|
\
n)*\\s+)|^)@(?<username>
${
oldValidUsernameRegexString
}
)?$`
,
);
export
type
TypeaheadTooltipAction
=
{
+
key
:
string
,
+
onClick
:
(
SyntheticEvent
<
HTMLButtonElement
>
)
=>
mixed
,
+
actionButtonContent
:
React
.
Node
,
};
export
type
TooltipPosition
=
{
+
top
:
number
,
+
left
:
number
,
};
function
getCaretOffsets
(
textarea
:
HTMLTextAreaElement
,
text
:
string
,
)
:
{
caretTopOffset
:
number
,
caretLeftOffset
:
number
}
{
if
(
!
textarea
)
{
return
{
caretTopOffset
:
0
,
caretLeftOffset
:
0
};
}
// terribly hacky but it works I guess :D
// we had to use it, as it's hard to count lines in textarea
// and track cursor position within it as
// lines can be wrapped into new lines without \n character
// as result of overflow
const
textareaStyle
:
CSSStyleDeclaration
=
window
.
getComputedStyle
(
textarea
,
null
,
);
const
div
=
document
.
createElement
(
'div'
);
for
(
const
styleName
of
textareaStyle
)
{
div
.
style
.
setProperty
(
styleName
,
textareaStyle
.
getPropertyValue
(
styleName
));
}
div
.
style
.
display
=
'inline-block'
;
div
.
style
.
position
=
'absolute'
;
div
.
textContent
=
text
;
const
span
=
document
.
createElement
(
'span'
);
span
.
textContent
=
textarea
.
value
.
slice
(
text
.
length
);
div
.
appendChild
(
span
);
document
.
body
?
.
appendChild
(
div
);
const
{
offsetTop
,
offsetLeft
}
=
span
;
document
.
body
?
.
removeChild
(
div
);
const
textareaWidth
=
parseInt
(
textareaStyle
.
getPropertyValue
(
'width'
));
const
caretLeftOffset
=
offsetLeft
+
typeaheadStyle
.
tooltipWidth
>
textareaWidth
?
textareaWidth
-
typeaheadStyle
.
tooltipWidth
:
offsetLeft
;
return
{
caretTopOffset
:
offsetTop
-
textarea
.
scrollTop
,
caretLeftOffset
,
};
}
export
type
GetTypeaheadTooltipActionsParams
=
{
+
inputStateDraft
:
string
,
+
inputStateSetDraft
:
(
draft
:
string
)
=>
mixed
,
+
inputStateSetTextCursorPosition
:
(
newPosition
:
number
)
=>
mixed
,
+
suggestedUsers
:
$ReadOnlyArray
<
RelativeMemberInfo
>
,
+
textBeforeAtSymbol
:
string
,
+
usernamePrefix
:
string
,
};
function
getTypeaheadTooltipActions
(
params
:
GetTypeaheadTooltipActionsParams
,
)
:
$ReadOnlyArray
<
TypeaheadTooltipAction
>
{
const
{
inputStateDraft
,
inputStateSetDraft
,
inputStateSetTextCursorPosition
,
suggestedUsers
,
textBeforeAtSymbol
,
usernamePrefix
,
}
=
params
;
return
suggestedUsers
.
filter
(
suggestedUser
=>
stringForUserExplicit
(
suggestedUser
)
!==
'anonymous'
,
)
.
map
(
suggestedUser
=>
({
key
:
suggestedUser
.
id
,
onClick
:
()
=>
{
const
newPrefixText
=
textBeforeAtSymbol
;
const
totalMatchLength
=
textBeforeAtSymbol
.
length
+
usernamePrefix
.
length
+
1
;
// 1 for @ char
let
newSuffixText
=
inputStateDraft
.
slice
(
totalMatchLength
);
newSuffixText
=
(
newSuffixText
[
0
]
!==
' '
?
' '
:
''
)
+
newSuffixText
;
const
newText
=
newPrefixText
+
'@'
+
stringForUserExplicit
(
suggestedUser
)
+
newSuffixText
;
inputStateSetDraft
(
newText
);
inputStateSetTextCursorPosition
(
newText
.
length
-
newSuffixText
.
length
+
1
,
);
},
actionButtonContent
:
stringForUserExplicit
(
suggestedUser
),
}));
}
function
getTypeaheadTooltipButtons
(
actions
:
$ReadOnlyArray
<
TypeaheadTooltipAction
>
,
)
:
$ReadOnlyArray
<
React
.
Node
>
{
return
actions
.
map
(({
key
,
onClick
,
actionButtonContent
})
=>
(
<
Button
key
=
{
key
}
onClick
=
{
onClick
}
className
=
{
css
.
suggestion
}
>
<
span
>
@
{
actionButtonContent
}
<
/span>
<
/Button>
));
}
function
getTypeaheadTooltipPosition
(
textarea
:
HTMLTextAreaElement
,
actionsLength
:
number
,
textBeforeAtSymbol
:
string
,
)
:
TooltipPosition
{
const
{
caretTopOffset
,
caretLeftOffset
}
=
getCaretOffsets
(
textarea
,
textBeforeAtSymbol
,
);
const
textareaBoundingClientRect
=
textarea
.
getBoundingClientRect
();
const
top
:
number
=
textareaBoundingClientRect
.
top
-
Math
.
min
(
typeaheadStyle
.
tooltipVerticalPadding
+
actionsLength
*
typeaheadStyle
.
rowHeight
,
typeaheadStyle
.
tooltipMaxHeight
,
)
-
typeaheadStyle
.
tooltipTopOffset
+
caretTopOffset
;
const
left
:
number
=
textareaBoundingClientRect
.
left
-
typeaheadStyle
.
tooltipLeftOffset
+
caretLeftOffset
;
return
{
top
,
left
};
}
export
{
webTypeaheadRegex
,
getCaretOffsets
,
getTypeaheadTooltipActions
,
getTypeaheadTooltipButtons
,
getTypeaheadTooltipPosition
,
};
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Mon, Dec 23, 11:52 AM (19 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2690854
Default Alt Text
typeahead-utils.js (4 KB)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment