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

Replace editor <textarea> with CodeMirror v6 #2866

Merged
merged 4 commits into from
Jun 15, 2023
Merged

Conversation

eemeli
Copy link
Member

@eemeli eemeli commented May 28, 2023

Fixes #2180
Fixes #2781

Replaces the translation editor <textarea> using CodeMirror, an editor we'll be able to extend to provide e.g. syntax and error highlighting as well as autocompletion.

As CodeMirror isn't React, but does manage its internal state in a similar manner, matching that within the app requires some finessing. A new context EditorResult is added to EditorProvider as an output; it's updated by the editor, but not read by it. The pre-existing EditorData now no longer directly contains the value being edited, but does have references to EditFieldHandles, an interface that abstracts the get/set/focus operations for each field.

@eemeli
Copy link
Member Author

eemeli commented May 29, 2023

Rebased to not include other PR contents. Got tests to pass, though needed to skip a couple due to the difference in selection handling. Will need to work on that to help ensure that we also fix #2781.

@eemeli
Copy link
Member Author

eemeli commented May 30, 2023

Key shortcuts and default selection should now work. This now fixes #2781.

@eemeli
Copy link
Member Author

eemeli commented May 30, 2023

I'll stop rebasing this now, as it's starting to look properly reviewable

@eemeli eemeli marked this pull request as ready for review May 30, 2023 17:39
@eemeli eemeli requested a review from mathjazz May 30, 2023 17:40
@@ -10,7 +10,7 @@ import css from 'rollup-plugin-css-only';
/** @type {import('rollup').RollupOptions} */
const config = {
input: 'src/index.tsx',
output: { file: 'dist/translate.js' },
output: { file: 'dist/translate.js', format: 'iife' },
Copy link
Member Author

Choose a reason for hiding this comment

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

One of the new dependencies includes a module with a top-level statement const top = .... With the default output.format, Rollup presumes that the code will be included in a <script type=module> so leaves it at the top level. However, as we don't do that, the output needs to be wrapped in an IIFE.


const [state, setState] = useState<EditorData>(initEditorData);
const [state, setState] = useState(initEditorData);
const [result, setResult] = useState<EditorResult>([]);

const actions = useMemo<EditorActions>(() => {
Copy link
Member Author

Choose a reason for hiding this comment

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

The actions that directly set a new value for fields need to also update result. Those that call field.handle.current.setValue() don't, as the EditField and EditAccessKey will take care of that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Contents refactored & moved to translate/src/modules/translationform/utils/editFieldShortcuts.ts.

Copy link
Member Author

Choose a reason for hiding this comment

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

textarea styles refactored & moved to translate/src/modules/translationform/components/TranslationForm.css

const locale = useContext(Locale);
const { setEditorFromInput } = useContext(EditorActions);
const message = useEditorValue();
function useHandleShortcuts(): (event: React.KeyboardEvent) => void {
Copy link
Member Author

Choose a reason for hiding this comment

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

CodeMirror has its own way of specifying key handlers, so this is now specific to EditAccesskey.

As a follow-up change, we should probably move these handlers out of the editors, so that they're applied also when the focus isn't on the input field, rather like Alt/Option+ArrowDown/ArrowUp.

cursor: default;
}

.cm-editor {
Copy link
Member Author

Choose a reason for hiding this comment

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

CodeMirror packages in some styling for itself via CSS-in-JS; these are overrides on those defaults. It does include its own styling system, but relying on CSS is just as valid, and more in line with what we've been doing so far.

labels: Array<{ label: string; plural: boolean }>;
}) => (
<label htmlFor={htmlFor}>
<label>
Copy link
Member Author

@eemeli eemeli May 30, 2023

Choose a reason for hiding this comment

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

Sadly, HTML <label> and contentEditable doesn't really play well together, so clicking on the label will no longer focus the appropriate field.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's a nasty UX issue. Why do we need contenteditable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it's used by CodeMirror, and effectively the basis on which any solution other than <textarea> is built.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, ok - I thought we were also using contenteditable in the label.

userInput={userInput}
value={value[0]?.value}
/>
return pk !== entity.pk ? null : fields.length === 1 ? (
Copy link
Member Author

Choose a reason for hiding this comment

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

Sometimes, we render TranslationForm before EditorProvider updates on entity change. This pk check ensures that we don't try to build a form with intermediate data, because that gets pretty messy.

bracketMatching(),
closeBrackets(),
EditorView.lineWrapping,
keymap.of([
Copy link
Member Author

Choose a reason for hiding this comment

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

The first binding that matches and returns true stops further bindings from being run, so order here matters. As standardKeymap includes bindings matching most of our own customizations, those need to come first.

@mathjazz
Copy link
Collaborator

mathjazz commented Jun 14, 2023

Fixes #2180

This PR doesn't fix the whole issue. We should either file a separate issue to track the implementation of the rich editor, or separate issues to track syntax highlighting and error annotations.

@eemeli
Copy link
Member Author

eemeli commented Jun 14, 2023

Fixes #2180

This PR doesn't fix the whole issue. We should either file a separate issue to track the implementation of the rich editor, or separate issues to track syntax highlighting and error annotations.

Point taken. Removed the closing link.

Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

Works very well, excellent job!

The only thing we should fix in addition to the CSS comment inline is add a short delay before we trigger trailing space indicator. Currently it's annoying to see the red color blinking as you type.

@eemeli
Copy link
Member Author

eemeli commented Jun 15, 2023

The only thing we should fix in addition to the CSS comment inline is add a short delay before we trigger trailing space indicator. Currently it's annoying to see the red color blinking as you type.

The default extension for this isn't configurable like that, so I removed it for now.

@eemeli eemeli merged commit 9a1ee55 into mozilla:master Jun 15, 2023
@eemeli eemeli deleted the codemirror branch June 15, 2023 13:09
mathjazz added a commit to mathjazz/pontoon that referenced this pull request Jun 20, 2023
mathjazz added a commit to mathjazz/pontoon that referenced this pull request Jun 20, 2023
mathjazz added a commit to mathjazz/pontoon that referenced this pull request Jun 22, 2023
mathjazz added a commit that referenced this pull request Jun 22, 2023
* Revert "Re-enable spellchecker in translation editor (#2884)"

This reverts commit efa6859.

* Revert "Add syntax highlighting for placeholders and tags (#2879)"

This reverts commit 9220827.

* Revert "Replace editor `<textarea>` with CodeMirror v6 (#2866)"

This reverts commit 9a1ee55.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants