web/composer: surround selection with markdown when pasting link

This commit is contained in:
Tulir Asokan 2024-10-28 00:28:59 +02:00
parent 7c95ce35fd
commit 7e793ec0ba
3 changed files with 45 additions and 10 deletions

View file

@ -17,6 +17,7 @@ import { JSX, use, useEffect } from "react"
import { getAvatarURL, getMediaURL } from "@/api/media.ts"
import { RoomStateStore, useCustomEmojis } from "@/api/statestore"
import { Emoji, emojiToMarkdown, useSortedAndFilteredEmojis } from "@/util/emoji"
import { escapeMarkdown } from "@/util/markdown.ts"
import useEvent from "@/util/useEvent.ts"
import ClientContext from "../ClientContext.ts"
import type { ComposerState } from "./MessageComposer.tsx"
@ -111,11 +112,7 @@ export const EmojiAutocompleter = ({ params, room, ...rest }: AutocompleterProps
return useAutocompleter({ params, room, ...rest, items, ...emojiFuncs })
}
const escapeDisplayname = (input: string) => input
.replace("\n", " ")
.replace(/([\\`*_[\]])/g, "\\$1")
.replace("<", "&lt;")
.replace(">", "&gt;")
const escapeDisplayname = (input: string) => escapeMarkdown(input).replace("\n", " ")
const userFuncs = {
getText: (user: AutocompleteUser) =>

View file

@ -26,6 +26,7 @@ import type {
RoomID,
} from "@/api/types"
import { PartialEmoji, emojiToMarkdown } from "@/util/emoji"
import { escapeMarkdown } from "@/util/markdown.ts"
import useEvent from "@/util/useEvent.ts"
import ClientContext from "../ClientContext.ts"
import EmojiPicker from "../emojipicker/EmojiPicker.tsx"
@ -266,11 +267,26 @@ const MessageComposer = () => {
const onAttachFile = useEvent(
(evt: React.ChangeEvent<HTMLInputElement>) => doUploadFile(evt.target.files?.[0]),
)
useEffect(() => {
const listener = (evt: ClipboardEvent) => doUploadFile(evt.clipboardData?.files?.[0])
document.addEventListener("paste", listener)
return () => document.removeEventListener("paste", listener)
}, [doUploadFile])
const onPaste = useEvent((evt: React.ClipboardEvent<HTMLTextAreaElement>) => {
const file = evt.clipboardData?.files?.[0]
const text = evt.clipboardData.getData("text/plain")
const input = evt.currentTarget
if (file) {
doUploadFile(file)
} else if (
input.selectionStart !== input.selectionEnd
&& (text.startsWith("http://") || text.startsWith("https://") || text.startsWith("matrix:"))
) {
setState({
text: `${state.text.slice(0, input.selectionStart)}[${
escapeMarkdown(state.text.slice(input.selectionStart, input.selectionEnd))
}](${escapeMarkdown(text)})${state.text.slice(input.selectionEnd)}`,
})
} else {
return
}
evt.preventDefault()
})
// To ensure the cursor jumps to the end, do this in an effect rather than as the initial value of useState
// To try to avoid the input bar flashing, use useLayoutEffect instead of useEffect
useLayoutEffect(() => {
@ -370,6 +386,7 @@ const MessageComposer = () => {
onKeyDown={onComposerKeyDown}
onKeyUp={onComposerCaretChange}
onClick={onComposerCaretChange}
onPaste={onPaste}
onChange={onChange}
placeholder="Send a message"
id="message-composer"

21
web/src/util/markdown.ts Normal file
View file

@ -0,0 +1,21 @@
// gomuks - A Matrix client written in Go.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export const escapeMarkdown = (input: string) => input
.replace(/([\\`*_[\]])/g, "\\$1")
.replace("<", "&lt;")
.replace(">", "&gt;")