@c-rex/templates 0.1.8 → 0.1.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-rex/templates",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -10,29 +10,37 @@
10
10
  "access": "public"
11
11
  },
12
12
  "exports": {
13
- "./home/layout": {
14
- "types": "./src/home/layout.tsx",
15
- "import": "./src/home/layout.tsx"
13
+ "./doc/home/layout": {
14
+ "types": "./src/doc/home/layout.tsx",
15
+ "import": "./src/doc/home/layout.tsx"
16
16
  },
17
- "./home/page": {
18
- "types": "./src/home/page.tsx",
19
- "import": "./src/home/page.tsx"
17
+ "./doc/home/page": {
18
+ "types": "./src/doc/home/page.tsx",
19
+ "import": "./src/doc/home/page.tsx"
20
20
  },
21
- "./articles/topics/page": {
22
- "types": "./src/articles/topics/page.tsx",
23
- "import": "./src/articles/topics/page.tsx"
21
+ "./doc/articles/topics": {
22
+ "types": "./src/doc/articles/topics.tsx",
23
+ "import": "./src/doc/articles/topics.tsx"
24
24
  },
25
- "./articles/blog/page": {
26
- "types": "./src/articles/blog/page.tsx",
27
- "import": "./src/articles/blog/page.tsx"
28
- },
29
- "./articles/documents/page": {
30
- "types": "./src/articles/documents/page.tsx",
31
- "import": "./src/articles/documents/page.tsx"
25
+ "./doc/articles/documents": {
26
+ "types": "./src/doc/articles/documents.tsx",
27
+ "import": "./src/doc/articles/documents.tsx"
32
28
  },
33
29
  "./layout": {
34
30
  "types": "./src/layout.tsx",
35
31
  "import": "./src/layout.tsx"
32
+ },
33
+ "./blog/article": {
34
+ "types": "./src/blog/article/page.tsx",
35
+ "import": "./src/blog/article/page.tsx"
36
+ },
37
+ "./blog/home/page": {
38
+ "types": "./src/blog/home/page.tsx",
39
+ "import": "./src/blog/home/page.tsx"
40
+ },
41
+ "./blog/home/layout": {
42
+ "types": "./src/blog/home/layout.tsx",
43
+ "import": "./src/blog/home/layout.tsx"
36
44
  }
37
45
  },
38
46
  "scripts": {
@@ -1,4 +1,4 @@
1
- import { extractHtmlContent, getInfoWithCache, getPrimaryInfo } from "../utils";
1
+ import { extractHtmlContent, getInfoWithCache, getPrimaryInfo } from "./utils";
2
2
  import { BLOG_TYPE_AND_LINK } from "@c-rex/constants";
3
3
  import { PageWrapper } from "@c-rex/components/page-wrapper";
4
4
  import { Metadata } from "next";
@@ -30,23 +30,23 @@ export const generateMetadata = async (
30
30
  }
31
31
 
32
32
  export const BlogPageTemplate = async ({ params }: { params: { id: string } }) => {
33
- const { htmlContent, lang, availableVersions } = await getInfoWithCache(params.id, BLOG_TYPE_AND_LINK, infoCache);
33
+ const { htmlContent, articleLang, availableVersions } = await getInfoWithCache(params.id, BLOG_TYPE_AND_LINK, infoCache);
34
34
  const { articleHtml } = extractHtmlContent(htmlContent)
35
35
 
36
36
  return (
37
- <PageWrapper title="" showInput={false}>
37
+ <PageWrapper title="" pageType="BLOG">
38
38
  <SidebarProvider>
39
39
 
40
40
  <CheckArticleLangToast availableVersions={availableVersions} />
41
41
 
42
42
  <AppSidebar
43
- lang={lang}
43
+ lang={articleLang}
44
44
  data={[]}
45
45
  availableVersions={availableVersions}
46
46
  />
47
47
  <SidebarInset>
48
48
  <div className="container flex flex-col">
49
- <RenderArticle htmlContent={articleHtml} contentLang={lang} />
49
+ <RenderArticle htmlContent={articleHtml} contentLang={articleLang} />
50
50
  </div>
51
51
  </SidebarInset>
52
52
  </SidebarProvider>
@@ -13,8 +13,9 @@ import { BLOG_TYPE_AND_LINK } from "@c-rex/constants";
13
13
  export const getPrimaryInfo = async (id: string, type: string): Promise<{
14
14
  htmlContent: string,
15
15
  title: string,
16
- lang: string,
16
+ articleLang: string,
17
17
  packageId: string,
18
+ documentLang: string,
18
19
  availableVersions: SidebarAvailableVersionsInterface[]
19
20
  }> => {
20
21
  const renditionService = new RenditionsService();
@@ -22,12 +23,13 @@ export const getPrimaryInfo = async (id: string, type: string): Promise<{
22
23
  const informationUnitsItem = await informationService.getItem({ id });
23
24
 
24
25
  let title = informationUnitsItem.labels[0]?.value
25
- const lang = informationUnitsItem.languages[0];
26
26
  const versionOf = informationUnitsItem.versionOf.shortId
27
+ let documentLang = ""
28
+ const articleLang = informationUnitsItem.languages[0];
27
29
 
28
30
  const promiseList: any = [
29
31
  informationService.getList({
30
- restrict: [`versionOf.shortId=${versionOf}`], // TODO: replace from filter to restrict
32
+ restrict: [`versionOf.shortId=${versionOf}`],
31
33
  fields: ["renditions", "class", "languages", "labels"],
32
34
  }),
33
35
  ]
@@ -36,6 +38,7 @@ export const getPrimaryInfo = async (id: string, type: string): Promise<{
36
38
 
37
39
  if (rootNode != null) {
38
40
  title = rootNode.informationUnits[0]?.labels[0]?.value;
41
+ documentLang = rootNode.informationUnits[0]?.labels[0]?.language as string;
39
42
  }
40
43
 
41
44
  if ([TOPICS_TYPE_AND_LINK, BLOG_TYPE_AND_LINK].includes(type)) {
@@ -68,7 +71,7 @@ export const getPrimaryInfo = async (id: string, type: string): Promise<{
68
71
  link: `/${type}/${item.shortId}`,
69
72
  lang: item.language,
70
73
  country: item.language.split("-")[1],
71
- active: item.language === lang,
74
+ active: item.language === articleLang,
72
75
  }
73
76
  }).sort((a: SidebarAvailableVersionsInterface, b: SidebarAvailableVersionsInterface) => {
74
77
  if (a.lang < b.lang) return -1;
@@ -80,8 +83,9 @@ export const getPrimaryInfo = async (id: string, type: string): Promise<{
80
83
  htmlContent,
81
84
  packageId: informationUnitsItem.packages[0]?.shortId as string,
82
85
  title: title as string,
83
- lang: lang as string,
84
- availableVersions
86
+ articleLang: articleLang as string,
87
+ documentLang: documentLang as string,
88
+ availableVersions,
85
89
  }
86
90
  }
87
91
 
@@ -12,12 +12,13 @@ interface HomeProps {
12
12
  wildcard: string;
13
13
  operator: string;
14
14
  like: string;
15
- package?: string;
15
+ packages?: string;
16
+ filter?: string;
16
17
  };
17
18
  }
18
19
 
19
20
  export const HomeLayout = async ({ searchParams }: HomeProps) => {
20
- const { search, page, language, wildcard, operator, like, package: packageParam } = searchParams;
21
+ const { search, page, language, wildcard, operator, like, packages, filter } = searchParams;
21
22
 
22
23
  let data = {
23
24
  items: [],
@@ -29,9 +30,11 @@ export const HomeLayout = async ({ searchParams }: HomeProps) => {
29
30
 
30
31
  if (search !== undefined) {
31
32
 
32
- const filters: string[] = []
33
- if (packageParam && packageParam.length > 0) {
34
- filters.push(`packages.shortId=${packageParam}`)
33
+ const filters: string[] = filter?.split(",") || []
34
+ const restrict: string[] = []
35
+
36
+ if (packages && packages.length > 0) {
37
+ restrict.push(`packages.shortId=${packages}`)
35
38
  }
36
39
  const service = new InformationUnitsService();
37
40
 
@@ -41,6 +44,7 @@ export const HomeLayout = async ({ searchParams }: HomeProps) => {
41
44
  fields: ["renditions", "class", "languages", "labels"],
42
45
  languages: language.split(","),
43
46
  wildcard: wildcard as WildCardType,
47
+ restrict: restrict,
44
48
  operator: operator,
45
49
  like: Boolean(like === "true"),
46
50
  filters: filters
@@ -48,7 +52,7 @@ export const HomeLayout = async ({ searchParams }: HomeProps) => {
48
52
  }
49
53
 
50
54
  return (
51
- <PageWrapper title="" showInput={false}>
55
+ <PageWrapper title="" pageType="HOME">
52
56
  <HomePage data={data} />
53
57
  </PageWrapper>
54
58
  );
@@ -0,0 +1,323 @@
1
+ "use client";
2
+
3
+ import React, { FC, useEffect, useState } from "react";
4
+ import { Trash2, X } from "lucide-react"
5
+ import { useTranslations } from 'next-intl'
6
+ import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
7
+ import { informationUnitsResponse } from "@c-rex/interfaces";
8
+ import { Button } from "@c-rex/ui/button";
9
+ import { Badge } from "@c-rex/ui/badge";
10
+ import {
11
+ SidebarContent,
12
+ SidebarGroup,
13
+ SidebarGroupContent,
14
+ SidebarGroupLabel,
15
+ SidebarHeader,
16
+ SidebarMenu,
17
+ SidebarMenuSub,
18
+ SidebarMenuSubButton,
19
+ SidebarMenuSubItem
20
+ } from "@c-rex/ui/sidebar";
21
+ import { ResultList } from "@c-rex/components/result-list";
22
+ import { DialogFilter } from "@c-rex/components/dialog-filter";
23
+ import { useAppConfig } from "@c-rex/contexts/config-provider";
24
+ import { AutoComplete } from "@c-rex/components/autocomplete";
25
+ import { OPERATOR_OPTIONS, WILD_CARD_OPTIONS } from "@c-rex/constants";
26
+ import { Loading } from "@c-rex/components/loading";
27
+
28
+ interface HomePageProps {
29
+ data: informationUnitsResponse;
30
+ }
31
+
32
+ type filterModel = {
33
+ key: string
34
+ name?: string
35
+ value: string
36
+ default?: string | boolean | null
37
+ removable: boolean
38
+ }
39
+
40
+ export const HomePage: FC<HomePageProps> = ({ data }) => {
41
+ const t = useTranslations();
42
+ const { configs } = useAppConfig()
43
+
44
+ const [tags, setTags] = useState<{ [key: string]: any[] }>(data.tags || {});
45
+ const [filters, setFilters] = useState<filterModel[] | null>(null)
46
+ const [disabled, setDisabled] = useState<boolean>(false)
47
+ const [loading, setLoading] = useState<boolean>(true)
48
+ const [params, setParams] = useQueryStates({
49
+ language: parseAsString,
50
+ page: parseAsInteger,
51
+ wildcard: parseAsString,
52
+ operator: parseAsString,
53
+ packages: parseAsString,
54
+ filter: parseAsString,
55
+ like: parseAsBoolean,
56
+ search: {
57
+ defaultValue: "",
58
+ parse(value) {
59
+ return value
60
+ },
61
+ },
62
+ }, {
63
+ history: 'push',
64
+ shallow: false,
65
+ });
66
+
67
+ useEffect(() => {
68
+ if (params.search.length > 0) {
69
+ setDisabled(false)
70
+ generateFiltersObj()
71
+ } else {
72
+ setDisabled(true)
73
+ setFilters(null)
74
+ }
75
+ }, [params])
76
+
77
+ useEffect(() => {
78
+ const newTags = { ...data.tags }
79
+
80
+ if (params.filter !== null) {
81
+ const splittedParam = params.filter.split(",")
82
+
83
+ splittedParam.forEach((item) => {
84
+ const aux = item.split(".shortId=")
85
+ const name = aux[0]
86
+ const shortId = aux[1]
87
+
88
+ if (!newTags[name]) {
89
+ newTags[name] = []
90
+ }
91
+
92
+ newTags[name].forEach((el) => {
93
+ if (el.shortId == shortId) {
94
+ el.active = true
95
+ } else {
96
+ el.active = false
97
+ }
98
+ })
99
+ })
100
+ }
101
+
102
+ if (params.packages !== null && newTags["packages"]) {
103
+ newTags["packages"].forEach((el) => {
104
+ if (el.shortId == params.packages) {
105
+ el.active = true
106
+ } else {
107
+ el.active = false
108
+ }
109
+ })
110
+ }
111
+
112
+ setTags(newTags)
113
+ setLoading(false)
114
+ }, [data])
115
+
116
+ const generateFiltersObj = () => {
117
+ const filters: filterModel[] = [{
118
+ key: "operator",
119
+ name: t("filter.operator"),
120
+ value: `${params?.operator !== OPERATOR_OPTIONS.OR}`,
121
+ default: OPERATOR_OPTIONS.OR,
122
+ removable: params?.operator !== OPERATOR_OPTIONS.OR
123
+ }, {
124
+ key: "like",
125
+ name: t("filter.like"),
126
+ value: `${params.like}`,
127
+ default: false,
128
+ removable: params?.like as boolean
129
+ }, {
130
+ key: "wildcard",
131
+ value: params.wildcard as string,
132
+ default: WILD_CARD_OPTIONS.NONE,
133
+ removable: params?.wildcard !== WILD_CARD_OPTIONS.NONE
134
+ }]
135
+
136
+ const languages = params.language?.split(",")
137
+ languages?.forEach((item) => {
138
+ const aux = languages?.filter(langItem => langItem !== item)
139
+ filters.push({ key: "language", value: item, removable: languages.length > 1, default: aux.join(",") })
140
+ })
141
+
142
+ if (params.filter !== null) {
143
+ const splittedParam = params.filter.split(",")
144
+
145
+ splittedParam.forEach((item, index) => {
146
+ const aux = item.split(".shortId=")
147
+ const name = aux[0]
148
+ const shortId = aux[1]
149
+
150
+ const defaultValue = [...splittedParam]
151
+ defaultValue.splice(index, 1)
152
+
153
+ if (!tags[name]) return;
154
+
155
+ const tag = tags[name].find(el => el.shortId === shortId)
156
+ if (!tag) return;
157
+
158
+ const value = defaultValue.length == 0 ? null : defaultValue.join(",")
159
+
160
+ filters.push({ key: "filter", name: t(`filter.tags.${name}`), value: tag.label, removable: true, default: value })
161
+ })
162
+ }
163
+
164
+ if (params.packages !== null && tags["packages"]) {
165
+ const packageTag = tags["packages"]?.find(el => el.shortId === params.packages)
166
+ filters.push({
167
+ key: "packages",
168
+ name: t("filter.tags.packages"),
169
+ value: packageTag.label,
170
+ removable: true,
171
+ default: null
172
+ })
173
+ }
174
+
175
+ Object.keys(params)
176
+ .filter(item => !["page", "search", "language", "operator", "like", "wildcard", "filter", "packages"].includes(item))
177
+ .filter(item => params[item] != null)
178
+ .forEach(item => {
179
+ filters.push({ key: item, value: params[item], removable: true, default: null })
180
+ })
181
+
182
+ setFilters(filters)
183
+ }
184
+
185
+ const updateFilterParam = (key: string, item: any) => {
186
+ setLoading(true)
187
+ if (key === "packages") {
188
+ setParams({ packages: item.shortId })
189
+ return;
190
+ } else {
191
+ const value = `${key}.shortId=${item.shortId}`
192
+ let aux = value
193
+
194
+ if (params.filter != null) {
195
+ const splittedParam = params.filter.split(",")
196
+ const finalValue = [...splittedParam]
197
+
198
+ const hasParams = params.filter.includes(key)
199
+
200
+ if (hasParams) {
201
+ let mainIndex = -1
202
+
203
+ splittedParam.forEach((el, index) => {
204
+ if (el.includes(key)) {
205
+ mainIndex = index
206
+ }
207
+ })
208
+ finalValue[mainIndex] = value
209
+ } else {
210
+ finalValue.push(value)
211
+ }
212
+
213
+ aux = finalValue.join(",")
214
+ }
215
+
216
+ setParams({ filter: aux })
217
+ }
218
+ };
219
+
220
+ return (
221
+ <div className="container">
222
+ {loading && <Loading opacity={true} />}
223
+
224
+ <div className="grid grid-cols-12 gap-4 py-6">
225
+ <div className="col-span-12 sm:col-span-10 md:col-span-10">
226
+ <AutoComplete
227
+ embedded={false}
228
+ initialValue={params.search}
229
+ searchByPackage={false}
230
+ />
231
+ </div>
232
+ <div className="col-span-12 sm:col-span-2 md:col-span-2">
233
+ <div className="flex justify-end">
234
+ <DialogFilter
235
+ setLoading={setLoading}
236
+ trigger={(
237
+ <Button variant="default" disabled={disabled}>{t("filter.filters")}</Button>
238
+ )}
239
+ />
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ {filters != null && filters.length > 0 && (
245
+ <div className="pb-4 flex justify-between">
246
+ <div>
247
+
248
+ {filters?.map((item) => (
249
+ <Badge key={`${item.key}-${item?.value}`} variant="outline" className="mr-2 mb-2 h-6">
250
+ {item?.name ? item.name : item.key}: {item.value}
251
+ {item.removable && (
252
+ <Button size="xs" variant="ghost" onClick={() => {
253
+ setLoading(true)
254
+ setParams({ [item.key]: item?.default })
255
+ }}>
256
+ <X className="h-2" />
257
+ </Button>
258
+ )}
259
+ </Badge>
260
+ ))}
261
+
262
+ </div>
263
+ <Button
264
+ size="sm"
265
+ variant="outline"
266
+ disabled={params.filter === null}
267
+ onClick={() => {
268
+ setLoading(true)
269
+ setParams({ filter: null, packages: null })
270
+ }}
271
+ >
272
+ {t("filter.clearFilters")}
273
+ <Trash2 className="h-2" />
274
+ </Button>
275
+ </div>
276
+ )}
277
+
278
+ <div className="flex flex-row gap-6 pb-6">
279
+ {data.items.length > 0 && (
280
+ <div className="w-80 bg-sidebar rounded-md border pb-4">
281
+ <SidebarHeader className="text-center font-bold">
282
+ Search Filters
283
+ </SidebarHeader>
284
+ <SidebarContent>
285
+ {Object.entries(tags).map(([key, value]: any) => (
286
+ <SidebarGroup key={key} className="py-0">
287
+ <SidebarGroupLabel>
288
+ {t(`filter.tags.${key}`)}
289
+ </SidebarGroupLabel>
290
+ <SidebarGroupContent>
291
+ <SidebarMenu>
292
+ <SidebarMenuSub>
293
+ {value.map(item => (
294
+ <SidebarMenuSubItem key={item.shortId}>
295
+ <SidebarMenuSubButton
296
+ className="cursor-pointer"
297
+ isActive={item.active}
298
+ onClick={() => updateFilterParam(key, item)}
299
+ >
300
+ {item.label} ({item.hits}/{item.total})
301
+ </SidebarMenuSubButton>
302
+ </SidebarMenuSubItem>
303
+ ))}
304
+ </SidebarMenuSub>
305
+ </SidebarMenu>
306
+ </SidebarGroupContent>
307
+ </SidebarGroup>
308
+ ))}
309
+ </SidebarContent>
310
+ </div>
311
+ )}
312
+
313
+ <div className="flex-1">
314
+ <ResultList
315
+ configs={configs}
316
+ items={data.items}
317
+ pagination={data.pageInfo}
318
+ />
319
+ </div>
320
+ </div>
321
+ </div>
322
+ );
323
+ };
@@ -0,0 +1,38 @@
1
+ import { getInfoWithCache, getPrimaryInfo } from "./utils";
2
+ import { ArticleWrapper } from "./wrapper";
3
+ import { PageWrapper } from "@c-rex/components/page-wrapper";
4
+ import { DOCUMENTS_TYPE_AND_LINK } from "@c-rex/constants";
5
+
6
+ const infoCache = new Map<string, Awaited<ReturnType<typeof getPrimaryInfo>>>();
7
+
8
+ export const DocumentsPageTemplate = async ({ params }: { params: { id: string } }) => {
9
+ /*
10
+ const {
11
+ htmlContent,
12
+ title,
13
+ articleLang,
14
+ availableVersions,
15
+ documentLang,
16
+ packageId
17
+ } = await getInfoWithCache(params.id, DOCUMENTS_TYPE_AND_LINK, infoCache);
18
+ */
19
+
20
+
21
+
22
+ const { htmlContent, title, availableVersions, packageId, articleLang, documentLang } = await getPrimaryInfo(params.id, DOCUMENTS_TYPE_AND_LINK);
23
+
24
+ return (
25
+ <PageWrapper title={title} pageType="DOC">
26
+ <ArticleWrapper
27
+ packageId={packageId}
28
+ title={title}
29
+ availableVersions={availableVersions}
30
+ htmlContent={htmlContent}
31
+ articleLang={articleLang}
32
+ documentLang={documentLang}
33
+ id={params.id}
34
+ type={DOCUMENTS_TYPE_AND_LINK}
35
+ />
36
+ </PageWrapper>
37
+ );
38
+ };
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
- import { extractHtmlContent, getInfoWithCache, getPrimaryInfo } from "../utils";
2
+ import { extractHtmlContent, getInfoWithCache, getPrimaryInfo } from "./utils";
3
3
  import { TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
4
- import { ArticleWrapper } from "../wrapper";
4
+ import { ArticleWrapper } from "./wrapper";
5
5
  import { PageWrapper } from "@c-rex/components/page-wrapper";
6
6
  import { Metadata } from "next";
7
7
 
@@ -30,18 +30,20 @@ export const TopicsPageTemplate = async ({ params }: { params: { id: string } })
30
30
  const {
31
31
  htmlContent,
32
32
  title,
33
- lang,
34
33
  availableVersions,
35
- packageId
34
+ packageId,
35
+ articleLang,
36
+ documentLang
36
37
  } = await getInfoWithCache(params.id, TOPICS_TYPE_AND_LINK, infoCache);
37
38
 
38
39
  const { articleHtml } = extractHtmlContent(htmlContent)
39
40
 
40
41
  return (
41
- <PageWrapper title={title} showInput>
42
+ <PageWrapper title={title} pageType="DOC">
42
43
  <ArticleWrapper
43
44
  title={title}
44
- lang={lang}
45
+ documentLang={documentLang}
46
+ articleLang={articleLang}
45
47
  packageId={packageId}
46
48
  availableVersions={availableVersions}
47
49
  htmlContent={articleHtml}
@@ -0,0 +1,142 @@
1
+ import { DirectoryNodesService, InformationUnitsService, RenditionsService } from "@c-rex/services";
2
+ import { DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
3
+ import { DirectoryNodes, informationUnitsResponseItem, SidebarAvailableVersionsInterface } from "@c-rex/interfaces";
4
+ import * as cheerio from 'cheerio'
5
+ import { BLOG_TYPE_AND_LINK } from "@c-rex/constants";
6
+
7
+ /*
8
+ * Get primary info for a given information unit
9
+ * @param id: string
10
+ * @param type: string
11
+ * @returns { htmlContent: string, title: string }
12
+ */
13
+ export const getPrimaryInfo = async (id: string, type: string): Promise<{
14
+ htmlContent: string,
15
+ title: string,
16
+ articleLang: string,
17
+ packageId: string,
18
+ documentLang: string,
19
+ availableVersions: SidebarAvailableVersionsInterface[]
20
+ }> => {
21
+ const renditionService = new RenditionsService();
22
+ const informationService = new InformationUnitsService();
23
+ const informationUnitsItem = await informationService.getItem({ id });
24
+
25
+ let title = informationUnitsItem.labels[0]?.value
26
+ const versionOf = informationUnitsItem.versionOf.shortId
27
+ let documentLang = ""
28
+ const articleLang = informationUnitsItem.languages[0];
29
+
30
+ const promiseList: any = [
31
+ informationService.getList({
32
+ restrict: [`versionOf.shortId=${versionOf}`],
33
+ fields: ["renditions", "class", "languages", "labels"],
34
+ }),
35
+ ]
36
+
37
+ const rootNode = await getRootNode(informationUnitsItem.directoryNodes)
38
+
39
+ if (rootNode != null) {
40
+ title = rootNode.informationUnits[0]?.labels[0]?.value;
41
+ documentLang = rootNode.informationUnits[0]?.labels[0]?.language as string;
42
+ }
43
+
44
+ if ([TOPICS_TYPE_AND_LINK, BLOG_TYPE_AND_LINK].includes(type)) {
45
+ promiseList.push(
46
+ renditionService.getHTMLRendition({ renditions: informationUnitsItem.renditions })
47
+ )
48
+
49
+ } else if (rootNode != null && type == DOCUMENTS_TYPE_AND_LINK) {
50
+
51
+ const service = new DirectoryNodesService();
52
+
53
+ const directoryId = rootNode.childNodes[0]?.shortId as string;
54
+
55
+ const response = await service.getItem(directoryId);
56
+
57
+ const infoId = response.informationUnits[0]?.shortId;
58
+
59
+ const childInformationUnit = await informationService.getItem({ id: infoId as string });
60
+
61
+ promiseList.push(
62
+ renditionService.getHTMLRendition({ renditions: childInformationUnit.renditions })
63
+ )
64
+ }
65
+
66
+ const [versions, htmlContent] = await Promise.all(promiseList)
67
+
68
+ const availableVersions = versions.items.map((item: informationUnitsResponseItem) => {
69
+ return {
70
+ shortId: item.shortId,
71
+ link: `/${type}/${item.shortId}`,
72
+ lang: item.language,
73
+ country: item.language.split("-")[1],
74
+ active: item.language === articleLang,
75
+ }
76
+ }).sort((a: SidebarAvailableVersionsInterface, b: SidebarAvailableVersionsInterface) => {
77
+ if (a.lang < b.lang) return -1;
78
+ if (a.lang > b.lang) return 1;
79
+ return 0;
80
+ }) as SidebarAvailableVersionsInterface[];
81
+
82
+ return {
83
+ htmlContent,
84
+ packageId: informationUnitsItem.packages[0]?.shortId as string,
85
+ title: title as string,
86
+ articleLang: articleLang as string,
87
+ documentLang: documentLang as string,
88
+ availableVersions,
89
+ }
90
+ }
91
+
92
+ export const getRootNode = async (directoryNodes: DirectoryNodes[]): Promise<DirectoryNodes | null> => {
93
+ const service = new DirectoryNodesService();
94
+
95
+ if (directoryNodes.length == 0 || directoryNodes[0] == undefined) {
96
+ return null;
97
+ }
98
+
99
+ let id = directoryNodes[0].shortId;
100
+ let response = await service.getItem(id);
101
+
102
+ while (response.parents != undefined) {
103
+
104
+ const hasInfo = (response.informationUnits != undefined) && (response.informationUnits[0] != undefined);
105
+ const hasLabel = (response.labels != undefined) && (response.labels[0] != undefined);
106
+ const hasParent = (response.parents != undefined) && (response.parents[0] != undefined);
107
+
108
+ if (!hasInfo || !hasLabel || !hasParent) {
109
+ return null;
110
+ }
111
+
112
+ id = response.parents[0]?.shortId as string;
113
+ response = await service.getItem(id);
114
+ }
115
+
116
+ return response;
117
+ };
118
+
119
+ export const getInfoWithCache = async (id: string, type: string, infoCache: any): Promise<Awaited<ReturnType<typeof getPrimaryInfo>>> => {
120
+ if (!infoCache.has(id)) {
121
+ const info = await getPrimaryInfo(id, type);
122
+ infoCache.set(id, info);
123
+ }
124
+ return infoCache.get(id)!;
125
+ }
126
+
127
+ export const extractHtmlContent = (htmlString: string) => {
128
+ const $ = cheerio.load(htmlString)
129
+
130
+ const metaTags = $('meta').map((_, el) => {
131
+ const name = $(el).attr('name')
132
+ const content = $(el).attr('content')
133
+ return name && content ? { name, content } : null
134
+ }).get().filter(Boolean)
135
+
136
+ const articleHtml = $('main').html() || ''
137
+
138
+ return {
139
+ metaTags,
140
+ articleHtml,
141
+ }
142
+ }
@@ -19,7 +19,8 @@ type Props = {
19
19
  title: string,
20
20
  id: string,
21
21
  type: string,
22
- lang: string,
22
+ documentLang: string,
23
+ articleLang: string,
23
24
  availableVersions: SidebarAvailableVersionsInterface[],
24
25
  packageId: string,
25
26
  }
@@ -70,12 +71,10 @@ const loadArticleData = async (id: string, type: string, title: string) => {
70
71
  }
71
72
  };
72
73
 
73
- export const ArticleWrapper = ({ htmlContent, title, id, type, lang, availableVersions, packageId }: Props) => {
74
- const { setPackageID } = useAppConfig()
74
+ export const ArticleWrapper = ({ htmlContent, title, id, type, documentLang, articleLang, availableVersions, packageId }: Props) => {
75
+ console.log(availableVersions)
76
+ const { setPackageID, setArticleLang } = useAppConfig()
75
77
  const [loading, setLoading] = useState<boolean>(true)
76
-
77
-
78
- //set available versions on confgi context so will be possible to get the link when change contant language
79
78
  const [breadcrumbItems, setBreadcrumbItems] = useState<TreeOfContent[]>([])
80
79
  const [treeOfContent, setTreeOfContent] = useState<TreeOfContent[]>([])
81
80
  const [documents, setDocuments] = useState<{
@@ -94,6 +93,7 @@ export const ArticleWrapper = ({ htmlContent, title, id, type, lang, availableVe
94
93
 
95
94
  useEffect(() => {
96
95
  setPackageID(packageId)
96
+ setArticleLang(articleLang)
97
97
 
98
98
  const fetchData = async () => {
99
99
  const {
@@ -118,7 +118,7 @@ export const ArticleWrapper = ({ htmlContent, title, id, type, lang, availableVe
118
118
  <CheckArticleLangToast availableVersions={availableVersions} />
119
119
 
120
120
  <AppSidebar
121
- lang={lang}
121
+ lang={documentLang}
122
122
  data={treeOfContent}
123
123
  availableVersions={availableVersions}
124
124
  loading={loading}
@@ -126,7 +126,7 @@ export const ArticleWrapper = ({ htmlContent, title, id, type, lang, availableVe
126
126
  <SidebarInset>
127
127
  <div className="container flex flex-col">
128
128
  <header className="flex h-16 shrink-0 items-center justify-between">
129
- <Breadcrumb items={breadcrumbItems} loading={loading} lang={lang} />
129
+ <Breadcrumb items={breadcrumbItems} loading={loading} lang={documentLang} />
130
130
 
131
131
  <div className="flex">
132
132
  {documents.filesToDownload.length > 0 && (
@@ -144,7 +144,7 @@ export const ArticleWrapper = ({ htmlContent, title, id, type, lang, availableVe
144
144
  </div>
145
145
  </header>
146
146
 
147
- <RenderArticle htmlContent={htmlContent} contentLang={lang} />
147
+ <RenderArticle htmlContent={htmlContent} contentLang={articleLang} />
148
148
  </div>
149
149
  </SidebarInset>
150
150
  </SidebarProvider>
@@ -0,0 +1,59 @@
1
+ import { informationUnitsResponse } from "@c-rex/interfaces";
2
+ import { InformationUnitsService } from "@c-rex/services";
3
+ import { HomePage } from "./page";
4
+ import { PageWrapper } from "@c-rex/components/page-wrapper";
5
+ import { WildCardType } from "@c-rex/types";
6
+
7
+ interface HomeProps {
8
+ searchParams: {
9
+ search?: string;
10
+ page: string;
11
+ language: string;
12
+ wildcard: string;
13
+ operator: string;
14
+ like: string;
15
+ packages?: string;
16
+ filter?: string;
17
+ };
18
+ }
19
+
20
+ export const HomeLayout = async ({ searchParams }: HomeProps) => {
21
+ const { search, page, language, wildcard, operator, like, packages, filter } = searchParams;
22
+
23
+ let data = {
24
+ items: [],
25
+ pageInfo: {
26
+ pageCount: 0,
27
+ pageNumber: 0
28
+ }
29
+ } as unknown as informationUnitsResponse;
30
+
31
+ if (search !== undefined) {
32
+
33
+ const filters: string[] = filter?.split(",") || []
34
+ const restrict: string[] = []
35
+
36
+ if (packages && packages.length > 0) {
37
+ restrict.push(`packages.shortId=${packages}`)
38
+ }
39
+ const service = new InformationUnitsService();
40
+
41
+ data = await service.getList({
42
+ queries: search,
43
+ page: Number(page),
44
+ fields: ["renditions", "class", "languages", "labels"],
45
+ languages: language.split(","),
46
+ wildcard: wildcard as WildCardType,
47
+ restrict: restrict,
48
+ operator: operator,
49
+ like: Boolean(like === "true"),
50
+ filters: filters
51
+ });
52
+ }
53
+
54
+ return (
55
+ <PageWrapper title="" pageType="HOME">
56
+ <HomePage data={data} />
57
+ </PageWrapper>
58
+ );
59
+ };
@@ -0,0 +1,324 @@
1
+ "use client";
2
+
3
+ import React, { FC, useEffect, useState } from "react";
4
+ import { Trash2, X } from "lucide-react"
5
+ import { useTranslations } from 'next-intl'
6
+ import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
7
+ import { informationUnitsResponse } from "@c-rex/interfaces";
8
+ import { Button } from "@c-rex/ui/button";
9
+ import { Badge } from "@c-rex/ui/badge";
10
+ import {
11
+ SidebarContent,
12
+ SidebarGroup,
13
+ SidebarGroupContent,
14
+ SidebarGroupLabel,
15
+ SidebarHeader,
16
+ SidebarMenu,
17
+ SidebarMenuSub,
18
+ SidebarMenuSubButton,
19
+ SidebarMenuSubItem
20
+ } from "@c-rex/ui/sidebar";
21
+ import { ResultList } from "@c-rex/components/result-list";
22
+ import { DialogFilter } from "@c-rex/components/dialog-filter";
23
+ import { useAppConfig } from "@c-rex/contexts/config-provider";
24
+ import { AutoComplete } from "@c-rex/components/autocomplete";
25
+ import { OPERATOR_OPTIONS, WILD_CARD_OPTIONS } from "@c-rex/constants";
26
+ import { Loading } from "@c-rex/components/loading";
27
+
28
+ interface HomePageProps {
29
+ data: informationUnitsResponse;
30
+ }
31
+
32
+ type filterModel = {
33
+ key: string
34
+ name?: string
35
+ value: string
36
+ default?: string | boolean | null
37
+ removable: boolean
38
+ }
39
+
40
+ export const HomePage: FC<HomePageProps> = ({ data }) => {
41
+ const t = useTranslations();
42
+ const { configs } = useAppConfig()
43
+
44
+ const [tags, setTags] = useState<{ [key: string]: any[] }>(data.tags || {});
45
+ const [filters, setFilters] = useState<filterModel[] | null>(null)
46
+ const [disabled, setDisabled] = useState<boolean>(false)
47
+ const [loading, setLoading] = useState<boolean>(true)
48
+ const [params, setParams] = useQueryStates({
49
+ language: parseAsString,
50
+ page: parseAsInteger,
51
+ wildcard: parseAsString,
52
+ operator: parseAsString,
53
+ packages: parseAsString,
54
+ filter: parseAsString,
55
+ like: parseAsBoolean,
56
+ search: {
57
+ defaultValue: "",
58
+ parse(value) {
59
+ return value
60
+ },
61
+ },
62
+ }, {
63
+ history: 'push',
64
+ shallow: false,
65
+ });
66
+
67
+ useEffect(() => {
68
+ if (params.search.length > 0) {
69
+ setDisabled(false)
70
+ generateFiltersObj()
71
+ } else {
72
+ setDisabled(true)
73
+ setFilters(null)
74
+ }
75
+ }, [params])
76
+
77
+ useEffect(() => {
78
+ const newTags = { ...data.tags }
79
+
80
+ if (params.filter !== null) {
81
+ const splittedParam = params.filter.split(",")
82
+
83
+ splittedParam.forEach((item) => {
84
+ const aux = item.split(".shortId=")
85
+ const name = aux[0] as string
86
+ const shortId = aux[1] as string
87
+
88
+ if (!Object.keys(newTags).includes(name)) {
89
+ newTags[name] = []
90
+ }
91
+
92
+ newTags[name].forEach((el: any) => {
93
+ if (el.shortId == shortId) {
94
+ el.active = true
95
+ } else {
96
+ el.active = false
97
+ }
98
+ })
99
+ })
100
+ }
101
+
102
+ if (params.packages !== null && newTags["packages"]) {
103
+ newTags["packages"].forEach((el: any) => {
104
+ if (el.shortId == params.packages) {
105
+ el.active = true
106
+ } else {
107
+ el.active = false
108
+ }
109
+ })
110
+ }
111
+
112
+ setTags(newTags)
113
+ setLoading(false)
114
+ }, [data])
115
+
116
+ const generateFiltersObj = () => {
117
+ const filters: filterModel[] = [{
118
+ key: "operator",
119
+ name: t("filter.operator"),
120
+ value: `${params?.operator !== OPERATOR_OPTIONS.OR}`,
121
+ default: OPERATOR_OPTIONS.OR,
122
+ removable: params?.operator !== OPERATOR_OPTIONS.OR
123
+ }, {
124
+ key: "like",
125
+ name: t("filter.like"),
126
+ value: `${params.like}`,
127
+ default: false,
128
+ removable: params?.like as boolean
129
+ }, {
130
+ key: "wildcard",
131
+ value: params.wildcard as string,
132
+ default: WILD_CARD_OPTIONS.NONE,
133
+ removable: params?.wildcard !== WILD_CARD_OPTIONS.NONE
134
+ }]
135
+
136
+ const languages = params.language?.split(",")
137
+ languages?.forEach((item) => {
138
+ const aux = languages?.filter(langItem => langItem !== item)
139
+ filters.push({ key: "language", value: item, removable: languages.length > 1, default: aux.join(",") })
140
+ })
141
+
142
+ if (params.filter !== null) {
143
+ const splittedParam = params.filter.split(",")
144
+
145
+ splittedParam.forEach((item, index) => {
146
+ const aux = item.split(".shortId=")
147
+ const name = aux[0] as string
148
+ const shortId = aux[1] as string
149
+
150
+ const defaultValue = [...splittedParam]
151
+ defaultValue.splice(index, 1)
152
+
153
+ if (!Object.keys(tags).includes(name) || !tags[name]) return;
154
+
155
+ const tag = tags[name].find(el => el.shortId === shortId)
156
+ if (!tag) return;
157
+
158
+ const value = defaultValue.length == 0 ? null : defaultValue.join(",")
159
+
160
+ filters.push({ key: "filter", name: t(`filter.tags.${name}`), value: tag.label, removable: true, default: value })
161
+ })
162
+ }
163
+
164
+ if (params.packages !== null && tags["packages"]) {
165
+ const packageTag = tags["packages"]?.find(el => el.shortId === params.packages)
166
+ filters.push({
167
+ key: "packages",
168
+ name: t("filter.tags.packages"),
169
+ value: packageTag.label,
170
+ removable: true,
171
+ default: null
172
+ })
173
+ }
174
+
175
+ Object.keys(params)
176
+ .filter(item => !["page", "search", "language", "operator", "like", "wildcard", "filter", "packages"].includes(item))
177
+ .forEach(item => {
178
+ if (params[item as keyof typeof params] === null || params[item as keyof typeof params] === undefined) return;
179
+ const value = params[item as keyof typeof params] as string
180
+ filters.push({ key: item, value: value, removable: true, default: null })
181
+ })
182
+
183
+ setFilters(filters)
184
+ }
185
+
186
+ const updateFilterParam = (key: string, item: any) => {
187
+ setLoading(true)
188
+ if (key === "packages") {
189
+ setParams({ packages: item.shortId })
190
+ return;
191
+ } else {
192
+ const value = `${key}.shortId=${item.shortId}`
193
+ let aux = value
194
+
195
+ if (params.filter != null) {
196
+ const splittedParam = params.filter.split(",")
197
+ const finalValue = [...splittedParam]
198
+
199
+ const hasParams = params.filter.includes(key)
200
+
201
+ if (hasParams) {
202
+ let mainIndex = -1
203
+
204
+ splittedParam.forEach((el, index) => {
205
+ if (el.includes(key)) {
206
+ mainIndex = index
207
+ }
208
+ })
209
+ finalValue[mainIndex] = value
210
+ } else {
211
+ finalValue.push(value)
212
+ }
213
+
214
+ aux = finalValue.join(",")
215
+ }
216
+
217
+ setParams({ filter: aux })
218
+ }
219
+ };
220
+
221
+ return (
222
+ <div className="container">
223
+ {loading && <Loading opacity={true} />}
224
+
225
+ <div className="grid grid-cols-12 gap-4 py-6">
226
+ <div className="col-span-12 sm:col-span-10 md:col-span-10">
227
+ <AutoComplete
228
+ embedded={false}
229
+ initialValue={params.search}
230
+ searchByPackage={false}
231
+ />
232
+ </div>
233
+ <div className="col-span-12 sm:col-span-2 md:col-span-2">
234
+ <div className="flex justify-end">
235
+ <DialogFilter
236
+ setLoading={setLoading}
237
+ trigger={(
238
+ <Button variant="default" disabled={disabled}>{t("filter.filters")}</Button>
239
+ )}
240
+ />
241
+ </div>
242
+ </div>
243
+ </div>
244
+
245
+ {filters != null && filters.length > 0 && (
246
+ <div className="pb-4 flex justify-between">
247
+ <div>
248
+
249
+ {filters?.map((item) => (
250
+ <Badge key={`${item.key}-${item?.value}`} variant="outline" className="mr-2 mb-2 h-6">
251
+ {item?.name ? item.name : item.key}: {item.value}
252
+ {item.removable && (
253
+ <Button size="xs" variant="ghost" onClick={() => {
254
+ setLoading(true)
255
+ setParams({ [item.key]: item?.default })
256
+ }}>
257
+ <X className="h-2" />
258
+ </Button>
259
+ )}
260
+ </Badge>
261
+ ))}
262
+
263
+ </div>
264
+ <Button
265
+ size="sm"
266
+ variant="outline"
267
+ disabled={params.filter === null}
268
+ onClick={() => {
269
+ setLoading(true)
270
+ setParams({ filter: null, packages: null })
271
+ }}
272
+ >
273
+ {t("filter.clearFilters")}
274
+ <Trash2 className="h-2" />
275
+ </Button>
276
+ </div>
277
+ )}
278
+
279
+ <div className="flex flex-row gap-6 pb-6">
280
+ {data.items.length > 0 && (
281
+ <div className="w-80 bg-sidebar rounded-md border pb-4">
282
+ <SidebarHeader className="text-center font-bold">
283
+ Search Filters
284
+ </SidebarHeader>
285
+ <SidebarContent>
286
+ {Object.entries(tags).map(([key, value]: any) => (
287
+ <SidebarGroup key={key} className="py-0">
288
+ <SidebarGroupLabel>
289
+ {t(`filter.tags.${key}`)}
290
+ </SidebarGroupLabel>
291
+ <SidebarGroupContent>
292
+ <SidebarMenu>
293
+ <SidebarMenuSub>
294
+ {value.map((item: any) => (
295
+ <SidebarMenuSubItem key={item.shortId}>
296
+ <SidebarMenuSubButton
297
+ className="cursor-pointer"
298
+ isActive={item.active}
299
+ onClick={() => updateFilterParam(key, item)}
300
+ >
301
+ {item.label} ({item.hits}/{item.total})
302
+ </SidebarMenuSubButton>
303
+ </SidebarMenuSubItem>
304
+ ))}
305
+ </SidebarMenuSub>
306
+ </SidebarMenu>
307
+ </SidebarGroupContent>
308
+ </SidebarGroup>
309
+ ))}
310
+ </SidebarContent>
311
+ </div>
312
+ )}
313
+
314
+ <div className="flex-1">
315
+ <ResultList
316
+ configs={configs}
317
+ items={data.items}
318
+ pagination={data.pageInfo}
319
+ />
320
+ </div>
321
+ </div>
322
+ </div>
323
+ );
324
+ };
@@ -1,22 +0,0 @@
1
- import { getPrimaryInfo } from "../utils";
2
- import { ArticleWrapper } from "../wrapper";
3
- import { PageWrapper } from "@c-rex/components/page-wrapper";
4
- import { DOCUMENTS_TYPE_AND_LINK } from "@c-rex/constants";
5
-
6
- export const DocumentsPageTemplate = async ({ params }: { params: { id: string } }) => {
7
- const { htmlContent, title, lang, availableVersions, packageId } = await getPrimaryInfo(params.id, DOCUMENTS_TYPE_AND_LINK);
8
-
9
- return (
10
- <PageWrapper title={title} showInput>
11
- <ArticleWrapper
12
- packageId={packageId}
13
- title={title}
14
- availableVersions={availableVersions}
15
- htmlContent={htmlContent}
16
- lang={lang}
17
- id={params.id}
18
- type={DOCUMENTS_TYPE_AND_LINK}
19
- />
20
- </PageWrapper>
21
- );
22
- };
package/src/home/page.tsx DELETED
@@ -1,71 +0,0 @@
1
- "use client";
2
-
3
- import React, { FC, useEffect, useState } from "react";
4
- import { informationUnitsResponse } from "@c-rex/interfaces";
5
- import { call, getFromCookieString } from "@c-rex/utils";
6
- import { Button } from "@c-rex/ui/button";
7
- import { ResultList } from "@c-rex/components/result-list";
8
- import { useTranslations } from 'next-intl'
9
- import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
10
- import { DialogFilter } from "@c-rex/components/dialog-filter";
11
- import { CONTENT_LANG_KEY, WILD_CARD_OPTIONS } from "@c-rex/constants";
12
- import { useAppConfig } from "@c-rex/contexts/config-provider";
13
- import { AutoComplete } from "@c-rex/components/autocomplete";
14
-
15
- interface HomePageProps {
16
- data: informationUnitsResponse;
17
- }
18
-
19
- export const HomePage: FC<HomePageProps> = ({ data }) => {
20
- const t = useTranslations();
21
- const { configs } = useAppConfig()
22
- const [disabled, setDisabled] = useState<boolean>(false)
23
- const [params, setParams] = useQueryStates({
24
- search: {
25
- defaultValue: "",
26
- parse(value) {
27
- return value
28
- },
29
- },
30
- }, {
31
- history: 'push',
32
- shallow: false,
33
- });
34
-
35
- useEffect(() => {
36
- if (params.search.length > 0) {
37
- setDisabled(false)
38
- } else {
39
- setDisabled(true)
40
- }
41
- }, [params])
42
-
43
- return (
44
- <div className="container">
45
- <div className="grid grid-cols-12 gap-4 py-6">
46
- <div className="col-span-12 sm:col-span-10 md:col-span-10">
47
- <AutoComplete
48
- embedded={false}
49
- initialValue={params.search}
50
- searchByPackage={false}
51
- />
52
- </div>
53
- <div className="col-span-12 sm:col-span-2 md:col-span-2">
54
- <div className="flex justify-end">
55
- <DialogFilter
56
- trigger={(
57
- <Button variant="default" disabled={disabled}>{t("filter.filters")}</Button>
58
- )}
59
- />
60
- </div>
61
- </div>
62
- </div>
63
-
64
- <ResultList
65
- configs={configs}
66
- items={data.items}
67
- pagination={data.pageInfo}
68
- />
69
- </div>
70
- );
71
- };