mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: start at the top of the body when tabbing into a new message
This commit is contained in:
@@ -66,3 +66,54 @@ describe('QuotedHtml focus', () => {
|
||||
expect(editor.state.selection.eq(before)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('editor focus from the keyboard', () => {
|
||||
// A new message is built with the signature in it, which leaves the
|
||||
// selection after the signature. Tabbing in from Subject typed there.
|
||||
function mountCompose(): Editor {
|
||||
const e = new Editor({
|
||||
extensions: [Document, Paragraph, Text, QuotedHtml],
|
||||
content: '<p></p><p>-- </p><p>User A</p>',
|
||||
});
|
||||
e.view.dispatch(e.state.tr.setSelection(TextSelection.atEnd(e.state.doc)));
|
||||
return e;
|
||||
}
|
||||
|
||||
it('puts the caret at the start on the first focus', () => {
|
||||
editor = mountCompose();
|
||||
|
||||
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
||||
|
||||
expect(editor.state.selection.from).toBe(1);
|
||||
});
|
||||
|
||||
it('keeps the caret where a click put it', () => {
|
||||
editor = mountCompose();
|
||||
const end = editor.state.selection.from;
|
||||
|
||||
// ProseMirror's own click handling asks for the element under the
|
||||
// pointer, which jsdom does not implement.
|
||||
const doc = document as Document & { elementFromPoint: Document['elementFromPoint'] };
|
||||
const original = doc.elementFromPoint;
|
||||
doc.elementFromPoint = () => null;
|
||||
try {
|
||||
editor.view.dom.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
||||
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
||||
} finally {
|
||||
doc.elementFromPoint = original;
|
||||
}
|
||||
|
||||
expect(editor.state.selection.from).toBe(end);
|
||||
});
|
||||
|
||||
it('keeps the position when focus comes back later', () => {
|
||||
editor = mountCompose();
|
||||
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
||||
editor.view.dispatch(editor.state.tr.setSelection(TextSelection.atEnd(editor.state.doc)));
|
||||
const end = editor.state.selection.from;
|
||||
|
||||
editor.view.dom.dispatchEvent(new FocusEvent('focus'));
|
||||
|
||||
expect(editor.state.selection.from).toBe(end);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -76,27 +76,38 @@ export const QuotedHtml = TiptapNode.create({
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
// Set once the user put the caret somewhere (click, tap) or the editor
|
||||
// already had focus, so later focus keeps that position.
|
||||
let placedByUser = false;
|
||||
const markPlaced = () => {
|
||||
placedByUser = true;
|
||||
return false;
|
||||
};
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey("quotedHtmlFocus"),
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
// A reply can open with the quote (or the signature) selected as
|
||||
// a node. Tabbing into the body kept that selection, and the first
|
||||
// key typed replaced the whole block. Focus puts the caret at the
|
||||
// start instead; a click still lands where it was aimed, because
|
||||
mousedown: markPlaced,
|
||||
touchstart: markPlaced,
|
||||
// Where the editor's selection rests before the user touched it
|
||||
// is an accident of how the body was built: on the quote (or
|
||||
// signature) as a node in a reply, where the first key typed
|
||||
// replaced the whole block, or after the signature in a new
|
||||
// message. Focus from the keyboard puts the caret at the start
|
||||
// instead; a click still lands where it was aimed, because
|
||||
// ProseMirror handles the click after the focus event.
|
||||
focus: (view) => {
|
||||
const { selection, doc } = view.state;
|
||||
if (
|
||||
selection instanceof NodeSelection &&
|
||||
ATOM_BLOCKS.has(selection.node.type.name)
|
||||
) {
|
||||
const onAtom = selection instanceof NodeSelection
|
||||
&& ATOM_BLOCKS.has(selection.node.type.name);
|
||||
if (onAtom || !placedByUser) {
|
||||
const start = Selection.atStart(doc);
|
||||
if (start instanceof TextSelection) {
|
||||
if (start instanceof TextSelection && !start.eq(selection)) {
|
||||
view.dispatch(view.state.tr.setSelection(start));
|
||||
}
|
||||
}
|
||||
placedByUser = true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user