@c-rex/components 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -1
- package/src/bookmark-button.tsx +67 -0
- package/src/favorite-button.tsx +74 -0
- package/src/info/info-table.tsx +25 -58
- package/src/navbar/navbar.tsx +4 -3
- package/src/render-article.tsx +1 -1
- package/src/result-list.tsx +1 -1
- package/src/result-view/table-with-images.tsx +113 -88
- package/src/right-sidebar.tsx +20 -3
- package/src/stores/favorites-store.ts +82 -0
- package/src/stores/language-store.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"files": [
|
|
5
5
|
"src"
|
|
6
6
|
],
|
|
@@ -105,6 +105,10 @@
|
|
|
105
105
|
"types": "./src/loading.tsx",
|
|
106
106
|
"import": "./src/loading.tsx"
|
|
107
107
|
},
|
|
108
|
+
"./favorite-button": {
|
|
109
|
+
"types": "./src/favorite-button.tsx",
|
|
110
|
+
"import": "./src/favorite-button.tsx"
|
|
111
|
+
},
|
|
108
112
|
"./language-store": {
|
|
109
113
|
"types": "./src/stores/language-store.ts",
|
|
110
114
|
"import": "./src/stores/language-store.ts"
|
|
@@ -112,6 +116,10 @@
|
|
|
112
116
|
"./highlight-store": {
|
|
113
117
|
"types": "./src/stores/highlight-store.ts",
|
|
114
118
|
"import": "./src/stores/highlight-store.ts"
|
|
119
|
+
},
|
|
120
|
+
"./favorites-store": {
|
|
121
|
+
"types": "./src/stores/favorites-store.ts",
|
|
122
|
+
"import": "./src/stores/favorites-store.ts"
|
|
115
123
|
}
|
|
116
124
|
},
|
|
117
125
|
"scripts": {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FC, ReactNode } from "react";
|
|
4
|
+
import { Button } from "@c-rex/ui/button";
|
|
5
|
+
import { Trash } from "lucide-react";
|
|
6
|
+
import { cn } from "@c-rex/utils";
|
|
7
|
+
import Link from "next/link";
|
|
8
|
+
import { Favorite } from "@c-rex/types";
|
|
9
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@c-rex/ui/dialog";
|
|
10
|
+
import {
|
|
11
|
+
Table,
|
|
12
|
+
TableBody,
|
|
13
|
+
TableCell,
|
|
14
|
+
TableRow,
|
|
15
|
+
} from "@c-rex/ui/table"
|
|
16
|
+
import { FaRegBookmark } from "react-icons/fa6";
|
|
17
|
+
|
|
18
|
+
export const BookmarkButton: FC<{ markersList: Favorite[], trigger?: ReactNode }> = ({ markersList, trigger }) => {
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Dialog>
|
|
22
|
+
<DialogTrigger asChild>
|
|
23
|
+
|
|
24
|
+
{trigger ? trigger : (
|
|
25
|
+
|
|
26
|
+
<Button variant="outline" size="icon" className="relative">
|
|
27
|
+
<FaRegBookmark className="text-primary" />
|
|
28
|
+
<span
|
|
29
|
+
className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
|
|
30
|
+
>
|
|
31
|
+
{markersList.length}
|
|
32
|
+
</span>
|
|
33
|
+
</Button>
|
|
34
|
+
)}
|
|
35
|
+
</DialogTrigger>
|
|
36
|
+
<DialogContent>
|
|
37
|
+
<DialogHeader>
|
|
38
|
+
<DialogTitle>Bookmarks</DialogTitle>
|
|
39
|
+
<DialogDescription>
|
|
40
|
+
Manage your bookmarks here
|
|
41
|
+
</DialogDescription>
|
|
42
|
+
</DialogHeader>
|
|
43
|
+
<Table>
|
|
44
|
+
<TableBody>
|
|
45
|
+
{markersList.map((item) => (
|
|
46
|
+
<TableRow key={item.id} className="min-h-12">
|
|
47
|
+
<TableCell>
|
|
48
|
+
<FaRegBookmark className={cn("w-5", `text-${item.color}`)} />
|
|
49
|
+
</TableCell>
|
|
50
|
+
<TableCell>
|
|
51
|
+
<Link href={`./${item.id}`}>
|
|
52
|
+
{item.label}
|
|
53
|
+
</Link>
|
|
54
|
+
</TableCell>
|
|
55
|
+
<TableCell>
|
|
56
|
+
<Button variant="destructive" size="icon">
|
|
57
|
+
<Trash className="w-5 hover:text-red-600 cursor-pointer" />
|
|
58
|
+
</Button>
|
|
59
|
+
</TableCell>
|
|
60
|
+
</TableRow>
|
|
61
|
+
))}
|
|
62
|
+
</TableBody>
|
|
63
|
+
</Table>
|
|
64
|
+
</DialogContent>
|
|
65
|
+
</Dialog>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FC, useEffect, useState } from "react";
|
|
4
|
+
import { Button } from "@c-rex/ui/button";
|
|
5
|
+
import { FaStar, FaRegStar } from "react-icons/fa";
|
|
6
|
+
import { useFavoritesStore } from "./stores/favorites-store";
|
|
7
|
+
import { MARKER_COLORS, RESULT_TYPES } from "@c-rex/constants";
|
|
8
|
+
import { ResultTypes } from "@c-rex/types";
|
|
9
|
+
|
|
10
|
+
export const FavoriteButton: FC<{ id: string, type: ResultTypes, label: string }> = ({ id, type, label }) => {
|
|
11
|
+
const addFavoriteTopic = useFavoritesStore((state) => state.favoriteTopic);
|
|
12
|
+
const addFavoriteDocument = useFavoritesStore((state) => state.favoriteDocument);
|
|
13
|
+
const removeFavoriteTopic = useFavoritesStore((state) => state.unfavoriteTopic);
|
|
14
|
+
const removeFavoriteDocument = useFavoritesStore((state) => state.unfavoriteDocument);
|
|
15
|
+
|
|
16
|
+
const favoriteDocumentList = useFavoritesStore((state) => state.documents);
|
|
17
|
+
const favoriteList = useFavoritesStore((state) => state.favorites);
|
|
18
|
+
const isFavorite = favoriteList.find((fav) => fav.id === id);
|
|
19
|
+
const [documentData, setDocumentData] = useState<{ id: string, label: string }>({ id, label });
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (type === RESULT_TYPES.TOPIC) {
|
|
23
|
+
getTopicDocumentData(id);
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const addFavorite = async (id: string) => {
|
|
28
|
+
if (type === RESULT_TYPES.DOCUMENT) {
|
|
29
|
+
addFavoriteDocument(id, label);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const length = favoriteDocumentList[documentData.id]?.topics.length || 0;
|
|
34
|
+
const color = MARKER_COLORS[length] || MARKER_COLORS[MARKER_COLORS.length - 1] as string;
|
|
35
|
+
|
|
36
|
+
addFavoriteTopic(documentData.id, id, label, color);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const removeFavorite = (id: string) => {
|
|
40
|
+
if (type === RESULT_TYPES.DOCUMENT) {
|
|
41
|
+
removeFavoriteDocument(id);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
removeFavoriteTopic(documentData.id, id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const getTopicDocumentData = async (topicId: string): Promise<void> => {
|
|
49
|
+
|
|
50
|
+
const response = await fetch(`/api/information-units/document-by-topic-id?shortId=${topicId}`, {
|
|
51
|
+
method: "GET"
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) throw new Error("Failed to fetch document by topic id")
|
|
55
|
+
|
|
56
|
+
const { documentId, label } = await response.json();
|
|
57
|
+
|
|
58
|
+
setDocumentData({ id: documentId, label });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isFavorite) {
|
|
62
|
+
return (
|
|
63
|
+
<Button variant="ghost" size="icon" onClick={() => removeFavorite(id)}>
|
|
64
|
+
<FaStar className="!h-5 !w-5 color-primary" />
|
|
65
|
+
</Button>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Button variant="ghost" size="icon" onClick={() => addFavorite(id)}>
|
|
71
|
+
<FaRegStar className="!h-5 !w-5" />
|
|
72
|
+
</Button>
|
|
73
|
+
)
|
|
74
|
+
}
|
package/src/info/info-table.tsx
CHANGED
|
@@ -15,8 +15,7 @@ import {
|
|
|
15
15
|
DropdownMenuItem,
|
|
16
16
|
DropdownMenuTrigger,
|
|
17
17
|
} from "@c-rex/ui/dropdown-menu";
|
|
18
|
-
import {
|
|
19
|
-
|
|
18
|
+
import { CloudDownload, Eye } from "lucide-react";
|
|
20
19
|
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
21
20
|
import { Flag } from "../flag";
|
|
22
21
|
import { Button } from "@c-rex/ui/button";
|
|
@@ -31,7 +30,8 @@ type Props = {
|
|
|
31
30
|
markersList?: Favorite[]
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
export const InfoTable: FC<Props> = ({ title, items, files, availableVersions }) => {
|
|
35
35
|
const t = useTranslations();
|
|
36
36
|
const uiLang = useLanguageStore(state => state.uiLang);
|
|
37
37
|
const newItems = filteredItems(items)
|
|
@@ -39,7 +39,9 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions, m
|
|
|
39
39
|
return (
|
|
40
40
|
<Card className="p-0 !pt-4">
|
|
41
41
|
<CardHeader>
|
|
42
|
-
<CardTitle className="text-lg">
|
|
42
|
+
<CardTitle className="text-lg flex justify-between">
|
|
43
|
+
{title}
|
|
44
|
+
</CardTitle>
|
|
43
45
|
</CardHeader>
|
|
44
46
|
<CardContent className="space-y-3 !p-0">
|
|
45
47
|
<Table>
|
|
@@ -61,6 +63,25 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions, m
|
|
|
61
63
|
</TableRow>
|
|
62
64
|
))}
|
|
63
65
|
|
|
66
|
+
{availableVersions && (
|
|
67
|
+
<TableRow className="min-h-12">
|
|
68
|
+
<TableCell className="font-medium w-28 pl-4">
|
|
69
|
+
<h4 className="text-sm font-medium">{t("availableIn")}</h4>
|
|
70
|
+
</TableCell>
|
|
71
|
+
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
72
|
+
{availableVersions.map((item) => {
|
|
73
|
+
return (
|
|
74
|
+
<span className="w-8 block border" key={item.shortId}>
|
|
75
|
+
<a href={item.link} title={item.lang}>
|
|
76
|
+
<Flag countryCode={item.country} />
|
|
77
|
+
</a>
|
|
78
|
+
</span>
|
|
79
|
+
)
|
|
80
|
+
})}
|
|
81
|
+
</TableCell>
|
|
82
|
+
</TableRow>
|
|
83
|
+
)}
|
|
84
|
+
|
|
64
85
|
{files && (
|
|
65
86
|
<TableRow className="min-h-12">
|
|
66
87
|
<TableCell className="font-medium w-28 pl-4">
|
|
@@ -98,60 +119,6 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions, m
|
|
|
98
119
|
</TableCell>
|
|
99
120
|
</TableRow>
|
|
100
121
|
)}
|
|
101
|
-
|
|
102
|
-
{availableVersions && (
|
|
103
|
-
<TableRow className="min-h-12">
|
|
104
|
-
<TableCell className="font-medium w-28 pl-4">
|
|
105
|
-
<h4 className="text-sm font-medium">{t("availableIn")}</h4>
|
|
106
|
-
</TableCell>
|
|
107
|
-
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
108
|
-
{availableVersions.map((item) => {
|
|
109
|
-
return (
|
|
110
|
-
<span className="w-8 block border" key={item.shortId}>
|
|
111
|
-
<a href={item.link} title={item.lang}>
|
|
112
|
-
<Flag countryCode={item.country} />
|
|
113
|
-
</a>
|
|
114
|
-
</span>
|
|
115
|
-
)
|
|
116
|
-
})}
|
|
117
|
-
</TableCell>
|
|
118
|
-
</TableRow>
|
|
119
|
-
)}
|
|
120
|
-
|
|
121
|
-
{markersList && markersList.length > 0 && (
|
|
122
|
-
<TableRow className="min-h-12">
|
|
123
|
-
<TableCell className="font-medium w-28 pl-4">
|
|
124
|
-
<h4 className="text-sm font-medium">Bookmarks</h4>
|
|
125
|
-
</TableCell>
|
|
126
|
-
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
127
|
-
<DropdownMenu>
|
|
128
|
-
<DropdownMenuTrigger className="mr-2" asChild>
|
|
129
|
-
<Button variant="outline" size="icon" className="relative">
|
|
130
|
-
<Bookmark className="text-primary" />
|
|
131
|
-
|
|
132
|
-
<span
|
|
133
|
-
className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
|
|
134
|
-
>{markersList.length}</span>
|
|
135
|
-
</Button>
|
|
136
|
-
</DropdownMenuTrigger>
|
|
137
|
-
<DropdownMenuContent>
|
|
138
|
-
|
|
139
|
-
{markersList.map((marker) => (
|
|
140
|
-
<DropdownMenuItem key={marker.id}>
|
|
141
|
-
<a href={`./${marker.id}`} target="_blank" rel="noreferrer" className="flex items-center">
|
|
142
|
-
{marker.label}
|
|
143
|
-
</a>
|
|
144
|
-
</DropdownMenuItem>
|
|
145
|
-
))}
|
|
146
|
-
|
|
147
|
-
</DropdownMenuContent>
|
|
148
|
-
</DropdownMenu>
|
|
149
|
-
|
|
150
|
-
</TableCell>
|
|
151
|
-
</TableRow>
|
|
152
|
-
)}
|
|
153
|
-
|
|
154
|
-
|
|
155
122
|
</TableBody>
|
|
156
123
|
</Table>
|
|
157
124
|
</CardContent>
|
package/src/navbar/navbar.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { getServerSession } from "next-auth";
|
|
|
5
5
|
import { SettingsMenu } from "./settings";
|
|
6
6
|
import { UserMenu } from "./user-menu";
|
|
7
7
|
import { SearchInput } from "./search-input";
|
|
8
|
-
import {
|
|
8
|
+
import { CrexSDK } from "@c-rex/core/sdk";
|
|
9
9
|
|
|
10
10
|
interface NavBarProps {
|
|
11
11
|
title: string;
|
|
@@ -14,8 +14,9 @@ interface NavBarProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const NavBar: FC<NavBarProps> = async ({ title, showInput, showPkgFilter }) => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const sdk = new CrexSDK();
|
|
18
|
+
const clientConfigs = sdk.getClientConfig();
|
|
19
|
+
const serverConfigs = sdk.getServerConfig();
|
|
19
20
|
|
|
20
21
|
let session: any;
|
|
21
22
|
if (clientConfigs.OIDC.userEnabled) {
|
package/src/render-article.tsx
CHANGED
|
@@ -61,7 +61,7 @@ export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
|
|
|
61
61
|
}, [highlightedContent, registerContainer]);
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
|
-
<main ref={containerRef} lang={contentLang} className="pb-4 min-h-[70vh]">
|
|
64
|
+
<main ref={containerRef} lang={contentLang} className="pb-4 pr-4 min-h-[70vh]">
|
|
65
65
|
{highlightedContent}
|
|
66
66
|
</main>
|
|
67
67
|
);
|
package/src/result-list.tsx
CHANGED
|
@@ -32,7 +32,7 @@ export const ResultList: FC<ResultListProps> = ({ items, pagination }: ResultLis
|
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
34
|
<>
|
|
35
|
-
{listComponent[configs.results.resultViewStyle]}
|
|
35
|
+
{listComponent[configs.results.resultViewStyle as ResultViewStyles]}
|
|
36
36
|
|
|
37
37
|
<Pagination
|
|
38
38
|
totalPages={pagination.pageCount}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { FC, useEffect, useState } from "react";
|
|
2
2
|
import { informationUnitsResponseItem } from "@c-rex/interfaces";
|
|
3
3
|
import { CloudDownload, Eye, FileStack, ImageOff } from "lucide-react";
|
|
4
|
-
import { FaFilePdf } from "react-icons/fa6";
|
|
5
4
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@c-rex/ui/dropdown-menu";
|
|
6
5
|
import { cn } from "@c-rex/utils";
|
|
7
6
|
import { useQueryState } from "nuqs";
|
|
@@ -9,16 +8,16 @@ import { Skeleton } from "@c-rex/ui/skeleton";
|
|
|
9
8
|
import { FavoriteButton } from "../favorite-button";
|
|
10
9
|
import { Button } from "@c-rex/ui/button";
|
|
11
10
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
12
|
-
import { ResultTypes } from "@c-rex/types";
|
|
11
|
+
import { Favorite, ResultTypes } from "@c-rex/types";
|
|
12
|
+
import { FileIcon } from "../file-icon";
|
|
13
|
+
import { useFavoritesStore } from "../stores/favorites-store";
|
|
14
|
+
import { BookmarkButton } from "../bookmark-button";
|
|
15
|
+
import { FaRegBookmark } from "react-icons/fa6";
|
|
13
16
|
|
|
14
17
|
interface TableWithImageViewProps {
|
|
15
18
|
items: informationUnitsResponseItem[];
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
const IconsToFileExtension: Record<string, React.ReactNode> = {
|
|
19
|
-
"application/pdf": <FaFilePdf className="h-5 w-5" />,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
21
|
const Image: FC<{ id: string }> = ({ id }) => {
|
|
23
22
|
const [imageUrl, setImageUrl] = useState("");
|
|
24
23
|
const [loading, setLoading] = useState(true);
|
|
@@ -72,101 +71,127 @@ const Image: FC<{ id: string }> = ({ id }) => {
|
|
|
72
71
|
);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
const TableWithImageView: FC<TableWithImageViewProps> = ({ items
|
|
74
|
+
const TableWithImageView: FC<TableWithImageViewProps> = ({ items }) => {
|
|
76
75
|
const [query] = useQueryState("search");
|
|
77
76
|
|
|
77
|
+
const favoritesDocuments = useFavoritesStore((state) => state.documents);
|
|
78
|
+
|
|
78
79
|
return (
|
|
79
80
|
<div className="mb-6">
|
|
80
|
-
{items.map((item, index) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded mb-2",
|
|
84
|
-
`c-rex-result-item c-rex-result-${item.type.toLowerCase()}`,
|
|
85
|
-
item.disabled && "c-rex-result-item-disabled",
|
|
86
|
-
)}
|
|
87
|
-
key={index}
|
|
88
|
-
>
|
|
89
|
-
|
|
90
|
-
<div className="h-16 w-16 flex items-center">
|
|
91
|
-
<Image id={item.shortId} {...params} />
|
|
92
|
-
</div>
|
|
81
|
+
{items.map((item, index) => {
|
|
82
|
+
const isFavoriteDocument = Object.keys(favoritesDocuments).some((docId) => docId === item.shortId);
|
|
83
|
+
let topicList: Favorite[] = []
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
{item.
|
|
109
|
-
</
|
|
110
|
-
|
|
111
|
-
|
|
85
|
+
if (isFavoriteDocument && favoritesDocuments[item.shortId]) {
|
|
86
|
+
topicList = favoritesDocuments[item.shortId]?.topics || []
|
|
87
|
+
}
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
key={index}
|
|
91
|
+
className={cn(
|
|
92
|
+
"min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded mb-2",
|
|
93
|
+
`c-rex-result-item c-rex-result-${item.type.toLowerCase()}`,
|
|
94
|
+
item.disabled && "c-rex-result-item-disabled",
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
|
|
98
|
+
<div className="h-16 w-16 flex items-center">
|
|
99
|
+
<Image id={item.shortId} />
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="flex-1 p-2 flex flex-col">
|
|
103
|
+
<span className=" text-sm text-muted-foreground">
|
|
104
|
+
{item.language.split("-")[0]?.toUpperCase()}
|
|
105
|
+
</span>
|
|
106
|
+
|
|
107
|
+
<span className="text-lg font-medium">
|
|
108
|
+
{item.disabled ? (
|
|
109
|
+
item.title
|
|
110
|
+
) : (
|
|
111
|
+
<a className="hover:underline" href={`${item.link}?q=${query}`}>{item.title}</a>
|
|
112
|
+
)}
|
|
113
|
+
</span>
|
|
114
|
+
|
|
115
|
+
<span className="text-sm">
|
|
116
|
+
{item.type}
|
|
117
|
+
</span>
|
|
118
|
+
<span className="text-sm">
|
|
119
|
+
short description here {/*
|
|
112
120
|
TODO: get abstract if has the value key on it. Same behavior on class props
|
|
113
121
|
|
|
114
122
|
abstract and description are not being returned by the API yet
|
|
115
123
|
*/}
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
</span>
|
|
125
|
+
</div>
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
<div className="flex flex-col p-2 ml-auto justify-center">
|
|
128
|
+
<span className="text-end text-sm text-muted-foreground mb-2">
|
|
129
|
+
{item.revision}
|
|
130
|
+
</span>
|
|
131
|
+
<div className="flex gap-2">
|
|
132
|
+
{Object.keys(item.files).map((fileKey, index) => {
|
|
133
|
+
if (!item.files[fileKey]) return null
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<DropdownMenu key={index}>
|
|
137
|
+
<DropdownMenuTrigger>
|
|
138
|
+
<Button variant="ghost" size="icon">
|
|
139
|
+
<FileIcon extension={fileKey} />
|
|
140
|
+
</Button>
|
|
141
|
+
</DropdownMenuTrigger>
|
|
142
|
+
<DropdownMenuContent>
|
|
143
|
+
<DropdownMenuItem>
|
|
144
|
+
<a href={item.files[fileKey].view} target="_blank" rel="noreferrer" className="flex items-center">
|
|
145
|
+
<Eye className="mr-2" /> Open {/*TODO: use i18n functions*/}
|
|
146
|
+
</a>
|
|
147
|
+
</DropdownMenuItem>
|
|
148
|
+
<DropdownMenuItem>
|
|
149
|
+
<a href={item.files[fileKey].download} target="_blank" rel="noreferrer" className="flex items-center">
|
|
150
|
+
<CloudDownload className="mr-2" /> Download {/*TODO: use i18n functions*/}
|
|
151
|
+
</a>
|
|
152
|
+
</DropdownMenuItem>
|
|
153
|
+
</DropdownMenuContent>
|
|
154
|
+
</DropdownMenu>
|
|
155
|
+
)
|
|
156
|
+
})}
|
|
157
|
+
|
|
158
|
+
{(topicList && topicList.length > 0) && (
|
|
159
|
+
<BookmarkButton
|
|
160
|
+
markersList={topicList.filter(item => item.label.length > 0)}
|
|
161
|
+
trigger={
|
|
162
|
+
<Button variant="ghost" size="icon" className="relative">
|
|
163
|
+
<FaRegBookmark className="!w-5 color-primary" />
|
|
164
|
+
<span
|
|
165
|
+
className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
|
|
166
|
+
>
|
|
167
|
+
{topicList.length}
|
|
168
|
+
</span>
|
|
132
169
|
</Button>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<a href={item.files[fileKey].view} target="_blank" rel="noreferrer" className="flex items-center">
|
|
137
|
-
<Eye className="mr-2" /> Open {/*TODO: use i18n functions*/}
|
|
138
|
-
</a>
|
|
139
|
-
</DropdownMenuItem>
|
|
140
|
-
<DropdownMenuItem>
|
|
141
|
-
<a href={item.files[fileKey].download} target="_blank" rel="noreferrer" className="flex items-center">
|
|
142
|
-
<CloudDownload className="mr-2" /> Download {/*TODO: use i18n functions*/}
|
|
143
|
-
</a>
|
|
144
|
-
</DropdownMenuItem>
|
|
145
|
-
</DropdownMenuContent>
|
|
146
|
-
</DropdownMenu>
|
|
147
|
-
)
|
|
148
|
-
})}
|
|
149
|
-
|
|
150
|
-
<FavoriteButton id={item.shortId} type={item.type as ResultTypes} label={item.title} />
|
|
151
|
-
|
|
152
|
-
{item.multipleVersions.length > 1 && (
|
|
153
|
-
<Tooltip>
|
|
154
|
-
<TooltipTrigger asChild>
|
|
155
|
-
<Button variant="ghost" size="icon" rounded="full">
|
|
156
|
-
<FileStack /> {JSON.stringify(item.multipleVersions)}
|
|
157
|
-
</Button>
|
|
158
|
-
</TooltipTrigger>
|
|
159
|
-
<TooltipContent>
|
|
160
|
-
|
|
161
|
-
Available in: {item.multipleVersions}
|
|
162
|
-
</TooltipContent>
|
|
163
|
-
</Tooltip>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
170
|
+
}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
166
173
|
|
|
174
|
+
<FavoriteButton id={item.shortId} type={item.type as ResultTypes} label={item.title} />
|
|
175
|
+
|
|
176
|
+
{item.multipleVersions.length > 1 && (
|
|
177
|
+
<Tooltip>
|
|
178
|
+
<TooltipTrigger asChild>
|
|
179
|
+
<Button variant="ghost" size="icon">
|
|
180
|
+
<FileStack />
|
|
181
|
+
</Button>
|
|
182
|
+
</TooltipTrigger>
|
|
183
|
+
<TooltipContent>
|
|
184
|
+
|
|
185
|
+
Available in: {item.multipleVersions.join(", ")}
|
|
186
|
+
</TooltipContent>
|
|
187
|
+
</Tooltip>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
</div>
|
|
167
192
|
</div>
|
|
168
|
-
|
|
169
|
-
)
|
|
193
|
+
)
|
|
194
|
+
})}
|
|
170
195
|
</div>
|
|
171
196
|
);
|
|
172
197
|
};
|
package/src/right-sidebar.tsx
CHANGED
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
SidebarContent,
|
|
7
7
|
} from "@c-rex/ui/sidebar";
|
|
8
8
|
import { useTranslations } from "next-intl";
|
|
9
|
-
import { articleInfoItemType, DocumentsType } from "@c-rex/types";
|
|
9
|
+
import { articleInfoItemType, DocumentsType, Favorite } from "@c-rex/types";
|
|
10
10
|
import { InfoTable } from "./info/info-table";
|
|
11
11
|
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
12
|
+
import { useFavoritesStore } from "./stores/favorites-store";
|
|
12
13
|
|
|
13
14
|
interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
14
15
|
articleInfo: articleInfoItemType[],
|
|
@@ -16,11 +17,26 @@ interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
|
16
17
|
files: DocumentsType,
|
|
17
18
|
articleAvailableVersions: AvailableVersionsInterface[],
|
|
18
19
|
documentAvailableVersions: AvailableVersionsInterface[]
|
|
19
|
-
|
|
20
|
+
documentId: string
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export function RightSidebar({
|
|
23
|
+
export function RightSidebar({
|
|
24
|
+
articleInfo,
|
|
25
|
+
documentInfo,
|
|
26
|
+
files,
|
|
27
|
+
articleAvailableVersions,
|
|
28
|
+
documentAvailableVersions,
|
|
29
|
+
documentId,
|
|
30
|
+
...props
|
|
31
|
+
}: SidebarProps) {
|
|
23
32
|
const t = useTranslations();
|
|
33
|
+
const favoritesDocuments = useFavoritesStore((state) => state.documents);
|
|
34
|
+
const isFavoriteDocument = Object.keys(favoritesDocuments).some((docId) => docId === documentId);
|
|
35
|
+
let topicList: Favorite[] = []
|
|
36
|
+
|
|
37
|
+
if (isFavoriteDocument && favoritesDocuments[documentId]) {
|
|
38
|
+
topicList = favoritesDocuments[documentId].topics
|
|
39
|
+
}
|
|
24
40
|
|
|
25
41
|
return (
|
|
26
42
|
<Sidebar {...props}>
|
|
@@ -34,6 +50,7 @@ export function RightSidebar({ articleInfo, documentInfo, files, articleAvailabl
|
|
|
34
50
|
items={documentInfo}
|
|
35
51
|
files={files}
|
|
36
52
|
availableVersions={documentAvailableVersions}
|
|
53
|
+
markersList={isFavoriteDocument ? topicList : []}
|
|
37
54
|
/>
|
|
38
55
|
)}
|
|
39
56
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Favorite } from "@c-rex/types";
|
|
2
|
+
import { create } from "zustand";
|
|
3
|
+
import { persist } from "zustand/middleware";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
type FavoritesStore = {
|
|
7
|
+
favorites: Favorite[];
|
|
8
|
+
documents: Record<string, { topics: Favorite[], label?: string }>;
|
|
9
|
+
favoriteTopic: (documentId: string, id: string, label: string, color: string) => void;
|
|
10
|
+
unfavoriteTopic: (documentId: string, id: string) => void;
|
|
11
|
+
favoriteDocument: (id: string, label: string) => void;
|
|
12
|
+
unfavoriteDocument: (id: string) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const useFavoritesStore = create<FavoritesStore>()(
|
|
16
|
+
persist((set) => ({
|
|
17
|
+
documents: {},
|
|
18
|
+
favorites: [],
|
|
19
|
+
favoriteTopic: (documentId: string, id: string, label: string, color: string) =>
|
|
20
|
+
set((state) => ({
|
|
21
|
+
documents: favoriteTopic(state.documents, documentId, id, label, color),
|
|
22
|
+
favorites: [...state.favorites, { id, label, color }, { id: documentId, label: "", color: "" }],
|
|
23
|
+
})),
|
|
24
|
+
unfavoriteTopic: (documentId: string, id: string) =>
|
|
25
|
+
set((state) => ({
|
|
26
|
+
documents: unfavoriteTopic(state.documents, documentId, id),
|
|
27
|
+
favorites: state.favorites.filter((topic) => topic.id !== id),
|
|
28
|
+
})),
|
|
29
|
+
favoriteDocument: (id: string, label: string) =>
|
|
30
|
+
set((state) => {
|
|
31
|
+
const documentsCopy = { ...state.documents };
|
|
32
|
+
if (documentsCopy[id]) {
|
|
33
|
+
return state;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
documents: {
|
|
38
|
+
...state.documents,
|
|
39
|
+
[id]: { topics: [], label },
|
|
40
|
+
},
|
|
41
|
+
favorites: [...state.favorites, { id, label, color: "" }],
|
|
42
|
+
}
|
|
43
|
+
}),
|
|
44
|
+
unfavoriteDocument: (id: string) =>
|
|
45
|
+
set((state) => {
|
|
46
|
+
const documentsCopy = { ...state.documents };
|
|
47
|
+
delete documentsCopy[id];
|
|
48
|
+
return { documents: documentsCopy };
|
|
49
|
+
}),
|
|
50
|
+
}), {
|
|
51
|
+
name: "c-rex-favorites",
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
const favoriteTopic = (documents: Record<string, { topics: Favorite[] }>, documentId: string, id: string, label: string, color: string): Record<string, { topics: Favorite[] }> => {
|
|
57
|
+
|
|
58
|
+
const documentsCopy = { ...documents };
|
|
59
|
+
const notFound = documents[documentId] == undefined;
|
|
60
|
+
|
|
61
|
+
if (notFound) {
|
|
62
|
+
documentsCopy[documentId] = { topics: [] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
documentsCopy[documentId]!.topics.push({ id, label, color });
|
|
66
|
+
|
|
67
|
+
return documentsCopy
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const unfavoriteTopic = (documents: Record<string, { topics: Favorite[] }>, documentId: string, id: string): Record<string, { topics: Favorite[] }> => {
|
|
71
|
+
|
|
72
|
+
const documentsCopy = { ...documents };
|
|
73
|
+
const notFound = documents[documentId] == undefined;
|
|
74
|
+
|
|
75
|
+
if (notFound) {
|
|
76
|
+
return documentsCopy;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
documentsCopy[documentId]!.topics = documentsCopy[documentId]!.topics.filter(topic => topic.id !== id);
|
|
80
|
+
|
|
81
|
+
return documentsCopy
|
|
82
|
+
}
|
|
@@ -2,7 +2,7 @@ import { UI_LANG_KEY } from "@c-rex/constants";
|
|
|
2
2
|
import { AVAILABLE_CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
3
3
|
import { CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
4
4
|
import { LanguageAndCountries } from "@c-rex/interfaces";
|
|
5
|
-
import {
|
|
5
|
+
import { getCookieFromDocument, setCookie } from "@c-rex/utils";
|
|
6
6
|
import { create } from "zustand";
|
|
7
7
|
|
|
8
8
|
type LanguageStoreType = {
|
|
@@ -33,10 +33,10 @@ export const useLanguageStore = create<LanguageStoreType>((set) => ({
|
|
|
33
33
|
},
|
|
34
34
|
hydrate: () => {
|
|
35
35
|
set({
|
|
36
|
-
uiLang:
|
|
37
|
-
contentLang:
|
|
36
|
+
uiLang: getCookieFromDocument(UI_LANG_KEY),
|
|
37
|
+
contentLang: getCookieFromDocument(CONTENT_LANG_KEY),
|
|
38
38
|
availableLanguages: (() => {
|
|
39
|
-
const raw =
|
|
39
|
+
const raw = getCookieFromDocument(AVAILABLE_CONTENT_LANG_KEY);
|
|
40
40
|
try {
|
|
41
41
|
return raw ? JSON.parse(raw) : [];
|
|
42
42
|
} catch {
|