From 6dc9a3db50529d33e08438f0532d54cf1239b718 Mon Sep 17 00:00:00 2001 From: Luis Novo Date: Sun, 25 Jan 2026 21:36:58 -0300 Subject: [PATCH] feat: detect HTML content in clipboard for text sources (#475) * chore: bump content-core to support html to markdown * feat: detect HTML content in clipboard for text sources - Add paste handler to detect text/html format in clipboard - Use HTML content instead of plain text when available - Display info message when HTML is detected - Add translations for all supported languages (en-US, pt-BR, ja-JP, zh-CN, zh-TW) * fix: reset HTML detection banner on plain text paste Clear the hasHtmlContent flag when pasting plain text (no HTML in clipboard) so the banner doesn't persist incorrectly after replacing HTML content with plain text. --- CHANGELOG.md | 9 ++++ .../components/sources/AddSourceDialog.tsx | 2 + .../sources/steps/SourceTypeStep.tsx | 41 +++++++++++++++++-- frontend/src/lib/locales/en-US/index.ts | 1 + frontend/src/lib/locales/ja-JP/index.ts | 1 + frontend/src/lib/locales/pt-BR/index.ts | 1 + frontend/src/lib/locales/zh-CN/index.ts | 1 + frontend/src/lib/locales/zh-TW/index.ts | 1 + pyproject.toml | 2 +- uv.lock | 22 ++++++++-- 10 files changed, 73 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5298649..9b892bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- HTML clipboard detection for text sources (#426) + - When pasting content, automatically detects HTML format (e.g., from Word, web pages) + - Shows info message when HTML is detected, informing user it will be converted to Markdown + - Preserves formatting that would be lost with plain text paste + - Bump content-core to 0.11.0 for HTML to Markdown conversion support + ## [1.6.2] - 2026-01-24 ### Fixed diff --git a/frontend/src/components/sources/AddSourceDialog.tsx b/frontend/src/components/sources/AddSourceDialog.tsx index 195b542..a49b2d4 100644 --- a/frontend/src/components/sources/AddSourceDialog.tsx +++ b/frontend/src/components/sources/AddSourceDialog.tsx @@ -126,6 +126,7 @@ export function AddSourceDialog({ handleSubmit, control, watch, + setValue, formState: { errors }, reset, } = useForm({ @@ -553,6 +554,7 @@ export function AddSourceDialog({ // @ts-expect-error - Type inference issue with zod schema control={control} register={register} + setValue={setValue} // @ts-expect-error - Type inference issue with zod schema errors={errors} urlValidationErrors={urlValidationErrors} diff --git a/frontend/src/components/sources/steps/SourceTypeStep.tsx b/frontend/src/components/sources/steps/SourceTypeStep.tsx index 349ff2c..a4c4756 100644 --- a/frontend/src/components/sources/steps/SourceTypeStep.tsx +++ b/frontend/src/components/sources/steps/SourceTypeStep.tsx @@ -1,7 +1,7 @@ "use client" -import { useMemo } from "react" -import { Control, FieldErrors, UseFormRegister, useWatch } from "react-hook-form" +import { useMemo, useState } from "react" +import { Control, FieldErrors, UseFormRegister, UseFormSetValue, useWatch } from "react-hook-form" import { FileIcon, LinkIcon, FileTextIcon } from "lucide-react" import { useTranslation } from "@/lib/hooks/use-translation" import { FormSection } from "@/components/ui/form-section" @@ -89,6 +89,7 @@ const getSourceTypes = (t: TranslationKeys) => [ interface SourceTypeStepProps { control: Control register: UseFormRegister + setValue: UseFormSetValue errors: FieldErrors urlValidationErrors?: { url: string; line: number }[] onClearUrlErrors?: () => void @@ -96,13 +97,39 @@ interface SourceTypeStepProps { const MAX_BATCH_SIZE = 50 -export function SourceTypeStep({ control, register, errors, urlValidationErrors, onClearUrlErrors }: SourceTypeStepProps) { +export function SourceTypeStep({ control, register, setValue, errors, urlValidationErrors, onClearUrlErrors }: SourceTypeStepProps) { const { t } = useTranslation() // Watch the selected type and inputs to detect batch mode const selectedType = useWatch({ control, name: 'type' }) const urlInput = useWatch({ control, name: 'url' }) const fileInput = useWatch({ control, name: 'file' }) + // Track if HTML content was pasted + const [hasHtmlContent, setHasHtmlContent] = useState(false) + + // Handle paste event to check for HTML content in clipboard + const handleTextPaste = (event: React.ClipboardEvent) => { + const htmlContent = event.clipboardData.getData('text/html') + + // If HTML content is available, use it instead of plain text + if (htmlContent) { + event.preventDefault() + // Get current content and cursor position + const textarea = event.currentTarget + const start = textarea.selectionStart + const end = textarea.selectionEnd + const currentValue = textarea.value + + // Insert HTML content at cursor position (replacing selection if any) + const newValue = currentValue.substring(0, start) + htmlContent + currentValue.substring(end) + setValue('content', newValue, { shouldValidate: true }) + setHasHtmlContent(true) + } else { + // Plain text paste - clear the HTML indicator + setHasHtmlContent(false) + } + } + // Batch mode detection const { isBatchMode, itemCount, urlCount, fileCount } = useMemo(() => { let urlCount = 0 @@ -258,11 +285,19 @@ export function SourceTypeStep({ control, register, errors, urlValidationErrors, {type.value === 'text' && (
+ {hasHtmlContent && ( +
+

+ {t.sources.htmlDetected} +

+
+ )}