From 04238826ddc07506e42eb4cee6816028d3b11194 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Thu, 25 Jan 2024 20:57:57 -0800 Subject: [PATCH 01/14] Establish up types for a InsertLink Popup --- libs/ui/src/components/rich-input.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 6b6f2f3b2c..058714af12 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -113,6 +113,19 @@ const InsertImageForm = ({ onInsert }: InsertImageProps) => { ); }; +const InsertLinkFormSchema = z.object({ + url: z.string(), + displayText: z.string(), +}); + +type InsertLinkFormValues = z.infer; + +type InsertLinkProps = { + onInsert: (value: InsertLinkFormValues) => void; + displayText?: string; + previousUrl?: string; +}; + const Toolbar = ({ editor }: { editor: Editor }) => { const setLink = useCallback(() => { const previousUrl = editor.getAttributes("link").href; From 7b8fba779568aaa78462d9ff28c75888839f5ba9 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Thu, 25 Jan 2024 20:59:19 -0800 Subject: [PATCH 02/14] Set up skeleton for InsertLinkForm component --- libs/ui/src/components/rich-input.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 058714af12..d3a342d3f9 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -126,6 +126,20 @@ type InsertLinkProps = { previousUrl?: string; }; +const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: InsertLinkProps) => { + const form = useForm({ + resolver: zodResolver(InsertLinkFormSchema), + defaultValues: { url: previousUrl, displayText }, + }); + + const onSubmit = (values: InsertLinkFormValues) => { + onInsert(values); + form.reset(); + }; + + return null; +}; + const Toolbar = ({ editor }: { editor: Editor }) => { const setLink = useCallback(() => { const previousUrl = editor.getAttributes("link").href; From 617a9d7faa537ffe01fbdf7ec9847b82a473f563 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Thu, 25 Jan 2024 20:59:54 -0800 Subject: [PATCH 03/14] At FormField elements and button to add a link --- libs/ui/src/components/rich-input.tsx | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index d3a342d3f9..41160f3b6a 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -137,7 +137,44 @@ const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: Insert form.reset(); }; - return null; + return ( +
+ + ( + + URL + + + + + + )} + /> + + ( + + Display Text + + + + + )} + /> + +
+ +
+ + + ); }; const Toolbar = ({ editor }: { editor: Editor }) => { From 0ac23f4e1b1436631009b1cb4c6c6624178f7d00 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Thu, 25 Jan 2024 21:00:52 -0800 Subject: [PATCH 04/14] Replace Hyperlink button with Popover --- libs/ui/src/components/rich-input.tsx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 41160f3b6a..d1bdbf3ee6 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -255,11 +255,26 @@ const Toolbar = ({ editor }: { editor: Editor }) => { - - - + + + + + + + + {}} + displayText={editor.state.doc.textBetween( + editor.view.state.selection.from, + editor.view.state.selection.to, + " ", + )} + previousUrl={editor.getAttributes("link").href} + /> + + Date: Thu, 25 Jan 2024 21:02:15 -0800 Subject: [PATCH 05/14] Add partially altered setLink function Coding timeblock exceeded, stopping for now to resume another day --- libs/ui/src/components/rich-input.tsx | 60 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index d1bdbf3ee6..31fac26337 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -178,25 +178,42 @@ const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: Insert }; const Toolbar = ({ editor }: { editor: Editor }) => { - const setLink = useCallback(() => { - const previousUrl = editor.getAttributes("link").href; - const url = window.prompt("URL", previousUrl); - - // cancelled - if (url === null) { - return; - } - - // empty - if (url === "") { - editor.chain().focus().extendMarkRange("link").unsetLink().run(); - - return; - } - - // update link - editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); - }, [editor]); + const setLink = useCallback( + (url: string, displayText: string) => { + /** + * @todo Implementation incomplete, need to continue to revise + */ + // empty + if (url === "") { + editor.chain().focus().extendMarkRange("link").unsetLink().run(); + + return; + } + const { from, to } = editor.view.state.selection; + + // 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 { + editor + .chain() + .setTextSelection({ from, to }) + .extendMarkRange("link") + .setLink({ href: url }) + .run(); + } + }, + [editor], + ); return (
@@ -265,7 +282,10 @@ const Toolbar = ({ editor }: { editor: Editor }) => { {}} + onInsert={(props) => { + const { url, displayText } = props; + setLink(url, displayText); + }} displayText={editor.state.doc.textBetween( editor.view.state.selection.from, editor.view.state.selection.to, From e37072becb2f585104934ac8c1bded2ad60cf590 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Fri, 26 Jan 2024 20:15:36 -0800 Subject: [PATCH 06/14] Add chained .focus call to close Popover Fixes bug where the Popover would not close when adding a link to previously selected text --- libs/ui/src/components/rich-input.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 31fac26337..d4a467dcc1 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -180,9 +180,6 @@ const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: Insert const Toolbar = ({ editor }: { editor: Editor }) => { const setLink = useCallback( (url: string, displayText: string) => { - /** - * @todo Implementation incomplete, need to continue to revise - */ // empty if (url === "") { editor.chain().focus().extendMarkRange("link").unsetLink().run(); @@ -209,6 +206,7 @@ const Toolbar = ({ editor }: { editor: Editor }) => { .setTextSelection({ from, to }) .extendMarkRange("link") .setLink({ href: url }) + .focus() .run(); } }, From 906997b24e31632ca24f1245e16d883da8f60904 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Fri, 26 Jan 2024 20:25:03 -0800 Subject: [PATCH 07/14] Account for new text being added Replace the original text, in-case the user inputted new text --- libs/ui/src/components/rich-input.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index d4a467dcc1..32091e71f9 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -201,9 +201,11 @@ const Toolbar = ({ editor }: { editor: Editor }) => { }) .run(); } else { + // Text was selected, so replace the selected text with the displayText input editor .chain() - .setTextSelection({ from, to }) + .insertContentAt({ from, to }, displayText) + .setTextSelection({ from, to: displayText.length + 1 }) .extendMarkRange("link") .setLink({ href: url }) .focus() From 40a8963b9cbfb2924caf31aabb2eb30bcbe512b2 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Fri, 26 Jan 2024 20:27:30 -0800 Subject: [PATCH 08/14] Account for no URL, but new text being added --- libs/ui/src/components/rich-input.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 32091e71f9..89f542c6c1 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -180,13 +180,19 @@ const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: Insert 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().run(); + editor + .chain() + .focus() + .extendMarkRange("link") + .unsetLink() + .insertContentAt({ from, to }, displayText) + .run(); return; } - const { from, to } = editor.view.state.selection; // No text was previously selected, so add new text if (from === to) { From 511efaad10bf6f8e7418fc3a3ebda8b25349d1f4 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Fri, 26 Jan 2024 20:32:31 -0800 Subject: [PATCH 09/14] Add todo for bug found when testing --- libs/ui/src/components/rich-input.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 89f542c6c1..8f12802a7c 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -181,6 +181,9 @@ const Toolbar = ({ editor }: { editor: Editor }) => { const setLink = useCallback( (url: string, displayText: string) => { const { from, to } = editor.view.state.selection; + /** + * @todo Fix bug where a highlight contains both a link, and non-linked text + */ // empty if (url === "") { editor From 5f9f058709fa9cdc7c958a5c22eb4b6eca9b070f Mon Sep 17 00:00:00 2001 From: CorreyL Date: Sun, 28 Jan 2024 18:19:11 -0800 Subject: [PATCH 10/14] Have insertion account for non-beginning text Setting `to` to displayText.length only works if `from === 1` (i.e. the cursor is at the beginning of the textbox) The correct calculation is to offset from `from`, then add `displayText.length` to it --- libs/ui/src/components/rich-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 8f12802a7c..5d8f47ddbc 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -214,7 +214,7 @@ const Toolbar = ({ editor }: { editor: Editor }) => { editor .chain() .insertContentAt({ from, to }, displayText) - .setTextSelection({ from, to: displayText.length + 1 }) + .setTextSelection({ from, to: from + displayText.length }) .extendMarkRange("link") .setLink({ href: url }) .focus() From f7ad75af51e971be079db12842f9bca3da1e8d44 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Sun, 28 Jan 2024 18:28:20 -0800 Subject: [PATCH 11/14] Prepend "https://" as default url when empty Saves the user presses on the keyboard from having to type the protocol, while leaving it editable on the off-chance the user wishes to change it to http://, or some other protocol --- libs/ui/src/components/rich-input.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 5d8f47ddbc..cf5532f9a2 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -129,7 +129,8 @@ type InsertLinkProps = { const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: InsertLinkProps) => { const form = useForm({ resolver: zodResolver(InsertLinkFormSchema), - defaultValues: { url: previousUrl, displayText }, + // Defaulting to "https://" to save the user from having to type it + defaultValues: { url: previousUrl || "https://", displayText }, }); const onSubmit = (values: InsertLinkFormValues) => { From 35806a3cbde7f7659055b8d83fcf14be745e2870 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Sun, 28 Jan 2024 18:31:49 -0800 Subject: [PATCH 12/14] Replace todo Original bug fixed, new bug found --- libs/ui/src/components/rich-input.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index cf5532f9a2..95cad6515b 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -183,7 +183,11 @@ const Toolbar = ({ editor }: { editor: Editor }) => { (url: string, displayText: string) => { const { from, to } = editor.view.state.selection; /** - * @todo Fix bug where a highlight contains both a link, and non-linked text + * @todo Fix bug where an existing link that is either partially + * highlighted, or the cursor is in the middle of the linked text, only + * has the URL populated in the Popover, but not the displayText + * + * The editor will also need to select this text */ // empty if (url === "") { From 8bec5f9d9614afd7f153fd5c3b4f1853049cc1cd Mon Sep 17 00:00:00 2001 From: CorreyL Date: Sun, 28 Jan 2024 19:05:16 -0800 Subject: [PATCH 13/14] Account for cursor in middle of link text If the cursor was in the middle of text with a hyperlink, or only part of the text with a hyperlink was selected, then the displayText would not accurately reflect the full text that the link was associated with Since this refactor required accessing and modifying the editor, pass the editor as a prop instead, and instantiate what were previously props as variables within the InsertLinkForm component --- libs/ui/src/components/rich-input.tsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 95cad6515b..27d32e3d08 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -122,11 +122,21 @@ type InsertLinkFormValues = z.infer; type InsertLinkProps = { onInsert: (value: InsertLinkFormValues) => void; - displayText?: string; - previousUrl?: string; + editor: Editor; }; -const InsertLinkForm = ({ onInsert, displayText = "", previousUrl = "" }: InsertLinkProps) => { +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 @@ -300,12 +310,7 @@ const Toolbar = ({ editor }: { editor: Editor }) => { const { url, displayText } = props; setLink(url, displayText); }} - displayText={editor.state.doc.textBetween( - editor.view.state.selection.from, - editor.view.state.selection.to, - " ", - )} - previousUrl={editor.getAttributes("link").href} + editor={editor} /> From eec9a40348b43e77a35a7502e7ab0f1295d0d202 Mon Sep 17 00:00:00 2001 From: CorreyL Date: Sun, 28 Jan 2024 19:10:32 -0800 Subject: [PATCH 14/14] Remove fixed todo Fixed in 8bec5f9d --- libs/ui/src/components/rich-input.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libs/ui/src/components/rich-input.tsx b/libs/ui/src/components/rich-input.tsx index 27d32e3d08..badb6a57d1 100644 --- a/libs/ui/src/components/rich-input.tsx +++ b/libs/ui/src/components/rich-input.tsx @@ -192,13 +192,6 @@ const Toolbar = ({ editor }: { editor: Editor }) => { const setLink = useCallback( (url: string, displayText: string) => { const { from, to } = editor.view.state.selection; - /** - * @todo Fix bug where an existing link that is either partially - * highlighted, or the cursor is in the middle of the linked text, only - * has the URL populated in the Popover, but not the displayText - * - * The editor will also need to select this text - */ // empty if (url === "") { editor