diff --git a/build/spas.js b/build/spas.js index 858a406f3b32..fc0b44c1fff5 100644 --- a/build/spas.js +++ b/build/spas.js @@ -139,47 +139,68 @@ async function buildSPAs(options) { } } - // Building the MDN Plus feature pages. - const featuresDir = path.join(__dirname, "../copy/plus/features"); - for (const page of fs.readdirSync(featuresDir)) { - const locale = "en-us"; - const feature = page.split(".")[0]; - const markdown = fs.readFileSync(path.join(featuresDir, page), "utf8"); + // Building the MDN Plus pages. - const frontMatter = frontmatter(markdown); - const rawHTML = await m2h(frontMatter.body, locale); + /** + * + * @param {string} dirpath + * @param {string} slug + * @param {string} title + */ + async function buildStaticPages(dirpath, slug, title = "MDN") { + for (const file of fs.readdirSync(dirpath)) { + const filepath = path.join(dirpath, file); + const stat = fs.lstatSync(filepath); + const page = file.split(".")[0]; - const { sections, toc } = splitSections(rawHTML); + if (stat.isDirectory()) { + await buildStaticPages(filepath, `${slug}/${page}`, title); + return; + } - const url = `/${locale}/plus/feature/${feature}`; - const hyData = { - id: feature, - ...frontMatter.attributes, - sections, - toc, - }; - const context = { - hyData, - pageTitle: `${frontMatter.attributes.title || ""} | MDN Plus`, - }; - const html = renderHTML(url, context); - const outPath = path.join( - BUILD_OUT_ROOT, - locale, - "plus", - "feature", - feature - ); - fs.mkdirSync(outPath, { recursive: true }); - const filePath = path.join(outPath, "index.html"); - fs.writeFileSync(filePath, html); - buildCount++; - if (options.verbose) { - console.log("Wrote", filePath); + const locale = "en-us"; + const markdown = fs.readFileSync(filepath, "utf8"); + + const frontMatter = frontmatter(markdown); + const rawHTML = await m2h(frontMatter.body, locale); + + const { sections, toc } = splitSections(rawHTML); + + const url = `/${locale}/${slug}/${page}`; + const hyData = { + id: page, + ...frontMatter.attributes, + sections, + toc, + }; + const context = { + hyData, + pageTitle: `${frontMatter.attributes.title || ""} | ${title}`, + }; + + const html = renderHTML(url, context); + const outPath = path.join( + BUILD_OUT_ROOT, + locale, + ...slug.split("/"), + page + ); + fs.mkdirSync(outPath, { recursive: true }); + const filePath = path.join(outPath, "index.html"); + fs.writeFileSync(filePath, html); + buildCount++; + if (options.verbose) { + console.log("Wrote", filePath); + } + const filePathContext = path.join(outPath, "index.json"); + fs.writeFileSync(filePathContext, JSON.stringify(context)); } - const filePathContext = path.join(outPath, "index.json"); - fs.writeFileSync(filePathContext, JSON.stringify(context)); } + await buildStaticPages( + path.join(__dirname, "../copy/plus"), + "plus", + "MDN Plus" + ); // Build all the home pages in all locales. // Fetch merged content PRs for the latest contribution section. diff --git a/client/src/plus/feature-highlight/index.scss b/client/src/homepage/static-page/index.scss similarity index 96% rename from client/src/plus/feature-highlight/index.scss rename to client/src/homepage/static-page/index.scss index 7042324cfa75..7865c791e9c9 100644 --- a/client/src/plus/feature-highlight/index.scss +++ b/client/src/homepage/static-page/index.scss @@ -1,6 +1,6 @@ @use "../../ui/vars" as *; -.feature-highlight { +.static-page { width: 100%; padding: 2rem 1rem; @@ -19,7 +19,7 @@ } gap: 2rem; - .ft-sidebar { + .static-sidebar { @media screen and (min-width: $screen-md) { position: sticky; } @@ -34,7 +34,7 @@ } } - .ft-content { + .static-content { em { text-decoration: underline; font-style: normal; diff --git a/client/src/homepage/static-page/index.tsx b/client/src/homepage/static-page/index.tsx new file mode 100644 index 000000000000..006b186de85b --- /dev/null +++ b/client/src/homepage/static-page/index.tsx @@ -0,0 +1,67 @@ +import React, { ReactElement } from "react"; +import useSWR from "swr"; +import { CRUD_MODE } from "../../constants"; +import { TOC } from "../../document/organisms/toc"; +import { PageNotFound } from "../../page-not-found"; +import "./index.scss"; + +interface StaticPageProps { + locale: string; + slug: string; + initialData?: any; + title?: string; + sidebarHeader?: ReactElement; +} + +function StaticPage({ + locale, + slug, + initialData = undefined, + title = "MDN", + sidebarHeader = <>, +}: StaticPageProps) { + const baseURL = `/${locale.toLowerCase()}/${slug}`; + const featureJSONUrl = `${baseURL}/index.json`; + const { data: { hyData } = {} } = useSWR( + featureJSONUrl, + async (url) => { + const response = await fetch(url); + if (!response.ok) { + const text = await response.text(); + throw new Error(`${response.status} on ${url}: ${text}`); + } + return await response.json(); + }, + { + initialData, + revalidateOnFocus: CRUD_MODE, + } + ); + React.useEffect(() => { + window.scrollTo(0, 0); + }, []); + React.useEffect(() => { + const pageTitle = hyData && `${hyData.title} | ${title}`; + document.title = pageTitle; + }, [hyData, title]); + + if (!hyData) { + return ; + } + + return ( +
+
+ {sidebarHeader || null} + {(hyData.toc?.length && ) || null} +
+
+ {hyData.sections.map((section) => ( +
+ ))} +
+
+ ); +} + +export default StaticPage; diff --git a/client/src/plus/feature-highlight/index.tsx b/client/src/plus/feature-highlight/index.tsx index 436d3e034026..c2bf7dc5f399 100644 --- a/client/src/plus/feature-highlight/index.tsx +++ b/client/src/plus/feature-highlight/index.tsx @@ -1,55 +1,16 @@ -import React from "react"; import { useParams } from "react-router-dom"; -import useSWR from "swr"; -import { CRUD_MODE } from "../../constants"; -import { TOC } from "../../document/organisms/toc"; -import { Button } from "../../ui/atoms/button"; -import "./index.scss"; +import StaticPlusPage from "../static-plus-page"; function FeatureHighlight(props) { - const { feature, locale } = useParams(); + const { feature } = useParams(); - const baseURL = `/${locale.toLowerCase()}/plus/feature/${feature}`; - const featureJSONUrl = `${baseURL}/index.json`; - const { data: { hyData } = {} } = useSWR( - featureJSONUrl, - async (url) => { - const response = await fetch(url); - if (!response.ok) { - const text = await response.text(); - throw new Error(`${response.status} on ${url}: ${text}`); - } - return await response.json(); - }, - { - initialData: props.hyData ? props : undefined, - revalidateOnFocus: CRUD_MODE, - } - ); - React.useEffect(() => { - window.scrollTo(0, 0); - }, []); - React.useEffect(() => { - const pageTitle = hyData && `${hyData.title} | MDN Plus`; - document.title = pageTitle; - }, [hyData]); - - console.log(props); - if (!hyData) { - return <>NaN; - } return ( -
-
- - {(hyData.toc?.length && ) || null} -
-
- {hyData.sections.map((section) => ( -
- ))} -
-
+ ); } diff --git a/client/src/plus/index.tsx b/client/src/plus/index.tsx index 7cd07f688857..4cf07f8ee046 100644 --- a/client/src/plus/index.tsx +++ b/client/src/plus/index.tsx @@ -9,6 +9,7 @@ import Notifications from "./notifications"; const OfferOverview = React.lazy(() => import("./offer-overview")); const Collections = React.lazy(() => import("./collections")); const FeatureHighlight = React.lazy(() => import("./feature-highlight")); +const StaticPlusPage = React.lazy(() => import("./static-plus-page")); export function Plus({ pageTitle, ...props }: { pageTitle?: string }) { const defaultPageTitle = "MDN Plus"; @@ -55,13 +56,21 @@ export function Plus({ pageTitle, ...props }: { pageTitle?: string }) { } /> } /> + + + + } + /> } /> ); diff --git a/client/src/plus/offer-overview/offer-overview-feature/index.tsx b/client/src/plus/offer-overview/offer-overview-feature/index.tsx index ab83993e84b6..454a366d0e8b 100644 --- a/client/src/plus/offer-overview/offer-overview-feature/index.tsx +++ b/client/src/plus/offer-overview/offer-overview-feature/index.tsx @@ -38,7 +38,7 @@ export default function OfferOverviewFeatures() { get customizable notifications when documentation changes, CSS features launch, and APIs ship.

- + - + - + ); diff --git a/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx b/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx index 07bae5c91c9b..f5bcd23a8bc2 100644 --- a/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx +++ b/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx @@ -174,7 +174,14 @@ function OfferDetails({ {offerDetails.upgradeCta} - )) || Not available} + )) || ( + + Not available + + )}

