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

Mobile: Add keyboard-activatable markdown commands (e.g. bold, italicize) #6707

Merged
merged 103 commits into from
Aug 8, 2022

Conversation

personalizedrefrigerator
Copy link
Collaborator

@personalizedrefrigerator personalizedrefrigerator commented Jul 31, 2022

Summary

  • Creates a set of commands that can be used by a future markdown toolbar.
  • Adds a find/replace dialog and a 'edit link' dialog that can be opened with ctrl+F/ctrl+K.
    • screenshot of find/replace dialog
    • screenshot of edit link dialog. Has an input field for the link's title and a field for the link's URL.
  • Connects some of these commands to keyboard shortcuts (e.g. ctrl+F opens a ReactNative component for find/replace, ctrl+` toggles code, ctrl+B bolds).

Keyboard shortcuts that are present in Joplin desktop, but not in this PR:

  • ctrl+/ = comment

There may be others...

Keyboard shortcuts not present in Joplin desktop, but that users might expect

  • ctrl+H = toggle find/replace
  • alt+1 = toggle heading level 1
  • alt+2 = toggle heading level 2
  • ...
  • ctrl+shift+. = add quote

I haven't implemented these shortcuts in this PR. alt+1, alt+2, ..., alt+6 are very easy to implement. Should I add them to this PR?

personalizedrefrigerator and others added 30 commits June 23, 2022 19:03
Math blocks and regions of inline math were missing their original
containing nodes.
@personalizedrefrigerator personalizedrefrigerator changed the title Pr/markdown commands Mobile: Add keyboard-activatable markdown commands (e.g. bold, italicize) Jul 31, 2022
Copy link
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

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

Looks good overall. For the React Hooks, please make sure you always pass all the dependencies to the effects (there's an eslint rule to detect this normally but unfortunately we can't currently use it).

There are still tests with nested describes - my concern is, when a test fails, how easy is it to run just the failing test in these nested configurations? Could you try and confirm please? Fail one of the test, then see what command is required to run just that test (based only on the jest output). If you could post the result here that will allow us to make a decision about these nested describes.

import createEditor from './createEditor';
import { toggleList } from './markdownCommands';

describe('should distinguish between checklists and unordered lists', () => {
Copy link
Owner

Choose a reason for hiding this comment

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

Please check how other tests are written. The describe part should have the same name as the filename (minus .test.ts). Again this is to make it easier to run a specific tests or group of tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed!

toggleList(ListType.CheckList)(editor);
expect(editor.state.doc.toString()).toBe(
`${bulletedListPart}\n\nThis is a checklist\nwith multiple items.\n☑`
);
Copy link
Owner

Choose a reason for hiding this comment

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

That's brilliant you can create the editor inside the test and run commands directly. Going to make things so much more reliable.

useEffect(() => {
setLinkLabel(editorLinkData.linkText ?? props.selectionState.selectedText);
setLinkURL(editorLinkData.linkURL ?? '');
}, [props.visible]);
Copy link
Owner

Choose a reason for hiding this comment

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

Please make sure to add all dependencies to the array.

But it should strictly be what's required and nothing more - so editorLinkData.linkText, props.selectionState.selectedText, editorLinkData.linkURL (and not editorLinkData, props.selectionState)

updateEditor();
props.editorControl.hideLinkDialog();
};
const linkInputRef = useRef();
Copy link
Owner

Choose a reason for hiding this comment

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

Please declare all ref, state, etc at the the top of the hook, because they can be used anywhere within the function.

const submit = () => {
updateEditor();
props.editorControl.hideLinkDialog();
};
Copy link
Owner

Choose a reason for hiding this comment

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

All function that are used in callbacks should be defined using useCallback to avoid stale data, and used variables should be passed in the dependencies array. Also please name it onSomething (here onSubmit) to make it clear it's a callback. Maybe we need some eslint rule for this.

};


const useStyles = (theme: any) => {
Copy link
Owner

Choose a reason for hiding this comment

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

This should have the Theme type (unless it's not possible to import it here for some reason).

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 wasn't aware that type existed! Good catch!

Comment on lines 138 to 139
{ placeholder, value, onChange, autoFocus = false }:
{ placeholder: string; value: string; onChange: (text: string)=> void; autoFocus?: boolean }
Copy link
Owner

@laurent22 laurent22 Aug 1, 2022

Choose a reason for hiding this comment

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

Again please don't use this pattern.

  • If an object interface is used more than once, define a properly named interface

  • If it's used only once, there's a chance you don't need a interface. In this particular case, just use regular function arguments. That way you don't need to repeat the names twice over two lines.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch! (I wrote this code a while ago and forgot to update it.)

@personalizedrefrigerator
Copy link
Collaborator Author

Fail one of the test, then see what command is required to run just that test (based only on the jest output).

Suppose we have the following output:

 FAIL  components/NoteEditor/CodeMirror/markdownReformatter.test.ts (18.674 s)
  ● markdownReformatter › should match start and end of bolded regions › should return -1 if no match is found

    expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 0

      28 |                      const doc = DocumentText.of(['**...** test.']);
      29 |                      const sel = EditorSelection.range(3, 3);
    > 30 |                      expect(0).toBe(1);
         |                                ^
      31 |                      expect(findInlineMatch(doc, spec, sel, MatchSide.Start)).toBe(-1);
      32 |              });
      33 |      });

      at Object.<anonymous> (components/NoteEditor/CodeMirror/markdownReformatter.test.ts:30:14)

Running

yarn test --testNamePattern 'should match start and end of bolded regions should return -1 if no match is found' --runTestsByPath components/NoteEditor/CodeMirror/markdownReformatter.test.ts

runs the failing test.

Of course,

yarn test -f

or

yarn test --onlyFailures

also work, but they require knowledge of which tests failed in the last execution.

@laurent22
Copy link
Owner

Thanks for checking for the tests, but I feel that the extra complexity of nested describes is not worth it. We managed without this for several years, so hopefully we can keep doing so. So please could you remove these nested describes and replace them with a flat structure?

@laurent22
Copy link
Owner

I think it's good now, thanks for adding this!

@laurent22 laurent22 merged commit 03c3188 into laurent22:dev Aug 8, 2022
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.

2 participants