MM-12148: Prevent emdash convert in a Code Block on ios (#3885)
* Add fix to prevent emdash autocorrect in a Code Block on ios * Add fix to prevent emdash autocorrect in a Code Block on ios * code clean up * separate to new switchKeyboardForCodeBlocks function * added emdash prevention to edit_post and also code cleanup * moved keyboardType logic to its own utils function and added unit tests * clean up - align event argument names * removed unnecessary event check
This commit is contained in:
parent
b46996a841
commit
30594cec5d
4 changed files with 137 additions and 12 deletions
|
|
@ -43,6 +43,7 @@ import FileUploadPreview from 'app/components/file_upload_preview';
|
|||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
|
||||
import {switchKeyboardForCodeBlocks} from 'app/utils/markdown';
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
|
|
@ -409,17 +410,25 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handlePostDraftSelectionChanged = (event) => {
|
||||
const cursorPosition = event.nativeEvent.selection.end;
|
||||
handleOnSelectionChange = (event) => {
|
||||
this.handlePostDraftSelectionChanged(event, false);
|
||||
};
|
||||
|
||||
handlePostDraftSelectionChanged = (event, fromHandleTextChange) => {
|
||||
const cursorPosition = fromHandleTextChange ? this.state.cursorPosition : event.nativeEvent.selection.end;
|
||||
|
||||
const {cursorPositionEvent} = this.props;
|
||||
|
||||
if (cursorPositionEvent) {
|
||||
EventEmitter.emit(cursorPositionEvent, cursorPosition);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
cursorPosition,
|
||||
});
|
||||
if (Platform.OS === 'ios') {
|
||||
const keyboardType = switchKeyboardForCodeBlocks(this.state.value, cursorPosition);
|
||||
this.setState({cursorPosition, keyboardType});
|
||||
} else {
|
||||
this.setState({cursorPosition});
|
||||
}
|
||||
};
|
||||
|
||||
handleSendMessage = () => {
|
||||
|
|
@ -517,7 +526,13 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
|
||||
this.checkMessageLength(value);
|
||||
|
||||
this.setState(nextState);
|
||||
// Workaround to avoid iOS emdash autocorrect in Code Blocks
|
||||
if (Platform.OS === 'ios') {
|
||||
const callback = () => this.handlePostDraftSelectionChanged(null, true);
|
||||
this.setState(nextState, callback);
|
||||
} else {
|
||||
this.setState(nextState);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
actions.userTyping(channelId, rootId);
|
||||
|
|
@ -926,7 +941,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
value={textValue}
|
||||
style={{...style.input, ...inputStyle, maxHeight}}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
onSelectionChange={this.handleOnSelectionChange}
|
||||
placeholder={intl.formatMessage(placeholder, {channelDisplayName})}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
multiline={true}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Loading from 'app/components/loading';
|
|||
import StatusBar from 'app/components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {switchKeyboardForCodeBlocks} from 'app/utils/markdown';
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
|
|
@ -62,6 +63,7 @@ export default class EditPost extends PureComponent {
|
|||
message: props.post.message,
|
||||
cursorPosition: 0,
|
||||
autocompleteVisible: false,
|
||||
keyboardType: 'default',
|
||||
};
|
||||
|
||||
this.rightButton.color = props.theme.sidebarHeaderTextColor;
|
||||
|
|
@ -151,7 +153,14 @@ export default class EditPost extends PureComponent {
|
|||
};
|
||||
|
||||
onPostChangeText = (message) => {
|
||||
this.setState({message});
|
||||
// Workaround to avoid iOS emdash autocorrect in Code Blocks
|
||||
if (Platform.OS === 'ios') {
|
||||
const callback = () => this.onPostSelectionChange(null, true);
|
||||
this.setState({message}, callback);
|
||||
} else {
|
||||
this.setState({message});
|
||||
}
|
||||
|
||||
if (message) {
|
||||
this.emitCanEditPost(true);
|
||||
} else {
|
||||
|
|
@ -159,9 +168,19 @@ export default class EditPost extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onPostSelectionChange = (event) => {
|
||||
const cursorPosition = event.nativeEvent.selection.end;
|
||||
this.setState({cursorPosition});
|
||||
handleOnSelectionChange = (event) => {
|
||||
this.onPostSelectionChange(event, false);
|
||||
};
|
||||
|
||||
onPostSelectionChange = (event, fromOnPostChangeText) => {
|
||||
const cursorPosition = fromOnPostChangeText ? this.state.cursorPosition : event.nativeEvent.selection.end;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
const keyboardType = switchKeyboardForCodeBlocks(this.state.message, cursorPosition);
|
||||
this.setState({cursorPosition, keyboardType});
|
||||
} else {
|
||||
this.setState({cursorPosition});
|
||||
}
|
||||
};
|
||||
|
||||
onAutocompleteVisible = (autocompleteVisible) => {
|
||||
|
|
@ -220,7 +239,8 @@ export default class EditPost extends PureComponent {
|
|||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(this.props.theme)}
|
||||
onSelectionChange={this.onPostSelectionChange}
|
||||
onSelectionChange={this.handleOnSelectionChange}
|
||||
keyboardType={this.state.keyboardType}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -182,3 +182,24 @@ export function getDisplayNameForLanguage(language) {
|
|||
export function escapeRegex(text) {
|
||||
return text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
export function switchKeyboardForCodeBlocks(value, cursorPosition) {
|
||||
const regexForCodeBlock = /^```$(.*?)^```$|^```$(.*)/gms;
|
||||
|
||||
const matches = [];
|
||||
let nextMatch;
|
||||
while ((nextMatch = regexForCodeBlock.exec(value)) !== null) {
|
||||
matches.push({
|
||||
startOfMatch: regexForCodeBlock.lastIndex - nextMatch[0].length,
|
||||
endOfMatch: regexForCodeBlock.lastIndex + 1,
|
||||
});
|
||||
}
|
||||
|
||||
const cursorIsInsideCodeBlock = matches.some((match) => cursorPosition >= match.startOfMatch && cursorPosition <= match.endOfMatch);
|
||||
|
||||
// 'email-address' keyboardType prevents iOS emdash autocorrect
|
||||
if (cursorIsInsideCodeBlock) {
|
||||
return 'email-address';
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
|
|
|
|||
69
app/utils/markdown.test.js
Normal file
69
app/utils/markdown.test.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {switchKeyboardForCodeBlocks} from './markdown';
|
||||
|
||||
describe('switchKeyboardForCodeBlocks', () => {
|
||||
const testCases = [{
|
||||
name: 'Empty text input',
|
||||
value: '',
|
||||
cursorPosition: 0,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Cursor within an open Code Block',
|
||||
value: '```',
|
||||
cursorPosition: 3,
|
||||
expected: 'email-address',
|
||||
}, {
|
||||
name: 'Cursor within a closed Code Block',
|
||||
value: '```\ntest\n```',
|
||||
cursorPosition: 4,
|
||||
expected: 'email-address',
|
||||
}, {
|
||||
name: 'Cursor outside AFTER a closed Code Block',
|
||||
value: '```\ntest\n```\ntest',
|
||||
cursorPosition: 14,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Cursor outside BEFORE a closed Code Block',
|
||||
value: 'test\n```\ntest\n```',
|
||||
cursorPosition: 4,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Cursor outside BEFORE an open Code Block',
|
||||
value: 'test\n```',
|
||||
cursorPosition: 4,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Cursor outside between two Code Blocks',
|
||||
value: '```\ntest\n```\ntest\n```\ntest',
|
||||
cursorPosition: 14,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Opening triple backtick not on its own line - text after backticks',
|
||||
value: '```test',
|
||||
cursorPosition: 3,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Opening triple backtick not on its own line - text before backticks',
|
||||
value: 'test```',
|
||||
cursorPosition: 7,
|
||||
expected: 'default',
|
||||
}, {
|
||||
name: 'Closing triple backtick not on its own line - text after backticks',
|
||||
value: '```\ntest\n```test',
|
||||
cursorPosition: 12,
|
||||
expected: 'email-address',
|
||||
}, {
|
||||
name: 'Closing triple backtick not on its own line - text before backticks',
|
||||
value: '```\ntest\ntest```',
|
||||
cursorPosition: 16,
|
||||
expected: 'email-address',
|
||||
}];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
it(`test - ${testCase.name}`, () => {
|
||||
expect(switchKeyboardForCodeBlocks(testCase.value, testCase.cursorPosition)).toEqual(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue