* sub-landing: implement filtering in react, other cleanup
This commit is contained in:
Mike Surowiec 2021-06-15 11:16:24 -07:00 коммит произвёл GitHub
Родитель cfd962c2f5
Коммит e26a3446a7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 128 добавлений и 87 удалений

Просмотреть файл

@ -42,6 +42,7 @@ module.exports = {
'no-undef': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'jsx-a11y/no-onchange': 'off',
}
},
]

Просмотреть файл

@ -9,7 +9,10 @@ export type BreadcrumbT = {
href?: string
}
export const Breadcrumbs = () => {
type Props = {
variant?: 'default' | 'large'
}
export const Breadcrumbs = ({ variant = 'default' }: Props) => {
const router = useRouter()
const pathWithLocale = `/${router.locale}${router.asPath.split('?')[0]}` // remove query string
const { breadcrumbs } = useMainContext()
@ -26,20 +29,6 @@ export const Breadcrumbs = () => {
<span key={title} title={title}>
{breadcrumb.title}
</span>
) : pathWithLocale.includes('/guides') ? (
<span className="text-mono color-text-secondary text-uppercase">
<Link
key={title}
href={breadcrumb.href}
title={title}
className={cx(
'd-inline-block',
pathWithLocale === breadcrumb.href && 'color-text-tertiary'
)}
>
{breadcrumb.title}
</Link>
</span>
) : (
<Link
key={title}
@ -47,6 +36,7 @@ export const Breadcrumbs = () => {
title={title}
className={cx(
'd-inline-block',
variant === 'large' && 'text-uppercase text-mono',
pathWithLocale === breadcrumb.href && 'color-text-tertiary'
)}
>

Просмотреть файл

@ -45,20 +45,22 @@ export const getProductSubLandingContextFromRequest = (req: any): ProductSubLand
return {
...pick(page, ['intro', 'allTopics']),
title: req.context.productMap[req.context.currentProduct].name,
featuredTrack: page.featuredTrack ? {
...pick(page.featuredTrack, ['title', 'description', 'trackName', 'guides']),
guides: (page.featuredTrack?.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
})
} : null,
featuredTrack: page.featuredTrack
? {
...pick(page.featuredTrack, ['title', 'description', 'trackName']),
guides: (page.featuredTrack?.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),
}
: null,
learningTracks: (page.learningTracks || []).map((track: any) => ({
...pick(track, ['title', 'description', 'trackName', 'guides']),
...pick(track, ['title', 'description', 'trackName']),
guides: (track.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),
})),
includeGuides: (page.includeGuides || []).map((guide: any) => {
return pick(guide, ['href', 'title', 'intro', 'page.type', 'topics'])
return pick(guide, ['href', 'title', 'intro', 'type', 'topics'])
}),
}
}

Просмотреть файл

@ -34,22 +34,24 @@ export const LandingHero = () => {
/>
{introLinks &&
Object.entries(introLinks).filter(([key, link])=> {
return link && !key.includes('raw')
}).map(([key, link], i) => {
if (!link) {
return null
}
return (
<FullLink
key={link}
href={link}
className={cx('btn-mktg bt-large f4 mt-3 mr-3', i !== 0 && 'btn-outline-mktg')}
>
{t(key)}
</FullLink>
)
})}
Object.entries(introLinks)
.filter(([key, link]) => {
return link && !key.includes('raw')
})
.map(([key, link], i) => {
if (!link) {
return null
}
return (
<FullLink
key={link}
href={link}
className={cx('btn-mktg bt-large f4 mt-3 mr-3', i !== 0 && 'btn-outline-mktg')}
>
{t(key)}
</FullLink>
)
})}
</div>
{product_video && (

Просмотреть файл

@ -1,5 +1,4 @@
import cx from 'classnames'
import { useTranslation } from 'components/hooks/useTranslation'
type Props = {
title?: React.ReactNode
@ -9,8 +8,6 @@ type Props = {
description?: string
}
export const LandingSection = ({ title, children, className, sectionLink, description }: Props) => {
const { t } = useTranslation('product_sublanding')
return (
<div className={cx('container-xl px-3 px-md-6', className)} id={sectionLink}>
{title && (
@ -21,7 +18,7 @@ export const LandingSection = ({ title, children, className, sectionLink, descri
{description && (
<div
className="lead-mktg color-text-secondary f4 description-text"
dangerouslySetInnerHTML={{ __html: t(description) }}
dangerouslySetInnerHTML={{ __html: description }}
/>
)}
{children}

Просмотреть файл

@ -32,7 +32,10 @@ export const TocLanding = () => {
</div>
{productCallout && (
<div className="product-callout border rounded-1 mb-4 p-3 color-border-success color-bg-success" dangerouslySetInnerHTML={{__html: productCallout }} />
<div
className="product-callout border rounded-1 mb-4 p-3 color-border-success color-bg-success"
dangerouslySetInnerHTML={{ __html: productCallout }}
/>
)}
</div>

Просмотреть файл

@ -2,26 +2,24 @@ import { ArticleGuide } from 'components/context/ProductSubLandingContext'
type Props = {
card: ArticleGuide
type: string
display?: string
typeLabel: string
}
export const ArticleCard = ({ card, type, display }: Props) => {
export const ArticleCard = ({ card, typeLabel }: Props) => {
return (
<div
className={`d-flex col-12 col-md-4 pr-0 pr-md-6 pr-lg-8 ${display} js-filter-card`}
data-type={card.type}
data-topics={card.topics.join(',')}
>
<div className="d-flex col-12 col-md-4 pr-0 pr-md-6 pr-lg-8">
<a className="no-underline d-flex flex-column py-3 border-bottom" href={card.href}>
<h4 className="h4 color-text-primary mb-1">{card.title}</h4>
<div className="h6 text-uppercase">{type}</div>
<div className="h6 text-uppercase">{typeLabel}</div>
<p className="color-text-secondary my-3">{card.intro}</p>
{card.topics.length > 0 && (
<div>
{card.topics.map((topic) => {
return (
<span key={topic} className="IssueLabel bg-gradient--pink-blue color-text-inverse mr-1">
<span
key={topic}
className="IssueLabel bg-gradient--pink-blue color-text-inverse mr-1"
>
{topic}
</span>
)

Просмотреть файл

@ -1,12 +1,42 @@
import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext'
import { useEffect, useState } from 'react'
import {
ArticleGuide,
useProductSubLandingContext,
} from 'components/context/ProductSubLandingContext'
import { useTranslation } from 'components/hooks/useTranslation'
import { ArticleCard } from './ArticleCard'
const MAX_ARTICLES = 9
const PAGE_SIZE = 9
export const ArticleCards = () => {
const { t } = useTranslation('product_sublanding')
const guideTypes: Record<string, string> = t('guide_types')
const { allTopics, includeGuides } = useProductSubLandingContext()
const [numVisible, setNumVisible] = useState(PAGE_SIZE)
const [typeFilter, setTypeFilter] = useState('')
const [topicFilter, setTopicFilter] = useState('')
const [filteredResults, setFilteredResults] = useState<Array<ArticleGuide>>([])
useEffect(() => {
setNumVisible(PAGE_SIZE)
setFilteredResults(
(includeGuides || []).filter((card) => {
const matchesType = card.type === typeFilter
const matchesTopic = card.topics.some((key) => key === topicFilter)
return (typeFilter ? matchesType : true) && (topicFilter ? matchesTopic : true)
})
)
}, [typeFilter, topicFilter])
const isUserFiltering = typeFilter !== '' || topicFilter !== ''
const onChangeTypeFilter = (e: React.ChangeEvent<HTMLSelectElement>) => {
setTypeFilter(e.target.value)
}
const onChangeTopicFilter = (e: React.ChangeEvent<HTMLSelectElement>) => {
setTopicFilter(e.target.value)
}
const guides = isUserFiltering ? filteredResults : includeGuides || []
return (
<div>
@ -16,13 +46,19 @@ export const ArticleCards = () => {
{t('filters.type')}
</label>
<select
className="form-select js-filter-card-filter-dropdown f4 text-bold border-0 rounded-0 border-top box-shadow-none pl-0 js-filter-card-filter-dropdown"
value={typeFilter}
className="form-select f4 text-bold border-0 rounded-0 border-top box-shadow-none pl-0"
name="type"
aria-label="guide types"
onChange={onChangeTypeFilter}
>
<option value="">{t('filters.all')}</option>
{Object.entries(guideTypes).map(([key, val]) => {
return <option key={key} value={key}>{val}</option>
return (
<option key={key} value={key}>
{val}
</option>
)
})}
</select>
</div>
@ -31,37 +67,44 @@ export const ArticleCards = () => {
{t('filters.topic')}
</label>
<select
className="form-select js-filter-card-filter-dropdown f4 text-bold border-0 rounded-0 border-top box-shadow-none pl-0 js-filter-card-filter-dropdown"
value={topicFilter}
className="form-select f4 text-bold border-0 rounded-0 border-top box-shadow-none pl-0"
name="topics"
aria-label="guide topics"
onChange={onChangeTopicFilter}
>
<option value="">{t('filters.all')}</option>
{allTopics?.map((topic) => {
return <option key={topic} value={topic}>{topic}</option>
return (
<option key={topic} value={topic}>
{topic}
</option>
)
})}
</select>
</div>
</form>
<div className="d-flex flex-wrap mr-0 mr-md-n6 mr-lg-n8">
{(includeGuides || []).map((card, index) => {
return index + 1 > MAX_ARTICLES ? (
<ArticleCard key={card.title} card={card} type={guideTypes[card.type]} display={'d-none'} />
) : (
<ArticleCard key={card.title} card={card} type={guideTypes[card.type]} />
)
{guides.slice(0, numVisible).map((card) => {
return <ArticleCard key={card.href} card={card} typeLabel={guideTypes[card.type]} />
})}
</div>
{includeGuides && includeGuides.length > MAX_ARTICLES && (
{guides.length > numVisible && (
<button
className="col-12 mt-5 text-center text-bold color-text-link btn-link js-filter-card-show-more"
data-js-filter-card-max={MAX_ARTICLES}
className="col-12 mt-5 text-center text-bold color-text-link btn-link"
onClick={() => setNumVisible(numVisible + PAGE_SIZE)}
>
{t('load_more')}
</button>
)}
<div className="js-filter-card-no-results d-none py-4 text-center color-text-secondary">
<h4 className="text-normal">{t('no_result')}</h4>
</div>
{guides.length === 0 && (
<div className="py-4 text-center color-text-secondary">
<h4 className="text-normal">{t('no_result')}</h4>
</div>
)}
</div>
)
}

Просмотреть файл

@ -5,10 +5,11 @@ import { LandingSection } from 'components/landing/LandingSection'
import { SubLandingHero } from 'components/sublanding/SubLandingHero'
import { LearningTracks } from 'components/sublanding/LearningTracks'
import { ArticleCards } from 'components/sublanding/ArticleCards'
import { useTranslation } from 'components/hooks/useTranslation'
export const ProductSubLanding = () => {
const { title, learningTracks, includeGuides } = useProductSubLandingContext()
const { t } = useTranslation('sub_landing')
return (
<DefaultLayout>
<LandingSection className="pt-3">
@ -20,7 +21,7 @@ export const ProductSubLanding = () => {
title={`${title} learning paths`}
className="border-top py-6"
sectionLink="learning-paths"
description="learning_paths_desc"
description={t('learning_paths_desc')}
>
<LearningTracks />
</LandingSection>

Просмотреть файл

@ -2,6 +2,7 @@ import { Breadcrumbs } from '../Breadcrumbs'
import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext'
import { ArrowRightIcon, StarFillIcon } from '@primer/octicons-react'
import { useTranslation } from 'components/hooks/useTranslation'
import { Link } from 'components/Link'
export const SubLandingHero = () => {
const { title, intro, featuredTrack } = useProductSubLandingContext()
@ -9,7 +10,7 @@ export const SubLandingHero = () => {
const guideItems = featuredTrack?.guides?.map((guide) => (
<li className="px-2 d-flex flex-shrink-0">
<a
<Link
href={`${guide.href}?learn=${featuredTrack.trackName}`}
className="d-inline-block Box p-5 color-bg-primary color-border-primary no-underline"
>
@ -32,7 +33,7 @@ export const SubLandingHero = () => {
<div className="lead-mktg color-text-secondary f5 my-4 truncate-overflow-8">
{guide.intro}
</div>
</a>
</Link>
</li>
))
@ -40,7 +41,7 @@ export const SubLandingHero = () => {
<div>
<header className="d-flex gutter mb-6">
<div className="col-12">
<Breadcrumbs />
<Breadcrumbs variant="large" />
<h1 className="my-3 font-mktg">{title} guides</h1>
<div
className="lead-mktg color-text-secondary f4 description-text"
@ -64,7 +65,7 @@ export const SubLandingHero = () => {
{featuredTrack.description}
</div>
{featuredTrack.guides && (
<a
<Link
className="d-inline-block border color-border-inverse color-text-inverse px-4 py-2 f5 no-underline text-bold"
role="button"
href={`${featuredTrack.guides[0].href}?learn=${featuredTrack.trackName}`}
@ -73,7 +74,7 @@ export const SubLandingHero = () => {
<ArrowRightIcon size={20} />
</span>
{t(`start_path`)}
</a>
</Link>
)}
</div>
</li>

Просмотреть файл

@ -148,7 +148,7 @@ product_landing:
sorry: Sorry, there is no result for
no_example: It looks like we don't have an example that fits your filter.
try_another: Try another filter or add your code example.
no_result: Sorry, there is no guide that match your filter.
no_result: Sorry, there are no guides that match your filter.
learn: Learn how to add a code example
communities_using_discussions: Communities using discussions
add_your_community: Add your community
@ -165,6 +165,7 @@ product_sublanding:
learning_paths_desc: Learning paths are a collection of guides that help you master a particular subject.
guides: '{{ productMap[currentProduct].name }} guides'
more_guides: more guides
no_result: Sorry, there are no guides that match your filter.
load_more: Load more guides
all_guides: 'All {{ productMap[currentProduct].name }} guides'
filters:

Просмотреть файл

@ -19,8 +19,7 @@ export default function displayPlatformSpecificContent() {
if (!platformsInContent.includes(platform)) {
// uses the order of the supportedPlatforms array to
// determine the default platform
platform = supportedPlatforms
.filter(elem => platformsInContent.includes(elem))[0]
platform = supportedPlatforms.filter((elem) => platformsInContent.includes(elem))[0]
}
showPlatformSpecificContent(platform)
@ -83,17 +82,20 @@ function hideSwitcherLinks(platformsInContent: Array<string>) {
}
// gets the list of detected platforms in the current article
function getDetectedPlatforms (): Array<string> {
function getDetectedPlatforms(): Array<string> {
// find all platform-specific *block* elements and hide or show as appropriate
// example: {{ #mac }} block content {{/mac}}
const allEls = Array.from(document.querySelectorAll('.extended-markdown')) as Array<HTMLElement>
allEls.filter(el => supportedPlatforms.some(platform => el.classList.contains(platform)))
.forEach(el => detectPlatforms(el))
allEls
.filter((el) => supportedPlatforms.some((platform) => el.classList.contains(platform)))
.forEach((el) => detectPlatforms(el))
// find all platform-specific *inline* elements and hide or show as appropriate
// example: <span class="platform-mac">inline content</span>
const platformEls = Array.from(document.querySelectorAll('.platform-mac, .platform-windows, .platform-linux')) as Array<HTMLElement>
platformEls.forEach(el => detectPlatforms(el))
const platformEls = Array.from(
document.querySelectorAll('.platform-mac, .platform-windows, .platform-linux')
) as Array<HTMLElement>
platformEls.forEach((el) => detectPlatforms(el))
return Array.from(detectedPlatforms) as Array<string>
}