{offerDetails.includes}

    {offerDetails.features.map(([href, text]) => ( diff --git a/client/src/plus/static-plus-page/index.tsx b/client/src/plus/static-plus-page/index.tsx new file mode 100644 index 000000000000..19d3e80152ac --- /dev/null +++ b/client/src/plus/static-plus-page/index.tsx @@ -0,0 +1,23 @@ +import { useParams } from "react-router-dom"; +import StaticPage from "../../homepage/static-page"; +import { Button } from "../../ui/atoms/button"; + +function StaticPlusPage({ slug, ...props }) { + const { locale } = useParams(); + + return ( + ← Back to Overview + ), + initialData: props.hyData ? props : undefined, + }} + /> + ); +} + +export default StaticPlusPage; diff --git a/client/src/ui/molecules/plus-menu/index.tsx b/client/src/ui/molecules/plus-menu/index.tsx index c5c2d1128bcd..fffab21955ce 100644 --- a/client/src/ui/molecules/plus-menu/index.tsx +++ b/client/src/ui/molecules/plus-menu/index.tsx @@ -12,38 +12,50 @@ export const PlusMenu = ({ visibleSubMenuId, toggleMenu }) => { const locale = useLocale(); const userData = useUserData(); + const isAuthenticated = userData && userData.isAuthenticated; + const plusMenu = { label: "MDN Plus", id: "mdn-plus", items: [ + ...(isAuthenticated + ? [ + { + description: "More MDN. Your MDN.", + hasIcon: true, + extraClasses: "mobile-only", + iconClasses: "submenu-icon", + label: "Overview", + url: `/${locale}/plus`, + }, + { + description: "Your saved articles from across MDN", + hasIcon: true, + iconClasses: "submenu-icon bookmarks-icon", + label: "Collections", + url: `/${locale}/plus/collection`, + }, + { + description: "Updates from the pages you’re watching", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Notifications", + url: `/${locale}/plus/notifications`, + }, + ] + : []), { - description: "More MDN. Your MDN.", + description: "Frequently asked questions about MDN Plus", hasIcon: true, - extraClasses: "mobile-only", iconClasses: "submenu-icon", - label: "Overview", - url: `/${locale}/plus`, - }, - { - description: "Your saved articles from across MDN", - hasIcon: true, - iconClasses: "submenu-icon bookmarks-icon", - label: "Collections", - url: `/${locale}/plus/collection`, - }, - { - description: "Updates from the pages you’re watching", - hasIcon: true, - iconClasses: "submenu-icon", - label: "Notifications", - url: `/${locale}/plus/notifications`, + label: "FAQ", + url: `/${locale}/plus/faq`, }, ], }; const isOpen = visibleSubMenuId === plusMenu.id; - const isAuthenticated = userData && userData.isAuthenticated; - return isAuthenticated ? ( + return (
  • - ) : ( -
  • - - MDN Plus - -
  • ); }; diff --git a/copy/plus/faq.md b/copy/plus/faq.md new file mode 100644 index 000000000000..1d4bf50e4cc2 --- /dev/null +++ b/copy/plus/faq.md @@ -0,0 +1,98 @@ +--- +title: FAQ +--- + +# Frequently asked questions + +## What is MDN Plus? + +MDN Plus is a premium subscription service launched in March 2022 by Mozilla. +The service allows users to customize their MDN Web Docs experience through +premium features such as [Notifications](/en-US/plus/features/notifications), +[Collections](/en-US/plus/features/collections) and +[MDN Offline](/en-US/plus/features/offline). + +## Why are we working on premium features on MDN? + +The extensive research we have done in 2020 and 2021 showed us that MDN users +would appreciate a customized experience on MDN. We’ve got information on what +you would find useful and what you would be interested in. All the premium +features we propose today reflect that feedback. MDN Plus is an initial step +towards making the experience on the site more interactive and helpful for our +users. + +## How much does MDN Plus cost? + +A three-tiered pricing model has been put in place in order to try and +accommodate our users’ needs as much as possible: + +- _MDN Core_ - A free plan, for those ones who want to try out a limited + version of the premium features. +- _MDN Plus 5_ - A 5$/month/50$/year plan that offers unlimited access to the + premium features included in MDN Plus. +- _MDN Supporter 10_ - A 10$/month/100$/year plan for users who want to + support MDN with a higher amount. On top of the MDN Plus premium features, + MDN supporters will be able to contribute and shape the product moving + forward, by having early access to premium features and a direct feedback + channel with the MDN team. + +Subscribing for a yearly plan activates a 20% discount for all the paid options. + +## Can I upgrade/downgrade my plan? + +Currently, you can only upgrade your plan. For getting a downgrade, please +cancel your current subscription first and then activate the new one. + +## What is happening with the existing MDN Web Docs? + +Nothing. We will continue to develop & maintain our web documentation that will +remain free and accessible for everyone. There will be no change there. Even +more, we believe that MDN Web Docs will benefit from MDN Plus, as we plan to +reinvest part of the gains from MDN Plus and improve our documentation as well +as the overall user experience on the website. + +## Are we violating any OS license obligation by adding a paid product to MDN? + +MDN content is made available under a CC BY-SA 2.5 license. That license +doesn't preclude Mozilla (or other users of MDN content) from having a paid +product. MDN Plus adds premium features like notifications and collections on +top of the free content. Regular users can still access and reuse MDN content +under the Creative Commons license. + +## Where will the money from MDN Plus go? + +Since its beginning in 2005, MDN Web Docs has been a project hosted and +provided by Mozilla. Mozilla covers the cost of infrastructure, development +and maintenance of the MDN platform, including a team of engineers and its +own team of dedicated writers. + +Mozilla wants MDN Plus to help ensure that MDN's open source content continues +to be supported into the future. MDN Plus has been built only with Mozilla +resources, and any revenue generated by MDN Plus will stay within Mozilla. +We are looking into ways to reinvest some of these additional funds into open +source projects contributing to MDN but it is still in the early stages. + +## Does the launch of MDN Plus impact the relationship with partners like OWD? + +The existence of a new subscription model will not detract from MDN's current +free Web Docs offering in any way. The current experience of accessing web +documentation will not change for users who do not wish to sign up for a +premium subscription. Open Web Docs (OWD) and Mozilla will continue to work +closely together on MDN for the best possible web platform documentation for +everyone. For more information about how our organizations work together, +please check [this article](https://hacks.mozilla.org/2022/03/mozilla-and-open-web-docs-working-together-on-mdn/). + +## What regions will MDN Plus be available in? + +Today, MDN Plus is available in the US and Canada. In the coming months, we are +planning to roll out to other countries including France, Germany, Italy, +Spain, Belgium, Austria, the Netherlands, Ireland, United Kingdom, +Switzerland, Malaysia, New Zealand and Singapore. + +## I have an idea for MDN Plus, who should I contact? + +In case you have an idea you would like to share about MDN Plus, you can add +your suggestions to our [MDN-feedback](https://github.com/mdn/MDN-feedback) repo. + +If a subscriber, you can also leave us feedback by accessing the ‘Feedback’ option +in the user menu.