Summary:
- Simplify undoManager to just maintain the undo/redo history items
- DraftEditingSession manages snapshotting state of draft, hack allows it to also save selection (still hoping to eventually put selection in body HTML as markers)
- Switch from `debounce` to `throttle` style behavior so typing for along time followed by undo doesn't undo away your entire block.
This resolves two issues:
+ Changes to participant fields are no longer undoable because they go straight to the session.
+ Changes to metadata weren't undoable.
Test Plan: Tests WIP
Reviewers: evan, juan
Reviewed By: juan
Differential Revision: https://phab.nylas.com/D2956
- This prevented emoji selection via the popover from working on a new
window because the plugin is loaded after the contenteditable sets up
the action listeners, so we need to re set them on update
Summary:
Adds Mail Merge Plugin
- Adds new table components to component kit
- Adds new extension points to allow dragging and dropping into composer contenteditable and participant fields and customizing participant fields
- Adds new decorators and other misc updates
- #1608
Test Plan: TODO
Reviewers: bengotow, evan
Reviewed By: bengotow, evan
Differential Revision: https://phab.nylas.com/D2895
Summary:
- Removes controlled focus in the composer!
- No React components ever perfom focus in lifecycle methods. Never again.
- A new `Utils.schedule({action, after, timeout})` helper makes it easy to say "setState or load draft, etc. and then focus"
- The DraftStore issues a focusDraft action after creating a draft, which causes the MessageList to focus and scroll to the desired composer, which itself decides which field to focus.
- The MessageList never focuses anything automatically.
- Refactors ComposerView apart — ComposerHeader handles all top fields, DraftSessionContainer handles draft session initialization and exposes props to ComposerView
- ComposerHeader now uses a KeyCommandRegion (with focusIn and focusOut) to do the expanding and collapsing of the participants fields. May rename that container very soon.
- Removes all CommandRegistry handling of tab and shift-tab. Unless you preventDefault, the browser does it's thing.
- Removes all tabIndexes greater than 1. This is an anti-pattern—assigning everything a tabIndex of 0 tells the browser to move between them based on their order in the DOM, and is almost always what you want.
- Adds "TabGroupRegion" which allows you to create a tab/shift-tabbing group, (so tabbing does not leave the active composer). Can't believe this isn't a browser feature.
Todos:
- Occasionally, clicking out of the composer contenteditable requires two clicks. This is because atomicEdit is restoring selection within the contenteditable and breaking blur.
- Because the ComposerView does not render until it has a draft, we're back to it being white in popout composers for a brief moment. We will fix this another way - all the "return unless draft" statements were untenable.
- Clicking a row in the thread list no longer shifts focus to the message list and focuses the last draft. This will be restored soon.
Test Plan: Broken
Reviewers: juan, evan
Reviewed By: juan, evan
Differential Revision: https://phab.nylas.com/D2814
Summary:
Previously we always created <blockquote class="gmail_quote"> to wrap quoted text. This is not correct.
Gmail uses blockquotes only when it wants visual indentation, and <div>s to wrap other quoted text, like forwarded
messages which are not displayed indented.
This diff updates N1 to match Gmail exactly. Note that for replies, Gmail actually nests a blockquote.gmail_quote
inside a div.gmail_quote.
I also updated signature handling because it turns out the regexp that was removing existing signatures would blow
away any and all divs until it reached a <blockquote> tag.
Test Plan: See updated specs. Manually tested by creating a thread in Google Inbox and then performing fwd and reply in both N1 and Inbox. Results match.
Reviewers: juan, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2750
Summary:
This is a refactor of the toolbar in the contenteditable. Goals of this
are:
1. Allow developers to add new buttons to the toolbar
2. Allow developers to add other component types to the floating toolbar (like the LinkEditor)
3. Make the toolbar declaratively defined instead of imperatively set
4. Separate out logical units of the toolbar into individual sections
5. Clean up `innerState` of the Contenteditable
The Floating Toolbar used to be an imperative mess. Doing simple
functionality additions required re-understanding a very complex set of
logic to hide and show the toolbar and delecately manage focus states.
There also was no real capacity for any developer to extend the toolbar.
It also used to be completely outside of our `atomicEdit` system and was a
legacy of having raw access to contenteditable controls (since it all used
to be directly inside of the contenteditable)
Finally it was difficult to declaratively define things because the
`innerState` of the Contenteditable was inconsistently used and its
lifecycle not properly thought through. This fixed several lifecycle bugs
with that.
Along the way several of the DOMUtils methods were also subtly not
functional and fixed.
The Toolbar is now broken apart into separate logical units.
There are now `ContentedtiableExtension`s that declare what should be
displayed in the toolbar at any given moment.
They define a method called `toolbarComponentData`. This is a pure
function of the state of the `Contenteditable`. If selection and content
conditions look correct, then that method will return a component to
render. This is how we declaratively define whether a toolbar should be
visible or not instead of manually setting `hide` & `show` bits.
There is also a `toolbarButtons` method that declaratively defines buttons
that can go in the new `<ToolbarButtons>` component.
The `ToolbarButtonManager` takes care of extracting these and binding the
correct editorAPI context.
Now the `<LinkEditor>` is a separate component from the `<ToolbarButtons>`
instead of being smashed together.
The `LinkManager` takes care of declaring when the `LinkEditor` should be
displayed and has properly bound methods to update the `contenteditable`
through the standard `atomicEdit` interface.
If users have additional contenteditable popup plugins (like displaying
extra info on a name or some content in the composer), they can now
implement the `toolbarComponentData` api and declaratively define that
information based on the state of the contenteditable.
Test Plan: TODO
Reviewers: bengotow, juan
Reviewed By: bengotow
Differential Revision: https://phab.nylas.com/D2442
clicking outside:
- When focusing the composer via click inside the contenteditable region or via
tabbing, last text node before the signature (or blockquotes) will be focused.
- When focusing composer by clicking outside contenteditable region, it
will default to default contenteditable focus behavior via new method:
`nativeFocus`
Summary: When focusing the composer, select the end of the last text block above any signatures / quoted text (which can be visible by default in Fwd:).
Test Plan: Run tests
Reviewers: juan, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2411
Summary:
Remove FocusTrackingRegion—all CommandRegions should be focusable, and nesting the two creates varying behavior based on which is the parent
Calling focus() on an injected / unsafe component should always do /something/. Try the inner React method, inner DOM method, or call on ourselves
Rename contentEditable._focusEditor to "focus" since it intends to replace default focus behavior
In ComposerView, always change focus via setState, never by calling focus() directly. Rather than tracking `_lastFocusedField`, just focus whenever the activeElement isnt within the focusedField. Make body initial focus when draft is pristine...
...(ensures new drafts are focused)
Test Plan: Run tests
Reviewers: evan, juan
Reviewed By: evan, juan
Differential Revision: https://phab.nylas.com/D2406
Summary:
- The main purpose of this is to be able to properly register the editor for the markdown plugin (and any other plugins to come)
- Refactors ComposerView and Contenteditable ->
- Replaces Contenteditable with an InjectedComponent for a new region role:
"Composer:Editor"
- Creates a new component called ComposerEditor, which is the one that is
being registered by default as "Composer:Editor"
- I used this class to try to standardize the props that should be
passed to any would be editor Component:
- Renamed a bunch of the props which (I think) had a bit of
confusing names
- Added a bunch of docs for these in the source file, although
I feel like those docs should live elsewhere, like in the
ComponentRegion docs.
- In the process, I ended up pulling some stuff out of ComposerView and
some stuff out of the Contenteditable, namely:
- The scrolling logic to ensure that the composer is visible while
typing was moved outside of the Contenteditable -- this feels more
like the ComposerEditor's responsibility, especially since the
Contenteditable is meant to be used in other contexts as well.
- The ComposerExtensions state; it feels less awkward for me if this
is inside the ComposerEditor because 1) ComposerView does less
things, 2) these are actually just being passed to the
Contenteditable, 3) I feel like other plugins shouldn't need to
mess around with ComposerExtensions, so we shouldn't pass them to the
editor. If you register an editor different from our default one,
any other ComposerExtension callbacks will be disabled, which
I feel is expected behavior.
- I think there is still some more refactoring to be done, and I left some TODOS
here and there, but I think this diff is already big enough and its a minimal
set of changes to get the markdown editor working in a not so duck
tapish way.
- New props for InjectedComponent:
- `requiredMethods`: allows you to define a collection of methods that
should be implemented by any Component that registers for your
desired region.
- It will throw an error if these are not implemented
- It will automatically pass calls made on the InjectedComponent to these methods
down to the instance of the actual registered component
- Would love some comments on this approach and impl
- `fallback`: allows you to define a default component to use if none were
registered through the ComponentRegistry
- Misc:
- Added a new test case for the QuotedHTMLTransformer
- Tests:
- They were minimally updated so that they don't break, but a big TODO
is to properly refactor them. I plan to do that in an upcoming
diff.
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2372