diff --git a/src/common/ui/form/components/richtext.tsx b/src/common/ui/form/components/richtext.tsx index 72b3c8dd..8edfef47 100644 --- a/src/common/ui/form/components/richtext.tsx +++ b/src/common/ui/form/components/richtext.tsx @@ -76,6 +76,28 @@ export const RichTextEditor: React.FC = ({ HTMLAttributes: { class: "text-blue-600 hover:text-blue-800 underline", }, + }).extend({ + addKeyboardShortcuts() { + return { + // Ctrl/Cmd+K: add a link on plain text, or edit the URL when the + // cursor is on hyperlinked text (the prompt pre-fills with the + // existing href). Submitting an empty string removes the link. + "Mod-k": () => { + const previousUrl: string | undefined = this.editor.getAttributes("link").href; + const url = window.prompt("Enter URL:", previousUrl ?? ""); + + if (url === null) return false; + + if (url === "") { + this.editor.chain().focus().extendMarkRange("link").unsetLink().run(); + return true; + } + + this.editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); + return true; + }, + }; + }, }), ], content: value || `

${placeholder ?? ""}

`, diff --git a/src/entities/campaign/components/editor.tsx b/src/entities/campaign/components/editor.tsx index aaa0887e..370c31b1 100644 --- a/src/entities/campaign/components/editor.tsx +++ b/src/entities/campaign/components/editor.tsx @@ -135,13 +135,24 @@ export const CampaignEditor = ({ existingData, campaignId, close }: CampaignEdit // Keep the min attribute on datetime inputs up to date (client-side only). useEffect(() => { const updateMinDateTime = () => { - const newMin = Temporal.Now.instant() - .add({ minutes: 1 }) + const newMinInstant = Temporal.Now.instant().add({ minutes: 1 }); + + const newMin = newMinInstant .toZonedDateTimeISO(Temporal.Now.timeZoneId()) .toPlainDateTime() .toString({ smallestUnit: "minute" }); setMinStartDateTime(newMin); + + // If the form's stored start_ms has fallen into the past while the user + // was filling out the rest of the form, silently bump it to the new min. + // Otherwise submit fails with a confusing "please fill in required field" + // browser error because the input value is now below its min attribute. + const currentStartMs = form.getValues("start_ms"); + + if (typeof currentStartMs === "number" && currentStartMs < newMinInstant.epochMilliseconds) { + form.setValue("start_ms", newMinInstant.epochMilliseconds, { shouldDirty: true }); + } }; // Set initial value immediately on mount (client-side only) @@ -151,7 +162,7 @@ export const CampaignEditor = ({ existingData, campaignId, close }: CampaignEdit const interval = setInterval(updateMinDateTime, 60000); return () => clearInterval(interval); - }, []); + }, [form]); // "Set to current" — sets start date to right now const handleStartNow = () => {