forked from Mirrors/gomuks
web/composer: surround selection with markdown when pasting link
This commit is contained in:
parent
7c95ce35fd
commit
7e793ec0ba
3 changed files with 45 additions and 10 deletions
|
@ -17,6 +17,7 @@ import { JSX, use, useEffect } from "react"
|
||||||
import { getAvatarURL, getMediaURL } from "@/api/media.ts"
|
import { getAvatarURL, getMediaURL } from "@/api/media.ts"
|
||||||
import { RoomStateStore, useCustomEmojis } from "@/api/statestore"
|
import { RoomStateStore, useCustomEmojis } from "@/api/statestore"
|
||||||
import { Emoji, emojiToMarkdown, useSortedAndFilteredEmojis } from "@/util/emoji"
|
import { Emoji, emojiToMarkdown, useSortedAndFilteredEmojis } from "@/util/emoji"
|
||||||
|
import { escapeMarkdown } from "@/util/markdown.ts"
|
||||||
import useEvent from "@/util/useEvent.ts"
|
import useEvent from "@/util/useEvent.ts"
|
||||||
import ClientContext from "../ClientContext.ts"
|
import ClientContext from "../ClientContext.ts"
|
||||||
import type { ComposerState } from "./MessageComposer.tsx"
|
import type { ComposerState } from "./MessageComposer.tsx"
|
||||||
|
@ -111,11 +112,7 @@ export const EmojiAutocompleter = ({ params, room, ...rest }: AutocompleterProps
|
||||||
return useAutocompleter({ params, room, ...rest, items, ...emojiFuncs })
|
return useAutocompleter({ params, room, ...rest, items, ...emojiFuncs })
|
||||||
}
|
}
|
||||||
|
|
||||||
const escapeDisplayname = (input: string) => input
|
const escapeDisplayname = (input: string) => escapeMarkdown(input).replace("\n", " ")
|
||||||
.replace("\n", " ")
|
|
||||||
.replace(/([\\`*_[\]])/g, "\\$1")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace(">", ">")
|
|
||||||
|
|
||||||
const userFuncs = {
|
const userFuncs = {
|
||||||
getText: (user: AutocompleteUser) =>
|
getText: (user: AutocompleteUser) =>
|
||||||
|
|
|
@ -26,6 +26,7 @@ import type {
|
||||||
RoomID,
|
RoomID,
|
||||||
} from "@/api/types"
|
} from "@/api/types"
|
||||||
import { PartialEmoji, emojiToMarkdown } from "@/util/emoji"
|
import { PartialEmoji, emojiToMarkdown } from "@/util/emoji"
|
||||||
|
import { escapeMarkdown } from "@/util/markdown.ts"
|
||||||
import useEvent from "@/util/useEvent.ts"
|
import useEvent from "@/util/useEvent.ts"
|
||||||
import ClientContext from "../ClientContext.ts"
|
import ClientContext from "../ClientContext.ts"
|
||||||
import EmojiPicker from "../emojipicker/EmojiPicker.tsx"
|
import EmojiPicker from "../emojipicker/EmojiPicker.tsx"
|
||||||
|
@ -266,11 +267,26 @@ const MessageComposer = () => {
|
||||||
const onAttachFile = useEvent(
|
const onAttachFile = useEvent(
|
||||||
(evt: React.ChangeEvent<HTMLInputElement>) => doUploadFile(evt.target.files?.[0]),
|
(evt: React.ChangeEvent<HTMLInputElement>) => doUploadFile(evt.target.files?.[0]),
|
||||||
)
|
)
|
||||||
useEffect(() => {
|
const onPaste = useEvent((evt: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||||
const listener = (evt: ClipboardEvent) => doUploadFile(evt.clipboardData?.files?.[0])
|
const file = evt.clipboardData?.files?.[0]
|
||||||
document.addEventListener("paste", listener)
|
const text = evt.clipboardData.getData("text/plain")
|
||||||
return () => document.removeEventListener("paste", listener)
|
const input = evt.currentTarget
|
||||||
}, [doUploadFile])
|
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 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
|
// To try to avoid the input bar flashing, use useLayoutEffect instead of useEffect
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
@ -370,6 +386,7 @@ const MessageComposer = () => {
|
||||||
onKeyDown={onComposerKeyDown}
|
onKeyDown={onComposerKeyDown}
|
||||||
onKeyUp={onComposerCaretChange}
|
onKeyUp={onComposerCaretChange}
|
||||||
onClick={onComposerCaretChange}
|
onClick={onComposerCaretChange}
|
||||||
|
onPaste={onPaste}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder="Send a message"
|
placeholder="Send a message"
|
||||||
id="message-composer"
|
id="message-composer"
|
||||||
|
|
21
web/src/util/markdown.ts
Normal file
21
web/src/util/markdown.ts
Normal 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("<", "<")
|
||||||
|
.replace(">", ">")
|
Loading…
Add table
Reference in a new issue