Handle more completion types in rte autocomplete (#10560)
* handle at-room * remove console log * update and add tests * tidy up * refactor to switch statement * fix TS error * expand tests * consolidate similar if/else if blocks
This commit is contained in:
parent
1ae0662872
commit
e4ebcf5731
4 changed files with 113 additions and 23 deletions
|
@ -59,22 +59,45 @@ const WysiwygAutocomplete = forwardRef(
|
||||||
const client = useMatrixClientContext();
|
const client = useMatrixClientContext();
|
||||||
|
|
||||||
function handleConfirm(completion: ICompletion): void {
|
function handleConfirm(completion: ICompletion): void {
|
||||||
// TODO handle all of the completion types
|
if (client === undefined || room === undefined) {
|
||||||
// Using this to pick out the ones we can handle during implementation
|
return;
|
||||||
if (completion.type === "command") {
|
}
|
||||||
// TODO determine if utils in SlashCommands.tsx are required
|
|
||||||
|
|
||||||
// trim the completion as some include trailing spaces, but we always insert a
|
switch (completion.type) {
|
||||||
|
case "command": {
|
||||||
|
// TODO determine if utils in SlashCommands.tsx are required.
|
||||||
|
// Trim the completion as some include trailing spaces, but we always insert a
|
||||||
// trailing space in the rust model anyway
|
// trailing space in the rust model anyway
|
||||||
handleCommand(completion.completion.trim());
|
handleCommand(completion.completion.trim());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (client && room && completion.href && (completion.type === "room" || completion.type === "user")) {
|
case "at-room": {
|
||||||
|
// TODO improve handling of at-room to either become a span or use a placeholder href
|
||||||
|
// We have an issue in that we can't use a placeholder because the rust model is always
|
||||||
|
// applying a prefix to the href, so an href of "#" becomes https://# and also we can not
|
||||||
|
// represent a plain span in rust
|
||||||
|
handleMention(
|
||||||
|
window.location.href,
|
||||||
|
getMentionDisplayText(completion, client),
|
||||||
|
getMentionAttributes(completion, client, room),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "room":
|
||||||
|
case "user": {
|
||||||
|
if (typeof completion.href === "string") {
|
||||||
handleMention(
|
handleMention(
|
||||||
completion.href,
|
completion.href,
|
||||||
getMentionDisplayText(completion, client),
|
getMentionDisplayText(completion, client),
|
||||||
getMentionAttributes(completion, client, room),
|
getMentionAttributes(completion, client, room),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO - handle "community" type
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - determine if we show all of the /command suggestions, there are some options in the
|
// TODO - determine if we show all of the /command suggestions, there are some options in the
|
||||||
|
|
|
@ -74,7 +74,7 @@ export function getRoomFromCompletion(completion: ICompletion, client: MatrixCli
|
||||||
* @returns the text to display in the mention
|
* @returns the text to display in the mention
|
||||||
*/
|
*/
|
||||||
export function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string {
|
export function getMentionDisplayText(completion: ICompletion, client: MatrixClient): string {
|
||||||
if (completion.type === "user") {
|
if (completion.type === "user" || completion.type === "at-room") {
|
||||||
return completion.completion;
|
return completion.completion;
|
||||||
} else if (completion.type === "room") {
|
} else if (completion.type === "room") {
|
||||||
// try and get the room and use it's name, if not available, fall back to
|
// try and get the room and use it's name, if not available, fall back to
|
||||||
|
@ -132,7 +132,8 @@ export function getMentionAttributes(completion: ICompletion, client: MatrixClie
|
||||||
"data-mention-type": completion.type,
|
"data-mention-type": completion.type,
|
||||||
"style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`,
|
"style": `--avatar-background: url(${avatarUrl}); --avatar-letter: '${initialLetter}'`,
|
||||||
};
|
};
|
||||||
|
} else if (completion.type === "at-room") {
|
||||||
|
return { "data-mention-type": completion.type };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,7 @@ describe("WysiwygComposer", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Mentions", () => {
|
describe("Mentions and commands", () => {
|
||||||
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
|
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||||
|
|
||||||
const mockCompletions: ICompletion[] = [
|
const mockCompletions: ICompletion[] = [
|
||||||
|
@ -181,6 +181,7 @@ describe("WysiwygComposer", () => {
|
||||||
{
|
{
|
||||||
// no href user
|
// no href user
|
||||||
type: "user",
|
type: "user",
|
||||||
|
href: undefined,
|
||||||
completion: "user_without_href",
|
completion: "user_without_href",
|
||||||
completionId: "@user_3:host.local",
|
completionId: "@user_3:host.local",
|
||||||
range: { start: 1, end: 1 },
|
range: { start: 1, end: 1 },
|
||||||
|
@ -201,6 +202,24 @@ describe("WysiwygComposer", () => {
|
||||||
range: { start: 1, end: 1 },
|
range: { start: 1, end: 1 },
|
||||||
component: <div>room_without_completion_id</div>,
|
component: <div>room_without_completion_id</div>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "command",
|
||||||
|
completion: "/spoiler",
|
||||||
|
range: { start: 1, end: 1 },
|
||||||
|
component: <div>/spoiler</div>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "at-room",
|
||||||
|
completion: "@room",
|
||||||
|
range: { start: 1, end: 1 },
|
||||||
|
component: <div>@room</div>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "community",
|
||||||
|
completion: "community-completion",
|
||||||
|
range: { start: 1, end: 1 },
|
||||||
|
component: <div>community</div>,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const constructMockProvider = (data: ICompletion[]) =>
|
const constructMockProvider = (data: ICompletion[]) =>
|
||||||
|
@ -211,9 +230,10 @@ describe("WysiwygComposer", () => {
|
||||||
} as unknown as AutocompleteProvider);
|
} as unknown as AutocompleteProvider);
|
||||||
|
|
||||||
// for each test we will insert input simulating a user mention
|
// for each test we will insert input simulating a user mention
|
||||||
|
const initialInput = "@abc";
|
||||||
const insertMentionInput = async () => {
|
const insertMentionInput = async () => {
|
||||||
fireEvent.input(screen.getByRole("textbox"), {
|
fireEvent.input(screen.getByRole("textbox"), {
|
||||||
data: "@abc",
|
data: initialInput,
|
||||||
inputType: "insertText",
|
inputType: "insertText",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -349,6 +369,36 @@ describe("WysiwygComposer", () => {
|
||||||
// check that it has inserted a link and falls back to the completion text
|
// check that it has inserted a link and falls back to the completion text
|
||||||
expect(screen.getByRole("link", { name: "#room_without_completion_id" })).toBeInTheDocument();
|
expect(screen.getByRole("link", { name: "#room_without_completion_id" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("selecting a command inserts the command", async () => {
|
||||||
|
await insertMentionInput();
|
||||||
|
|
||||||
|
// select the room suggestion
|
||||||
|
await userEvent.click(screen.getByText("/spoiler"));
|
||||||
|
|
||||||
|
// check that it has inserted the plain text
|
||||||
|
expect(screen.getByText("/spoiler")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selecting an at-room completion inserts @room", async () => {
|
||||||
|
await insertMentionInput();
|
||||||
|
|
||||||
|
// select the room suggestion
|
||||||
|
await userEvent.click(screen.getByText("@room"));
|
||||||
|
|
||||||
|
// check that it has inserted the @room link
|
||||||
|
expect(screen.getByRole("link", { name: "@room" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows a community completion to pass through", async () => {
|
||||||
|
await insertMentionInput();
|
||||||
|
|
||||||
|
// select the room suggestion
|
||||||
|
await userEvent.click(screen.getByText("community"));
|
||||||
|
|
||||||
|
// check that it we still have the initial text
|
||||||
|
expect(screen.getByText(initialInput)).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("When settings require Ctrl+Enter to send", () => {
|
describe("When settings require Ctrl+Enter to send", () => {
|
||||||
|
|
|
@ -100,8 +100,8 @@ describe("getRoomFromCompletion", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getMentionDisplayText", () => {
|
describe("getMentionDisplayText", () => {
|
||||||
it("returns an empty string if we are not handling a user or a room type", () => {
|
it("returns an empty string if we are not handling a user, room or at-room type", () => {
|
||||||
const nonHandledCompletionTypes = ["at-room", "community", "command"] as const;
|
const nonHandledCompletionTypes = ["community", "command"] as const;
|
||||||
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
||||||
|
|
||||||
nonHandledCompletions.forEach((completion) => {
|
nonHandledCompletions.forEach((completion) => {
|
||||||
|
@ -131,12 +131,18 @@ describe("getMentionDisplayText", () => {
|
||||||
// as this uses the mockClient, the name will be the mock room name returned from there
|
// as this uses the mockClient, the name will be the mock room name returned from there
|
||||||
expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion);
|
expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns the completion if we are handling an at-room completion", () => {
|
||||||
|
const testCompletion = "display this";
|
||||||
|
const atRoomCompletion = createMockCompletion({ type: "at-room", completion: testCompletion });
|
||||||
|
|
||||||
|
expect(getMentionDisplayText(atRoomCompletion, mockClient)).toBe(testCompletion);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getMentionAttributes", () => {
|
describe("getMentionAttributes", () => {
|
||||||
// TODO handle all completion types
|
it("returns an empty object for completion types other than room, user or at-room", () => {
|
||||||
it("returns an empty object for completion types other than room or user", () => {
|
const nonHandledCompletionTypes = ["community", "command"] as const;
|
||||||
const nonHandledCompletionTypes = ["at-room", "community", "command"] as const;
|
|
||||||
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
||||||
|
|
||||||
nonHandledCompletions.forEach((completion) => {
|
nonHandledCompletions.forEach((completion) => {
|
||||||
|
@ -218,4 +224,14 @@ describe("getMentionAttributes", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("at-room mentions", () => {
|
||||||
|
it("returns expected attributes", () => {
|
||||||
|
const atRoomCompletion = createMockCompletion({ type: "at-room" });
|
||||||
|
|
||||||
|
const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom);
|
||||||
|
|
||||||
|
expect(result).toEqual({ "data-mention-type": "at-room" });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue