Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix backticks not removed when entering diacritics using option key commands on iOS #182

Closed
wants to merge 6 commits into from

Conversation

tomekzaw
Copy link
Collaborator

@tomekzaw tomekzaw commented Feb 12, 2024

Details

This PR fixes incorrect behavior of MarkdownTextInput component when entering diacritics using option+` mode and aligns the behaviour with RN's built-in TextInput component.

Before After
before.mov
after.mov

Related Issues

Fixes #181.

Manual Tests

Linked PRs

@tomekzaw tomekzaw changed the title Fix backticks are not removed when entering diacritics using option key commands on iOS Fix backticks not removed when entering diacritics using option key commands on iOS Feb 12, 2024
@@ -47,13 +48,17 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input
JSValue *result = [function callWithArguments:@[inputString]];
NSArray *ranges = [result toArray];

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:_backedTextInputView.defaultTextAttributes];
NSMutableAttributedString *attributedString = [input mutableCopy];
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to use mutableCopy here because creating NSMutableAttributedString from NSString loses the information about diacritics context.

Comment on lines +54 to +56
NSMutableDictionary<NSAttributedStringKey, id> *attributes = [_backedTextInputView.defaultTextAttributes mutableCopy];
[attributes removeObjectForKey:RCTTextAttributesTagAttributeName];
[attributedString addAttributes:attributes range:NSMakeRange(0, attributedString.length)];
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the above change, now the attributed string also contains previous styling so I need to apply default text attributes in order to reset it.

For some reason, restoring RCTTextAttributesTagAttributeName attribute also breaks the diacritics context so I need to make sure it is not applied. This can be done either by removing it from the dictionary or calling [NSAttributedString removeAttribute] later on.

This attribute is also removed by React Native itself: https://github.com/facebook/react-native/blob/f9a5b30e5a805061416123d037351e5c03860756/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm#L175-L179

Comment on lines +60 to +61
// The workaround is to remove NSUnderlineStyleAttributeName attribute for whole string before iterating over ranges.
[attributedString removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, attributedString.length)];
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This previous version of this workaround breaks the diacritics context:

Screen.Recording.2024-02-13.at.15.20.11.mov

If we remove the workaround, the diacritics context works correctly but the link underline persists:

Screen.Recording.2024-02-13.at.15.18.25.mov

If we modify the workaround so it uses removeAttribute, it works correctly:

Screen.Recording.2024-02-13.at.15.22.56.mov

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check on the single line input as well? I'm testing on it, and the current version of the underline workaroud doesn't work for me when multiline={false}

Reproduction steps
  1. set multiline prop of MarkdownTextInput to false
  2. Write any link inside text input
  3. press enter to submit - notice that it blurs the input
  4. focus the input and start typing - notice that underline is applied to whole input
Screen.Recording.2024-02-13.at.19.01.46.mov

Copy link
Collaborator Author

@tomekzaw tomekzaw Feb 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks, didn't notice this comment. I confirm this is reproducible on my end as well. Will investigate that!

Screen.Recording.2024-02-14.at.10.54.36.mov

@tomekzaw
Copy link
Collaborator Author

One thing that could still be improved is the missing underline below backtick character entered after pressing option+`.

Here's how it's displayed on macOS in Slack Screenshot 2024-02-13 at 15 40 28, Firefox: , Chrome: Screenshot 2024-02-13 at 15 44 58

Here's the current behavior on iOS (no underline):

Screen.Recording.2024-02-13.at.15.37.44.mov

Please note that the built-in TextInput component from React Native also does not underline the backtick, so perhaps this needs to be fixed in React Native itself.

Also, here's a video recording from iOS Reminders app (also no underline):

Screen.Recording.2024-02-13.at.15.47.17.mov

Copy link
Collaborator

@robertKozik robertKozik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated version of workaround doesn't work for me in case of single line input. Could you look on this?
Btw. Great research 😮 - diacritics are working perfectly

@tomekzaw
Copy link
Collaborator Author

@robertKozik What exactly seems to be wrong? How can I reproduce it? Looks like it's working correctly:

App.tsx
import * as React from 'react';

import {Button, Platform, StyleSheet, Text, View} from 'react-native';

import {MarkdownTextInput} from '@expensify/react-native-live-markdown';

const DEFAULT_TEXT = '';

function isWeb() {
  return Platform.OS === 'web';
}

