@c-rex/components 0.3.0-build.35 → 0.3.0-build.36
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 +28 -36
- package/src/article/article-action-bar.tsx +4 -1
- package/src/{check-article-lang.tsx → article/check-article-lang.tsx} +1 -1
- package/src/article/render-article-highlight.tsx +108 -0
- package/src/article/render-article.tsx +28 -0
- package/src/autocomplete.tsx +2 -2
- package/src/carousel/carousel.tsx +5 -2
- package/src/carousel/information-unit-carousel-item.tsx +1 -1
- package/src/content-unavailable.tsx +20 -0
- package/src/directoryNodes/directory-tree-context.tsx +9 -4
- package/src/documents/description-preview.tsx +14 -4
- package/src/documents/result-list-item.tsx +35 -46
- package/src/favorites/__tests__/favorites-hydration.test.tsx +245 -0
- package/src/favorites/bookmark-button.tsx +38 -20
- package/src/favorites/favorite-button.tsx +23 -24
- package/src/favorites/favorites-context.tsx +287 -0
- package/src/icons/file-icon.tsx +9 -26
- package/src/info/information-unit-metadata-grid-client.tsx +21 -21
- package/src/page-wrapper.tsx +1 -1
- package/src/renditions/html-client.tsx +8 -6
- package/src/renditions/html.tsx +3 -1
- package/src/restriction-menu/restriction-menu-item.tsx +48 -58
- package/src/restriction-menu/restriction-selection-command-menu.tsx +444 -0
- package/src/restriction-menu/restriction-selection-menu.tsx +3 -5
- package/src/restriction-menu/taxonomy-restriction-command-menu.tsx +111 -0
- package/src/restriction-menu/taxonomy-restriction-menu.tsx +1 -7
- package/src/results/filter-navbar.tsx +81 -76
- package/src/results/filter-sidebar/context.tsx +32 -0
- package/src/results/filter-sidebar/index.tsx +44 -35
- package/src/results/generic/search-results-client.tsx +5 -4
- package/src/results/generic/table-result-list.tsx +16 -16
- package/src/results/information-unit-search-results-card-list.tsx +4 -1
- package/src/results/pagination.tsx +43 -40
- package/src/search-input.tsx +4 -2
- package/src/toc/toc-browse-controls.tsx +2 -2
- package/src/toc/toc-tree-panel.tsx +19 -16
- package/src/article/article-content.tsx +0 -19
- package/src/breadcrumb.tsx +0 -124
- package/src/directoryNodes/tree-of-content.tsx +0 -68
- package/src/render-article.tsx +0 -75
- package/src/restriction-menu/restriction-menu-container.tsx +0 -4
- package/src/restriction-menu/restriction-menu.tsx +0 -4
- package/src/stores/__tests__/favorites-store.test.ts +0 -54
- package/src/stores/favorites-store.ts +0 -163
- /package/src/{render-article.module.css → article/render-article.module.css} +0 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Favorite } from "@c-rex/types";
|
|
4
|
+
import { getLocalStorageJson, removeLocalStorageItem, setLocalStorageJson } from "@c-rex/utils";
|
|
5
|
+
import { ReactNode, createContext, useContext, useEffect, useState } from "react";
|
|
6
|
+
|
|
7
|
+
type FavoriteDocumentState = { topics: Favorite[]; label?: string };
|
|
8
|
+
type FavoriteDocumentsMap = Record<string, FavoriteDocumentState>;
|
|
9
|
+
|
|
10
|
+
export type FavoritesState = {
|
|
11
|
+
favorites: Favorite[];
|
|
12
|
+
documents: FavoriteDocumentsMap;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type FavoritesActions = {
|
|
16
|
+
favoriteTopic: (documentId: string, id: string, label: string, color: string) => void;
|
|
17
|
+
unfavoriteTopic: (documentId: string, id: string) => void;
|
|
18
|
+
favoriteDocument: (id: string, label: string) => void;
|
|
19
|
+
unfavoriteDocument: (id: string) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type FavoritesContextValue = FavoritesState & FavoritesActions & {
|
|
23
|
+
isHydrated: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type PersistedFavoritesState = FavoritesState | {
|
|
27
|
+
state?: FavoritesState;
|
|
28
|
+
version?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const FAVORITES_STORAGE_KEY = "c-rex-favorites";
|
|
32
|
+
|
|
33
|
+
export const defaultFavoritesState: FavoritesState = {
|
|
34
|
+
documents: {},
|
|
35
|
+
favorites: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const loadFavoritesState = (fallbackState: FavoritesState = defaultFavoritesState): FavoritesState => {
|
|
39
|
+
const persisted = getLocalStorageJson<PersistedFavoritesState>(FAVORITES_STORAGE_KEY);
|
|
40
|
+
|
|
41
|
+
if (!persisted) {
|
|
42
|
+
return fallbackState;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isFavoritesState(persisted)) {
|
|
46
|
+
return persisted.state ?? fallbackState;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return persisted;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const persistFavoritesState = (state: FavoritesState): void => {
|
|
53
|
+
setLocalStorageJson(FAVORITES_STORAGE_KEY, state);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const clearFavoritesState = (): void => {
|
|
57
|
+
removeLocalStorageItem(FAVORITES_STORAGE_KEY);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const applyFavoriteTopic = (state: FavoritesState, documentId: string, id: string, label: string, color: string): FavoritesState => {
|
|
61
|
+
const documents = favoriteTopic(state.documents, documentId, id, label, color);
|
|
62
|
+
const favorites = upsertFavorites(state.favorites, [
|
|
63
|
+
{ id, label, color },
|
|
64
|
+
{ id: documentId, label: state.documents[documentId]?.label || "", color: "" },
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
if (documents === state.documents && favorites === state.favorites) {
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { ...state, documents, favorites };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const applyUnfavoriteTopic = (state: FavoritesState, documentId: string, id: string): FavoritesState => ({
|
|
75
|
+
...state,
|
|
76
|
+
documents: unfavoriteTopic(state.documents, documentId, id),
|
|
77
|
+
favorites: state.favorites.filter((topic) => topic.id !== id),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const applyFavoriteDocument = (state: FavoritesState, id: string, label: string): FavoritesState => {
|
|
81
|
+
const documents = upsertDocument(state.documents, id, label);
|
|
82
|
+
const favorites = upsertFavorites(state.favorites, [{ id, label, color: "" }]);
|
|
83
|
+
|
|
84
|
+
if (documents === state.documents && favorites === state.favorites) {
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...state,
|
|
90
|
+
documents,
|
|
91
|
+
favorites,
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const applyUnfavoriteDocument = (state: FavoritesState, id: string): FavoritesState => {
|
|
96
|
+
const documentsCopy = { ...state.documents };
|
|
97
|
+
if (!documentsCopy[id]) {
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const favoritesToRemove = documentsCopy[id]?.topics.map((topic) => topic.id) || [];
|
|
102
|
+
const newFavorites = state.favorites.filter((fav) => fav.id !== id && !favoritesToRemove.includes(fav.id));
|
|
103
|
+
delete documentsCopy[id];
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...state,
|
|
107
|
+
documents: documentsCopy,
|
|
108
|
+
favorites: newFavorites,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const FavoritesContext = createContext<FavoritesContextValue | undefined>(undefined);
|
|
113
|
+
|
|
114
|
+
type FavoritesProviderProps = {
|
|
115
|
+
children: ReactNode;
|
|
116
|
+
initialState?: FavoritesState;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const FavoritesProvider = ({
|
|
120
|
+
children,
|
|
121
|
+
initialState = defaultFavoritesState,
|
|
122
|
+
}: FavoritesProviderProps) => {
|
|
123
|
+
const [state, setState] = useState<FavoritesState>(initialState);
|
|
124
|
+
const [isHydrated, setIsHydrated] = useState(false);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
setState(loadFavoritesState(initialState));
|
|
128
|
+
setIsHydrated(true);
|
|
129
|
+
|
|
130
|
+
const handleStorageChange = () => {
|
|
131
|
+
setState(loadFavoritesState(initialState));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
window.addEventListener("storage", handleStorageChange);
|
|
135
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
136
|
+
}, [initialState]);
|
|
137
|
+
|
|
138
|
+
const favoriteTopic = (documentId: string, id: string, label: string, color: string) => {
|
|
139
|
+
setState((currentState) => {
|
|
140
|
+
const nextState = applyFavoriteTopic(currentState, documentId, id, label, color);
|
|
141
|
+
persistFavoritesState(nextState);
|
|
142
|
+
return nextState;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const unfavoriteTopic = (documentId: string, id: string) => {
|
|
147
|
+
setState((currentState) => {
|
|
148
|
+
const nextState = applyUnfavoriteTopic(currentState, documentId, id);
|
|
149
|
+
persistFavoritesState(nextState);
|
|
150
|
+
return nextState;
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const favoriteDocument = (id: string, label: string) => {
|
|
155
|
+
setState((currentState) => {
|
|
156
|
+
const nextState = applyFavoriteDocument(currentState, id, label);
|
|
157
|
+
persistFavoritesState(nextState);
|
|
158
|
+
return nextState;
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const unfavoriteDocument = (id: string) => {
|
|
163
|
+
setState((currentState) => {
|
|
164
|
+
const nextState = applyUnfavoriteDocument(currentState, id);
|
|
165
|
+
persistFavoritesState(nextState);
|
|
166
|
+
return nextState;
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const contextValue: FavoritesContextValue = {
|
|
171
|
+
...state,
|
|
172
|
+
isHydrated,
|
|
173
|
+
favoriteTopic,
|
|
174
|
+
unfavoriteTopic,
|
|
175
|
+
favoriteDocument,
|
|
176
|
+
unfavoriteDocument,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<FavoritesContext.Provider value={contextValue}>
|
|
181
|
+
{children}
|
|
182
|
+
</FavoritesContext.Provider>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const useFavorites = <T,>(selector: (state: FavoritesContextValue) => T): T => {
|
|
187
|
+
const favoritesContext = useContext(FavoritesContext);
|
|
188
|
+
|
|
189
|
+
if (!favoritesContext) {
|
|
190
|
+
throw new Error("useFavorites must be used within FavoritesProvider");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return selector(favoritesContext);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const favoriteTopic = (documents: FavoriteDocumentsMap, documentId: string, id: string, label: string, color: string): FavoriteDocumentsMap => {
|
|
197
|
+
const currentDocument = documents[documentId];
|
|
198
|
+
const currentTopics = currentDocument?.topics ?? [];
|
|
199
|
+
if (currentTopics.some((topic) => topic.id === id)) {
|
|
200
|
+
return documents;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
...documents,
|
|
205
|
+
[documentId]: {
|
|
206
|
+
...currentDocument,
|
|
207
|
+
topics: [...currentTopics, { id, label, color }],
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const unfavoriteTopic = (documents: FavoriteDocumentsMap, documentId: string, id: string): FavoriteDocumentsMap => {
|
|
213
|
+
const currentDocument = documents[documentId];
|
|
214
|
+
if (!currentDocument) {
|
|
215
|
+
return documents;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
...documents,
|
|
220
|
+
[documentId]: {
|
|
221
|
+
...currentDocument,
|
|
222
|
+
topics: currentDocument.topics.filter((topic) => topic.id !== id),
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const upsertDocument = (documents: FavoriteDocumentsMap, documentId: string, label: string): FavoriteDocumentsMap => {
|
|
228
|
+
const currentDocument = documents[documentId];
|
|
229
|
+
|
|
230
|
+
if (!currentDocument) {
|
|
231
|
+
return {
|
|
232
|
+
...documents,
|
|
233
|
+
[documentId]: { topics: [], label },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (currentDocument.label === label) {
|
|
238
|
+
return documents;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
...documents,
|
|
243
|
+
[documentId]: {
|
|
244
|
+
...currentDocument,
|
|
245
|
+
label,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const upsertFavorites = (favorites: Favorite[], entries: Favorite[]): Favorite[] => {
|
|
251
|
+
let nextFavorites = favorites;
|
|
252
|
+
|
|
253
|
+
for (const entry of entries) {
|
|
254
|
+
nextFavorites = upsertFavorite(nextFavorites, entry);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return nextFavorites;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const upsertFavorite = (favorites: Favorite[], entry: Favorite): Favorite[] => {
|
|
261
|
+
const index = favorites.findIndex((favorite) => favorite.id === entry.id);
|
|
262
|
+
if (index === -1) {
|
|
263
|
+
return [...favorites, entry];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const current = favorites[index];
|
|
267
|
+
const next = {
|
|
268
|
+
...current,
|
|
269
|
+
label: current.label || entry.label,
|
|
270
|
+
color: current.color || entry.color,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
current.label === next.label &&
|
|
275
|
+
current.color === next.color
|
|
276
|
+
) {
|
|
277
|
+
return favorites;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const copy = [...favorites];
|
|
281
|
+
copy[index] = next;
|
|
282
|
+
return copy;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const isFavoritesState = (value: PersistedFavoritesState): value is FavoritesState => (
|
|
286
|
+
"favorites" in value && "documents" in value
|
|
287
|
+
);
|
package/src/icons/file-icon.tsx
CHANGED
|
@@ -34,34 +34,25 @@ const getGenericDisplayCode = (mimeType: string) => {
|
|
|
34
34
|
|
|
35
35
|
const GenericFileTypeIcon = ({ code }: { code: string }) => (
|
|
36
36
|
<svg
|
|
37
|
-
|
|
37
|
+
strokeWidth="0"
|
|
38
|
+
viewBox="0 0 256 256"
|
|
38
39
|
className="size-5 text-primary"
|
|
39
40
|
aria-hidden="true"
|
|
40
|
-
fill="
|
|
41
|
+
fill="currentColor"
|
|
41
42
|
xmlns="http://www.w3.org/2000/svg"
|
|
42
43
|
>
|
|
43
44
|
<path
|
|
44
|
-
d="
|
|
45
|
-
stroke="currentColor"
|
|
46
|
-
strokeWidth="1.5"
|
|
47
|
-
fill="currentColor"
|
|
48
|
-
fillOpacity="0.08"
|
|
49
|
-
/>
|
|
50
|
-
<path
|
|
51
|
-
d="M14 2v6h5"
|
|
52
|
-
stroke="currentColor"
|
|
53
|
-
strokeWidth="1.5"
|
|
54
|
-
strokeLinecap="round"
|
|
55
|
-
strokeLinejoin="round"
|
|
45
|
+
d="M36,112V40A16,16,0,0,1,52,24h100a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88v24a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H52v72a8,8,0,0,1-16,0ZM160,80h28.69L160,51.31Z"
|
|
56
46
|
/>
|
|
57
47
|
<text
|
|
58
|
-
x="
|
|
59
|
-
y="
|
|
48
|
+
x="128"
|
|
49
|
+
y="220"
|
|
60
50
|
textAnchor="middle"
|
|
61
|
-
fontSize="
|
|
51
|
+
fontSize="110"
|
|
62
52
|
fontWeight="700"
|
|
63
53
|
fill="currentColor"
|
|
64
|
-
|
|
54
|
+
fontFamily="inherit"
|
|
55
|
+
letterSpacing={code.length >= 4 ? -2 : 0}
|
|
65
56
|
>
|
|
66
57
|
{code}
|
|
67
58
|
</text>
|
|
@@ -69,13 +60,5 @@ const GenericFileTypeIcon = ({ code }: { code: string }) => (
|
|
|
69
60
|
);
|
|
70
61
|
|
|
71
62
|
export const FileIcon = ({ extension }: { extension: string }) => {
|
|
72
|
-
if (extension === "application/pdf") {
|
|
73
|
-
return <PiFilePdf className="text-primary" />;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (extension === "application/zip") {
|
|
77
|
-
return <PiFileZip className="text-primary" />;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
63
|
return <GenericFileTypeIcon code={getGenericDisplayCode(extension)} />;
|
|
81
64
|
};
|
|
@@ -344,7 +344,7 @@ export const InformationUnitMetadataGridClient = ({
|
|
|
344
344
|
|
|
345
345
|
const cardContent = (
|
|
346
346
|
<CardContent className="space-y-3 !p-0">
|
|
347
|
-
<Table className="table-fixed">
|
|
347
|
+
<Table className="table-fixed overflow-ellipsis">
|
|
348
348
|
<TableBody>
|
|
349
349
|
{showBookmarkButton && (
|
|
350
350
|
<TableRow className="min-h-12">
|
|
@@ -374,26 +374,26 @@ export const InformationUnitMetadataGridClient = ({
|
|
|
374
374
|
availableInVersions
|
|
375
375
|
.filter((item) => item.shortId !== data.shortId)
|
|
376
376
|
.length > 0 ? (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
377
|
+
<TableRow className="min-h-12">
|
|
378
|
+
<TableCell className="font-medium w-28 pl-4">
|
|
379
|
+
<h4 className="text-sm font-medium">{t("availableIn")}</h4>
|
|
380
|
+
</TableCell>
|
|
381
|
+
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
382
|
+
{availableInVersions
|
|
383
|
+
.filter((item) => item.shortId !== data.shortId)
|
|
384
|
+
.map((item) => (
|
|
385
|
+
<span className="w-8 block border" key={item.shortId}>
|
|
386
|
+
<Link
|
|
387
|
+
href={linkPattern.replace("{shortId}", item.shortId)}
|
|
388
|
+
title={item.language}
|
|
389
|
+
>
|
|
390
|
+
<Flag countryCode={extractCountryCodeFromLanguage(item.language)} />
|
|
391
|
+
</Link>
|
|
392
|
+
</span>
|
|
393
|
+
))}
|
|
394
|
+
</TableCell>
|
|
395
|
+
</TableRow>
|
|
396
|
+
) : null
|
|
397
397
|
) : (
|
|
398
398
|
<AvailableInRow
|
|
399
399
|
versionOfShortId={data.versionOf?.shortId}
|
package/src/page-wrapper.tsx
CHANGED
|
@@ -46,7 +46,7 @@ export const PageWrapper = ({
|
|
|
46
46
|
<NavBar showInput={showInput} showPkgFilter={showPkgFilter} {...props} />
|
|
47
47
|
|
|
48
48
|
{showRestrictMenu && (
|
|
49
|
-
<div className="
|
|
49
|
+
<div className="container pt-6">
|
|
50
50
|
{renderRestrictionMenu ? renderRestrictionMenu(restrictionMenuProps) : (
|
|
51
51
|
<TaxonomyRestrictionMenu {...restrictionMenuProps} />
|
|
52
52
|
)}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react";
|
|
|
4
4
|
import { FragmentsGetByIdClient } from "../generated/client-components";
|
|
5
5
|
import type { RenditionModel } from "@c-rex/interfaces";
|
|
6
6
|
import { RenderArticle } from "../render-article";
|
|
7
|
+
import { useTranslations } from "next-intl";
|
|
7
8
|
|
|
8
9
|
type HtmlRenditionClientProps = {
|
|
9
10
|
htmlFormats?: string[];
|
|
@@ -11,8 +12,6 @@ type HtmlRenditionClientProps = {
|
|
|
11
12
|
renditions?: RenditionModel[] | null;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const EMPTY = <div>No rendition available</div>;
|
|
15
|
-
|
|
16
15
|
const findHtmlViewUrl = (
|
|
17
16
|
renditions?: RenditionModel[] | null,
|
|
18
17
|
htmlFormats: string[] = ["application/xhtml+xml", "application/html", "text/html"]
|
|
@@ -30,6 +29,7 @@ const HtmlFromRenditions = ({
|
|
|
30
29
|
renditions?: RenditionModel[] | null;
|
|
31
30
|
htmlFormats?: string[];
|
|
32
31
|
}) => {
|
|
32
|
+
const t = useTranslations();
|
|
33
33
|
const [htmlContent, setHtmlContent] = useState<string | null>(null);
|
|
34
34
|
const [hasError, setHasError] = useState(false);
|
|
35
35
|
const viewUrl = useMemo(() => findHtmlViewUrl(renditions, htmlFormats), [htmlFormats, renditions]);
|
|
@@ -62,8 +62,8 @@ const HtmlFromRenditions = ({
|
|
|
62
62
|
};
|
|
63
63
|
}, [viewUrl]);
|
|
64
64
|
|
|
65
|
-
if (!viewUrl || hasError) return
|
|
66
|
-
if (htmlContent == null) return <div className="text-muted-foreground text-sm">
|
|
65
|
+
if (!viewUrl || hasError) return <div>{t("noRenditionAvailable")}</div>;
|
|
66
|
+
if (htmlContent == null) return <div className="text-muted-foreground text-sm">{t("loadingContent")}</div>;
|
|
67
67
|
|
|
68
68
|
return <RenderArticle htmlContent={htmlContent} />;
|
|
69
69
|
};
|
|
@@ -73,11 +73,13 @@ export const HtmlRenditionClient = ({
|
|
|
73
73
|
htmlFormats = ["application/xhtml+xml", "application/html", "text/html"],
|
|
74
74
|
renditions,
|
|
75
75
|
}: HtmlRenditionClientProps) => {
|
|
76
|
+
const t = useTranslations();
|
|
77
|
+
|
|
76
78
|
if (renditions !== undefined) {
|
|
77
79
|
return <HtmlFromRenditions renditions={renditions} htmlFormats={htmlFormats} />;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
if (!fragmentShortId) return
|
|
82
|
+
if (!fragmentShortId) return <div>{t("noRenditionAvailable")}</div>;
|
|
81
83
|
|
|
82
84
|
return (
|
|
83
85
|
<FragmentsGetByIdClient
|
|
@@ -90,7 +92,7 @@ export const HtmlRenditionClient = ({
|
|
|
90
92
|
>
|
|
91
93
|
{({ data, isLoading }) => {
|
|
92
94
|
if (isLoading && !data) {
|
|
93
|
-
return <div className="text-muted-foreground text-sm">
|
|
95
|
+
return <div className="text-muted-foreground text-sm">{t("loadingContent")}</div>;
|
|
94
96
|
}
|
|
95
97
|
return <HtmlFromRenditions renditions={data?.renditions} htmlFormats={htmlFormats} />;
|
|
96
98
|
}}
|
package/src/renditions/html.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import * as cheerio from "cheerio"
|
|
|
3
3
|
import { RenditionModel } from "@c-rex/interfaces";
|
|
4
4
|
import { fragmentsGetByIdServer } from "@c-rex/services/server-requests";
|
|
5
5
|
import { call } from "@c-rex/utils";
|
|
6
|
+
import { getTranslations } from "next-intl/server";
|
|
6
7
|
|
|
7
8
|
interface HtmlRenditionProps {
|
|
8
9
|
htmlFormats?: string[]
|
|
@@ -23,7 +24,8 @@ export const HtmlRendition: FC<HtmlRenditionProps> = async ({
|
|
|
23
24
|
render = defaultRender,
|
|
24
25
|
renditions,
|
|
25
26
|
}) => {
|
|
26
|
-
const
|
|
27
|
+
const t = await getTranslations();
|
|
28
|
+
const empty = <div>{t("noRenditionAvailable")}</div>;
|
|
27
29
|
|
|
28
30
|
if (renditions == undefined) {
|
|
29
31
|
if (fragmentShortId) {
|
|
@@ -38,47 +38,30 @@ export const RestrictionNavigationItem: FC<Props> = ({
|
|
|
38
38
|
currentRestrict: restrict,
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
const labelStyle = {
|
|
42
|
-
display: 'inline-block',
|
|
43
|
-
maxWidth: 100,
|
|
44
|
-
overflow: 'hidden',
|
|
45
|
-
textOverflow: 'ellipsis',
|
|
46
|
-
whiteSpace: 'nowrap',
|
|
47
|
-
verticalAlign: 'middle',
|
|
48
|
-
} as React.CSSProperties;
|
|
49
|
-
|
|
50
|
-
const needsTooltip = label.length > 12;
|
|
51
|
-
|
|
52
|
-
const labelNode = needsTooltip ? (
|
|
53
|
-
<TooltipProvider delayDuration={300}>
|
|
54
|
-
<Tooltip>
|
|
55
|
-
<TooltipTrigger asChild>
|
|
56
|
-
<span style={labelStyle}>{label}</span>
|
|
57
|
-
</TooltipTrigger>
|
|
58
|
-
<TooltipContent>{label}</TooltipContent>
|
|
59
|
-
</Tooltip>
|
|
60
|
-
</TooltipProvider>
|
|
61
|
-
) : (
|
|
62
|
-
<span style={labelStyle}>{label}</span>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
41
|
return (
|
|
66
42
|
<NavigationMenuItem key={shortId} asChild>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
43
|
+
<TooltipProvider delayDuration={300}>
|
|
44
|
+
<Tooltip>
|
|
45
|
+
<TooltipTrigger asChild>
|
|
46
|
+
<Button
|
|
47
|
+
variant={selected ? "default" : "outline"}
|
|
48
|
+
rounded="full"
|
|
49
|
+
onClick={() => {
|
|
50
|
+
startSearchNavigation();
|
|
51
|
+
if (shouldRemoveRestrictParam) {
|
|
52
|
+
setRestrict(null);
|
|
53
|
+
} else {
|
|
54
|
+
setRestrict(restrictionValue);
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
className="cursor-pointer"
|
|
58
|
+
>
|
|
59
|
+
{label}
|
|
60
|
+
</Button>
|
|
61
|
+
</TooltipTrigger>
|
|
62
|
+
<TooltipContent>{label}</TooltipContent>
|
|
63
|
+
</Tooltip>
|
|
64
|
+
</TooltipProvider>
|
|
82
65
|
</NavigationMenuItem>
|
|
83
66
|
);
|
|
84
67
|
};
|
|
@@ -104,25 +87,32 @@ export const RestrictionDropdownItem: FC<Props> = ({
|
|
|
104
87
|
});
|
|
105
88
|
|
|
106
89
|
return (
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
90
|
+
<TooltipProvider delayDuration={300}>
|
|
91
|
+
<Tooltip>
|
|
92
|
+
<TooltipTrigger asChild>
|
|
93
|
+
<Button
|
|
94
|
+
variant={selected ? "default" : "ghost"}
|
|
95
|
+
onClick={() => {
|
|
96
|
+
if (onClick) {
|
|
97
|
+
onClick();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
startSearchNavigation();
|
|
101
|
+
if (shouldRemoveRestrictParam) {
|
|
102
|
+
setRestrict(null);
|
|
103
|
+
} else {
|
|
104
|
+
setRestrict(restrictionValue);
|
|
105
|
+
}
|
|
106
|
+
}}
|
|
107
|
+
rounded="full"
|
|
108
|
+
className="text-left text-wrap !h-auto min-h-10 w-full !justify-start cursor-pointer"
|
|
109
|
+
>
|
|
110
|
+
{label}
|
|
111
|
+
</Button>
|
|
112
|
+
</TooltipTrigger>
|
|
113
|
+
<TooltipContent>{label}</TooltipContent>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
</TooltipProvider>
|
|
126
116
|
);
|
|
127
117
|
};
|
|
128
118
|
|