Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add surround with feature #5510

Merged
merged 14 commits into from
Aug 5, 2021
Merged
46 changes: 46 additions & 0 deletions src/components/views/rooms/BasicMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.sourc

const IS_MAC = navigator.platform.indexOf("Mac") !== -1;

const SURROUND_WITH_CHARACTERS = ["\"", "_", "`", "'", "*", "~", "$"];
const SURROUND_WITH_DOUBLE_CHARACTERS = new Map([
["(", ")"],
["[", "]"],
["{", "}"],
["<", ">"],
]);

function ctrlShortcutLabel(key) {
return (IS_MAC ? "⌘" : "Ctrl") + "+" + key;
}
Expand Down Expand Up @@ -107,6 +115,7 @@ interface IState {
showVisualBell?: boolean;
autoComplete?: AutocompleteWrapperModel;
completionIndex?: number;
surroundWith: boolean,
}

@replaceableComponent("views.rooms.BasicMessageEditor")
Expand All @@ -125,19 +134,23 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>

private readonly emoticonSettingHandle: string;
private readonly shouldShowPillAvatarSettingHandle: string;
private readonly surroundWithHandle: string;
private readonly historyManager = new HistoryManager();

constructor(props) {
super(props);
this.state = {
showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"),
surroundWith: SettingsStore.getValue("MessageComposerInput.surroundWith"),
};

this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null,
this.configureEmoticonAutoReplace);
this.configureEmoticonAutoReplace();
this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null,
this.configureShouldShowPillAvatar);
this.surroundWithHandle = SettingsStore.watchSetting("MessageComposerInput.surroundWith", null,
this.surroundWithSettingChanged);
}

public componentDidUpdate(prevProps: IProps) {
Expand Down Expand Up @@ -430,6 +443,33 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private onKeyDown = (event: React.KeyboardEvent) => {
const model = this.props.model;
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_CHARACTERS.includes(event.key)) {
this.historyManager.ensureLastChangesPushed(this.props.model);
this.modifiedFlag = true;
toggleInlineFormat(selectionRange, event.key);
handled = true;
} else if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys()].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);
switch (action) {
case MessageComposerAction.FormatBold:
Expand Down Expand Up @@ -582,13 +622,19 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.setState({ showPillAvatar });
};

private surroundWithSettingChanged = () => {
const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith");
this.setState({ surroundWith });
};

componentWillUnmount() {
document.removeEventListener("selectionchange", this.onSelectionChange);
this.editorRef.current.removeEventListener("input", this.onInput, true);
this.editorRef.current.removeEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true);
SettingsStore.unwatchSetting(this.emoticonSettingHandle);
SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
SettingsStore.unwatchSetting(this.surroundWithHandle);
}

componentDidMount() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
'MessageComposerInput.suggestEmoji',
'sendTypingNotifications',
'MessageComposerInput.ctrlEnterToSend',
'MessageComposerInput.surroundWith',
'MessageComposerInput.showStickersButton',
];

Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@
"Use Ctrl + F to search": "Use Ctrl + F to search",
"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 surround with": "Use surround with",
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
"Mirror local video feed": "Mirror local video feed",
"Enable Community Filter Panel": "Enable Community Filter Panel",
Expand Down
5 changes: 5 additions & 0 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,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"),
default: false,
},
"MessageComposerInput.surroundWith": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Use surround with"),
default: false,
},
"MessageComposerInput.autoReplaceEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Automatically replace plain text Emoji'),
Expand Down