feat: replace rich text editor with custom Tiptap implementation and add HTML formatting utilities
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
} from "@/app/actions/questions";
|
||||
import { Plus, Trash, ArrowUp, ArrowDown, Loader2, Save, Image as ImageIcon, X } from "lucide-react";
|
||||
import Image from 'next/image';
|
||||
import { RichTextEditor } from "@/components/ui/rich-text-editor";
|
||||
import { RichTextEditor } from "@/components/rich-text-editor";
|
||||
|
||||
interface Question {
|
||||
id: string;
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
import { Copy, Check, Users, Calendar, Clock } from "lucide-react";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import Image from 'next/image';
|
||||
import { stripHtml } from "@/lib/utils";
|
||||
import { HtmlDisplay } from "@/components/html-display";
|
||||
|
||||
interface Question {
|
||||
id: string;
|
||||
@@ -112,9 +114,9 @@ export default function SubmissionDetailClient({
|
||||
>
|
||||
<CardHeader className="pb-3 flex flex-row items-start justify-between space-y-0 gap-4">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-base font-semibold leading-normal">
|
||||
{q.label}
|
||||
</Label>
|
||||
<div className="text-base font-semibold leading-normal">
|
||||
<HtmlDisplay html={q.label} />
|
||||
</div>
|
||||
{wasEdited && (
|
||||
<p className="text-xs text-muted-foreground italic flex items-center gap-1">
|
||||
edited
|
||||
@@ -173,7 +175,7 @@ export default function SubmissionDetailClient({
|
||||
History
|
||||
</ContextMenuLabel>
|
||||
<ContextMenuLabel className="text-sm font-medium text-foreground truncate pb-2">
|
||||
{q.label}
|
||||
{stripHtml(q.label)}
|
||||
</ContextMenuLabel>
|
||||
|
||||
<ContextMenuSeparator />
|
||||
|
||||
+4
-41
@@ -9,6 +9,9 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { cn, stripHtml } from "@/lib/utils";
|
||||
import { HtmlDisplay } from "@/components/html-display";
|
||||
import { FormattedText } from "@/components/formatted-text";
|
||||
import { Dialog, DialogContent, DialogTrigger, DialogTitle } from "@/components/ui/dialog";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||
import { toast } from "sonner";
|
||||
@@ -28,40 +31,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
|
||||
function FormattedText({ text }: { text: string }) {
|
||||
const lines = text.split('\n');
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
|
||||
return (
|
||||
<>
|
||||
{lines.map((line, i) => {
|
||||
const parts = line.split(urlRegex);
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
{i > 0 && <br />}
|
||||
{parts.map((part, j) => {
|
||||
if (part.match(urlRegex)) {
|
||||
return (
|
||||
<a
|
||||
key={j}
|
||||
href={part}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline underline-offset-4"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{part}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return part;
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormLayoutClient({
|
||||
userName,
|
||||
@@ -119,10 +89,6 @@ interface FormFillerClientProps {
|
||||
const LS_KEY = (formId: string) => `form-draft-${formId}`;
|
||||
const QUESTIONS_PER_STEP = 3;
|
||||
|
||||
const stripHtml = (html: string) => {
|
||||
return (html || "").replace(/<[^>]*>?/gm, '').trim();
|
||||
};
|
||||
|
||||
const buildSchema = (questions: Question[]) => {
|
||||
const shape: Record<string, z.ZodTypeAny> = {};
|
||||
questions.forEach(q => {
|
||||
@@ -315,10 +281,7 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="text-base leading-normal mt-1 flex-1">
|
||||
<div
|
||||
className="prose prose-sm dark:prose-invert max-w-none [&>p]:m-0"
|
||||
dangerouslySetInnerHTML={{ __html: q.label || "" }}
|
||||
/>
|
||||
<HtmlDisplay html={q.label} />
|
||||
</div>
|
||||
<div className="flex items-start gap-4 shrink-0">
|
||||
{q.imageUrl && (
|
||||
|
||||
Reference in New Issue
Block a user