Skip to content

Commit 7310fc9

Browse files
authored
Refactor display-platform-specific-content (#22665)
* refactor display-platform-specific-content * update PlatformPicker tests and cleanup
1 parent 12f5437 commit 7310fc9

File tree

8 files changed

+216
-189
lines changed

8 files changed

+216
-189
lines changed

components/article/ArticlePage.tsx

+2-30
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { LearningTrackNav } from './LearningTrackNav'
1414
import { MarkdownContent } from 'components/ui/MarkdownContent'
1515
import { Lead } from 'components/ui/Lead'
1616
import { ArticleGridLayout } from './ArticleGridLayout'
17+
import { PlatformPicker } from 'components/article/PlatformPicker'
1718

1819
// Mapping of a "normal" article to it's interactive counterpart
1920
const interactiveAlternatives: Record<string, { href: string }> = {
@@ -35,7 +36,6 @@ export const ArticlePage = () => {
3536
contributor,
3637
permissions,
3738
includesPlatformSpecificContent,
38-
defaultPlatform,
3939
product,
4040
miniTocItems,
4141
currentLearningTrack,
@@ -87,35 +87,7 @@ export const ArticlePage = () => {
8787
</div>
8888
)}
8989

90-
{includesPlatformSpecificContent && (
91-
<nav
92-
className="UnderlineNav my-3"
93-
data-default-platform={defaultPlatform || undefined}
94-
>
95-
<div className="UnderlineNav-body">
96-
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
97-
<a href="#" className="UnderlineNav-item platform-switcher" data-platform="mac">
98-
Mac
99-
</a>
100-
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
101-
<a
102-
href="#"
103-
className="UnderlineNav-item platform-switcher"
104-
data-platform="windows"
105-
>
106-
Windows
107-
</a>
108-
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
109-
<a
110-
href="#"
111-
className="UnderlineNav-item platform-switcher"
112-
data-platform="linux"
113-
>
114-
Linux
115-
</a>
116-
</div>
117-
</nav>
118-
)}
90+
{includesPlatformSpecificContent && <PlatformPicker variant="underlinenav" />}
11991

12092
{product && (
12193
<Callout

components/article/PlatformPicker.tsx

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { useEffect, useState } from 'react'
2+
import Cookies from 'js-cookie'
3+
import { SubNav, TabNav, UnderlineNav } from '@primer/components'
4+
import { sendEvent, EventType } from 'components/lib/events'
5+
6+
import { useArticleContext } from 'components/context/ArticleContext'
7+
import parseUserAgent from 'components/lib/user-agent'
8+
9+
const platforms = [
10+
{ id: 'mac', label: 'Mac' },
11+
{ id: 'windows', label: 'Windows' },
12+
{ id: 'linux', label: 'Linux' },
13+
]
14+
15+
// Imperatively modify article content to show only the selected platform
16+
// find all platform-specific *block* elements and hide or show as appropriate
17+
// example: {% mac } block content {% mac %}
18+
function showPlatformSpecificContent(platform: string) {
19+
const markdowns = Array.from(document.querySelectorAll<HTMLElement>('.extended-markdown'))
20+
markdowns
21+
.filter((el) => platforms.some((platform) => el.classList.contains(platform.id)))
22+
.forEach((el) => {
23+
el.style.display = el.classList.contains(platform) ? '' : 'none'
24+
})
25+
26+
// find all platform-specific *inline* elements and hide or show as appropriate
27+
// example: <span class="platform-mac">inline content</span>
28+
const platformEls = Array.from(
29+
document.querySelectorAll<HTMLElement>(
30+
platforms.map((platform) => `.platform-${platform.id}`).join(', ')
31+
)
32+
)
33+
platformEls.forEach((el) => {
34+
el.style.display = el.classList.contains(`platform-${platform}`) ? '' : 'none'
35+
})
36+
}
37+
38+
// uses the order of the supportedPlatforms array to
39+
// determine the default platform
40+
const getFallbackPlatform = (detectedPlatforms: Array<string>): string => {
41+
const foundPlatform = platforms.find((platform) => detectedPlatforms.includes(platform.id))
42+
return foundPlatform?.id || 'linux'
43+
}
44+
45+
type Props = {
46+
variant?: 'subnav' | 'tabnav' | 'underlinenav'
47+
}
48+
export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
49+
const { defaultPlatform, detectedPlatforms } = useArticleContext()
50+
const [currentPlatform, setCurrentPlatform] = useState(defaultPlatform || '')
51+
52+
// Run on mount for client-side only features
53+
useEffect(() => {
54+
let userAgent = parseUserAgent().os
55+
if (userAgent === 'ios') {
56+
userAgent = 'mac'
57+
}
58+
59+
setCurrentPlatform(defaultPlatform || Cookies.get('osPreferred') || userAgent || 'linux')
60+
}, [])
61+
62+
// Make sure we've always selected a platform that exists in the article
63+
useEffect(() => {
64+
// Only check *after* current platform has been determined
65+
if (currentPlatform && !detectedPlatforms.includes(currentPlatform)) {
66+
setCurrentPlatform(getFallbackPlatform(detectedPlatforms))
67+
}
68+
}, [currentPlatform, detectedPlatforms.join(',')])
69+
70+
const onClickPlatform = (platform: string) => {
71+
setCurrentPlatform(platform)
72+
73+
// imperatively modify the article content
74+
showPlatformSpecificContent(platform)
75+
76+
sendEvent({
77+
type: EventType.preference,
78+
preference_name: 'os',
79+
preference_value: platform,
80+
})
81+
82+
Cookies.set('osPreferred', platform, {
83+
sameSite: 'strict',
84+
secure: true,
85+
})
86+
}
87+
88+
// only show platforms that are in the current article
89+
const platformOptions = platforms.filter((platform) => detectedPlatforms.includes(platform.id))
90+
91+
const sharedContainerProps = {
92+
'data-testid': 'platform-picker',
93+
'aria-label': 'Platform picker',
94+
'data-default-platform': defaultPlatform,
95+
className: 'mb-4',
96+
}
97+
98+
if (variant === 'subnav') {
99+
return (
100+
<SubNav {...sharedContainerProps}>
101+
<SubNav.Links>
102+
{platformOptions.map((option) => {
103+
return (
104+
<SubNav.Link
105+
key={option.id}
106+
data-platform={option.id}
107+
as="button"
108+
selected={option.id === currentPlatform}
109+
onClick={() => {
110+
onClickPlatform(option.id)
111+
}}
112+
>
113+
{option.label}
114+
</SubNav.Link>
115+
)
116+
})}
117+
</SubNav.Links>
118+
</SubNav>
119+
)
120+
}
121+
122+
if (variant === 'underlinenav') {
123+
return (
124+
<UnderlineNav {...sharedContainerProps}>
125+
{platformOptions.map((option) => {
126+
return (
127+
<UnderlineNav.Link
128+
key={option.id}
129+
data-platform={option.id}
130+
as="button"
131+
selected={option.id === currentPlatform}
132+
onClick={() => {
133+
onClickPlatform(option.id)
134+
}}
135+
>
136+
{option.label}
137+
</UnderlineNav.Link>
138+
)
139+
})}
140+
</UnderlineNav>
141+
)
142+
}
143+
144+
return (
145+
<TabNav {...sharedContainerProps}>
146+
{platformOptions.map((option) => {
147+
return (
148+
<TabNav.Link
149+
key={option.id}
150+
data-platform={option.id}
151+
as="button"
152+
selected={option.id === currentPlatform}
153+
onClick={() => {
154+
onClickPlatform(option.id)
155+
}}
156+
>
157+
{option.label}
158+
</TabNav.Link>
159+
)
160+
})}
161+
</TabNav>
162+
)
163+
}

components/context/ArticleContext.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type ArticleContextT = {
2525
defaultPlatform?: string
2626
product?: string
2727
currentLearningTrack?: LearningTrack
28+
detectedPlatforms: Array<string>
2829
}
2930

3031
export const ArticleContext = createContext<ArticleContextT | null>(null)
@@ -62,5 +63,6 @@ export const getArticleContextFromRequest = (req: any): ArticleContextT => {
6263
defaultPlatform: page.defaultPlatform || '',
6364
product: page.product || '',
6465
currentLearningTrack: req.context.currentLearningTrack,
66+
detectedPlatforms: page.detectedPlatforms || [],
6567
}
6668
}

components/lib/display-platform-specific-content.ts

-132
This file was deleted.

lib/page.js

+6
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,12 @@ class Page {
256256
html.includes('extended-markdown windows') ||
257257
html.includes('extended-markdown linux')
258258

259+
this.detectedPlatforms = [
260+
html.includes('extended-markdown mac') && 'mac',
261+
html.includes('extended-markdown windows') && 'windows',
262+
html.includes('extended-markdown linux') && 'linux',
263+
].filter(Boolean)
264+
259265
return html
260266
}
261267

pages/[versionId]/[productId]/index.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useRouter } from 'next/router'
44
// "legacy" javascript needed to maintain existing functionality
55
// typically operating on elements **within** an article.
66
import copyCode from 'components/lib/copy-code'
7-
import displayPlatformSpecificContent from 'components/lib/display-platform-specific-content'
87
import displayToolSpecificContent from 'components/lib/display-tool-specific-content'
98
import localization from 'components/lib/localization'
109
import wrapCodeTerms from 'components/lib/wrap-code-terms'
@@ -41,7 +40,6 @@ import { useEffect } from 'react'
4140

4241
function initiateArticleScripts() {
4342
copyCode()
44-
displayPlatformSpecificContent()
4543
displayToolSpecificContent()
4644
localization()
4745
wrapCodeTerms()

0 commit comments

Comments
 (0)