Skip to content

Commit 8ab3ccc

Browse files
authored
Merge branch 'main' into feat/portal-scoped-api
2 parents f223343 + 21e17ca commit 8ab3ccc

File tree

9 files changed

+4746
-3173
lines changed

9 files changed

+4746
-3173
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from "react";
2+
import ReactMarkdown from "react-markdown";
3+
4+
interface Props {
5+
content: string;
6+
}
7+
8+
const MarkdownRenderer: React.FC<Props> = ({ content }) => {
9+
return (
10+
<div >
11+
<ReactMarkdown
12+
components={{
13+
h1: ({ node, ...props }) => (
14+
<h1 className="text-3xl font-bold mt-4 mb-2" {...props} />
15+
),
16+
h2: ({ node, ...props }) => (
17+
<h2 className="text-2xl font-semibold mt-3 mb-1" {...props} />
18+
),
19+
p: ({ node, ...props }) => (
20+
<p className="leading-relaxed mb-4" {...props} />
21+
),
22+
a: ({ node, ...props }) => (
23+
<a
24+
{...props}
25+
className="underline text-[#000] hover:text-accent"
26+
target="_blank"
27+
rel="noopener noreferrer"
28+
/>
29+
),
30+
code: ({ node, inline, ...props }) =>
31+
inline ? (
32+
<code className="bg-gray-200 px-1 rounded text-sm" {...props} />
33+
) : (
34+
<pre className="bg-gray-900 text-white p-3 rounded overflow-x-auto">
35+
<code {...props} />
36+
</pre>
37+
),
38+
ul: ({ node, ...props }) => (
39+
<ul className="list-disc pl-6 mb-4" {...props} />
40+
),
41+
ol: ({ node, ...props }) => (
42+
<ol className="list-decimal pl-6 mb-4" {...props} />
43+
),
44+
blockquote: ({ node, ...props }) => (
45+
<blockquote
46+
className="border-l-4 border-gray-300 pl-4 italic text-gray-600"
47+
{...props}
48+
/>
49+
),
50+
hr: ({ node, ...props }) => (
51+
<hr className="my-6 border-gray-300" {...props} />
52+
),
53+
}}
54+
>
55+
{content}
56+
</ReactMarkdown>
57+
</div>
58+
);
59+
};
60+
61+
export default MarkdownRenderer;

components/dataset/individualPage/DatasetInfo.tsx

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import Link from "next/link";
2-
import { Resource, Tag } from "@portaljs/ckan";
3-
import { ArrowDownTrayIcon } from "@heroicons/react/20/solid";
2+
import { Resource, Tag } from "@portaljs/ckan";
3+
import {
4+
ArrowDownTrayIcon,
5+
ChevronDownIcon,
6+
ChevronUpIcon,
7+
} from "@heroicons/react/20/solid";
48
import { getTimeAgo } from "@/lib/utils";
59
import { Dataset } from "@/schemas/dataset.interface";
610
import { RiExternalLinkLine } from "react-icons/ri";
11+
import { useEffect, useRef, useState } from "react";
12+
import MarkdownRenderer from "@/components/_shared/MarkdownRenderer";
713

