From 0f3f6e843af8de33d9984fc96dd9324c1cdafe2c Mon Sep 17 00:00:00 2001 From: KetanKBaboo Date: Mon, 11 May 2026 11:19:23 +0530 Subject: [PATCH 1/2] handle RTL language rendering part for OBS --- frontend/src/components/DetailsDialog.tsx | 195 +++++++++++----------- frontend/src/components/OBSViewDialog.tsx | 3 +- frontend/src/utils/obsParser.ts | 61 ++++--- 3 files changed, 142 insertions(+), 117 deletions(-) diff --git a/frontend/src/components/DetailsDialog.tsx b/frontend/src/components/DetailsDialog.tsx index 709da46..2436f79 100644 --- a/frontend/src/components/DetailsDialog.tsx +++ b/frontend/src/components/DetailsDialog.tsx @@ -1,108 +1,117 @@ import { useMemo, useState } from "react"; import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogFooter, - DialogClose, + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogClose, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { MoreHorizontal } from "lucide-react"; type DetailsCellProps = { - text?: string | null; - limit?: number; - title?: string; - triggerVariant?: "text" | "icon"; - justify?: "between" | "start" | "center" + text?: string | null; + limit?: number; + title?: string; + triggerVariant?: "text" | "icon"; + justify?: "between" | "start" | "center"; }; export function DetailsDialog({ - text, - limit = 25, - title = "Details", - triggerVariant = "text", - justify = "between" + text, + limit = 25, + title = "Details", + triggerVariant = "text", + justify = "between", }: DetailsCellProps) { - const [open, setOpen] = useState(false); - const value = text ?? ""; - - const isTruncated = value.length > limit; - const wrapperJustifyClass = justify === "start" ? "justify-start" : justify === "center" ? "justify-center" : "justify-between" - - const display = useMemo(() => { - if (!value) return ""; - - if (!isTruncated) return value; - - if (triggerVariant === "text") { - return value.slice(0, limit) + "..."; - } - - return value.slice(0, limit); - }, [value, limit, isTruncated, triggerVariant]); - - const handleOpen = () => setOpen(true); - - return ( - <> -
-
- {display} -
- - - {isTruncated && ( - <> - {triggerVariant === "text" && ( - - )} - - {triggerVariant === "icon" && ( - + )} + + {triggerVariant === "icon" && ( + - )} - - )} + title="See More" + > + + + )} + + )} +
+ + + +
+ + {title} +
+ + +
+
+ {value || "No details available."} +
- - - -
- - {title} -
- - -
-
- {value || "No details available."} -
-
- - - - - - -
- -
- - ); + + + + + +
+ +
+ + ); } diff --git a/frontend/src/components/OBSViewDialog.tsx b/frontend/src/components/OBSViewDialog.tsx index e755946..9a81b0e 100644 --- a/frontend/src/components/OBSViewDialog.tsx +++ b/frontend/src/components/OBSViewDialog.tsx @@ -613,7 +613,7 @@ export const OBSViewDialog = ({ className="flex-1 overflow-auto border rounded" > {!editMode && ( -
+
{previewMode ? ( + header + .replace(/^\uFEFF/, "") + .trim() + .toLowerCase(); + export const isISLOBSCSV = (headers: string[]) => - ISL_HEADERS.every((h) => - headers.map((x) => x.toLowerCase()).includes(h.toLowerCase()), - ); + ISL_HEADERS.every((h) => headers.map(cleanHeader).includes(h.toLowerCase())); + +function extractHeadingData(headingLine: string): { + story_no: number; + title: string; +} { + const normalized = headingLine.normalize("NFC"); + + const match = normalized.match(OBS_HEADING_REGEX); + + if (!match) { + throw new Error( + "Invalid OBS heading. Expected '# 1. Title' or '# 1۔ عنوان'", + ); + } + + return { + story_no: Number(match[1]), + title: match[2].trim(), + }; +} export function parseISLOBSCSV(file: File): Promise { return new Promise((resolve, reject) => { @@ -43,20 +72,13 @@ export async function parseOBSMarkdown(file: File): Promise { const lines = content.split("\n"); // markdown heading - const headingLine = lines.find((l) => l.startsWith("#")); + const headingLine = lines.find((l) => l.trim().startsWith("#")); if (!headingLine) { throw new Error("Invalid OBS markdown: missing heading"); } - const match = headingLine.match(/^#+\s*(\d+)[.\s]+(.+)/); - - if (!match) { - throw new Error("Invalid OBS heading. Expected format: '# 1. Title'"); - } - - const story_no = Number(match[1]); - const title = match[2].trim(); + const { story_no, title } = extractHeadingData(headingLine); return { story_no, @@ -78,20 +100,13 @@ export function extractOBSMetadataFromMarkdown(markdown: string): { title: string; } { const lines = markdown.split("\n"); - const headingLine = lines.find((l) => l.startsWith("#")); + const headingLine = lines.find((l) => + l.trim().startsWith("#"), + ); if (!headingLine) { throw new Error("Invalid OBS markdown: missing heading"); } - const match = headingLine.match(/^#+\s*(\d+)[.\s]+(.+)/); - - if (!match) { - throw new Error("Invalid OBS heading. Expected format: '# 1. Title'"); - } - - return { - story_no: Number(match[1]), - title: match[2].trim(), - }; + return extractHeadingData(headingLine); } From 5769c591a2940b3ab6c8c4d901f09d9b578635e7 Mon Sep 17 00:00:00 2001 From: RevantCI Date: Mon, 11 May 2026 17:30:21 +0530 Subject: [PATCH 2/2] handle BOM in windows OS for OBS md file --- frontend/src/utils/obsParser.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frontend/src/utils/obsParser.ts b/frontend/src/utils/obsParser.ts index aa8bab1..9fbc44e 100644 --- a/frontend/src/utils/obsParser.ts +++ b/frontend/src/utils/obsParser.ts @@ -6,7 +6,7 @@ const ISL_HEADERS = ["storyNo", "title", "url", "description"]; // Supports: // # 1. Creation // # 1۔ تخلیق -const OBS_HEADING_REGEX = /^#+\s*(\d+)\s*[.۔]\s*(.+)$/u; +const OBS_HEADING_REGEX = /^#+\s*(\d+)\s*[^\p{L}\p{N}]\s*(.+)$/u; const cleanHeader = (header: string) => header @@ -69,9 +69,9 @@ export function parseISLOBSCSV(file: File): Promise { export async function parseOBSMarkdown(file: File): Promise { const content = await file.text(); - const lines = content.split("\n"); - // markdown heading + const lines = normalizeContent(content).split("\n"); + const headingLine = lines.find((l) => l.trim().startsWith("#")); if (!headingLine) { @@ -99,10 +99,9 @@ export function extractOBSMetadataFromMarkdown(markdown: string): { story_no: number; title: string; } { - const lines = markdown.split("\n"); - const headingLine = lines.find((l) => - l.trim().startsWith("#"), - ); + const lines = normalizeContent(markdown).split("\n"); + + const headingLine = lines.find((l) => l.trim().startsWith("#")); if (!headingLine) { throw new Error("Invalid OBS markdown: missing heading"); @@ -110,3 +109,9 @@ export function extractOBSMetadataFromMarkdown(markdown: string): { return extractHeadingData(headingLine); } + +const normalizeContent = (content: string) => + content + .replace(/^\uFEFF/, "") + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n"); \ No newline at end of file