Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 110 additions & 10 deletions src/components/SearchPage/DatasetCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Typography, Card, CardContent, Stack, Chip } from "@mui/material";
import { Colors } from "design/theme";
import React from "react";
import { useMemo } from "react";
import { Link } from "react-router-dom";
import RoutesEnum from "types/routes.enum";

Expand All @@ -17,14 +18,73 @@ interface DatasetCardProps {
info?: {
Authors?: string[];
DatasetDOI?: string;
[k: string]: any;
};
[k: string]: any;
};
};
index: number;
onChipClick: (key: string, value: string) => void;
keyword?: string; // for keyword highlight
}

/** ---------- utility helpers ---------- **/
const normalize = (s: string) =>
s
?.replace(/[\u2018\u2019\u2032]/g, "'") // curly → straight
?.replace(/[\u201C\u201D\u2033]/g, '"') ?? // curly → straight
"";

const containsKeyword = (text?: string, kw?: string) => {
if (!text || !kw) return false;
const t = normalize(text).toLowerCase();
const k = normalize(kw).toLowerCase();
return t.includes(k);
};

/** Find a short snippet in secondary fields if not already visible */
function findMatchSnippet(
v: any,
kw?: string
): { label: string; html: string } | null {
if (!kw) return null;

// Which fields to scan (can add/remove fields here)
const CANDIDATE_FIELDS: Array<[string, (v: any) => string | undefined]> = [
["Acknowledgements", (v) => v?.info?.Acknowledgements],
[
"Funding",
(v) =>
Array.isArray(v?.info?.Funding)
? v.info.Funding.join(" ")
: v?.info?.Funding,
],
["ReferencesAndLinks", (v) => v?.info?.ReferencesAndLinks],
];

const k = normalize(kw).toLowerCase();

for (const [label, getter] of CANDIDATE_FIELDS) {
const raw = getter(v); // v = parsedJson.value
if (!raw) continue;
const text = normalize(String(raw));
const i = text.toLowerCase().indexOf(k); // k is the lowercase version of keyword
if (i >= 0) {
const start = Math.max(0, i - 40);
const end = Math.min(text.length, i + k.length + 40);
const before = text.slice(start, i);
const hit = text.slice(i, i + k.length);
const after = text.slice(i + k.length, end);
const html = `${
start > 0 ? "…" : ""
}${before}<mark>${hit}</mark>${after}${end < text.length ? "…" : ""}`;
return { label, html };
}
}
return null;
}
/** ---------- end of helpers ---------- **/

const DatasetCard: React.FC<DatasetCardProps> = ({
dbname,
dsname,
Expand All @@ -40,7 +100,29 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
const rawDOI = info?.DatasetDOI?.replace(/^doi:/, "");
const doiLink = rawDOI ? `https://doi.org/${rawDOI}` : null;

// keyword hightlight functional component
// precompute what’s visible & whether it already contains the keyword
const authorsJoined = Array.isArray(info?.Authors)
? info!.Authors.join(", ")
: typeof info?.Authors === "string"
? info!.Authors
: "";

const visibleHasKeyword = useMemo(
() =>
containsKeyword(name, keyword) ||
containsKeyword(readme, keyword) ||
containsKeyword(authorsJoined, keyword),
[name, readme, authorsJoined, keyword]
);

// If not visible, produce a one-line snippet from other fields (for non-visible fields)
const snippet = useMemo(
() =>
!visibleHasKeyword ? findMatchSnippet(parsedJson.value, keyword) : null,
[parsedJson.value, keyword, visibleHasKeyword]
);

// keyword hightlight functional component (only for visible fields)
const highlightKeyword = (text: string, keyword?: string) => {
if (!keyword || !text?.toLowerCase().includes(keyword.toLowerCase())) {
return text;
Expand Down Expand Up @@ -99,7 +181,10 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
{highlightKeyword(name || "Untitled Dataset", keyword)}
</Typography>
<Typography>
Database: {dbname} &nbsp;&nbsp;|&nbsp;&nbsp; Dataset: {dsname}
{/* Database: {dbname} &nbsp;&nbsp;|&nbsp;&nbsp; Dataset: {dsname} */}
<strong>Database:</strong> {highlightKeyword(dbname, keyword)}
{" "}&nbsp;&nbsp;|&nbsp;&nbsp;{" "}
<strong>Dataset:</strong> {highlightKeyword(dsname, keyword)}
</Typography>

<Stack spacing={2} margin={1}>
Expand Down Expand Up @@ -168,20 +253,35 @@ const DatasetCard: React.FC<DatasetCardProps> = ({
{info?.Authors && (
<Typography variant="body2" mt={1}>
<strong>Authors:</strong>{" "}
{highlightKeyword(
Array.isArray(info.Authors)
? info.Authors.join(", ")
: typeof info.Authors === "string"
? info.Authors
: "N/A",
keyword
)}
{highlightKeyword(authorsJoined || "N/A", keyword)}
</Typography>
)}
</Typography>
)}
</Stack>

{/* show why it matched if not visible in main fields */}
{snippet && (
<Stack direction="row" spacing={1} flexWrap="wrap" gap={1}>
<Chip
label={`Matched in ${snippet.label}`}
size="small"
sx={{
height: 22,
backgroundColor: "#f9f9ff",
color: Colors.darkPurple,
border: `1px solid ${Colors.lightGray}`,
}}
/>
<Typography
variant="body2"
sx={{ mt: 0.5 }}
// safe: snippet is derived from our own strings with <mark> only
dangerouslySetInnerHTML={{ __html: snippet.html }}
/>
</Stack>
)}

<Stack direction="row" spacing={1} flexWrap="wrap" gap={1}>
{doiLink && (
<Stack mt={1}>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/DatasetDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ const DatasetDetailPage: React.FC = () => {
useEffect(() => {
if (datasetDocument) {
// ✅ Extract External Data & Assign `index`
console.log("datasetDocument", datasetDocument);
// console.log("datasetDocument", datasetDocument);
const links = extractDataLinks(datasetDocument, "").map(
(link, index) => ({
...link,
Expand Down
26 changes: 25 additions & 1 deletion src/pages/UpdatedDatasetDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const UpdatedDatasetDetailPage: React.FC = () => {
const [copiedKey, setCopiedKey] = useState<string | null>(null);
const copyTimer = useRef<number | null>(null);
const aiSummary = datasetDocument?.[".datainfo"]?.AISummary ?? "";
const readme = datasetDocument?.["README"] ?? "";
const handleSelectRevision = (newRev?: string | null) => {
setSearchParams((prev) => {
const p = new URLSearchParams(prev); // copy of the query url
Expand Down Expand Up @@ -808,7 +809,7 @@ const UpdatedDatasetDetailPage: React.FC = () => {
)}

{/* ai summary */}
{aiSummary && (
{aiSummary ? (
<>
<Box
sx={{
Expand Down Expand Up @@ -866,6 +867,29 @@ const UpdatedDatasetDetailPage: React.FC = () => {

<ReadMoreText text={aiSummary} />
</>
) : readme ? (
<>
<Box
sx={{
display: "flex",
alignItems: "center",
mb: 0.5,
mt: 1,
gap: 0.5,
}}
>
<Typography
color={Colors.purple}
sx={{ fontWeight: "bold", mb: 0.5, mt: 1 }}
>
Summary
</Typography>
</Box>

<ReadMoreText text={readme} />
</>
) : (
""
)}

<Box
Expand Down