This repository was archived by the owner on Sep 11, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 816
Mentions as links rte #10422
Merged
Merged
Mentions as links rte #10422
Changes from all commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
177d861
create autocomplete component
a52aa38
use autocomplete component and add click handlers
1a17948
handle focus when autocomplete open
05cc8a3
add placeholder for pill styling
be01b92
make width of autocomplete sensible
7ba881d
fix TS issue
8abcb62
Merge remote-tracking branch 'origin/develop' into alunturner/mention…
03c6509
bump package json to new version of wysiwyg
cbc3723
yarn
6b34a80
get composer tests passing
a4e1cdf
rough setup of new autocomplete tests
1c34cdc
WIP cypress tests
ff12d92
add test id
8e45663
tidy up test setup
2ad0eab
progress on getting something rendering
191f58f
get the list item components rendering
37b2043
make tests work as expected
76eb06f
Merge remote-tracking branch 'origin/develop' into alunturner/mention…
74c3a23
fix TS error
013c22e
remove unused cypress test
ab0e84b
remove commented import
99a98f1
wrap wysiwyg composer tests in contexts
631ec37
Merge branch 'develop' into alunturner/mentions-as-links-rte
14cd947
add required autocomplete mocks for composer tests
8fe6390
fix type issue
a97d57c
fix TS issue
9d4b330
fix console error in test run
cf9456c
fix errors in send wysiwyg composer
9c2e71a
fix edit wysiwyg composer test
d3bf2f5
fix send wysiwyg composer test
84576bc
improve autocomplete tests
26dfec7
expand composer tests
036a04b
comment out todo code for coverage
75ecc15
improve autocomplete test code
a48f87b
add some room autocompletion tests for composer
587ea1a
tidy up tests
6c4d893
add clicking on user link test
5616f99
Merge remote-tracking branch 'origin/develop' into alunturner/mention…
d47234b
use stubClient, not createTestClient
78100d9
consolidate mentions test and setup in single describe
392bc86
Merge branch 'develop' into alunturner/mentions-as-links-rte
artcodespace 7457b4e
revert unneeded changes
a834963
improve consistency
0b1e510
remove unneccesary hook
cde55ab
remove comment
57d4db5
improve assertion
21cc283
improve test variables for legibility
c3d3da8
Merge remote-tracking branch 'origin/develop' into alunturner/mention…
ec61bef
improve comment
9b964d1
remove code from autocomplete
c277cbe
Translate comments to TSDoc
108b3d8
split if logic up and add comments
e2522d5
add more TSDoc
b5a5af9
update comment
6501013
Merge branch 'develop' into alunturner/mentions-as-links-rte
artcodespace File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
Copyright 2023 The Matrix.org Foundation C.I.C. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import React, { ForwardedRef, forwardRef } from "react"; | ||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; | ||
import { FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; | ||
|
||
import { useRoomContext } from "../../../../../contexts/RoomContext"; | ||
import Autocomplete from "../../Autocomplete"; | ||
import { ICompletion } from "../../../../../autocomplete/Autocompleter"; | ||
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; | ||
|
||
interface WysiwygAutocompleteProps { | ||
/** | ||
* The suggestion output from the rust model is used to build the query that is | ||
* passed to the `<Autocomplete />` component | ||
*/ | ||
suggestion: MappedSuggestion | null; | ||
|
||
/** | ||
* This handler will be called with the href and display text for a mention on clicking | ||
* a mention in the autocomplete list or pressing enter on a selected item | ||
*/ | ||
handleMention: FormattingFunctions["mention"]; | ||
} | ||
|
||
/** | ||
* Builds the query for the `<Autocomplete />` component from the rust suggestion. This | ||
* will change as we implement handling / commands. | ||
* | ||
* @param suggestion - represents if the rust model is tracking a potential mention | ||
* @returns an empty string if we can not generate a query, otherwise a query beginning | ||
* with @ for a user query, # for a room or space query | ||
*/ | ||
function buildQuery(suggestion: MappedSuggestion | null): string { | ||
if (!suggestion || !suggestion.keyChar || suggestion.type === "command") { | ||
// if we have an empty key character, we do not build a query | ||
// TODO implement the command functionality | ||
return ""; | ||
} | ||
|
||
return `${suggestion.keyChar}${suggestion.text}`; | ||
} | ||
|
||
/** | ||
* Given a room type mention, determine the text that should be displayed in the mention | ||
* TODO expand this function to more generally handle outputting the display text from a | ||
* given completion | ||
* | ||
* @param completion - the item selected from the autocomplete, currently treated as a room completion | ||
* @param client - the MatrixClient is required for us to look up the correct room mention text | ||
* @returns the text to display in the mention | ||
*/ | ||
function getRoomMentionText(completion: ICompletion, client: MatrixClient): string { | ||
const roomId = completion.completionId; | ||
const alias = completion.completion; | ||
|
||
let roomForAutocomplete: Room | null | undefined; | ||
|
||
// Not quite sure if the logic here makes sense - specifically calling .getRoom with an alias | ||
// that doesn't start with #, but keeping the logic the same as in PartCreator.roomPill for now | ||
if (roomId) { | ||
roomForAutocomplete = client.getRoom(roomId); | ||
} else if (!alias.startsWith("#")) { | ||
roomForAutocomplete = client.getRoom(alias); | ||
} else { | ||
roomForAutocomplete = client.getRooms().find((r) => { | ||
return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); | ||
}); | ||
} | ||
|
||
// if we haven't managed to find the room, use the alias as a fallback | ||
return roomForAutocomplete?.name || alias; | ||
} | ||
|
||
/** | ||
* Given the current suggestion from the rust model and a handler function, this component | ||
* will display the legacy `<Autocomplete />` component (as used in the BasicMessageComposer) | ||
* and call the handler function with the required arguments when a mention is selected | ||
* | ||
* @param props.ref - the ref will be attached to the rendered `<Autocomplete />` component | ||
*/ | ||
const WysiwygAutocomplete = forwardRef( | ||
({ suggestion, handleMention }: WysiwygAutocompleteProps, ref: ForwardedRef<Autocomplete>): JSX.Element | null => { | ||
const { room } = useRoomContext(); | ||
const client = useMatrixClientContext(); | ||
|
||
function handleConfirm(completion: ICompletion): void { | ||
if (!completion.href) return; | ||
|
||
switch (completion.type) { | ||
case "user": | ||
handleMention(completion.href, completion.completion); | ||
break; | ||
case "room": { | ||
handleMention(completion.href, getRoomMentionText(completion, client)); | ||
break; | ||
} | ||
// TODO implement the command functionality | ||
// case "command": | ||
// console.log("/command functionality not yet in place"); | ||
// break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
return room ? ( | ||
<div className="mx_SendWysiwygComposer_AutoCompleteWrapper" data-testid="autocomplete-wrapper"> | ||
<Autocomplete | ||
ref={ref} | ||
query={buildQuery(suggestion)} | ||
onConfirm={handleConfirm} | ||
selection={{ start: 0, end: 0 }} | ||
room={room} | ||
/> | ||
</div> | ||
) : null; | ||
}, | ||
); | ||
|
||
WysiwygAutocomplete.displayName = "WysiwygAutocomplete"; | ||
|
||
export { WysiwygAutocomplete }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,15 +14,21 @@ See the License for the specific language governing permissions and | |
limitations under the License. | ||
*/ | ||
|
||
import React, { memo, MutableRefObject, ReactNode, useEffect } from "react"; | ||
import React, { memo, MutableRefObject, ReactNode, useEffect, useRef } from "react"; | ||
import { useWysiwyg, FormattingFunctions } from "@matrix-org/matrix-wysiwyg"; | ||
import classNames from "classnames"; | ||
|
||
import Autocomplete from "../../Autocomplete"; | ||
import { WysiwygAutocomplete } from "./WysiwygAutocomplete"; | ||
import { FormattingButtons } from "./FormattingButtons"; | ||
import { Editor } from "./Editor"; | ||
import { useInputEventProcessor } from "../hooks/useInputEventProcessor"; | ||
import { useSetCursorPosition } from "../hooks/useSetCursorPosition"; | ||
import { useIsFocused } from "../hooks/useIsFocused"; | ||
import { useRoomContext } from "../../../../../contexts/RoomContext"; | ||
import defaultDispatcher from "../../../../../dispatcher/dispatcher"; | ||
import { Action } from "../../../../../dispatcher/actions"; | ||
import { parsePermalink } from "../../../../../utils/permalinks/Permalinks"; | ||
|
||
interface WysiwygComposerProps { | ||
disabled?: boolean; | ||
|
@@ -47,21 +53,53 @@ export const WysiwygComposer = memo(function WysiwygComposer({ | |
rightComponent, | ||
children, | ||
}: WysiwygComposerProps) { | ||
const inputEventProcessor = useInputEventProcessor(onSend, initialContent); | ||
const { room } = useRoomContext(); | ||
const autocompleteRef = useRef<Autocomplete | null>(null); | ||
|
||
const { ref, isWysiwygReady, content, actionStates, wysiwyg } = useWysiwyg({ initialContent, inputEventProcessor }); | ||
const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent); | ||
const { ref, isWysiwygReady, content, actionStates, wysiwyg, suggestion } = useWysiwyg({ | ||
initialContent, | ||
inputEventProcessor, | ||
}); | ||
const { isFocused, onFocus } = useIsFocused(); | ||
|
||
const isReady = isWysiwygReady && !disabled; | ||
const computedPlaceholder = (!content && placeholder) || undefined; | ||
|
||
useSetCursorPosition(!isReady, ref); | ||
|
||
useEffect(() => { | ||
if (!disabled && content !== null) { | ||
onChange?.(content); | ||
} | ||
}, [onChange, content, disabled]); | ||
|
||
const isReady = isWysiwygReady && !disabled; | ||
useSetCursorPosition(!isReady, ref); | ||
useEffect(() => { | ||
function handleClick(e: Event): void { | ||
e.preventDefault(); | ||
if ( | ||
e.target && | ||
e.target instanceof HTMLAnchorElement && | ||
e.target.getAttribute("data-mention-type") === "user" | ||
) { | ||
const parsedLink = parsePermalink(e.target.href); | ||
if (room && parsedLink?.userId) | ||
defaultDispatcher.dispatch({ | ||
action: Action.ViewUser, | ||
member: room.getMember(parsedLink.userId), | ||
}); | ||
} | ||
} | ||
|
||
const { isFocused, onFocus } = useIsFocused(); | ||
const computedPlaceholder = (!content && placeholder) || undefined; | ||
const mentions = ref.current?.querySelectorAll("a[data-mention-type]"); | ||
if (mentions) { | ||
mentions.forEach((mention) => mention.addEventListener("click", handleClick)); | ||
} | ||
|
||
return () => { | ||
if (mentions) mentions.forEach((mention) => mention.removeEventListener("click", handleClick)); | ||
}; | ||
}, [ref, room, content]); | ||
Comment on lines
+77
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We get link tags (representing mentions) from the rust model in a string of HTML that gets set as inner HTML on any update to the rust model. We will be able to style the links using attributes they have, but for click handling we need to add/remove the handlers on any change of the rust model output. |
||
|
||
return ( | ||
<div | ||
|
@@ -70,6 +108,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({ | |
onFocus={onFocus} | ||
onBlur={onFocus} | ||
> | ||
<WysiwygAutocomplete ref={autocompleteRef} suggestion={suggestion} handleMention={wysiwyg.mention} /> | ||
<FormattingButtons composer={wysiwyg} actionStates={actionStates} /> | ||
<Editor | ||
ref={ref} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.