fix: link without protocol (#6517)
This commit is contained in:
parent
01bd1bde64
commit
6a37a2ce21
1 changed files with 65 additions and 51 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react";
|
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
import { Check, Link, Trash } from "lucide-react";
|
import { Check, Link, Trash2 } from "lucide-react";
|
||||||
|
import { Dispatch, FC, SetStateAction, useCallback, useRef, useState } from "react";
|
||||||
// plane utils
|
// plane utils
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// helpers
|
// helpers
|
||||||
|
|
@ -15,22 +15,26 @@ type Props = {
|
||||||
|
|
||||||
export const BubbleMenuLinkSelector: FC<Props> = (props) => {
|
export const BubbleMenuLinkSelector: FC<Props> = (props) => {
|
||||||
const { editor, isOpen, setIsOpen } = props;
|
const { editor, isOpen, setIsOpen } = props;
|
||||||
|
// states
|
||||||
|
const [error, setError] = useState(false);
|
||||||
// refs
|
// refs
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const onLinkSubmit = useCallback(() => {
|
const handleLinkSubmit = useCallback(() => {
|
||||||
const input = inputRef.current;
|
const input = inputRef.current;
|
||||||
const url = input?.value;
|
if (!input) return;
|
||||||
if (url && isValidHttpUrl(url)) {
|
let url = input.value;
|
||||||
|
if (!url) return;
|
||||||
|
if (!url.startsWith("http")) url = `http://${url}`;
|
||||||
|
if (isValidHttpUrl(url)) {
|
||||||
setLinkEditor(editor, url);
|
setLinkEditor(editor, url);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
setError(false);
|
||||||
|
} else {
|
||||||
|
setError(true);
|
||||||
}
|
}
|
||||||
}, [editor, inputRef, setIsOpen]);
|
}, [editor, inputRef, setIsOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
inputRef.current && inputRef.current?.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full">
|
<div className="relative h-full">
|
||||||
<button
|
<button
|
||||||
|
|
@ -47,54 +51,64 @@ export const BubbleMenuLinkSelector: FC<Props> = (props) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>Link</span>
|
Link
|
||||||
<Link className="flex-shrink-0 size-3" />
|
<Link className="flex-shrink-0 size-3" />
|
||||||
</button>
|
</button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
<div className="fixed top-full z-[99999] mt-1 w-60 animate-in fade-in slide-in-from-top-1 rounded bg-custom-background-100 shadow-custom-shadow-rg">
|
||||||
<div
|
<div
|
||||||
className="dow-xl fixed top-full z-[99999] mt-1 flex w-60 overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 animate-in fade-in slide-in-from-top-1"
|
className={cn("flex rounded border border-custom-border-300 transition-colors", {
|
||||||
onKeyDown={(e) => {
|
"border-red-500": error,
|
||||||
if (e.key === "Enter") {
|
})}
|
||||||
e.preventDefault();
|
|
||||||
onLinkSubmit();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="Paste a link"
|
placeholder="Enter or paste a link"
|
||||||
onClick={(e) => {
|
onClick={(e) => e.stopPropagation()}
|
||||||
e.stopPropagation();
|
className="flex-1 border-r border-custom-border-300 bg-custom-background-100 py-2 px-1.5 text-xs outline-none placeholder:text-custom-text-400 rounded"
|
||||||
}}
|
|
||||||
className="flex-1 border-r border-custom-border-300 bg-custom-background-100 p-1 text-sm outline-none placeholder:text-custom-text-400"
|
|
||||||
defaultValue={editor.getAttributes("link").href || ""}
|
defaultValue={editor.getAttributes("link").href || ""}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
setError(false);
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
handleLinkSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={() => setError(false)}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{editor.getAttributes("link").href ? (
|
{editor.getAttributes("link").href ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center rounded-sm p-1 text-red-600 transition-all hover:bg-red-100 dark:hover:bg-red-800"
|
className="grid place-items-center rounded-sm p-1 text-red-500 hover:bg-red-500/20 transition-all"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
unsetLinkEditor(editor);
|
unsetLinkEditor(editor);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash className="h-4 w-4" />
|
<Trash2 className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="flex items-center rounded-sm p-1 text-custom-text-300 transition-all hover:bg-custom-background-90"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
className="h-full aspect-square grid place-items-center p-1 rounded-sm text-custom-text-300 hover:bg-custom-background-80 transition-all"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
onLinkSubmit();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
handleLinkSubmit();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Check className="h-4 w-4" />
|
<Check className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{error && (
|
||||||
|
<p className="text-xs text-red-500 my-1 px-2 pointer-events-none animate-in fade-in slide-in-from-top-0">
|
||||||
|
Please enter a valid URL
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue