Skip to content

Commit 6788007

Browse files
authored
Merge pull request #7950 from minhoryang/fix-7904-tutorials-now-respect-current-language
Fixed #7904, Respect current language for filtering tutorials
2 parents f47588e + 5993e73 commit 6788007

File tree

2 files changed

+137
-120
lines changed

2 files changed

+137
-120
lines changed

src/pages/developers/tutorials.tsx

Lines changed: 46 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react"
1+
import React, { useEffect, useMemo, useState } from "react"
22
import styled from "@emotion/styled"
33
import { graphql, PageProps } from "gatsby"
44
import { useIntl } from "react-intl"
@@ -22,11 +22,15 @@ import {
2222

2323
import { getLocaleTimestamp, INVALID_DATETIME } from "../../utils/time"
2424

25-
import foreignTutorials from "../../data/externalTutorials.json"
25+
import externalTutorials from "../../data/externalTutorials.json"
2626
import FeedbackCard from "../../components/FeedbackCard"
2727
import { getSkillTranslationId, Skill } from "../../components/TutorialMetadata"
2828
import { Context } from "../../types"
2929
import { Lang } from "../../utils/languages"
30+
import {
31+
filterTutorialsByLang,
32+
getSortedTutorialTagsForLang,
33+
} from "../../utils/tutorials"
3034

3135
const SubSlogan = styled.p`
3236
font-size: 1.25rem;
@@ -217,7 +221,7 @@ const published = (locale: string, published: string) => {
217221
) : null
218222
}
219223

220-
interface IExternalTutorial {
224+
export interface IExternalTutorial {
221225
url: string
222226
title: string
223227
description: string
@@ -230,7 +234,7 @@ interface IExternalTutorial {
230234
publishDate: string
231235
}
232236

233-
interface ITutorial {
237+
export interface ITutorial {
234238
to: string
235239
title: string
236240
description: string
@@ -243,7 +247,7 @@ interface ITutorial {
243247
isExternal: boolean
244248
}
245249

246-
interface ITutorialsState {
250+
export interface ITutorialsState {
247251
activeTagNames: Array<string>
248252
filteredTutorials: Array<ITutorial>
249253
}
@@ -252,131 +256,53 @@ const TutorialsPage = ({
252256
data,
253257
pageContext,
254258
}: PageProps<Queries.DevelopersTutorialsPageQuery, Context>) => {
255-
const intl = useIntl()
256-
// Filter tutorials by language and map to object
257-
const internalTutorials = data.allTutorials.nodes.map<ITutorial>(
258-
(tutorial) => ({
259-
to:
260-
tutorial?.fields?.slug?.substr(0, 3) === "/en"
261-
? tutorial.fields.slug.substr(3)
262-
: tutorial.fields?.slug || "/",
263-
title: tutorial?.frontmatter?.title || "",
264-
description: tutorial?.frontmatter?.description || "",
265-
author: tutorial?.frontmatter?.author || "",
266-
tags: tutorial?.frontmatter?.tags?.map((tag) =>
267-
(tag || "").toLowerCase().trim()
259+
const filteredTutorialsByLang = useMemo(
260+
() =>
261+
filterTutorialsByLang(
262+
data.allTutorials.nodes,
263+
externalTutorials,
264+
pageContext.locale
268265
),
269-
skill: tutorial?.frontmatter?.skill as Skill,
270-
timeToRead: tutorial?.fields?.readingTime?.minutes
271-
? Math.round(tutorial?.fields?.readingTime?.minutes)
272-
: null,
273-
published: tutorial?.frontmatter?.published,
274-
lang: tutorial?.frontmatter?.lang || "en",
275-
isExternal: false,
276-
})
266+
[pageContext.locale]
277267
)
278268

279-
const externalTutorials = foreignTutorials.map<ITutorial>(
280-
(tutorial: IExternalTutorial) => ({
281-
to: tutorial.url,
282-
title: tutorial.title,
283-
description: tutorial.description,
284-
author: tutorial.author,
285-
tags: tutorial.tags.map((tag) => tag.toLowerCase().trim()),
286-
skill: tutorial.skillLevel as Skill,
287-
timeToRead: Number(tutorial.timeToRead),
288-
published: new Date(tutorial.publishDate).toISOString(),
289-
lang: tutorial.lang || "en",
290-
isExternal: true,
291-
})
269+
const allTags = useMemo(
270+
() => getSortedTutorialTagsForLang(filteredTutorialsByLang),
271+
[filteredTutorialsByLang]
292272
)
293273

294-
const allTutorials: Array<ITutorial> = [
295-
...externalTutorials,
296-
...internalTutorials,
297-
]
298-
299-
const hasTutorialsCheck = allTutorials.some(
300-
(tutorial) => tutorial.lang === pageContext.language
274+
const intl = useIntl()
275+
const [isModalOpen, setModalOpen] = useState(false)
276+
const [filteredTutorials, setFilteredTutorials] = useState(
277+
filteredTutorialsByLang
301278
)
279+
const [selectedTags, setSelectedTags] = useState<Array<string>>([])
302280

303-
const filteredTutorials = allTutorials
304-
.filter((tutorial) =>
305-
hasTutorialsCheck
306-
? tutorial.lang === pageContext.language
307-
: tutorial.lang === "en"
308-
)
309-
.sort((a, b) => {
310-
if (a.published && b.published) {
311-
return new Date(b.published).getTime() - new Date(a.published).getTime()
312-
}
313-
// Dont order if no published is present
314-
return 0
315-
})
316-
317-
// Tally all subject tag counts
318-
const tagsConcatenated: Array<string> = []
319-
for (const tutorial of filteredTutorials) {
320-
if (tutorial.tags) {
321-
tagsConcatenated.push(...tutorial.tags)
281+
useEffect(() => {
282+
let tutorials = filteredTutorialsByLang
283+
284+
if (selectedTags.length) {
285+
tutorials = tutorials.filter((tutorial) => {
286+
return selectedTags.every((tag) => (tutorial.tags || []).includes(tag))
287+
})
322288
}
323-
}
324289

325-
const allTags = tagsConcatenated.map((tag) => ({ name: tag, totalCount: 1 }))
326-
const sanitizedAllTags = Array.from(
327-
allTags.reduce(
328-
(m, { name, totalCount }) =>
329-
m.set(
330-
name.toLowerCase().trim(),
331-
(m.get(name.toLowerCase().trim()) || 0) + totalCount
332-
),
333-
new Map()
334-
),
335-
([name, totalCount]) => ({ name, totalCount })
336-
).sort((a, b) => a.name.localeCompare(b.name))
337-
338-
const [state, setState] = useState<ITutorialsState>({
339-
activeTagNames: [],
340-
filteredTutorials: filteredTutorials,
341-
})
342-
343-
const clearActiveTags = () => {
344-
setState({
345-
activeTagNames: [],
346-
filteredTutorials: filteredTutorials,
347-
})
348-
}
290+
setFilteredTutorials(tutorials)
291+
}, [selectedTags])
349292

350293
const handleTagSelect = (tagName: string) => {
351-
const activeTagNames = state.activeTagNames
294+
const tempSelectedTags = selectedTags
352295

353-
// Add or remove the selected tag
354-
const index = activeTagNames.indexOf(tagName)
296+
const index = tempSelectedTags.indexOf(tagName)
355297
if (index > -1) {
356-
activeTagNames.splice(index, 1)
298+
tempSelectedTags.splice(index, 1)
357299
} else {
358-
activeTagNames.push(tagName)
300+
tempSelectedTags.push(tagName)
359301
}
360302

361-
// If no tags are active, show all tutorials, otherwise filter by active tag
362-
let filteredTutorials = allTutorials
363-
if (activeTagNames.length > 0) {
364-
filteredTutorials = filteredTutorials.filter((tutorial) => {
365-
for (const tag of activeTagNames) {
366-
if (!tutorial.tags?.includes(tag)) {
367-
return false
368-
}
369-
}
370-
return true
371-
})
372-
}
373-
setState({ activeTagNames, filteredTutorials })
303+
setSelectedTags([...tempSelectedTags])
374304
}
375305

376-
const hasActiveTags = state.activeTagNames.length > 0
377-
const hasNoTutorials = state.filteredTutorials.length === 0
378-
const [isModalOpen, setModalOpen] = useState(false)
379-
380306
return (
381307
<StyledPage>
382308
<PageMetadata
@@ -454,28 +380,28 @@ const TutorialsPage = ({
454380
<TutorialContainer>
455381
<TagsContainer>
456382
<TagContainer>
457-
{sanitizedAllTags.map((tag) => {
458-
const name = `${tag.name} (${tag.totalCount})`
459-
const isActive = state.activeTagNames.includes(tag.name)
383+
{Object.entries(allTags).map(([tagName, tagCount]) => {
384+
const name = `${tagName} (${tagCount})`
385+
const isActive = selectedTags.includes(tagName)
460386
return (
461387
<Tag
462388
name={name}
463389
key={name}
464390
isActive={isActive}
465391
shouldShowIcon={false}
466392
onClick={handleTagSelect}
467-
value={tag.name}
393+
value={tagName}
468394
/>
469395
)
470396
})}
471-
{hasActiveTags && (
472-
<ClearLink onClick={clearActiveTags}>
397+
{selectedTags.length > 0 && (
398+
<ClearLink onClick={() => setSelectedTags([])}>
473399
<Translation id="page-find-wallet-clear" />
474400
</ClearLink>
475401
)}
476402
</TagContainer>
477403
</TagsContainer>
478-
{hasNoTutorials && (
404+
{filteredTutorials.length === 0 && (
479405
<ResultsContainer>
480406
<Emoji text=":crying_face:" size={3} mb={`2em`} mt={`2em`} />
481407
<h2>
@@ -486,7 +412,7 @@ const TutorialsPage = ({
486412
</p>
487413
</ResultsContainer>
488414
)}
489-
{state.filteredTutorials.map((tutorial) => {
415+
{filteredTutorials.map((tutorial) => {
490416
return (
491417
<TutorialCard
492418
key={tutorial.to}

src/utils/tutorials.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { IExternalTutorial, ITutorial } from "../pages/developers/tutorials"
2+
import { Skill } from "../components/TutorialMetadata"
3+
import { Lang } from "./languages"
4+
5+
// Take all tutorials, and return a list of tutorials for a specific locale
6+
export const filterTutorialsByLang = (
7+
internalTutorials: any,
8+
externalTutorials: Array<IExternalTutorial>,
9+
locale: Lang
10+
): Array<ITutorial> => {
11+
const internalTutorialsMap = internalTutorials.map((tutorial) => ({
12+
to:
13+
tutorial?.fields?.slug?.substr(0, 3) === "/en"
14+
? tutorial.fields.slug.substr(3)
15+
: tutorial.fields?.slug || "/",
16+
title: tutorial?.frontmatter?.title || "",
17+
description: tutorial?.frontmatter?.description || "",
18+
author: tutorial?.frontmatter?.author || "",
19+
tags: tutorial?.frontmatter?.tags?.map((tag) =>
20+
(tag || "").toLowerCase().trim()
21+
),
22+
skill: tutorial?.frontmatter?.skill as Skill,
23+
timeToRead: tutorial?.fields?.readingTime?.minutes
24+
? Math.round(tutorial?.fields?.readingTime?.minutes)
25+
: null,
26+
published: tutorial?.frontmatter?.published,
27+
lang: tutorial?.frontmatter?.lang || "en",
28+
isExternal: false,
29+
}))
30+
31+
const externalTutorialsMap = externalTutorials.map<ITutorial>(
32+
(tutorial: IExternalTutorial) => ({
33+
to: tutorial.url,
34+
title: tutorial.title,
35+
description: tutorial.description,
36+
author: tutorial.author,
37+
tags: tutorial.tags.map((tag) => tag.toLowerCase().trim()),
38+
skill: tutorial.skillLevel as Skill,
39+
timeToRead: Number(tutorial.timeToRead),
40+
published: new Date(tutorial.publishDate).toISOString(),
41+
lang: tutorial.lang || "en",
42+
isExternal: true,
43+
})
44+
)
45+
46+
const allTutorials: Array<ITutorial> = [
47+
...externalTutorialsMap,
48+
...internalTutorialsMap,
49+
]
50+
51+
const filteredTutorials = allTutorials
52+
.filter((tutorial) => tutorial.lang === locale)
53+
.sort((a, b) => {
54+
if (a.published && b.published) {
55+
return new Date(b.published).getTime() - new Date(a.published).getTime()
56+
}
57+
// Dont order if no published is present
58+
return 0
59+
})
60+
61+
return filteredTutorials
62+
}
63+
64+
export const getSortedTutorialTagsForLang = (
65+
filteredTutorialsByLang: Array<ITutorial> = []
66+
) => {
67+
const allTags = filteredTutorialsByLang.reduce<Array<string>>(
68+
(tags, tutorial) => {
69+
return [...tags, ...(tutorial.tags || [])]
70+
},
71+
[]
72+
)
73+
74+
const reducedTags = allTags.reduce((acc, tag) => {
75+
if (acc[tag]) {
76+
acc[tag] = acc[tag] + 1
77+
} else {
78+
acc[tag] = 1
79+
}
80+
return acc
81+
}, {})
82+
83+
const sortedTags = Object.keys(reducedTags)
84+
.sort()
85+
.reduce((obj, key) => {
86+
obj[key] = reducedTags[key]
87+
return obj
88+
}, {})
89+
90+
return sortedTags
91+
}

0 commit comments

Comments
 (0)