diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 6b6f2f3b2c..badb6a57d1 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -113,26 +113,124 @@ const InsertImageForm = ({ onInsert }: InsertImageProps) => { ); }; -const Toolbar = ({ editor }: { editor: Editor }) => { - const setLink = useCallback(() => { - const previousUrl = editor.getAttributes("link").href; - const url = window.prompt("URL", previousUrl); +const InsertLinkFormSchema = z.object({ + url: z.string(), + displayText: z.string(), +}); - // cancelled - if (url === null) { - return; - } +type InsertLinkFormValues = z.infer; - // empty - if (url === "") { - editor.chain().focus().extendMarkRange("link").unsetLink().run(); +type InsertLinkProps = { + onInsert: (value: InsertLinkFormValues) => void; + editor: Editor; +}; - return; - } +const InsertLinkForm = ({ onInsert, editor }: InsertLinkProps) => { + const previousUrl = editor.getAttributes("link").href; + if (previousUrl) { + // If the current selection contains a link, then select the whole text + // to ensure the full displayText associated with the link is editable + editor.commands.extendMarkRange("link"); + } + const displayText = editor.state.doc.textBetween( + editor.view.state.selection.from, + editor.view.state.selection.to, + " ", + ); + const form = useForm({ + resolver: zodResolver(InsertLinkFormSchema), + // Defaulting to "https://" to save the user from having to type it + defaultValues: { url: previousUrl || "https://", displayText }, + }); - // update link - editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); - }, [editor]); + const onSubmit = (values: InsertLinkFormValues) => { + onInsert(values); + form.reset(); + }; + + return ( +
+ + ( + + URL + + + + + + )} + /> + + ( + + Display Text + + + + + )} + /> + +
+ +
+ + + ); +}; + +const Toolbar = ({ editor }: { editor: Editor }) => { + const setLink = useCallback( + (url: string, displayText: string) => { + const { from, to } = editor.view.state.selection; + // empty + if (url === "") { + editor + .chain() + .focus() + .extendMarkRange("link") + .unsetLink() + .insertContentAt({ from, to }, displayText) + .run(); + + return; + } + + // No text was previously selected, so add new text + if (from === to) { + editor + .chain() + .focus() + .extendMarkRange("link") + .setLink({ href: url }) + .command(({ tr }) => { + tr.insertText(displayText); + return true; + }) + .run(); + } else { + // Text was selected, so replace the selected text with the displayText input + editor + .chain() + .insertContentAt({ from, to }, displayText) + .setTextSelection({ from, to: from + displayText.length }) + .extendMarkRange("link") + .setLink({ href: url }) + .focus() + .run(); + } + }, + [editor], + ); return (
@@ -191,11 +289,24 @@ const Toolbar = ({ editor }: { editor: Editor }) => { - - - + + + + + + + + { + const { url, displayText } = props; + setLink(url, displayText); + }} + editor={editor} + /> + +