@c-rex/components 0.1.12 → 0.1.14
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 +12 -2
- package/src/autocomplete.tsx +12 -8
- package/src/check-article-lang.tsx +3 -1
- package/src/dialog-filter.tsx +43 -47
- package/src/info/info-card.tsx +3 -4
- package/src/info/info-table.tsx +8 -9
- package/src/navbar/language-switcher/content-language-switch.tsx +16 -7
- package/src/navbar/language-switcher/ui-language-switch.tsx +5 -6
- package/src/navbar/search-input.tsx +1 -1
- package/src/render-article.tsx +62 -10
- package/src/result-view/blog.tsx +8 -5
- package/src/result-view/table.tsx +9 -6
- package/src/right-sidebar.tsx +9 -8
- package/src/search-modal.tsx +7 -11
- package/src/stores/highlight-store.ts +16 -0
- package/src/stores/language-store.ts +59 -0
- package/src/stores/search-settings-store.ts +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"files": [
|
|
5
5
|
"src"
|
|
6
6
|
],
|
|
@@ -100,6 +100,14 @@
|
|
|
100
100
|
"./loading": {
|
|
101
101
|
"types": "./src/loading.tsx",
|
|
102
102
|
"import": "./src/loading.tsx"
|
|
103
|
+
},
|
|
104
|
+
"./language-store": {
|
|
105
|
+
"types": "./src/stores/language-store.ts",
|
|
106
|
+
"import": "./src/stores/language-store.ts"
|
|
107
|
+
},
|
|
108
|
+
"./highlight-store": {
|
|
109
|
+
"types": "./src/stores/highlight-store.ts",
|
|
110
|
+
"import": "./src/stores/highlight-store.ts"
|
|
103
111
|
}
|
|
104
112
|
},
|
|
105
113
|
"scripts": {
|
|
@@ -140,6 +148,7 @@
|
|
|
140
148
|
"@c-rex/ui": "*",
|
|
141
149
|
"@c-rex/utils": "*",
|
|
142
150
|
"country-flag-icons": "^1.5.19",
|
|
151
|
+
"html-react-parser": "^5.2.6",
|
|
143
152
|
"lucide-react": "^0.511.0",
|
|
144
153
|
"next": "^14",
|
|
145
154
|
"next-intl": "^4.1.0",
|
|
@@ -147,7 +156,8 @@
|
|
|
147
156
|
"react": "^18.3.1",
|
|
148
157
|
"react-dom": "^18.3.1",
|
|
149
158
|
"react-icons": "^5.5.0",
|
|
150
|
-
"tailwindcss-animate": "^1.0.7"
|
|
159
|
+
"tailwindcss-animate": "^1.0.7",
|
|
160
|
+
"zustand": "^5.0.8"
|
|
151
161
|
},
|
|
152
162
|
"eslintConfig": {
|
|
153
163
|
"extends": [
|
package/src/autocomplete.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { Input } from "@c-rex/ui/input";
|
|
3
3
|
import { call, generateQueryParams } from "@c-rex/utils";
|
|
4
|
-
import { WILD_CARD_OPTIONS } from "@c-rex/constants";
|
|
5
4
|
import { useTranslations } from "next-intl";
|
|
6
5
|
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
7
6
|
import { QueryParams } from "@c-rex/types";
|
|
7
|
+
import { useSearchSettingsStore } from "./stores/search-settings-store";
|
|
8
|
+
import { useLanguageStore } from "./stores/language-store";
|
|
8
9
|
|
|
9
10
|
type Props = {
|
|
10
11
|
initialValue: string;
|
|
@@ -15,7 +16,9 @@ type Props = {
|
|
|
15
16
|
export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props) => {
|
|
16
17
|
const t = useTranslations();
|
|
17
18
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
18
|
-
const
|
|
19
|
+
const contentLang = useLanguageStore.getState().contentLang;
|
|
20
|
+
|
|
21
|
+
const { articleLang, packageID } = useAppConfig()
|
|
19
22
|
|
|
20
23
|
const [open, setOpen] = useState(false);
|
|
21
24
|
const [query, setQuery] = useState(initialValue);
|
|
@@ -29,11 +32,13 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
|
|
|
29
32
|
const onSelect = (value: string) => {
|
|
30
33
|
const params: QueryParams[] = [
|
|
31
34
|
{ key: "search", value: value },
|
|
32
|
-
{ key: "operator", value: "OR" },
|
|
33
35
|
{ key: "page", value: "1" },
|
|
34
|
-
{ key: "
|
|
35
|
-
|
|
36
|
-
{ key: "
|
|
36
|
+
{ key: "operator", value: useSearchSettingsStore.getState().operator },
|
|
37
|
+
|
|
38
|
+
{ key: "language", value: useLanguageStore.getState().contentLang },
|
|
39
|
+
|
|
40
|
+
{ key: "wildcard", value: useSearchSettingsStore.getState().wildcard },
|
|
41
|
+
{ key: "like", value: useSearchSettingsStore.getState().like.toString() },
|
|
37
42
|
]
|
|
38
43
|
|
|
39
44
|
if (searchByPackage && packageID !== null) {
|
|
@@ -45,7 +50,6 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
|
|
|
45
50
|
window.location.href = `${window.location.origin}/?${aux}`
|
|
46
51
|
};
|
|
47
52
|
|
|
48
|
-
|
|
49
53
|
useEffect(() => {
|
|
50
54
|
setQuery(initialValue);
|
|
51
55
|
}, [initialValue]);
|
|
@@ -101,7 +105,7 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
|
|
|
101
105
|
/>
|
|
102
106
|
|
|
103
107
|
{open && (
|
|
104
|
-
<ul className="absolute z-10 w-full bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
|
108
|
+
<ul className="suggestions-list absolute z-10 w-full bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
|
105
109
|
{loading ? (
|
|
106
110
|
<li>
|
|
107
111
|
<div className="flex items-center justify-center py-4">
|
|
@@ -5,6 +5,7 @@ import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
|
5
5
|
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
6
6
|
import { toast } from "sonner"
|
|
7
7
|
import { useTranslations } from "next-intl"
|
|
8
|
+
import { useLanguageStore } from "./stores/language-store";
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
availableVersions: AvailableVersionsInterface[]
|
|
@@ -12,10 +13,11 @@ interface Props {
|
|
|
12
13
|
|
|
13
14
|
export const CheckArticleLangToast: FC<Props> = ({ availableVersions }) => {
|
|
14
15
|
const t = useTranslations();
|
|
15
|
-
const {
|
|
16
|
+
const { setAvailableVersions } = useAppConfig()
|
|
16
17
|
|
|
17
18
|
useEffect(() => {
|
|
18
19
|
setAvailableVersions(availableVersions)
|
|
20
|
+
const contentLang = useLanguageStore.getState().contentLang;
|
|
19
21
|
|
|
20
22
|
const activeArticle = availableVersions.filter((item) => item.active)[0]
|
|
21
23
|
if (activeArticle == undefined || activeArticle.lang == contentLang) return
|
package/src/dialog-filter.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { FC,
|
|
1
|
+
import React, { FC, useState } from "react"
|
|
2
2
|
import { Button } from "@c-rex/ui/button"
|
|
3
3
|
import {
|
|
4
4
|
Dialog,
|
|
@@ -14,11 +14,13 @@ import { Checkbox } from "@c-rex/ui/checkbox"
|
|
|
14
14
|
import { Label } from "@c-rex/ui/label"
|
|
15
15
|
import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"
|
|
16
16
|
import { useTranslations } from "next-intl"
|
|
17
|
-
import { useAppConfig } from "@c-rex/contexts/config-provider"
|
|
18
17
|
import { LanguageAndCountries } from "@c-rex/interfaces"
|
|
19
18
|
import { WILD_CARD_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants"
|
|
20
19
|
import { Switch } from "@c-rex/ui/switch"
|
|
21
20
|
import { useSearchContext } from "@c-rex/contexts/search"
|
|
21
|
+
import { useSearchSettingsStore } from "./stores/search-settings-store"
|
|
22
|
+
import { OperatorType, WildCardType } from "@c-rex/types"
|
|
23
|
+
import { useLanguageStore } from "./stores/language-store"
|
|
22
24
|
|
|
23
25
|
interface DialogFilterProps {
|
|
24
26
|
trigger: React.ReactNode;
|
|
@@ -32,35 +34,40 @@ interface Languages {
|
|
|
32
34
|
export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
33
35
|
const t = useTranslations("filter");
|
|
34
36
|
const { setLoading } = useSearchContext();
|
|
35
|
-
|
|
37
|
+
|
|
38
|
+
const savedLike = useSearchSettingsStore.getState().like;
|
|
39
|
+
const savedOperator = useSearchSettingsStore.getState().operator;
|
|
40
|
+
const savedWildcard = useSearchSettingsStore.getState().wildcard;
|
|
41
|
+
const savedLanguages = useLanguageStore.getState().contentLang;
|
|
42
|
+
const availableLanguagesAndCountries = useLanguageStore.getState().availableLanguages;
|
|
43
|
+
|
|
44
|
+
const [like, setLike] = useState<boolean>(savedLike);
|
|
45
|
+
const [operator, setOperator] = useState<string>(savedOperator);
|
|
46
|
+
const [wildcard, setWildcard] = useState<string>(savedWildcard);
|
|
47
|
+
const [languages, setLanguages] = useState<Languages[]>(
|
|
48
|
+
availableLanguagesAndCountries?.map((item: LanguageAndCountries) => {
|
|
49
|
+
const checked = savedLanguages.includes(item.value)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
lang: item.lang,
|
|
53
|
+
value: item.value,
|
|
54
|
+
checked,
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
36
59
|
const [params, setParams] = useQueryStates({
|
|
60
|
+
search: parseAsString,
|
|
37
61
|
language: parseAsString,
|
|
38
62
|
page: parseAsInteger,
|
|
39
63
|
wildcard: parseAsString,
|
|
40
64
|
operator: parseAsString,
|
|
41
|
-
filter: parseAsString,
|
|
42
65
|
like: parseAsBoolean,
|
|
43
66
|
}, {
|
|
44
67
|
history: 'push',
|
|
45
68
|
shallow: false,
|
|
46
69
|
});
|
|
47
70
|
|
|
48
|
-
const startSelectedLanguages = params.language?.split(',') || []
|
|
49
|
-
|
|
50
|
-
const [operator, setOperator] = useState<string>(params.operator || OPERATOR_OPTIONS.OR);
|
|
51
|
-
const [like, setLike] = useState<boolean>(params.like || false);
|
|
52
|
-
const [wildcard, setWildcard] = useState<string>(params.wildcard || "");
|
|
53
|
-
const [disabled, setDisabled] = useState<boolean>(false)
|
|
54
|
-
const [languages, setLanguages] = useState<Languages[]>(availableLanguagesAndCountries.map((item: LanguageAndCountries) => {
|
|
55
|
-
const checked = startSelectedLanguages.includes(item.value)
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
lang: item.lang,
|
|
59
|
-
value: item.value,
|
|
60
|
-
checked,
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
71
|
const onChangeCheckbox = (item: Languages) => {
|
|
65
72
|
const newLangList = languages.filter((lang) => lang.lang !== item.lang)
|
|
66
73
|
|
|
@@ -70,42 +77,29 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
const apply = () => {
|
|
73
|
-
|
|
80
|
+
const selectedLanguages = languages.filter((item) => item.checked).map((item) => item.value)
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
useSearchSettingsStore.getState().updatePreferences({
|
|
83
|
+
like,
|
|
84
|
+
operator: operator as OperatorType,
|
|
85
|
+
wildcard: wildcard as WildCardType,
|
|
86
|
+
language: selectedLanguages,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (params.search == null) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
79
92
|
|
|
93
|
+
setLoading(true);
|
|
80
94
|
setParams({
|
|
81
95
|
page: 1,
|
|
82
|
-
language: selectedLanguages,
|
|
96
|
+
language: selectedLanguages.join(','),
|
|
83
97
|
operator: operator,
|
|
84
98
|
wildcard: wildcard,
|
|
85
99
|
like: like,
|
|
86
|
-
filter: null,
|
|
87
100
|
});
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
setOperator(params.operator || OPERATOR_OPTIONS.OR);
|
|
92
|
-
setLike(params.like || false);
|
|
93
|
-
setWildcard(params.wildcard || "");
|
|
94
|
-
setLanguages(availableLanguagesAndCountries.map((item: LanguageAndCountries) => {
|
|
95
|
-
const checked = startSelectedLanguages.includes(item.value)
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
lang: item.lang,
|
|
99
|
-
value: item.value,
|
|
100
|
-
checked,
|
|
101
|
-
}
|
|
102
|
-
}));
|
|
103
|
-
}, [params])
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
setDisabled(languages.every((item) => !item.checked));
|
|
107
|
-
}, [languages])
|
|
108
|
-
|
|
109
103
|
return (
|
|
110
104
|
<Dialog>
|
|
111
105
|
<DialogTrigger asChild>
|
|
@@ -190,7 +184,9 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
190
184
|
|
|
191
185
|
<DialogFooter className="pt-2">
|
|
192
186
|
<DialogClose asChild>
|
|
193
|
-
<Button onClick={apply}
|
|
187
|
+
<Button onClick={apply}>
|
|
188
|
+
{params.search && params.search.length > 0 ? t("applyAndSearch") : t("apply")}
|
|
189
|
+
</Button>
|
|
194
190
|
</DialogClose>
|
|
195
191
|
</DialogFooter>
|
|
196
192
|
</DialogContent>
|
package/src/info/info-card.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
1
2
|
import { Card, CardContent, CardHeader, CardTitle } from "@c-rex/ui/card";
|
|
2
|
-
import { getFromCookieString } from "@c-rex/utils";
|
|
3
3
|
import { useTranslations } from "next-intl";
|
|
4
|
-
import React, { FC } from "react";
|
|
5
4
|
import { filteredItems, renderValue } from "./shared";
|
|
6
|
-
import {
|
|
5
|
+
import { useLanguageStore } from "../stores/language-store";
|
|
7
6
|
|
|
8
7
|
type Props = {
|
|
9
8
|
title: string;
|
|
@@ -15,7 +14,7 @@ type Props = {
|
|
|
15
14
|
}
|
|
16
15
|
export const InfoCard: FC<Props> = ({ title, items }) => {
|
|
17
16
|
const t = useTranslations();
|
|
18
|
-
const uiLang =
|
|
17
|
+
const uiLang = useLanguageStore(state => state.uiLang);
|
|
19
18
|
const newItems = filteredItems(items)
|
|
20
19
|
|
|
21
20
|
return (
|
package/src/info/info-table.tsx
CHANGED
|
@@ -8,8 +8,6 @@ import {
|
|
|
8
8
|
TableRow,
|
|
9
9
|
} from "@c-rex/ui/table"
|
|
10
10
|
import { useTranslations } from "next-intl";
|
|
11
|
-
import { getFromCookieString } from "@c-rex/utils";
|
|
12
|
-
import { UI_LANG_KEY } from "@c-rex/constants";
|
|
13
11
|
import { filteredItems, renderValue } from "./shared";
|
|
14
12
|
import {
|
|
15
13
|
DropdownMenu,
|
|
@@ -22,6 +20,7 @@ import { CloudDownload, Eye } from "lucide-react";
|
|
|
22
20
|
import { FaFilePdf } from "react-icons/fa6";
|
|
23
21
|
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
24
22
|
import { Flag } from "../flag";
|
|
23
|
+
import { useLanguageStore } from "../stores/language-store";
|
|
25
24
|
|
|
26
25
|
type Props = {
|
|
27
26
|
title: string;
|
|
@@ -36,7 +35,7 @@ const IconsToFileExtension: Record<string, React.ReactNode> = {
|
|
|
36
35
|
|
|
37
36
|
export const InfoTable: FC<Props> = ({ title, items, files, availableVersions }) => {
|
|
38
37
|
const t = useTranslations();
|
|
39
|
-
const uiLang =
|
|
38
|
+
const uiLang = useLanguageStore(state => state.uiLang);
|
|
40
39
|
const newItems = filteredItems(items)
|
|
41
40
|
|
|
42
41
|
return (
|
|
@@ -48,7 +47,7 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
|
|
|
48
47
|
<Table>
|
|
49
48
|
<TableBody>
|
|
50
49
|
{newItems.map((item, index) => (
|
|
51
|
-
<TableRow key={index} className="h-12">
|
|
50
|
+
<TableRow key={index} className="min-h-12">
|
|
52
51
|
<TableCell className="font-medium w-28 pl-4">
|
|
53
52
|
{item.link ? (
|
|
54
53
|
<a href={item.link} className="underline">
|
|
@@ -58,18 +57,18 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
|
|
|
58
57
|
<h4 className="text-sm font-medium">{t(item.label)}</h4>
|
|
59
58
|
)}
|
|
60
59
|
</TableCell>
|
|
61
|
-
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 h-12">
|
|
60
|
+
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
62
61
|
{renderValue(item, uiLang)}
|
|
63
62
|
</TableCell>
|
|
64
63
|
</TableRow>
|
|
65
64
|
))}
|
|
66
65
|
|
|
67
66
|
{files && (
|
|
68
|
-
<TableRow className="h-12">
|
|
67
|
+
<TableRow className="min-h-12">
|
|
69
68
|
<TableCell className="font-medium w-28 pl-4">
|
|
70
69
|
<h4 className="text-sm font-medium">{t("files")}</h4>
|
|
71
70
|
</TableCell>
|
|
72
|
-
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 h-12">
|
|
71
|
+
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
73
72
|
|
|
74
73
|
{Object.keys(files).map((item, index) => {
|
|
75
74
|
if (!files[item]) return null
|
|
@@ -101,11 +100,11 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
|
|
|
101
100
|
)}
|
|
102
101
|
|
|
103
102
|
{availableVersions && (
|
|
104
|
-
<TableRow className="h-12">
|
|
103
|
+
<TableRow className="min-h-12">
|
|
105
104
|
<TableCell className="font-medium w-28 pl-4">
|
|
106
105
|
<h4 className="text-sm font-medium">{t("availableIn")}</h4>
|
|
107
106
|
</TableCell>
|
|
108
|
-
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 h-12">
|
|
107
|
+
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
109
108
|
{availableVersions.map((item) => {
|
|
110
109
|
return (
|
|
111
110
|
<span className="w-8 block border" key={item.shortId}>
|
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import { startTransition } from "react";
|
|
4
4
|
import { SharedLanguageSwitch } from "./shared";
|
|
5
|
-
import {
|
|
6
|
-
import { BLOG_TYPE_AND_LINK, CONTENT_LANG_KEY, DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
|
|
5
|
+
import { BLOG_TYPE_AND_LINK, DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
|
|
7
6
|
import { useQueryState } from "nuqs"
|
|
8
7
|
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
9
8
|
import { useTranslations } from "next-intl";
|
|
10
9
|
import { toast } from "sonner"
|
|
10
|
+
import { useLanguageStore } from "../../stores/language-store";
|
|
11
|
+
import { useSearchSettingsStore } from "../../stores/search-settings-store";
|
|
11
12
|
|
|
12
13
|
export const ContentLanguageSwitch = () => {
|
|
13
14
|
const t = useTranslations();
|
|
14
|
-
const contentLang =
|
|
15
|
-
const
|
|
15
|
+
const contentLang = useLanguageStore.getState().contentLang;
|
|
16
|
+
const setContentLang = useLanguageStore.getState().setContentLang;
|
|
17
|
+
const updatePreferences = useSearchSettingsStore.getState().updatePreferences;
|
|
18
|
+
|
|
19
|
+
const { availableVersions } = useAppConfig()
|
|
16
20
|
|
|
17
21
|
const availableLanguagesAndCountries = () => {
|
|
22
|
+
const aux = useLanguageStore.getState().availableLanguages;
|
|
18
23
|
let result = aux.map(item => ({ ...item, link: "#" }))
|
|
19
24
|
if (availableVersions == null) return result
|
|
20
25
|
|
|
@@ -35,18 +40,22 @@ export const ContentLanguageSwitch = () => {
|
|
|
35
40
|
|
|
36
41
|
const changeContentLanguage = (locale: string) => {
|
|
37
42
|
startTransition(() => {
|
|
38
|
-
setCookie(CONTENT_LANG_KEY, locale)
|
|
39
43
|
setContentLang(locale)
|
|
44
|
+
updatePreferences({ language: [locale] })
|
|
40
45
|
|
|
41
46
|
if (queryLanguage != null) {
|
|
42
47
|
setContentLanguage(locale)
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
const currentPath = window.location.pathname;
|
|
46
|
-
const isTopicOrBlogOrDocument =
|
|
51
|
+
const isTopicOrBlogOrDocument = (
|
|
52
|
+
currentPath.includes(TOPICS_TYPE_AND_LINK) ||
|
|
53
|
+
currentPath.includes(BLOG_TYPE_AND_LINK) ||
|
|
54
|
+
currentPath.includes(DOCUMENTS_TYPE_AND_LINK)
|
|
55
|
+
)
|
|
47
56
|
|
|
48
57
|
if (!isTopicOrBlogOrDocument) {
|
|
49
|
-
window.location.reload();
|
|
58
|
+
setTimeout(() => window.location.reload(), 5);
|
|
50
59
|
return;
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { FC, startTransition } from "react";
|
|
4
4
|
import { SharedLanguageSwitch } from "./shared";
|
|
5
|
-
import {
|
|
6
|
-
import { getCountryCodeByLang
|
|
7
|
-
import {
|
|
5
|
+
import { UI_LANG_OPTIONS } from "@c-rex/constants";
|
|
6
|
+
import { getCountryCodeByLang } from "@c-rex/utils";
|
|
7
|
+
import { useLanguageStore } from "../../stores/language-store";
|
|
8
8
|
|
|
9
9
|
export const UILanguageSwitch: FC = () => {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const uiLang = useLanguageStore.getState().uiLang.toLowerCase();
|
|
11
|
+
const setUiLang = useLanguageStore.getState().setUiLang;
|
|
12
12
|
const UILanguages = UI_LANG_OPTIONS.map((lang) => {
|
|
13
13
|
const langCode = lang.split("-")[0] as string;
|
|
14
14
|
return {
|
|
@@ -20,7 +20,6 @@ export const UILanguageSwitch: FC = () => {
|
|
|
20
20
|
|
|
21
21
|
const setUILanguage = (locale: string) => {
|
|
22
22
|
startTransition(() => {
|
|
23
|
-
setCookie(UI_LANG_KEY, locale)
|
|
24
23
|
setUiLang(locale)
|
|
25
24
|
|
|
26
25
|
window.location.reload()
|
|
@@ -21,7 +21,7 @@ export const SearchInput: FC<Props> = ({ showInput, showPkgFilter, placedOn = "N
|
|
|
21
21
|
return (
|
|
22
22
|
<div className={cn(
|
|
23
23
|
placedOn === "NAVBAR" ? "hidden lg:flex" : "sm:flex-none sm:w-60 md:w-72 lg:w-full lg:hidden flex",
|
|
24
|
-
"flex-1 items-center px-3 border rounded-full h-8"
|
|
24
|
+
"flex-1 items-center px-3 border rounded-full h-8 c-rex-search-bar"
|
|
25
25
|
)}>
|
|
26
26
|
<Search className="h-4 w-4 shrink-0 opacity-50" />
|
|
27
27
|
|
package/src/render-article.tsx
CHANGED
|
@@ -1,16 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useRef, useEffect } from "react";
|
|
4
|
+
import parse from "html-react-parser";
|
|
5
|
+
import { useQueryState } from "nuqs";
|
|
6
|
+
import { useHighlight } from "@c-rex/contexts/highlight-provider";
|
|
7
|
+
import { useHighlightStore } from "./stores/highlight-store";
|
|
2
8
|
|
|
3
9
|
type Props = {
|
|
4
|
-
htmlContent: string
|
|
5
|
-
contentLang
|
|
6
|
-
}
|
|
10
|
+
htmlContent: string;
|
|
11
|
+
contentLang?: string;
|
|
12
|
+
};
|
|
7
13
|
|
|
8
14
|
export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
|
|
15
|
+
const [query] = useQueryState("q");
|
|
16
|
+
const containerRef = useRef<HTMLElement | null>(null);
|
|
17
|
+
const enableHighlight = useHighlightStore((state) => state.enable);
|
|
18
|
+
const { registerContainer } = useHighlight();
|
|
19
|
+
|
|
20
|
+
const highlightedContent = useMemo(() => {
|
|
21
|
+
if (!enableHighlight) return parse(htmlContent);
|
|
22
|
+
if (!query) return parse(htmlContent);
|
|
23
|
+
|
|
24
|
+
const terms = query
|
|
25
|
+
.split(/[+ ]/)
|
|
26
|
+
.map((t) => t.trim())
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
|
|
29
|
+
if (terms.length === 0) return parse(htmlContent);
|
|
30
|
+
|
|
31
|
+
const escaped = terms.map((t) =>
|
|
32
|
+
t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
33
|
+
);
|
|
34
|
+
const regex = new RegExp(`(${escaped.join("|")})`, "gi");
|
|
35
|
+
|
|
36
|
+
return parse(htmlContent, {
|
|
37
|
+
replace: (domNode: any) => {
|
|
38
|
+
if (domNode.type === "text") {
|
|
39
|
+
const parts = domNode.data.split(regex);
|
|
40
|
+
if (parts.length === 1) return;
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
{parts.map((part: any, i: any) =>
|
|
44
|
+
regex.test(part) ? (
|
|
45
|
+
<mark key={i} className="bg-yellow-200">
|
|
46
|
+
{part}
|
|
47
|
+
</mark>
|
|
48
|
+
) : (
|
|
49
|
+
part
|
|
50
|
+
)
|
|
51
|
+
)}
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}, [htmlContent, query, enableHighlight]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
registerContainer(containerRef.current);
|
|
61
|
+
}, [highlightedContent, registerContainer]);
|
|
62
|
+
|
|
9
63
|
return (
|
|
10
|
-
<main
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
|
14
|
-
/>
|
|
64
|
+
<main ref={containerRef} lang={contentLang} className="pb-4">
|
|
65
|
+
{highlightedContent}
|
|
66
|
+
</main>
|
|
15
67
|
);
|
|
16
|
-
}
|
|
68
|
+
};
|
package/src/result-view/blog.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { Badge } from "@c-rex/ui/badge";
|
|
|
8
8
|
import { useTranslations } from "next-intl";
|
|
9
9
|
import { TopicsResponseItem } from "@c-rex/interfaces";
|
|
10
10
|
import { Card } from "@c-rex/ui/card";
|
|
11
|
+
import { useQueryState } from "nuqs";
|
|
11
12
|
|
|
12
13
|
interface BlogViewProps {
|
|
13
14
|
items: TopicsResponseItem[];
|
|
@@ -16,18 +17,20 @@ interface BlogViewProps {
|
|
|
16
17
|
const BlogView: FC<BlogViewProps> = ({ items }) => {
|
|
17
18
|
const t = useTranslations("results")
|
|
18
19
|
const [isLoading, setLoading] = useState(true);
|
|
20
|
+
const [query] = useQueryState("search");
|
|
21
|
+
|
|
19
22
|
return (
|
|
20
|
-
<div className="grid gap-6 grid-cols-2
|
|
21
|
-
{items.map((item, index) => (
|
|
23
|
+
<div className="grid gap-6 grid-cols-2">
|
|
22
24
|
|
|
25
|
+
{items.map((item, index) => (
|
|
23
26
|
<Card
|
|
24
27
|
key={item.shortId}
|
|
25
28
|
className={cn(
|
|
26
|
-
`blog-card relative p-0 c-
|
|
29
|
+
`c-rex-result-item blog-card relative p-0 c-rex-result-${item.type.toLowerCase()}`,
|
|
27
30
|
index == 0
|
|
28
31
|
? "col-span-2 grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6"
|
|
29
32
|
: "flex flex-col space-y-2",
|
|
30
|
-
item.disabled ? "c-
|
|
33
|
+
item.disabled ? "c-rex-result-item-disabled" : ""
|
|
31
34
|
)}
|
|
32
35
|
>
|
|
33
36
|
|
|
@@ -82,7 +85,7 @@ const BlogView: FC<BlogViewProps> = ({ items }) => {
|
|
|
82
85
|
</div>
|
|
83
86
|
|
|
84
87
|
{!item.disabled && (
|
|
85
|
-
<Link href={item.link} className="absolute inset-0">
|
|
88
|
+
<Link href={`${item.link}?q=${query}`} className="absolute inset-0">
|
|
86
89
|
<span className="sr-only">{t("viewArticle")}</span>
|
|
87
90
|
</Link>
|
|
88
91
|
)}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { FC } from "react";
|
|
2
2
|
import { informationUnitsResponseItem } from "@c-rex/interfaces";
|
|
3
3
|
import { useTranslations } from "next-intl";
|
|
4
|
-
import {
|
|
4
|
+
import { CloudDownload, Eye } from "lucide-react";
|
|
5
5
|
import { FaFilePdf } from "react-icons/fa6";
|
|
6
6
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@c-rex/ui/dropdown-menu";
|
|
7
7
|
import { cn } from "@c-rex/utils";
|
|
8
8
|
import { Flag } from "../flag";
|
|
9
9
|
import { Badge } from "@c-rex/ui/badge";
|
|
10
|
+
import { useQueryState } from "nuqs";
|
|
10
11
|
|
|
11
12
|
interface TableViewProps {
|
|
12
13
|
items: informationUnitsResponseItem[];
|
|
@@ -19,6 +20,8 @@ const IconsToFileExtension: Record<string, React.ReactNode> = {
|
|
|
19
20
|
|
|
20
21
|
const TableView: FC<TableViewProps> = ({ items }) => {
|
|
21
22
|
const t = useTranslations("results")
|
|
23
|
+
const [query] = useQueryState("search");
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
return (
|
|
24
27
|
<div className="rounded-md border mb-6 last:border-b-0">
|
|
@@ -32,15 +35,15 @@ const TableView: FC<TableViewProps> = ({ items }) => {
|
|
|
32
35
|
{items.map((item, index) => (
|
|
33
36
|
<div
|
|
34
37
|
className={cn(
|
|
35
|
-
"min-h-12 c-
|
|
36
|
-
`c-
|
|
37
|
-
item.disabled && "c-
|
|
38
|
+
"min-h-12 c-rex-result-item flex flex-wrap items-center",
|
|
39
|
+
`c-rex-result-${item.type.toLowerCase()}`,
|
|
40
|
+
item.disabled && "c-rex-result-item-disabled",
|
|
38
41
|
"border-b"
|
|
39
42
|
)}
|
|
40
43
|
key={index}
|
|
41
44
|
>
|
|
42
45
|
<div className="w-4/5 md:w-2/5 p-2">
|
|
43
|
-
{item.disabled ? (item.title) : (<a href={item.link}>{item.title}</a>)}
|
|
46
|
+
{item.disabled ? (item.title) : (<a href={`${item.link}?q=${query}`}>{item.title}</a>)}
|
|
44
47
|
</div>
|
|
45
48
|
|
|
46
49
|
<div className="w-1/5 md:w-1/5 flex justify-center p-2">
|
|
@@ -59,7 +62,7 @@ const TableView: FC<TableViewProps> = ({ items }) => {
|
|
|
59
62
|
|
|
60
63
|
<div className="w-1/5 flex justify-center p-2">
|
|
61
64
|
{(item.disabled || (Object.keys(item.files).length === 0)) ? (
|
|
62
|
-
|
|
65
|
+
null // remove null rendering
|
|
63
66
|
) : (
|
|
64
67
|
<>
|
|
65
68
|
{Object.keys(item.files).map((fileKey, index) => {
|
package/src/right-sidebar.tsx
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
SidebarContent,
|
|
7
7
|
} from "@c-rex/ui/sidebar";
|
|
8
8
|
import { useTranslations } from "next-intl";
|
|
9
|
-
import { cn } from "@c-rex/utils";
|
|
10
9
|
import { articleInfoItemType, DocumentsType } from "@c-rex/types";
|
|
11
10
|
import { InfoTable } from "./info/info-table";
|
|
12
11
|
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
@@ -15,24 +14,26 @@ interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
|
15
14
|
articleInfo: articleInfoItemType[],
|
|
16
15
|
documentInfo: articleInfoItemType[],
|
|
17
16
|
files: DocumentsType,
|
|
18
|
-
|
|
17
|
+
articleAvailableVersions: AvailableVersionsInterface[],
|
|
18
|
+
documentAvailableVersions: AvailableVersionsInterface[]
|
|
19
19
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export function RightSidebar({ articleInfo, documentInfo,
|
|
22
|
+
export function RightSidebar({ articleInfo, documentInfo, files, articleAvailableVersions, documentAvailableVersions, ...props }: SidebarProps) {
|
|
23
23
|
const t = useTranslations();
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<Sidebar
|
|
27
|
-
<SidebarContent className="
|
|
26
|
+
<Sidebar {...props}>
|
|
27
|
+
<SidebarContent className="pt-4 pr-4 pb-10">
|
|
28
28
|
{(articleInfo.length > 0 || documentInfo.length > 0) && (
|
|
29
|
-
|
|
29
|
+
<>
|
|
30
30
|
|
|
31
31
|
{documentInfo.length > 0 && (
|
|
32
32
|
<InfoTable
|
|
33
33
|
title={t("aboutDocument")}
|
|
34
34
|
items={documentInfo}
|
|
35
35
|
files={files}
|
|
36
|
+
availableVersions={documentAvailableVersions}
|
|
36
37
|
/>
|
|
37
38
|
)}
|
|
38
39
|
|
|
@@ -40,11 +41,11 @@ export function RightSidebar({ articleInfo, documentInfo, className, files, avai
|
|
|
40
41
|
<InfoTable
|
|
41
42
|
title={t("aboutArticle")}
|
|
42
43
|
items={articleInfo}
|
|
43
|
-
availableVersions={
|
|
44
|
+
availableVersions={articleAvailableVersions}
|
|
44
45
|
/>
|
|
45
46
|
)}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
</>
|
|
48
49
|
)}
|
|
49
50
|
</SidebarContent>
|
|
50
51
|
</Sidebar>
|
package/src/search-modal.tsx
CHANGED
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
} from "@c-rex/ui/dialog"
|
|
14
14
|
import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"
|
|
15
15
|
import { useTranslations } from "next-intl"
|
|
16
|
-
import {
|
|
17
|
-
import { call
|
|
16
|
+
import { WILD_CARD_OPTIONS } from "@c-rex/constants"
|
|
17
|
+
import { call } from "@c-rex/utils"
|
|
18
18
|
import { Input } from "@c-rex/ui/input"
|
|
19
|
-
import { useAppConfig } from "@c-rex/contexts/config-provider"
|
|
20
19
|
import { Label } from "@c-rex/ui/label"
|
|
21
20
|
import { Checkbox } from "@c-rex/ui/checkbox"
|
|
22
21
|
import { Toggle } from "@c-rex/ui/toggle"
|
|
22
|
+
import { useLanguageStore } from "./stores/language-store"
|
|
23
23
|
|
|
24
24
|
interface SearchModalProps {
|
|
25
25
|
trigger: React.ReactNode;
|
|
@@ -27,11 +27,11 @@ interface SearchModalProps {
|
|
|
27
27
|
|
|
28
28
|
export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
|
|
29
29
|
const t = useTranslations("filter");
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
30
|
+
const contentLang = useLanguageStore(state => state.contentLang);
|
|
33
31
|
const [value, setValue] = useState<string>("");
|
|
34
32
|
const [loading, setLoading] = useState<boolean>(false);
|
|
33
|
+
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
34
|
+
|
|
35
35
|
const [params, setParams] = useQueryStates({
|
|
36
36
|
language: parseAsString,
|
|
37
37
|
page: parseAsInteger,
|
|
@@ -70,13 +70,11 @@ export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
const apply = () => {
|
|
73
|
-
const cookie = getFromCookieString(document.cookie, CONTENT_LANG_KEY)
|
|
74
|
-
|
|
75
73
|
setParams({
|
|
76
74
|
search: value,
|
|
77
75
|
operator: "OR",
|
|
78
76
|
page: 1,
|
|
79
|
-
language:
|
|
77
|
+
language: contentLang,
|
|
80
78
|
wildcard: WILD_CARD_OPTIONS.BOTH,
|
|
81
79
|
tags: false,
|
|
82
80
|
like: false,
|
|
@@ -94,8 +92,6 @@ export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
|
|
|
94
92
|
</DialogHeader>
|
|
95
93
|
<Input value={value} autoFocus onChange={(e) => setValue(e.target.value)} />
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
99
95
|
{loading ? (
|
|
100
96
|
<div className="flex items-center justify-center py-4">
|
|
101
97
|
<div className="animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-950" />
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { persist } from "zustand/middleware";
|
|
3
|
+
|
|
4
|
+
type HighlightStoreType = {
|
|
5
|
+
enable: boolean;
|
|
6
|
+
toggleHighlight: (v: boolean) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const useHighlightStore = create<HighlightStoreType>()(
|
|
10
|
+
persist((set) => ({
|
|
11
|
+
enable: false,
|
|
12
|
+
toggleHighlight: (v) => set({ enable: v }),
|
|
13
|
+
}), {
|
|
14
|
+
name: "c-rex-highlight-settings",
|
|
15
|
+
})
|
|
16
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UI_LANG_KEY } from "@c-rex/constants";
|
|
2
|
+
import { AVAILABLE_CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
3
|
+
import { CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
4
|
+
import { LanguageAndCountries } from "@c-rex/interfaces";
|
|
5
|
+
import { create } from "zustand";
|
|
6
|
+
|
|
7
|
+
type LanguageStoreType = {
|
|
8
|
+
contentLang: string;
|
|
9
|
+
uiLang: string;
|
|
10
|
+
availableLanguages: LanguageAndCountries[];
|
|
11
|
+
setContentLang: (v: string) => void;
|
|
12
|
+
setUiLang: (v: string) => void;
|
|
13
|
+
setAvailableLanguages: (list: LanguageAndCountries[]) => void;
|
|
14
|
+
hydrate: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function setCookie(name: string, value: string, days = 365) {
|
|
18
|
+
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
|
19
|
+
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getCookie(name: string): string {
|
|
23
|
+
return document.cookie
|
|
24
|
+
.split('; ')
|
|
25
|
+
.find(row => row.startsWith(name + '='))
|
|
26
|
+
?.split('=')[1] || '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useLanguageStore = create<LanguageStoreType>((set) => ({
|
|
30
|
+
contentLang: "",
|
|
31
|
+
uiLang: "",
|
|
32
|
+
availableLanguages: [],
|
|
33
|
+
setContentLang: (v) => {
|
|
34
|
+
set({ contentLang: v });
|
|
35
|
+
setCookie(CONTENT_LANG_KEY, v);
|
|
36
|
+
},
|
|
37
|
+
setUiLang: (v) => {
|
|
38
|
+
set({ uiLang: v });
|
|
39
|
+
setCookie(UI_LANG_KEY, v);
|
|
40
|
+
},
|
|
41
|
+
setAvailableLanguages: (list) => {
|
|
42
|
+
set({ availableLanguages: list });
|
|
43
|
+
setCookie(AVAILABLE_CONTENT_LANG_KEY, JSON.stringify(list));
|
|
44
|
+
},
|
|
45
|
+
hydrate: () => {
|
|
46
|
+
set({
|
|
47
|
+
uiLang: getCookie(UI_LANG_KEY),
|
|
48
|
+
contentLang: getCookie(CONTENT_LANG_KEY),
|
|
49
|
+
availableLanguages: (() => {
|
|
50
|
+
const raw = getCookie(AVAILABLE_CONTENT_LANG_KEY);
|
|
51
|
+
try {
|
|
52
|
+
return raw ? JSON.parse(raw) : [];
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
})(),
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { OperatorType, WildCardType } from "@c-rex/types";
|
|
2
|
+
import { create } from "zustand";
|
|
3
|
+
import { persist } from "zustand/middleware";
|
|
4
|
+
|
|
5
|
+
type SearchSettingsStore = {
|
|
6
|
+
language: string[],
|
|
7
|
+
wildcard: WildCardType,
|
|
8
|
+
operator: OperatorType,
|
|
9
|
+
like: boolean,
|
|
10
|
+
updatePreferences: (settings: Partial<Omit<SearchSettingsStore, "updatePreferences">>) => void,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useSearchSettingsStore = create<SearchSettingsStore>()(
|
|
14
|
+
persist((set) => ({
|
|
15
|
+
language: [],
|
|
16
|
+
wildcard: "BOTH",
|
|
17
|
+
operator: "OR",
|
|
18
|
+
like: false,
|
|
19
|
+
updatePreferences: (settings) => set((state) => ({ ...state, ...settings })),
|
|
20
|
+
}), {
|
|
21
|
+
name: "c-rex-search-settings",
|
|
22
|
+
})
|
|
23
|
+
);
|