814
function uniqueFormat(resources) {
915
const formats = resources.map((item: Resource) => item.format);
@@ -15,12 +21,28 @@ export default function DatasetInfo({
1521
}: {
1622
dataset: Dataset;
1723
}) {
24+
const [isTruncated, setIsTruncated] = useState(false);
25+
const [showFullDescription, setShowFullDescription] = useState(false);
26+
const textRef = useRef<HTMLParagraphElement>(null);
27+
28+
const description =
29+
dataset.notes?.replace(/<\/?[^>]+(>|$)/g, "") || "No description";
30+
1831
const metaFormats = [
1932
{ format: "jsonld", label: "JSON-LD" },
2033
{ format: "rdf", label: "RDF" },
2134
{ format: "ttl", label: "TTL" },
2235
];
2336

37+
useEffect(() => {
38+
const el = textRef.current;
39+
if (el) {
40+
requestAnimationFrame(() => {
41+
setIsTruncated(el.scrollHeight > el.clientHeight);
42+
});
43+
}
44+
}, [dataset.notes]);
45+
2446
return (
2547
<div className="flex flex-col">
2648
<div className="flex flex-col gap-y-3">
@@ -92,6 +114,43 @@ export default function DatasetInfo({
92114
Created:{" "}
93115
{dataset.metadata_created && getTimeAgo(dataset.metadata_created)}
94116
</span>
117+
{dataset.source && dataset.source.length > 0 && (
118+
<div className="font-medium text-gray-500">
119+
<div className="flex items-start gap-1">
120+
<svg
121+
xmlns="http://www.w3.org/2000/svg"
122+
fill="none"
123+
viewBox="0 0 24 24"
124+
strokeWidth={1.5}
125+
stroke="currentColor"
126+
className="w-5 h-5 text-accent inline-block mt-0.5 flex-shrink-0"
127+
>
128+
<path
129+
strokeLinecap="round"
130+
strokeLinejoin="round"
131+
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
132+
/>
133+
</svg>
134+
<div className="flex flex-col gap-1">
135+
<span>Source{dataset.source.length > 1 ? "s" : ""}:</span>
136+
<div className="flex flex-col gap-1.5">
137+
{dataset.source.map((url, index) => (
138+
<a
139+
key={index}
140+
href={url}
141+
target="_blank"
142+
rel="noopener noreferrer"
143+
className="text-accent hover:text-darkaccent flex items-center gap-1 break-all transition"
144+
>
145+
<RiExternalLinkLine className="w-4 h-4 flex-shrink-0" />
146+
<span className="underline">{url}</span>
147+
</a>
148+
))}
149+
</div>
150+
</div>
151+
</div>
152+
</div>
153+
)}
95154
<span className="font-medium text-gray-500 inline">
96155
<svg
97156
xmlns="http://www.w3.org/2000/svg"
@@ -112,9 +171,30 @@ export default function DatasetInfo({
112171
</span>
113172
</div>
114173
<div className="py-4 my-4 border-y">
115-
<p className="text-sm font-normal text-stone-500 line-clamp-4">
116-
{dataset.notes?.replace(/<\/?[^>]+(>|$)/g, "") || "No description"}
117-
</p>
174+
<div
175+
ref={textRef}
176+
className={`text-sm font-normal text-stone-500 transition-all ${
177+
!showFullDescription ? "line-clamp-4" : ""
178+
}`}
179+
>
180+
<MarkdownRenderer content={description} />
181+
</div>
182+
{isTruncated && (
183+
<button
184+
onClick={() => setShowFullDescription(!showFullDescription)}
185+
className="mt-2 border-b border-accent text-stone-500 hover:text-accent"
186+
>
187+
{showFullDescription ? (
188+
<span className="flex items-center">
189+
Read less <ChevronUpIcon className="text-accent w-4" />
190+
</span>
191+
) : (
192+
<span className="flex items-center">
193+
Read more <ChevronDownIcon className="text-accent w-4" />
194+
</span>
195+
)}
196+
</button>
197+
)}
118198
</div>
119199
<div className="flex flex-wrap gap-1">
120200
{dataset.tags?.map((tag: Tag) => (

components/dataset/search/Pagination.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default function Pagination({
6363
{x + 1}
6464
</button>
6565
))}
66-
{subsetOfPages !== Math.ceil(count / max) && count > 50 && (
66+
{count > max * options.limit && (subsetOfPages + max) * options.limit < count && (
6767
<button
6868
className="font-semibold flex items-center gap-2"
6969
onClick={() => setSubsetOfPages(subsetOfPages + max)}

components/responsiveGrid/Table.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1+
import { useEffect, useRef } from "react";
12
import { useResourceData } from "./DataProvider";
23
import TableColumnValue from "./TableColValue";
34
import TableHead from "./TableHead";
45

56
export default function TableData() {
6-
const { paginatedData, columns } = useResourceData();
7+
const { paginatedData, columns, data } = useResourceData();
8+
const scrollRef = useRef<HTMLDivElement>(null);
9+
useEffect(() => {
10+
if (scrollRef.current) {
11+
setTimeout(() => {
12+
if (scrollRef.current) {
13+
scrollRef.current.scrollLeft = 0;
14+
} }, 100);
15+
}
16+
}, [data]);
717
return (
8-
<div className="overflow-auto max-h-[750px] relative border-y min-h-[500px] w-full">
18+
<div ref={scrollRef} className="overflow-auto max-h-[750px] relative border-y min-h-[500px] w-full">
919
{/* Table */}
1020
<table
1121
className="min-w-full table-auto border-collapse border-0 static"

components/responsiveGrid/TableColValue.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ export default function TableColumnValue({ column, value }) {
44
const { visibleColumns, pinnedColumns } = useResourceData();
55
const isVisible = visibleColumns.includes(column);
66
const isPinned = pinnedColumns.includes(column);
7+
const _value = typeof value === "boolean" ? value.toString() : value;
8+
79
return (
810
<td
9-
className={` px-3 py-4 text-sm text-gray-500 ${
11+
className={`px-3 py-4 text-sm text-gray-500 ${
1012
!isVisible ? "hidden" : ""
1113
} ${isPinned ? "sticky left-[-1px] bg-accent-50 z-10 font-medium" : ""}`}
1214
role="gridcell"
1315
tabIndex={0}
14-
aria-label={value}
16+
aria-label={_value}
1517
>
16-
<span className=" block max-w-[400px] w-[max-content] ">{value}</span>
18+
<span className="block max-w-[400px] break-words w-[max-content]">{_value}</span>
1719
{isPinned && (
1820
<span className="absolute right-[-1px] h-full w-[1px] bg-gray-100 top-0"></span>
1921
)}

0 commit comments

Comments
 (0)