function getPlatform() {
  if (isWeb()) {
    return 'web';
  }
  // @ts-expect-error it works
  return Platform.constants.systemName || Platform.constants.Brand;
}

function getPlatformVersion() {
  return Platform.Version;
}

function getBundle() {
  return __DEV__ ? 'dev' : 'production';
}

function getRuntime() {
  if ('HermesInternal' in global) {
    const version =
      // @ts-expect-error this is fine
      // eslint-disable-next-line es/no-optional-chaining
      global.HermesInternal?.getRuntimeProperties?.()['OSS Release Version'];
    return `Hermes (${version})`;
  }
  if ('_v8runtime' in global) {
    // @ts-expect-error this is fine
    // eslint-disable-next-line no-underscore-dangle
    const version = global._v8runtime().version;
    return `V8 (${version})`;
  }
  return 'JSC';
}

function getArchitecture() {
  return 'nativeFabricUIManager' in global ? 'Fabric' : 'Paper';
}

function getReactNativeVersion() {
  const {major, minor, patch} = Platform.constants.reactNativeVersion;
  return `${major}.${minor}.${patch}`;
}

function getRandomColor() {
  return `#${Math.floor(Math.random() * 16777215)
    .toString(16)
    .padStart(6, '0')}`;
}

export default function App() {
  const [value, setValue] = React.useState(DEFAULT_TEXT);
  const [markdownStyle, setMarkdownStyle] = React.useState({});

  // TODO: use MarkdownTextInput ref instead of TextInput ref
  const ref = React.useRef<TextInput>(null);

  return (
    <View style={styles.container}>
      <View style={styles.platform}>
        <Text>
          Platform: {getPlatform()} {getPlatformVersion()}
        </Text>
        <Text>Bundle: {getBundle()}</Text>
        {!isWeb() && (
          <>
            <Text>Architecture: {getArchitecture()}</Text>
            <Text>RN version: {getReactNativeVersion()}</Text>
            <Text>RN runtime: {getRuntime()}</Text>
          </>
        )}
      </View>
      <Text>MarkdownTextInput singleline</Text>
      <MarkdownTextInput
        autoCapitalize="none"
        value={value}
        onChangeText={setValue}
        style={styles.input}
      />
      <Text>MarkdownTextInput multiline</Text>
      <MarkdownTextInput
        multiline
        autoCapitalize="none"
        value={value}
        onChangeText={setValue}
        style={styles.input}
        ref={ref}
        markdownStyle={markdownStyle}
      />
      <Text style={styles.text}>{JSON.stringify(value)}</Text>
      <Button
        title="Focus"
        onPress={() => {
          if (!ref.current) {
            return;
          }
          ref.current.focus();
        }}
      />
      <Button
        title="Blur"
        onPress={() => {
          if (!ref.current) {
            return;
          }
          ref.current.blur();
        }}
      />
      <Button
        title="Reset"
        onPress={() => {
          setValue(DEFAULT_TEXT);
          setMarkdownStyle({});
        }}
      />
      <Button
        title="Clear"
        onPress={() => setValue('')}
      />
      <Button
        title="Change style"
        onPress={() =>
          setMarkdownStyle({
            link: {
              color: getRandomColor(),
            },
          })
        }
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    marginTop: 60,
  },
  platform: {
    alignItems: 'center',
    marginBottom: 20,
  },
  input: {
    fontSize: 20,
    width: 300,
    padding: 5,
    borderColor: 'gray',
    borderWidth: 1,
    textAlignVertical: 'top',
  },
  text: {
    fontFamily: 'Courier New',
    marginTop: 10,
    height: 100,
  },
});
Screen.Recording.2024-02-13.at.23.49.23.mov

@robertKozik
Copy link
Collaborator

After typing the link inside single line, press enter to trigger blur event. I've prepared repoduction steps here. But if it's not reproductible we can call it a day as It can be something specific only on my end

@tomekzaw
Copy link
Collaborator Author

On f755217 diacritics are still not working properly inside bold:

Screen.Recording.2024-02-23.at.10.41.55.mov
Screen.Recording.2024-02-23.at.10.43.36.mov

@tomekzaw tomekzaw marked this pull request as draft February 23, 2024 09:44
@tomekzaw
Copy link
Collaborator Author

tomekzaw commented Nov 5, 2024

Closing in favor of #520.

@tomekzaw tomekzaw closed this Nov 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Backticks are not removed when entering diacritics using option key commands on iOS
2 participants