@@ -55,6 +55,14 @@ const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.sourc
5555
5656const IS_MAC = navigator . platform . indexOf ( "Mac" ) !== - 1 ;
5757
58+ const SURROUND_WITH_CHARACTERS = [ "\"" , "_" , "`" , "'" , "*" , "~" , "$" ] ;
59+ const SURROUND_WITH_DOUBLE_CHARACTERS = new Map ( [
60+ [ "(" , ")" ] ,
61+ [ "[" , "]" ] ,
62+ [ "{" , "}" ] ,
63+ [ "<" , ">" ] ,
64+ ] ) ;
65+
5866function ctrlShortcutLabel ( key : string ) : string {
5967 return ( IS_MAC ? "⌘" : "Ctrl" ) + "+" + key ;
6068}
@@ -99,6 +107,7 @@ interface IState {
99107 showVisualBell ?: boolean ;
100108 autoComplete ?: AutocompleteWrapperModel ;
101109 completionIndex ?: number ;
110+ surroundWith : boolean ;
102111}
103112
104113@replaceableComponent ( "views.rooms.BasicMessageEditor" )
@@ -117,19 +126,23 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
117126
118127 private readonly emoticonSettingHandle : string ;
119128 private readonly shouldShowPillAvatarSettingHandle : string ;
129+ private readonly surroundWithHandle : string ;
120130 private readonly historyManager = new HistoryManager ( ) ;
121131
122132 constructor ( props ) {
123133 super ( props ) ;
124134 this . state = {
125135 showPillAvatar : SettingsStore . getValue ( "Pill.shouldShowPillAvatar" ) ,
136+ surroundWith : SettingsStore . getValue ( "MessageComposerInput.surroundWith" ) ,
126137 } ;
127138
128139 this . emoticonSettingHandle = SettingsStore . watchSetting ( 'MessageComposerInput.autoReplaceEmoji' , null ,
129140 this . configureEmoticonAutoReplace ) ;
130141 this . configureEmoticonAutoReplace ( ) ;
131142 this . shouldShowPillAvatarSettingHandle = SettingsStore . watchSetting ( "Pill.shouldShowPillAvatar" , null ,
132143 this . configureShouldShowPillAvatar ) ;
144+ this . surroundWithHandle = SettingsStore . watchSetting ( "MessageComposerInput.surroundWith" , null ,
145+ this . surroundWithSettingChanged ) ;
133146 }
134147
135148 public componentDidUpdate ( prevProps : IProps ) {
@@ -422,6 +435,28 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
422435 private onKeyDown = ( event : React . KeyboardEvent ) : void => {
423436 const model = this . props . model ;
424437 let handled = false ;
438+
439+ if ( this . state . surroundWith && document . getSelection ( ) . type != "Caret" ) {
440+ // This surrounds the selected text with a character. This is
441+ // intentionally left out of the keybinding manager as the keybinds
442+ // here shouldn't be changeable
443+
444+ const selectionRange = getRangeForSelection (
445+ this . editorRef . current ,
446+ this . props . model ,
447+ document . getSelection ( ) ,
448+ ) ;
449+ // trim the range as we want it to exclude leading/trailing spaces
450+ selectionRange . trim ( ) ;
451+
452+ if ( [ ...SURROUND_WITH_DOUBLE_CHARACTERS . keys ( ) , ...SURROUND_WITH_CHARACTERS ] . includes ( event . key ) ) {
453+ this . historyManager . ensureLastChangesPushed ( this . props . model ) ;
454+ this . modifiedFlag = true ;
455+ toggleInlineFormat ( selectionRange , event . key , SURROUND_WITH_DOUBLE_CHARACTERS . get ( event . key ) ) ;
456+ handled = true ;
457+ }
458+ }
459+
425460 const action = getKeyBindingsManager ( ) . getMessageComposerAction ( event ) ;
426461 switch ( action ) {
427462 case MessageComposerAction . FormatBold :
@@ -574,13 +609,19 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
574609 this . setState ( { showPillAvatar } ) ;
575610 } ;
576611
612+ private surroundWithSettingChanged = ( ) => {
613+ const surroundWith = SettingsStore . getValue ( "MessageComposerInput.surroundWith" ) ;
614+ this . setState ( { surroundWith } ) ;
615+ } ;
616+
577617 componentWillUnmount ( ) {
578618 document . removeEventListener ( "selectionchange" , this . onSelectionChange ) ;
579619 this . editorRef . current . removeEventListener ( "input" , this . onInput , true ) ;
580620 this . editorRef . current . removeEventListener ( "compositionstart" , this . onCompositionStart , true ) ;
581621 this . editorRef . current . removeEventListener ( "compositionend" , this . onCompositionEnd , true ) ;
582622 SettingsStore . unwatchSetting ( this . emoticonSettingHandle ) ;
583623 SettingsStore . unwatchSetting ( this . shouldShowPillAvatarSettingHandle ) ;
624+ SettingsStore . unwatchSetting ( this . surroundWithHandle ) ;
584625 }
585626
586627 componentDidMount ( ) {
0 commit comments