From 8860cb1622883ce97295c3f3596c6b9ea159523a Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Tue, 12 May 2026 00:26:52 +0000 Subject: [PATCH] feat: add URL detection to note tab with dynamic CTA - Note tab auto-detects when content is a URL and changes CTA to 'Save link' - URL detection uses plain text (not markdown) to avoid Tiptap serialization issues - Requires dot in hostname for protocol-less URLs to prevent false positives - URL normalization happens in parent handler for consistent Cmd+Enter and CTA behavior - TextEditor now exposes onPlainTextChange callback --- apps/web/components/add-document/index.tsx | 36 ++++++++++++++--- apps/web/components/add-document/note.tsx | 45 ++++++++++++++++++++-- apps/web/components/text-editor/index.tsx | 6 +++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/apps/web/components/add-document/index.tsx b/apps/web/components/add-document/index.tsx index d6e953f7..fd5ea889 100644 --- a/apps/web/components/add-document/index.tsx +++ b/apps/web/components/add-document/index.tsx @@ -105,6 +105,9 @@ export function AddDocument({ // Form data state for button click handling const [noteContent, setNoteContent] = useState("") + const [noteContentType, setNoteContentType] = useState<"note" | "link">( + "note", + ) const [linkData, setLinkData] = useState({ url: "", title: "", @@ -129,19 +132,28 @@ export function AddDocument({ useEffect(() => { if (!isOpen) { setFileData({ items: [], title: "", description: "" }) + setNoteContentType("note") } }, [isOpen]) // Submit handlers const handleNoteSubmit = useCallback( - (content: string) => { + (content: string, contentType: "note" | "link") => { if (!content.trim()) { toast.error("Please enter some content") return } - noteMutation.mutate({ content, project: localSelectedProject }) + if (contentType === "link") { + const normalizedUrl = normalizeUrl(content.trim()) + linkMutation.mutate({ + url: normalizedUrl, + project: localSelectedProject, + }) + } else { + noteMutation.mutate({ content, project: localSelectedProject }) + } }, - [noteMutation, localSelectedProject], + [noteMutation, linkMutation, localSelectedProject], ) const handleLinkSubmit = useCallback( @@ -215,6 +227,12 @@ export function AddDocument({ setNoteContent(content) }, []) + const handleNoteRequestSubmit = useCallback(() => { + // This will be called by Cmd+Enter from the editor + // For now it just does the note submit; Task 3 will expand this for files + handleNoteSubmit(noteContent, noteContentType) + }, [handleNoteSubmit, noteContent, noteContentType]) + const handleLinkDataChange = useCallback((data: LinkData) => { setLinkData(data) }, []) @@ -226,7 +244,7 @@ export function AddDocument({ const handleButtonClick = () => { switch (activeTab) { case "note": - handleNoteSubmit(noteContent) + handleNoteSubmit(noteContent, noteContentType) break case "link": handleLinkSubmit(linkData) @@ -311,6 +329,8 @@ export function AddDocument({ @@ -389,7 +409,13 @@ export function AddDocument({ ) : ( <> - + Add {activeTab}{" "} + {activeTab === "note" + ? noteContentType === "link" + ? "Save link" + : "Save note" + : activeTab === "link" + ? "Save link" + : `+ Add ${activeTab}`}{" "} {!isMobile && ( void + onSubmit?: (content: string, contentType: "note" | "link") => void onContentChange?: (content: string) => void + onContentTypeChange?: (type: "note" | "link") => void + onRequestSubmit?: () => void isSubmitting?: boolean isOpen?: boolean } @@ -13,16 +38,24 @@ interface NoteContentProps { export function NoteContent({ onSubmit, onContentChange, + onContentTypeChange, + onRequestSubmit, isSubmitting, isOpen, }: NoteContentProps) { const [content, setContent] = useState("") + const [plainText, setPlainText] = useState("") const canSubmit = content.trim().length > 0 && !isSubmitting const handleSubmit = () => { if (canSubmit && onSubmit) { - onSubmit(content) + const type = detectContentType(plainText) + if (type === "link") { + onSubmit(plainText.trim(), "link") + } else { + onSubmit(content, "note") + } } } @@ -35,6 +68,7 @@ export function NoteContent({ useEffect(() => { if (!isOpen) { setContent("") + setPlainText("") onContentChange?.("") } }, [isOpen, onContentChange]) @@ -44,7 +78,12 @@ export function NoteContent({ { + setPlainText(text) + const type = detectContentType(text) + onContentTypeChange?.(type) + }} + onSubmit={onRequestSubmit ?? handleSubmit} debounceMs={0} /> diff --git a/apps/web/components/text-editor/index.tsx b/apps/web/components/text-editor/index.tsx index 687c8065..c804f467 100644 --- a/apps/web/components/text-editor/index.tsx +++ b/apps/web/components/text-editor/index.tsx @@ -16,11 +16,13 @@ const extensions = [...defaultExtensions, slashCommand, Markdown] export function TextEditor({ content: initialContent, onContentChange, + onPlainTextChange, onSubmit, debounceMs = 500, }: { content: string | undefined onContentChange: (content: string) => void + onPlainTextChange?: (text: string) => void onSubmit: () => void debounceMs?: number }) { @@ -38,6 +40,8 @@ export function TextEditor({ const json = editor.getJSON() const markdown = editor.storage.markdown?.manager?.serialize(json) ?? "" onContentChange?.(markdown) + const plainText = editor.getText() + onPlainTextChange?.(plainText) }, debounceMs) const editor = useEditor({ @@ -55,6 +59,8 @@ export function TextEditor({ const json = editor.getJSON() const markdown = editor.storage.markdown?.manager?.serialize(json) ?? "" onContentChange?.(markdown) + const plainText = editor.getText() + onPlainTextChange?.(plainText) return } debouncedUpdates(editor)