Merge pull request #5510 from SimonBrandner/feature-surround-with
Add surround with featurepull/21833/head
						commit
						58575c335d
					
				| 
						 | 
					@ -55,6 +55,14 @@ const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.sourc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const IS_MAC = navigator.platform.indexOf("Mac") !== -1;
 | 
					const IS_MAC = navigator.platform.indexOf("Mac") !== -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SURROUND_WITH_CHARACTERS = ["\"", "_", "`", "'", "*", "~", "$"];
 | 
				
			||||||
 | 
					const SURROUND_WITH_DOUBLE_CHARACTERS = new Map([
 | 
				
			||||||
 | 
					    ["(", ")"],
 | 
				
			||||||
 | 
					    ["[", "]"],
 | 
				
			||||||
 | 
					    ["{", "}"],
 | 
				
			||||||
 | 
					    ["<", ">"],
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ctrlShortcutLabel(key: string): string {
 | 
					function ctrlShortcutLabel(key: string): string {
 | 
				
			||||||
    return (IS_MAC ? "⌘" : "Ctrl") + "+" + key;
 | 
					    return (IS_MAC ? "⌘" : "Ctrl") + "+" + key;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -99,6 +107,7 @@ interface IState {
 | 
				
			||||||
    showVisualBell?: boolean;
 | 
					    showVisualBell?: boolean;
 | 
				
			||||||
    autoComplete?: AutocompleteWrapperModel;
 | 
					    autoComplete?: AutocompleteWrapperModel;
 | 
				
			||||||
    completionIndex?: number;
 | 
					    completionIndex?: number;
 | 
				
			||||||
 | 
					    surroundWith: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@replaceableComponent("views.rooms.BasicMessageEditor")
 | 
					@replaceableComponent("views.rooms.BasicMessageEditor")
 | 
				
			||||||
| 
						 | 
					@ -117,12 +126,14 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private readonly emoticonSettingHandle: string;
 | 
					    private readonly emoticonSettingHandle: string;
 | 
				
			||||||
    private readonly shouldShowPillAvatarSettingHandle: string;
 | 
					    private readonly shouldShowPillAvatarSettingHandle: string;
 | 
				
			||||||
 | 
					    private readonly surroundWithHandle: string;
 | 
				
			||||||
    private readonly historyManager = new HistoryManager();
 | 
					    private readonly historyManager = new HistoryManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(props) {
 | 
					    constructor(props) {
 | 
				
			||||||
        super(props);
 | 
					        super(props);
 | 
				
			||||||
        this.state = {
 | 
					        this.state = {
 | 
				
			||||||
            showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"),
 | 
					            showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"),
 | 
				
			||||||
 | 
					            surroundWith: SettingsStore.getValue("MessageComposerInput.surroundWith"),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null,
 | 
					        this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null,
 | 
				
			||||||
| 
						 | 
					@ -130,6 +141,8 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
 | 
				
			||||||
        this.configureEmoticonAutoReplace();
 | 
					        this.configureEmoticonAutoReplace();
 | 
				
			||||||
        this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null,
 | 
					        this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null,
 | 
				
			||||||
            this.configureShouldShowPillAvatar);
 | 
					            this.configureShouldShowPillAvatar);
 | 
				
			||||||
 | 
					        this.surroundWithHandle = SettingsStore.watchSetting("MessageComposerInput.surroundWith", null,
 | 
				
			||||||
 | 
					            this.surroundWithSettingChanged);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public componentDidUpdate(prevProps: IProps) {
 | 
					    public componentDidUpdate(prevProps: IProps) {
 | 
				
			||||||
| 
						 | 
					@ -422,6 +435,28 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
 | 
				
			||||||
    private onKeyDown = (event: React.KeyboardEvent): void => {
 | 
					    private onKeyDown = (event: React.KeyboardEvent): void => {
 | 
				
			||||||
        const model = this.props.model;
 | 
					        const model = this.props.model;
 | 
				
			||||||
        let handled = false;
 | 
					        let handled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.state.surroundWith && document.getSelection().type != "Caret") {
 | 
				
			||||||
 | 
					            // This surrounds the selected text with a character. This is
 | 
				
			||||||
 | 
					            // intentionally left out of the keybinding manager as the keybinds
 | 
				
			||||||
 | 
					            // here shouldn't be changeable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const selectionRange = getRangeForSelection(
 | 
				
			||||||
 | 
					                this.editorRef.current,
 | 
				
			||||||
 | 
					                this.props.model,
 | 
				
			||||||
 | 
					                document.getSelection(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            // trim the range as we want it to exclude leading/trailing spaces
 | 
				
			||||||
 | 
					            selectionRange.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys(), ...SURROUND_WITH_CHARACTERS].includes(event.key)) {
 | 
				
			||||||
 | 
					                this.historyManager.ensureLastChangesPushed(this.props.model);
 | 
				
			||||||
 | 
					                this.modifiedFlag = true;
 | 
				
			||||||
 | 
					                toggleInlineFormat(selectionRange, event.key, SURROUND_WITH_DOUBLE_CHARACTERS.get(event.key));
 | 
				
			||||||
 | 
					                handled = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const action = getKeyBindingsManager().getMessageComposerAction(event);
 | 
					        const action = getKeyBindingsManager().getMessageComposerAction(event);
 | 
				
			||||||
        switch (action) {
 | 
					        switch (action) {
 | 
				
			||||||
            case MessageComposerAction.FormatBold:
 | 
					            case MessageComposerAction.FormatBold:
 | 
				
			||||||
| 
						 | 
					@ -574,6 +609,11 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
 | 
				
			||||||
        this.setState({ showPillAvatar });
 | 
					        this.setState({ showPillAvatar });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private surroundWithSettingChanged = () => {
 | 
				
			||||||
 | 
					        const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith");
 | 
				
			||||||
 | 
					        this.setState({ surroundWith });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    componentWillUnmount() {
 | 
					    componentWillUnmount() {
 | 
				
			||||||
        document.removeEventListener("selectionchange", this.onSelectionChange);
 | 
					        document.removeEventListener("selectionchange", this.onSelectionChange);
 | 
				
			||||||
        this.editorRef.current.removeEventListener("input", this.onInput, true);
 | 
					        this.editorRef.current.removeEventListener("input", this.onInput, true);
 | 
				
			||||||
| 
						 | 
					@ -581,6 +621,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
 | 
				
			||||||
        this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true);
 | 
					        this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true);
 | 
				
			||||||
        SettingsStore.unwatchSetting(this.emoticonSettingHandle);
 | 
					        SettingsStore.unwatchSetting(this.emoticonSettingHandle);
 | 
				
			||||||
        SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
 | 
					        SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
 | 
				
			||||||
 | 
					        SettingsStore.unwatchSetting(this.surroundWithHandle);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    componentDidMount() {
 | 
					    componentDidMount() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,6 +61,7 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
 | 
				
			||||||
        'MessageComposerInput.suggestEmoji',
 | 
					        'MessageComposerInput.suggestEmoji',
 | 
				
			||||||
        'sendTypingNotifications',
 | 
					        'sendTypingNotifications',
 | 
				
			||||||
        'MessageComposerInput.ctrlEnterToSend',
 | 
					        'MessageComposerInput.ctrlEnterToSend',
 | 
				
			||||||
 | 
					        'MessageComposerInput.surroundWith',
 | 
				
			||||||
        'MessageComposerInput.showStickersButton',
 | 
					        'MessageComposerInput.showStickersButton',
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -847,6 +847,7 @@
 | 
				
			||||||
    "Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline",
 | 
					    "Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline",
 | 
				
			||||||
    "Use Command + Enter to send a message": "Use Command + Enter to send a message",
 | 
					    "Use Command + Enter to send a message": "Use Command + Enter to send a message",
 | 
				
			||||||
    "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message",
 | 
					    "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message",
 | 
				
			||||||
 | 
					    "Surround selected text when typing special characters": "Surround selected text when typing special characters",
 | 
				
			||||||
    "Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
 | 
					    "Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
 | 
				
			||||||
    "Mirror local video feed": "Mirror local video feed",
 | 
					    "Mirror local video feed": "Mirror local video feed",
 | 
				
			||||||
    "Enable Community Filter Panel": "Enable Community Filter Panel",
 | 
					    "Enable Community Filter Panel": "Enable Community Filter Panel",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -449,6 +449,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
 | 
				
			||||||
        displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"),
 | 
					        displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"),
 | 
				
			||||||
        default: false,
 | 
					        default: false,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "MessageComposerInput.surroundWith": {
 | 
				
			||||||
 | 
					        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
 | 
				
			||||||
 | 
					        displayName: _td("Surround selected text when typing special characters"),
 | 
				
			||||||
 | 
					        default: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "MessageComposerInput.autoReplaceEmoji": {
 | 
					    "MessageComposerInput.autoReplaceEmoji": {
 | 
				
			||||||
        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
 | 
					        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
 | 
				
			||||||
        displayName: _td('Automatically replace plain text Emoji'),
 | 
					        displayName: _td('Automatically replace plain text Emoji'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue