From 75a3f2fa57a350054d56491702c10715abfcbad7 Mon Sep 17 00:00:00 2001 From: Leo McArdle Date: Tue, 15 Oct 2024 10:13:12 +0100 Subject: [PATCH] feat(community): new community page design (#11653) Co-authored-by: Claas Augner Co-authored-by: Florian Dieminger --- build/spas.ts | 31 +- client/src/app.tsx | 4 +- .../assets/community/community-calls-dark.svg | 1 + .../src/assets/community/community-calls.svg | 1 + client/src/assets/community/discord-dark.svg | 1 + client/src/assets/community/discord.svg | 1 + client/src/assets/community/people-dark.svg | 1 + client/src/assets/community/people.svg | 1 + .../src/assets/community/quote-end-dark.svg | 1 + client/src/assets/community/quote-end.svg | 1 + .../src/assets/community/quote-start-dark.svg | 1 + client/src/assets/community/quote-start.svg | 1 + client/src/assets/community/video-bg-dark.svg | 1 + client/src/assets/community/video-bg.svg | 1 + .../src/assets/community/video-thumbnail.png | Bin 0 -> 4418 bytes client/src/assets/community/youtube-play.svg | 1 + client/src/community/contributor-list.scss | 144 ++++ client/src/community/contributor-list.ts | 109 +++ client/src/community/index.scss | 698 ++++++++++++++++-- client/src/community/index.tsx | 335 +++++---- client/src/react-app.d.ts | 8 + client/src/ui/atoms/button/_mixins.scss | 49 ++ client/src/utils.ts | 44 ++ copy/community/community.md | 156 ++++ deployer/src/deployer/search/__init__.py | 4 +- libs/constants/index.js | 4 + ssr/react-app.d.ts | 8 + 27 files changed, 1380 insertions(+), 227 deletions(-) create mode 100644 client/src/assets/community/community-calls-dark.svg create mode 100644 client/src/assets/community/community-calls.svg create mode 100644 client/src/assets/community/discord-dark.svg create mode 100644 client/src/assets/community/discord.svg create mode 100644 client/src/assets/community/people-dark.svg create mode 100644 client/src/assets/community/people.svg create mode 100644 client/src/assets/community/quote-end-dark.svg create mode 100644 client/src/assets/community/quote-end.svg create mode 100644 client/src/assets/community/quote-start-dark.svg create mode 100644 client/src/assets/community/quote-start.svg create mode 100644 client/src/assets/community/video-bg-dark.svg create mode 100644 client/src/assets/community/video-bg.svg create mode 100644 client/src/assets/community/video-thumbnail.png create mode 100644 client/src/assets/community/youtube-play.svg create mode 100644 client/src/community/contributor-list.scss create mode 100644 client/src/community/contributor-list.ts create mode 100644 client/src/ui/atoms/button/_mixins.scss create mode 100644 copy/community/community.md diff --git a/build/spas.ts b/build/spas.ts index 70218b1f75c2..7076f3f4c014 100644 --- a/build/spas.ts +++ b/build/spas.ts @@ -222,7 +222,6 @@ export async function buildSPAs(options: { noIndexing: true, }, { prefix: "about", pageTitle: "About MDN" }, - { prefix: "community", pageTitle: "Contribute to MDN" }, { prefix: "advertising", pageTitle: "Advertise with us", @@ -269,16 +268,9 @@ export async function buildSPAs(options: { } // Building the MDN Plus pages. - - /** - * - * @param {string} dirpath - * @param {string} slug - * @param {string} title - */ async function buildStaticPages( dirpath: string, - slug: string, + slugPrefix?: string, title = "MDN" ) { const crawler = new fdir() @@ -299,13 +291,14 @@ export async function buildSPAs(options: { const frontMatter = frontmatter(markdown); const rawHTML = await m2h(frontMatter.body, { locale }); - const url = `/${locale}/${slug}/${page}`; + const slug = slugPrefix ? `${slugPrefix}/${page}` : `${page}`; + const url = `/${locale}/${slug}`; const d = { url, rawBody: rawHTML, metadata: { locale: DEFAULT_LOCALE, - slug: `${slug}/${page}`, + slug, url, }, @@ -327,16 +320,13 @@ export async function buildSPAs(options: { }; const context: HydrationData = { hyData, - pageTitle: `${frontMatter.attributes.title || ""} | ${title}`, + pageTitle: frontMatter.attributes.title + ? `${frontMatter.attributes.title} | ${title}` + : title, url, }; - const outPath = path.join( - BUILD_OUT_ROOT, - pathLocale, - ...slug.split("/"), - page - ); + const outPath = path.join(BUILD_OUT_ROOT, pathLocale, ...slug.split("/")); fs.mkdirSync(outPath, { recursive: true }); const jsonFilePath = path.join(outPath, "index.json"); fs.writeFileSync(jsonFilePath, JSON.stringify(context)); @@ -363,6 +353,11 @@ export async function buildSPAs(options: { "observatory/docs", OBSERVATORY_TITLE ); + await buildStaticPages( + fileURLToPath(new URL("../copy/community/", import.meta.url)), + "", + "Contribute to MDN" + ); // Build all the home pages in all locales. // Fetch merged content PRs for the latest contribution section. diff --git a/client/src/app.tsx b/client/src/app.tsx index a64b26a9e056..ff38d1aafa04 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -22,7 +22,7 @@ import { PageNotFound } from "./page-not-found"; import { Plus } from "./plus"; import { About } from "./about"; import { getCategoryByPathname } from "./utils"; -import { Contribute } from "./community"; +import { Community } from "./community"; import { ContributorSpotlight } from "./contributor-spotlight"; import { useIsServer, usePing } from "./hooks"; @@ -330,7 +330,7 @@ export function App(appProps: HydrationData) { path="/community/*" element={ - + } /> diff --git a/client/src/assets/community/community-calls-dark.svg b/client/src/assets/community/community-calls-dark.svg new file mode 100644 index 000000000000..2240154f1349 --- /dev/null +++ b/client/src/assets/community/community-calls-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/community-calls.svg b/client/src/assets/community/community-calls.svg new file mode 100644 index 000000000000..27cfe9867dd8 --- /dev/null +++ b/client/src/assets/community/community-calls.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/discord-dark.svg b/client/src/assets/community/discord-dark.svg new file mode 100644 index 000000000000..f836421aa365 --- /dev/null +++ b/client/src/assets/community/discord-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/discord.svg b/client/src/assets/community/discord.svg new file mode 100644 index 000000000000..7a2f39028ace --- /dev/null +++ b/client/src/assets/community/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/people-dark.svg b/client/src/assets/community/people-dark.svg new file mode 100644 index 000000000000..e8e6d6291378 --- /dev/null +++ b/client/src/assets/community/people-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/people.svg b/client/src/assets/community/people.svg new file mode 100644 index 000000000000..ed01e9f1ac51 --- /dev/null +++ b/client/src/assets/community/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/quote-end-dark.svg b/client/src/assets/community/quote-end-dark.svg new file mode 100644 index 000000000000..17111d53c590 --- /dev/null +++ b/client/src/assets/community/quote-end-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/quote-end.svg b/client/src/assets/community/quote-end.svg new file mode 100644 index 000000000000..7157f39d7558 --- /dev/null +++ b/client/src/assets/community/quote-end.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/quote-start-dark.svg b/client/src/assets/community/quote-start-dark.svg new file mode 100644 index 000000000000..3a4f97aae0f5 --- /dev/null +++ b/client/src/assets/community/quote-start-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/quote-start.svg b/client/src/assets/community/quote-start.svg new file mode 100644 index 000000000000..d668df1113f5 --- /dev/null +++ b/client/src/assets/community/quote-start.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/video-bg-dark.svg b/client/src/assets/community/video-bg-dark.svg new file mode 100644 index 000000000000..5e44ae3bfd1b --- /dev/null +++ b/client/src/assets/community/video-bg-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/video-bg.svg b/client/src/assets/community/video-bg.svg new file mode 100644 index 000000000000..28ed1f005ca3 --- /dev/null +++ b/client/src/assets/community/video-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/community/video-thumbnail.png b/client/src/assets/community/video-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe2f78e0601312471078b77dc10909d98f520ec GIT binary patch literal 4418 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 literal 0 HcmV?d00001 diff --git a/client/src/assets/community/youtube-play.svg b/client/src/assets/community/youtube-play.svg new file mode 100644 index 000000000000..f6d7dc4ca7b4 --- /dev/null +++ b/client/src/assets/community/youtube-play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/community/contributor-list.scss b/client/src/community/contributor-list.scss new file mode 100644 index 000000000000..232d5e7a6949 --- /dev/null +++ b/client/src/community/contributor-list.scss @@ -0,0 +1,144 @@ +@use "../ui/vars" as *; + +* { + box-sizing: border-box; +} + +.wrap { + --img-size: 3.75em; + --li-size: calc(var(--img-size) * 2.5); +} + +ul { + --circle-border-size: 0.375em; + align-content: start; + display: grid; + gap: 2rem 1rem; + grid-template-columns: repeat(auto-fit, minmax(var(--li-size), 1fr)); + justify-items: center; + margin: 0; + padding: 0; + + @media (max-width: $screen-md) { + display: flex; + margin-top: 2rem; + overflow-x: auto; + } +} + +li, +a { + align-items: center; + display: flex; + flex-direction: column; + flex-shrink: 0; + text-align: center; +} + +li { + color: var(--community-text-primary); + width: var(--li-size); +} + +img { + background: var(--community-circle-img-border); + border: var(--circle-border-size) var(--community-circle-img-border) solid; + border-radius: 50%; + flex-shrink: 0; + height: var(--img-size); + width: var(--img-size); +} + +a { + color: unset; + font-weight: 500; +} + +svg { + display: none; +} + +@supports (offset-path: ellipse(100% 50% at 100% 50%)) { + @media (min-width: $screen-md) { + .wrap { + container-type: size; + height: 100%; + width: 100%; + } + + .inner { + font-size: min(1rem, 3.5cqmin); + padding: calc(var(--img-size) / 2) calc(var(--li-size) / 2); + } + + ul { + aspect-ratio: 1 / 2; + display: block; + margin-left: auto; + max-height: var(--community-circle-height); + min-height: 0; + min-width: 0; + position: relative; + } + + svg { + display: block; + fill: none; + height: 100%; + overflow: visible; + position: absolute; + stroke: var(--community-circle-img-border); + stroke-width: var(--circle-border-size); + width: 100%; + } + + li { + $outer-elements: 5; + $inner-elements: 3; + + // necessary because Firefox seems to have a bug where the links aren't + // clickable until we force some kind of re-render which an animation does: + animation: community-circle 0.1ms forwards; + offset-anchor: center calc(0.5 * var(--img-size)); + offset-distance: var(--offset-distance); + offset-path: ellipse(100% 50% at 100% 50%); + offset-rotate: 0deg; + + &:nth-of-type(n + #{$outer-elements + 1}) { + --img-size: 5em; + offset-path: ellipse(50% 25% at 100% 50%); + } + + @keyframes community-circle { + from { + offset-distance: calc(var(--offset-distance) - 0.1%); + } + } + + @for $i from 1 through $outer-elements { + &:nth-of-type(#{$i}) { + --offset-distance: #{calc( + 75% - (($i - 1) * 50% / ($outer-elements - 1)) + )}; + } + } + + @for $i from 1 through $inner-elements { + &:nth-of-type(#{$outer-elements + $i}) { + --offset-distance: #{calc( + 75% - (($i - 1) * 50% / ($inner-elements - 1)) + )}; + } + } + } + + .org { + -webkit-box-orient: vertical; + /* stylelint-disable-next-line value-no-vendor-prefix */ + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + overflow: hidden; + } + } +} diff --git a/client/src/community/contributor-list.ts b/client/src/community/contributor-list.ts new file mode 100644 index 000000000000..79cc1d0143b4 --- /dev/null +++ b/client/src/community/contributor-list.ts @@ -0,0 +1,109 @@ +import { LitElement, html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import styles from "./contributor-list.scss?css" with { type: "css" }; + +interface ContributorData { + name: string; + github: string; + org?: string; +} + +@customElement("contributor-list") +export class ContributorList extends LitElement { + _contributors: ContributorData[] = []; + + static properties = { + _contributors: { state: true }, + }; + + static styles = styles; + + constructor() { + super(); + const contributorList = this.querySelector("ul"); + const randomContributors: ContributorData[] = []; + if (contributorList) { + const contributors = [...contributorList.querySelectorAll("li")]; + for (let index = 0; index < 8; index++) { + const contributor = popRandom(contributors); + if (!contributor) { + break; + } + const [name, github, org] = [...contributor.childNodes].map( + (node) => node?.textContent?.trim() || undefined + ); + if (!name || !github) { + index--; + continue; + } + randomContributors.push({ + name, + github, + org, + }); + } + this._contributors = randomContributors; + } + } + + render() { + return html`
+
+
    + + + + + + + + + + ${this._contributors.map(({ name, github, org }) => { + const imgSrc = `https://avatars.githubusercontent.com/${github + ?.split("/") + .slice(-1)}`; + return html`
  • + + + ${name} + + ${org} +
  • `; + })} +
+
+
`; + } +} + +function popRandom(array: Array) { + const i = Math.floor(Math.random() * array.length); + // mutate the array: + return array.splice(i, 1)[0]; +} diff --git a/client/src/community/index.scss b/client/src/community/index.scss index ee270592c610..e82e3e6b4645 100644 --- a/client/src/community/index.scss +++ b/client/src/community/index.scss @@ -1,88 +1,686 @@ @use "../ui/vars" as *; -@use "../ui/base/mdn" as *; +@use "../ui/atoms/button/mixins" as button; -main.contribute { - margin-bottom: 3rem; - width: 100%; +@mixin light-theme { + --community-bg-primary: #fcfcfc; + --community-bg-secondary: #f2f2f5; + --community-text-primary: #000; + --community-text-primary-alt: #000; + --community-text-secondary: #343434; + --community-text-success: #007936; + --community-header-text: #696969; + --community-header-bg: url("../assets/community/people.svg"); + --community-header-stats-bg: #e1f5e5; + --community-box-shadow: 4px -2px 1rem 0 rgba(179, 179, 179, 0.2), + 4px -4px 1rem 0 rgba(179, 179, 179, 0.15); + --community-circle-img-border: #fff; + --community-quote-start: url("../assets/community/quote-start.svg"); + --community-quote-end: url("../assets/community/quote-end.svg"); + --community-video-bg: url("../assets/community/video-bg.svg"); + --community-card-bg: #fff; + --community-card-header-bg: #e1f5e5; + --community-card-border: #e2e2e2; + --community-table-border: #e2e2e2; + --community-table-row: #f9f9fb; + --community-label-bg: #dff7e3; + --community-discord-bg: url("../assets/community/discord.svg"); + --community-calls-bg: url("../assets/community/community-calls.svg"); +} - .stats-container { - background-color: var(--background-primary); - color: var(--text-primary); - margin-bottom: 3rem; - width: 100%; +@mixin dark-theme { + --community-bg-primary: #101010; + --community-bg-secondary: #1b1b1b; + --community-text-primary: #fff; + --community-text-primary-alt: #cdcdcd; + --community-text-secondary: #cdcdcd; + --community-text-success: #8ff295; + --community-header-text: #b3b3b3; + --community-header-bg: url("../assets/community/people-dark.svg"); + --community-header-stats-bg: #394035; + --community-box-shadow: 4px -2px 15px 0 rgba(38, 38, 38, 0.2), + 4px -4px 15px 0 rgba(38, 38, 38, 0.15); + --community-circle-img-border: #4e4e4e; + --community-quote-start: url("../assets/community/quote-start-dark.svg"); + --community-quote-end: url("../assets/community/quote-end-dark.svg"); + --community-video-bg: url("../assets/community/video-bg-dark.svg"); + --community-card-bg: #000; + --community-card-header-bg: #354039; + --community-card-border: #343434; + --community-table-border: #1b1b1b; + --community-table-row: #1b1b1b; + --community-label-bg: #354039; + --community-discord-bg: url("../assets/community/discord-dark.svg"); + --community-calls-bg: url("../assets/community/community-calls-dark.svg"); +} + +.light { + @include light-theme; +} + +.dark { + @include dark-theme; +} + +// OS Default. +:root:not(.light):not(.dark) { + @media (prefers-color-scheme: light) { + @include light-theme; + } + + @media (prefers-color-scheme: dark) { + @include dark-theme; + } +} + +main.community-container { + --community-stats-height: 5.75rem; + --community-section-gap: 5rem; + --max-width: 74rem; + --negative-space: calc( + max(0px, 100vw - var(--max-width)) * -0.5 - var(--gutter) + ); + + background: var(--community-bg-secondary); + color: var(--community-text-secondary); + + h2, + h3, + p { + margin: 0; + } + + h2, + h3 { + color: var(--community-text-primary); + + a { + color: unset; + text-decoration: none; + } + } + + a { + text-decoration: underline; + + &:hover { + text-decoration: none; + } + } + + p + p { + margin-top: 1.5rem; } section { - margin: 0 auto; - max-width: 52rem; - padding: 0 1rem; + margin-left: auto; + margin-right: auto; + max-width: var(--max-width); + padding-left: var(--gutter); + padding-right: var(--gutter); + width: 100%; + } - &.stats-header { - align-items: center; - display: flex; - flex-direction: column; - padding: 0 0.5rem 2rem; + h2 { + font-size: 2rem; + font-weight: 600; + margin-bottom: 1rem; + + @media (max-width: $screen-md) { + font-size: 1.375rem; } + } - h1 { - font-size: 3rem; - margin-top: 8rem; + > header { + background: linear-gradient( + to top, + transparent 0%, + transparent calc(var(--community-stats-height) / 2), + var(--community-bg-primary) calc(var(--community-stats-height) / 2), + var(--community-bg-primary) 100% + ); + + @media (max-width: $screen-md) { + padding-top: 1rem; text-align: center; - @include mify; } - .quote { - &.owd { - background-color: var(--text-link); - color: var(--background-primary); + section { + background-image: var(--community-header-bg); + background-position: bottom calc(var(--community-stats-height) - 1rem) + right; + background-repeat: no-repeat; + background-size: 50%; + padding-top: var(--community-section-gap); + + @media (max-width: $screen-md) { + background-position: top center; + background-size: 80vw; + padding-top: 40vw; + } + } + + h1 { + color: var(--community-text-primary); + font-size: 2.5rem; + margin-bottom: 1rem; + + @media (max-width: $screen-md) { + font-size: 2rem; + } + } + + p { + color: var(--community-header-text); + margin-bottom: 1.5rem; + } + + ul:first-of-type { + display: flex; + flex-wrap: wrap; + gap: 1rem; - .icon { - background-color: var(--background-primary); + @media (max-width: $screen-md) { + justify-content: center; + } + + a { + @include button.primary; + } + + li:last-child a { + --button-color: var(--button-bg); + background: transparent; + } + } + + ul:last-of-type { + background: var(--community-card-bg); + border-radius: 0.5rem; + box-shadow: var(--community-box-shadow); + color: var(--community-text-primary); + display: flex; + gap: 1rem; + justify-content: space-around; + margin-top: var(--community-section-gap); + padding: 1rem; + + @media (max-width: $screen-md) { + flex-wrap: wrap; + margin-top: 2rem; + } + + li { + align-items: baseline; + column-gap: 1rem; + display: flex; + flex-wrap: wrap; + justify-content: center; + min-width: 7.75rem; + overflow-wrap: anywhere; + + @media (max-width: $screen-md) { + align-items: center; + flex: 1; + flex-direction: column; + justify-content: flex-start; + } + + strong { + align-items: center; + background: var(--community-header-stats-bg); + border-radius: 50%; + color: var(--community-text-success); + display: inline-flex; + height: 3.75rem; + justify-content: center; + width: 3.75rem; } } + } + } + + > header + section { + margin-top: 4.56rem; - &.pab { - background-color: var(--background-primary); - color: var(--text-primary); + @media (max-width: $screen-md) { + margin-top: 2rem; + } + } - .icon { - background-color: var(--text-primary); + > section { + --community-circle-height: 57rem; + column-gap: min(5rem, 5vw); + display: grid; + grid-template-columns: 4fr 6fr; + + @media (max-width: $screen-md) { + /* stylelint-disable-next-line length-zero-no-unit */ + --community-circle-height: 0rem; + display: block; + } + + > * { + min-width: 0; + } + + &[aria-labelledby="meet_our_contributors"] { + grid-template-rows: auto auto auto var(--community-circle-height); + margin-top: var(--community-section-gap); + + h2, + .section-content > * { + grid-column: 2; + } + + .section-content { + display: contents; + + > ul { + // contributor buttons + + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 1.5rem; + + @media (max-width: $screen-md) { + justify-content: center; + } + + a { + @include button.primary; + } + + li:last-child a { + --button-color: var(--button-bg); + background: transparent; + } + } + + contributor-list { + grid-column: 1; + grid-row: 1/5; + min-width: 0; + + > ul { + display: none; + } } } } - .stats { - display: grid; - gap: 0.3em; + &[aria-labelledby="contributor_spotlight"] { + margin-top: calc( + var(--community-section-gap) - var(--community-circle-height) + ); - @media (max-width: $screen-md) { - grid-template-columns: 1fr 1fr; + h2, + .section-content { + grid-column: 2; + } + + h2 { + margin-bottom: 0; + } + + .section-content { + position: relative; + + &::after { + background: linear-gradient( + to right, + transparent, + var(--community-bg-secondary) + ); + bottom: 0; + content: ""; + display: block; + pointer-events: none; + position: absolute; + right: 0; + top: 0; + width: 3rem; + + @media (max-width: $screen-md) { + display: none; + } + } + } + + ul { + display: flex; + gap: 2rem; + margin-bottom: 1.5rem; + margin-left: -1rem; + overflow-x: auto; + padding: 2.41rem 1rem; + padding-right: 3rem; + + @media (max-width: $screen-md) { + margin-left: calc(var(--gutter) * -1); + margin-right: calc(var(--gutter) * -1); + padding-right: 1rem; + } + } + + li { + background: var(--community-card-bg); + border-radius: 0.5rem; + box-shadow: var(--community-box-shadow); + display: block; + flex-shrink: 0; + padding: 2.35rem 2.9rem 1.5rem; + width: 20rem; + + a { + display: block; + font-style: italic; + + &::before { + content: "-"; + } + } + } + + blockquote { + border: none; + padding: 0; + position: relative; + + p { + color: var(--community-text-secondary); + font-style: italic; + } + + &::before, + &::after { + background-image: var(--community-quote-start); + background-position: right; + background-repeat: no-repeat; + background-size: contain; + content: ""; + display: block; + height: 2em; + left: -2.37em; + position: absolute; + top: -0.69em; + width: 2em; + } + + &::after { + background-image: var(--community-quote-end); + background-position: left; + bottom: -0.69em; + left: auto; + right: -2.37em; + top: auto; + } + } + } + + &[aria-labelledby="learn_how_to_get_started"] { + grid-template-rows: 1fr auto auto auto 1fr; + margin-top: var(--community-section-gap); + + &::before { + content: ""; + grid-row: 1; + } + + .section-content { + display: contents; } - @media (max-width: $screen-sm) { - grid-template-columns: 1fr; + h2, + .section-content > * { + grid-column: 2; } - @media (min-width: $screen-md) { - grid-template-columns: 1fr 1fr 1fr 1fr; + p:last-child { + background-image: var(--community-video-bg); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + // video link + + grid-column: 1; + grid-row: 1/6; + margin: 0; + + @media (max-width: $screen-md) { + display: flex; + justify-content: center; + margin: 0 auto; + max-width: 25rem; + } + + a { + aspect-ratio: 1; + background-image: url("../assets/community/youtube-play.svg"), + url("../assets/community/video-thumbnail.png"); + background-position: + 43% 50%, + 36% 50%; + background-repeat: no-repeat; + background-size: 13%, 60%; + clip-path: circle(35%); + color: transparent; + display: block; + overflow: hidden; + position: relative; + text-indent: -100rem; + + &:focus-visible::after { + content: ""; + display: block; + height: 30%; + left: calc(45% - 40% / 2); + outline-color: var(--accent-primary); + outline-offset: 1px; + outline-style: auto; + position: absolute; + top: calc(50% - 30% / 2); + width: 40%; + } + + @media (max-width: $screen-md) { + width: 28rem; + } + } + } + } + + &[aria-labelledby="join_us_in_shaping_a_better_web"] { + display: block; + margin-top: var(--community-section-gap); + + p { + margin-bottom: 2.86rem; + } + + ul { + display: grid; + gap: 2rem; + grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); + margin-bottom: var(--community-section-gap); } li { align-items: center; - background: var(--text-link); - color: var(--background-primary); + background: var(--community-card-bg); + border: 1px solid var(--community-card-border); + border-radius: 0.5rem; + box-shadow: var(--community-box-shadow); display: flex; flex-direction: column; - padding: 0.5rem 2rem; + gap: 1.5rem; + justify-content: space-between; + padding: 1.5rem; + text-align: center; + + h3 { + align-self: stretch; + background: var(--community-card-header-bg); + border-radius: 0.5rem 0.5rem 0 0; + font-size: 1.25rem; + font-weight: 500; + margin: -1.5rem; + margin-bottom: 0; + padding: 1.5rem; + } + + p { + margin: 0; + } + + a { + @include button.primary; + } + } + } + + &[aria-labelledby="help_us_fix_open_issues"], + &[aria-labelledby="help_us_fix_open_issues"] ~ section { + // reset layout/colors for the bottom section + + background: var(--community-bg-primary); + color: var(--community-text-primary-alt); + display: block; + max-width: 100%; + padding-bottom: var(--community-section-gap); + + h2, + .section-content, + .issues-table { + margin-left: auto; + margin-right: auto; + max-width: var(--max-width); + padding-left: var(--gutter); + padding-right: var(--gutter); + width: 100%; + } + } + + &[aria-labelledby="help_us_fix_open_issues"] { + padding-top: var(--community-section-gap); + + .issues-table { + margin-top: 1rem; + } + + table { + background: var(--community-card-bg); + border: 1px solid var(--community-table-border); + border-collapse: separate; + border-radius: 0.5rem; + border-spacing: 0; + color: var(--community-text-primary); + } + + th, + td { + border: none; + padding: 1.5rem; + + @media (max-width: $screen-md) { + &:last-of-type { + display: none; + } + } + } + + th { + background: none; + font-size: 1.25rem; + font-weight: 500; + + @media (max-width: $screen-md) { + display: none; + } + } + + tbody tr:nth-child(odd) { + background: var(--community-table-row); + } + + td > div { + align-items: baseline; + display: flex; + flex-wrap: wrap; + gap: 1rem 1.5rem; + } + + .label { + background: var(--community-label-bg); + border-radius: 0.25rem; + color: var(--community-text-success); + font-weight: 500; + padding: 0.5rem 1rem; + } + } + + &[aria-labelledby="join_the_conversation"] { + ul { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + margin-bottom: 1.5rem; + } + + li { + align-items: flex-start; + background: var(--community-card-bg); + border: 1px solid var(--community-table-border); + border-radius: 0.5rem; + box-shadow: var(--community-box-shadow); + display: flex; + flex: 1; + flex-direction: column; + gap: 1.5rem; + justify-content: space-between; + min-width: min(30rem, 100%); + padding: 2rem; + padding-left: 8.5rem; + position: relative; + + @media (max-width: $screen-md) { + padding-left: 5.5rem; + } + + &::before { + background-image: var(--community-discord-bg); + background-repeat: no-repeat; + background-size: contain; + content: ""; + height: 100%; + left: 2rem; + position: absolute; + width: 5rem; + + @media (max-width: $screen-md) { + width: 2rem; + } + } + + &:last-of-type::before { + background-image: var(--community-calls-bg); + } + + h3 { + font-size: 1.75rem; + font-weight: 600; + + @media (max-width: $screen-md) { + font-size: 1.25rem; + } + } - .number { - font-size: 3rem; + a { + @include button.primary; } - .legend { - font-size: 0.8rem; - max-width: 5rem; - text-align: center; + p { + margin: 0; } } } diff --git a/client/src/community/index.tsx b/client/src/community/index.tsx index f8b335fee7c9..26b9e6526f81 100644 --- a/client/src/community/index.tsx +++ b/client/src/community/index.tsx @@ -1,163 +1,188 @@ -import { Quote } from "../ui/molecules/quote"; import "./index.scss"; +import { HydrationData } from "../../../libs/types/hydration"; +import { useEffect, useMemo } from "react"; +import { Section } from "../../../libs/types/document"; +import useSWR, { SWRConfig } from "swr"; +import { HTTPError } from "../document"; +import { WRITER_MODE } from "../env"; +import { Prose } from "../document/ingredients/prose"; +import { SWRLocalStorageCache } from "../utils"; +import { useIsServer } from "../hooks"; -const STATS = [ - { id: 1, number: "2005", legend: "year founded" }, - { id: 2, number: "45k", legend: "total contributors" }, - { id: 3, number: "200", legend: "commits per week" }, - { id: 4, number: ">80M", legend: "page views per month" }, -]; +interface CommunityDoc { + title: string; + sections: Section[]; +} + +export function Community(appProps: HydrationData) { + const doc = useCommunityDoc(appProps); + + useEffect(() => { + import("./contributor-list"); + }, []); + + return ( + new SWRLocalStorageCache("community") }} + > +
+ { + if (i === 0) { + return ( +
+ ); + } else if (section.value.id === "help_us_fix_open_issues") { + return ; + } + return null; + }} + /> +
+
+ ); +} + +function useCommunityDoc( + appProps?: HydrationData +): CommunityDoc | undefined { + const { data } = useSWR( + "index.json", + async () => { + const url = new URL( + `${window.location.pathname.replace(/\/$/, "")}/index.json`, + window.location.origin + ).toString(); + const response = await fetch(url); + + if (!response.ok) { + switch (response.status) { + case 404: + throw new HTTPError(response.status, url, "Page not found"); + } + + const text = await response.text(); + throw new HTTPError(response.status, url, text); + } + + return (await response.json())?.hyData; + }, + { + fallbackData: appProps?.hyData, + revalidateOnFocus: WRITER_MODE, + revalidateOnMount: true, + } + ); + const doc: CommunityDoc | undefined = data || appProps?.hyData || undefined; + return doc; +} + +function RenderCommunityBody({ + doc, + renderer = () => null, +}: { + doc?: CommunityDoc; + renderer?: (section: Section, i: number) => null | JSX.Element; +}) { + return doc?.sections.map((section, i) => { + return ( + renderer(section, i) || ( + + ) + ); + }); +} -export function Contribute() { +function Header({ section, h1 }: { section: any; h1?: string }) { + const html = useMemo( + () => ({ __html: section.value?.content }), + [section.value?.content] + ); + return ( +
+
+
+ ); +} + +function Issues({ section }: { section: any }) { + const html = useMemo( + () => ({ __html: section.value?.content }), + [section.value?.content] + ); + const isServer = useIsServer(); + const LABELS = ["good first issue", "accepting PR"]; + const { data } = useSWR( + !isServer && + `is:open is:issue repo:mdn/content repo:mdn/translated-content repo:mdn/yari label:"good first issue","accepting PR" sort:created-desc no:assignee is:public`, + async (query) => { + const url = new URL("https://api.github.com/search/issues"); + url.searchParams.append("per_page", "5"); + url.searchParams.append("q", query); + const res = await fetch(url); + if (!res.ok) { + throw new Error(res.status.toString()); + } + return await res.json(); + }, + { + revalidateOnFocus: false, + } + ); return ( -
-
-
-

Community for a better web

-
    - {STATS.map((s) => ( -
  • - {s.number} - {s.legend} -
  • +
    +

    {section.value.title}

    +
    +
    + + + + + + + + + {data?.items.map(({ html_url, title, labels, repository_url }) => ( + + + + ))} - - + +
    TitleRepository
    +
    + + {title} + + {labels.map(({ name }) => + LABELS.includes(name) ? ( + + {name} + + ) : null + )} +
    +
    + + {repository_url.replace( + "https://api.github.com/repos/", + "" + )} + +
    -
    -

    Our volunteer community

    -

    - The power of MDN lies in its vast, vital community of active users and - contributors. Since 2005, approximately 45,000 contributors have - created the documentation we know and love. Together, contributors - have created over 45,000 documents that make up an up-to-date, - comprehensive, and free resource for web developers around the world. - In addition to English-language articles,{" "} - - over 35 volunteers lead translation and localization efforts - {" "} - for Chinese, French, Japanese, Korean, Portuguese, Russian, and - Spanish. With over 200 commits per week, the culture of active - contribution is going strong. And you can be a part of it. -

    -

    Our partners

    -

    The Product Advisory Board

    -

    - MDN collaborates with partners from across the industry, including - standards bodies, browser vendors, and other industry leaders. Since - 2017, these collaborators are formally represented through the{" "} - - Product Advisory Board - {" "} - (PAB). MDN is an influential resource and the PAB helps ensure that - MDN’s influence puts the web first, not any one vendor or - organization, and respects the needs of web developers across the - industry. Each quarter, the PAB meets to discuss problems, prioritize - content creation, and make connections for future collaborations. -

    - - MDN has a unique place right now as a vendor-neutral and authoritative - documentation and information resource for web developers. The MDN PAB - has helped to bring feedback from the wider web community (including - standards engineers, web browser makers and open source developers) - into MDN to help keep it strong. As a member of the web community and - a fan of MDN it’s been great to be a part of. - -

    Open Web Docs

    -

    - - Open Web Docs - {" "} - (OWD), an independent open source organization, is one of the most - productive contributors to MDN Web Docs. OWD contributes as part of{" "} - - their mission - {" "} - to support “web platform documentation for the benefit of web - developers & designers worldwide.” The team at OWD has led or - contributed to many projects to improve documentation on MDN. They're - an invaluable partner in the day-to-day work of making MDN. Read more - about OWD’s activities in their{" "} - - 2022 Impact and Transparency Report - {" "} - and get continuous updates on their{" "} - - Mastodon - {" "} - account. -

    - - Open Web Docs strongly believes in MDN as critical infrastructure for - the web platform. As a vendor-neutral organization, we are supporting - MDN with an independent editorial voice and with the needs of the - global community of web developers and designers in mind. - -

    Licensing

    -

    - MDN's resources are entirely available under various open source - licenses. Detailed information on licensing for reuse of MDN content, - especially regarding copyrights and attribution, can be found{" "} - - here. - -

    -

    How to contribute

    -

    - We are an open community of developers building resources for a better - web, regardless of brand, browser, or platform. Anyone can contribute, - and each person who does makes us stronger. Together we can continue - to drive innovation on the web to serve the greater good. It starts - here, with you. Join us! -

    -

    - No matter your specific level of expertise, individual strengths, and - interests in coding or writing, there are many ways for you to get - involved and tackle important documentation tasks. -

    -

    - Are you ready to become an active part of the MDN community but not - sure where to begin? We've got you covered. See our step-by-step - directions for{" "} - - making your first contribution to MDN on GitHub - - . -

    -
    -
+ ); } diff --git a/client/src/react-app.d.ts b/client/src/react-app.d.ts index 9c88c30b8449..23b53c872707 100644 --- a/client/src/react-app.d.ts +++ b/client/src/react-app.d.ts @@ -92,6 +92,14 @@ declare module "*?css" { export default sheet; } +// once https://github.com/microsoft/TypeScript/issues/46135 is fixed +// we'll be able to do something like: +// declare module '*' with {type: 'css'} { +declare module "*?css" { + const sheet: CSSStyleSheet; + export default sheet; +} + declare module "*.module.css" { const classes: { readonly [key: string]: string }; export default classes; diff --git a/client/src/ui/atoms/button/_mixins.scss b/client/src/ui/atoms/button/_mixins.scss new file mode 100644 index 000000000000..d6c6d84c448f --- /dev/null +++ b/client/src/ui/atoms/button/_mixins.scss @@ -0,0 +1,49 @@ +@mixin _button { + --button-font: var(--type-emphasis-m); + --button-padding: 0.43rem 1rem; + --button-radius: var(--elem-radius, 0.25rem); + + background-color: var(--button-bg); + border: 1px solid var(--button-border-color); + border-radius: var(--button-radius); + color: var(--button-color); + display: inline-block; + font: var(--button-font); + letter-spacing: normal; + padding: var(--button-padding); + text-align: center; + text-decoration: none; + + &.external:after { + display: none; + } + + &:hover { + --button-border-color: var(--button-bg-hover); + --button-bg: var(--button-bg-hover); + } + + &:active { + --button-bg: var(--button-bg-active); + } +} + +@mixin primary { + --button-bg: var(--button-primary-default); + --button-bg-hover: var(--button-primary-hover); + --button-bg-active: var(--button-primary-active); + --button-border-color: var(--button-primary-default); + --button-color: var(--background-primary); + + @include _button; +} + +@mixin secondary { + --button-bg: var(--button-secondary-default); + --button-bg-hover: var(--button-secondary-hover); + --button-bg-active: var(--button-secondary-active); + --button-border-color: var(--border-primary); + --button-color: var(--text-secondary); + + @include _button; +} diff --git a/client/src/utils.ts b/client/src/utils.ts index c1917d58d406..63bb0467a697 100644 --- a/client/src/utils.ts +++ b/client/src/utils.ts @@ -174,3 +174,47 @@ export function setCookieValue( export function deleteCookie(name: string) { setCookieValue(name, "", { expires: new Date(0) }); } + +export class SWRLocalStorageCache { + #key: string; + #data: Map; + + #writeToLocalStorage() { + const serialized = JSON.stringify([...this.#data]); + localStorage.setItem(this.#key, serialized); + } + + constructor(key: string) { + this.#key = `cache.${key}`; + try { + const serialized = localStorage.getItem(this.#key); + this.#data = new Map(JSON.parse(serialized || "[]")); + } catch { + this.#data = new Map(); + if (typeof localStorage === "undefined") { + // we're on the server + return; + } + console.warn(`Can't read data from ${this.#key}, resetting the cache`); + this.#writeToLocalStorage(); + } + } + + get(key: string): Data | undefined { + return this.#data.get(key); + } + + set(key: string, value: Data): void { + this.#data.set(key, value); + this.#writeToLocalStorage(); + } + + delete(key: string): void { + this.#data.delete(key); + this.#writeToLocalStorage(); + } + + keys(): IterableIterator { + return this.#data.keys(); + } +} diff --git a/copy/community/community.md b/copy/community/community.md new file mode 100644 index 000000000000..1f851e99e660 --- /dev/null +++ b/copy/community/community.md @@ -0,0 +1,156 @@ +# MDN Community + +Where web enthusiasts learn, collaborate, and create + +- [Start contributing](#join_us_in_shaping_a_better_web) +- [Join MDN Discord](https://mdn.dev/discord) + + + +- **45K+** Total contributors +- **200+** Weekly commits +- **7** Language communities + +## MDN's community powers the web + +MDN’s strength comes from the passion and dedication of our global community. +Since our founding in 2005, we’ve grown into a thriving network. Together, we’ve +created a comprehensive, open, and free resource that serves web developers +across the globe. With volunteers leading translation efforts in +[7 languages](/en-US/docs/MDN/Community/Contributing/Translated_content), we’re +truly international. + +## Meet our contributors + +We are an open-source community dedicated to building resources for a better +web. Our diverse contributors, including developers, technical writers, +students, educators, designers, and more, come from various backgrounds and +platforms. Anyone can contribute, and each contribution strengthens our +community, driving innovation and improving this vital resource for developers +worldwide. + +- [Join us](/en-US/docs/MDN/Community/Contributing/Getting_started) +- [View all contributors](https://github.com/mdn/content/graphs/contributors) + + + +- Jason Lam, 林家祥 https://github.com/JasonLamv-t Grantit +- Nicolò Ribaudo https://github.com/nicolo-ribaudo Igalia +- Joshua Chen https://github.com/Josh-Cena +- Kimchanmin https://github.com/c17an SK Planet +- Gibbeum Yoon https://github.com/givvemee +- Jongha Kim https://github.com/wisedog +- Qizhe ZHANG https://github.com/PassionPenguin +- Artem Shibakov https://github.com/saionaro Bright Data +- HoChan Lee https://github.com/hochan222 11STREET +- Sangchul Lee https://github.com/1ilsang WoowaBros +- Park Sunhee https://github.com/sunhpark42 WoowaBros +- FU CHUNHUI https://github.com/fuchunhui Baidu +- Estelle Weyl https://github.com/estelle Open Web Docs +- Yitao Yin https://github.com/yin1999 Northwestern Polytechnical University +- Florian Scholz https://github.com/Elchi3 Open Web Docs + + + +## Contributor spotlight + +- > There are many other things I like about MDN: the openness of its + > governance, the respect for contributors' work, the professional + > conversations, and the always timely reviews. MDN has consistently + > demonstrated the ideal form of an open-source project. + + [Joshua Chen](/en-US/community/spotlight/joshua-chen) (MDN contributor) + +- > MDN Web Docs has the most up-to-date and accurate information and the + > content is presented in an easy-to-understand manner. I also like that it's + > available in many languages (very important!). + + [Yuji](/en-US/community/spotlight/yuji) (MDN contributor) + +- > There are millions of web developers in China, and many of them begin their + > developer journey at MDN Web Docs. Contributing to MDN Web Docs is an + > excellent way to help people who are starting out. + + [YiTao Yin](/en-US/community/spotlight/yitao-yin) (MDN contributor) + +If you wish to be a part of the featured contributors here, +[let us know](https://forms.gle/7yk13Nn1WRLnuLvy5).
If you’re featured here +and would like to opt-out, +[please file an issue on GitHub](https://github.com/mdn/content/issues/new?assignees=&labels=needs+triage&projects=&template=content-bug.yml). + +## Learn how to get started + +We collaborate on [GitHub](https://github.com/mdn), our project's home, on +various tasks such as writing and improving documentation, fixing bugs, and +providing review feedback. It starts here, with you. Want to start right away, +but not sure how? Follow +[our guide](https://github.com/mdn/content/blob/main/CONTRIBUTING.md#mdn-web-docs-contribution-guide) +to make your first contribution. + +Watch this video on +[how to get started with contributing to MDN](https://www.youtube.com/watch?v=Xnhnu7PViQE). + +[Video from the community team on contributing to MDN](https://www.youtube.com/watch?v=Xnhnu7PViQE) + +## Join us in shaping a better web + +Become part of this globally cherished group that’s dedicated to documenting web +technologies. Whether you’re an expert or a beginner, there’s a place for you in +our inclusive community. Check out some of the ways you can contribute and +engage. + +- ### Fix issues + + Submit pull requests to fix reported issues. + + [Squash bugs](https://github.com/mdn/content/issues) + +- ### Improve content + + Fix inaccuracies and fill in missing information. + + [Start writing](https://github.com/mdn/content/#readme) + +- ### Localize content + + Participate in translating content into one of our supported languages. + + [Find your locale](/en-US/docs/MDN/Community/Contributing/Translated_content#active_locales) + +- ### Answer questions + + Share your knowledge and expertise and guide fellow learners. + + [Help on Discord](https://mdn.dev/discord) + +## Help us fix open issues + +New to MDN or open-source projects? Tackle our beginner-friendly issues to help +improve MDN. + +## Join the conversation + +- ### Chat with us on Discord + + Connect with the community. Engage with domain experts. Help others learn. + + [Join MDN Discord](https://mdn.dev/discord) + +- ### Join our Community Call + + Every month, get exclusive updates from the MDN team. Share your ideas and + contributions. + + [RSVP to the next community call](https://github.com/mdn/community-meetings?tab=readme-ov-file#mdn-community-meetings) + +While working in Mozilla spaces and communities, please adhere to the +[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/), +which promote respect, inclusion, and a harassment-free environment for all +community members. + +## Licensing and reuse + +MDN's resources are freely available under various open-source licenses. For +detailed information on reusing MDN content, check out our +[Attribution and Copyright Licensing](https://developer.mozilla.org/en-US/docs/MDN/About#using_mdn_web_docs_content) +page. diff --git a/deployer/src/deployer/search/__init__.py b/deployer/src/deployer/search/__init__.py index 06da5e8ebd79..dfdb286dbe7b 100644 --- a/deployer/src/deployer/search/__init__.py +++ b/deployer/src/deployer/search/__init__.py @@ -217,8 +217,8 @@ def to_search(file, _index=None): return doc = data["doc"] - if doc["mdn_url"].startswith("/en-US/curriculum/"): - # Skip curriculum content for now. + if "/docs/" not in doc["mdn_url"]: + # Skip non docs content for now. return locale, slug = doc["mdn_url"].split("/docs/", 1) diff --git a/libs/constants/index.js b/libs/constants/index.js index a6fd9289b8ad..a2ac0bbcb19b 100644 --- a/libs/constants/index.js +++ b/libs/constants/index.js @@ -115,6 +115,9 @@ export const CSP_DIRECTIVES = { "https://observatory-api.mdn.allizom.net", "https://observatory-api.mdn.mozilla.net", + // Community + "https://api.github.com/search/issues", + "stats.g.doubleclick.net", "https://api.stripe.com", ], @@ -140,6 +143,7 @@ export const CSP_DIRECTIVES = { ], "img-src": [ "'self'", + "data:", // Avatars "*.githubusercontent.com", diff --git a/ssr/react-app.d.ts b/ssr/react-app.d.ts index 2a9a1edb7dbe..4822f4144835 100644 --- a/ssr/react-app.d.ts +++ b/ssr/react-app.d.ts @@ -88,6 +88,14 @@ declare module "*?css" { export default sheet; } +// once https://github.com/microsoft/TypeScript/issues/46135 is fixed +// we'll be able to do something like: +// declare module '*' with {type: 'css'} { +declare module "*?css" { + const sheet: CSSStyleSheet; + export default sheet; +} + declare module "*.module.css" { const classes: { readonly [key: string]: string }; export default classes;