Fix link creation with backward selection (#9986)
Fix link creation with backward selection
This commit is contained in:
parent
222f8a919d
commit
406edfc27d
8 changed files with 65 additions and 5 deletions
|
@ -98,7 +98,7 @@ describe("Composer", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("WYSIWYG", () => {
|
describe("Rich text editor", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.enableLabsFeature("feature_wysiwyg_composer");
|
cy.enableLabsFeature("feature_wysiwyg_composer");
|
||||||
cy.initTestUser(homeserver, "Janet").then(() => {
|
cy.initTestUser(homeserver, "Janet").then(() => {
|
||||||
|
@ -165,5 +165,25 @@ describe("Composer", () => {
|
||||||
cy.contains(".mx_EventTile_body", "my message 3");
|
cy.contains(".mx_EventTile_body", "my message 3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("links", () => {
|
||||||
|
it("create link with a forward selection", () => {
|
||||||
|
// Type a message
|
||||||
|
cy.get("div[contenteditable=true]").type("my message 0{selectAll}");
|
||||||
|
|
||||||
|
// Open link modal
|
||||||
|
cy.get('button[aria-label="Link"]').click();
|
||||||
|
// Fill the link field
|
||||||
|
cy.get('input[label="Link"]').type("https://matrix.org/");
|
||||||
|
// Click on save
|
||||||
|
cy.get('button[type="submit"]').click();
|
||||||
|
// Send the message
|
||||||
|
cy.get('div[aria-label="Send message"]').click();
|
||||||
|
|
||||||
|
// It was sent
|
||||||
|
cy.contains(".mx_EventTile_body a", "my message 0");
|
||||||
|
cy.get(".mx_EventTile_body a").should("have.attr", "href").and("include", "https://matrix.org/");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { SubSelection } from "./types";
|
||||||
|
|
||||||
export function getDefaultContextValue(): { selection: SubSelection } {
|
export function getDefaultContextValue(): { selection: SubSelection } {
|
||||||
return {
|
return {
|
||||||
selection: { anchorNode: null, anchorOffset: 0, focusNode: null, focusOffset: 0 },
|
selection: { anchorNode: null, anchorOffset: 0, focusNode: null, focusOffset: 0, isForward: true },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ export function useComposerFunctions(
|
||||||
anchorOffset: anchorOffset + text.length,
|
anchorOffset: anchorOffset + text.length,
|
||||||
focusNode: ref.current.firstChild,
|
focusNode: ref.current.firstChild,
|
||||||
focusOffset: focusOffset + text.length,
|
focusOffset: focusOffset + text.length,
|
||||||
|
isForward: true,
|
||||||
});
|
});
|
||||||
setContent(ref.current.innerHTML);
|
setContent(ref.current.innerHTML);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,15 @@ function setSelectionContext(composerContext: ComposerContextState): void {
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
|
|
||||||
if (selection) {
|
if (selection) {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
const isForward = range.startContainer === selection.anchorNode && range.startOffset === selection.anchorOffset;
|
||||||
|
|
||||||
composerContext.selection = {
|
composerContext.selection = {
|
||||||
anchorNode: selection.anchorNode,
|
anchorNode: selection.anchorNode,
|
||||||
anchorOffset: selection.anchorOffset,
|
anchorOffset: selection.anchorOffset,
|
||||||
focusNode: selection.focusNode,
|
focusNode: selection.focusNode,
|
||||||
focusOffset: selection.focusOffset,
|
focusOffset: selection.focusOffset,
|
||||||
|
isForward,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,6 @@ export type ComposerFunctions = {
|
||||||
insertText: (text: string) => void;
|
insertText: (text: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubSelection = Pick<Selection, "anchorNode" | "anchorOffset" | "focusNode" | "focusOffset">;
|
export type SubSelection = Pick<Selection, "anchorNode" | "anchorOffset" | "focusNode" | "focusOffset"> & {
|
||||||
|
isForward: boolean;
|
||||||
|
};
|
||||||
|
|
|
@ -19,9 +19,14 @@ import { SubSelection } from "../types";
|
||||||
export function setSelection(selection: SubSelection): Promise<void> {
|
export function setSelection(selection: SubSelection): Promise<void> {
|
||||||
if (selection.anchorNode && selection.focusNode) {
|
if (selection.anchorNode && selection.focusNode) {
|
||||||
const range = new Range();
|
const range = new Range();
|
||||||
range.setStart(selection.anchorNode, selection.anchorOffset);
|
|
||||||
range.setEnd(selection.focusNode, selection.focusOffset);
|
|
||||||
|
|
||||||
|
if (selection.isForward) {
|
||||||
|
range.setStart(selection.anchorNode, selection.anchorOffset);
|
||||||
|
range.setEnd(selection.focusNode, selection.focusOffset);
|
||||||
|
} else {
|
||||||
|
range.setStart(selection.focusNode, selection.focusOffset);
|
||||||
|
range.setEnd(selection.anchorNode, selection.anchorOffset);
|
||||||
|
}
|
||||||
document.getSelection()?.removeAllRanges();
|
document.getSelection()?.removeAllRanges();
|
||||||
document.getSelection()?.addRange(range);
|
document.getSelection()?.addRange(range);
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,6 +299,7 @@ describe("SendWysiwygComposer", () => {
|
||||||
anchorOffset: 2,
|
anchorOffset: 2,
|
||||||
focusNode: textNode,
|
focusNode: textNode,
|
||||||
focusOffset: 2,
|
focusOffset: 2,
|
||||||
|
isForward: true,
|
||||||
});
|
});
|
||||||
// the event is not automatically fired by jest
|
// the event is not automatically fired by jest
|
||||||
document.dispatchEvent(new CustomEvent("selectionchange"));
|
document.dispatchEvent(new CustomEvent("selectionchange"));
|
||||||
|
@ -308,6 +309,32 @@ describe("SendWysiwygComposer", () => {
|
||||||
// Then
|
// Then
|
||||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(/wo🦫rd/));
|
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(/wo🦫rd/));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should add an emoji when a word is selected", async () => {
|
||||||
|
// When
|
||||||
|
screen.getByRole("textbox").focus();
|
||||||
|
screen.getByRole("textbox").innerHTML = "word";
|
||||||
|
fireEvent.input(screen.getByRole("textbox"), {
|
||||||
|
data: "word",
|
||||||
|
inputType: "insertText",
|
||||||
|
});
|
||||||
|
|
||||||
|
const textNode = screen.getByRole("textbox").firstChild;
|
||||||
|
await setSelection({
|
||||||
|
anchorNode: textNode,
|
||||||
|
anchorOffset: 3,
|
||||||
|
focusNode: textNode,
|
||||||
|
focusOffset: 2,
|
||||||
|
isForward: false,
|
||||||
|
});
|
||||||
|
// the event is not automatically fired by jest
|
||||||
|
document.dispatchEvent(new CustomEvent("selectionchange"));
|
||||||
|
|
||||||
|
emojiButton.click();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(/wo🦫d/));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,7 @@ describe("LinkModal", () => {
|
||||||
anchorNode: null,
|
anchorNode: null,
|
||||||
focusOffset: 3,
|
focusOffset: 3,
|
||||||
anchorOffset: 4,
|
anchorOffset: 4,
|
||||||
|
isForward: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const customRender = (isTextEnabled: boolean, onClose: () => void, isEditing = false) => {
|
const customRender = (isTextEnabled: boolean, onClose: () => void, isEditing = false) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue