feat: multiple playground choices (#5207)

* poc of using netlify functions for playground redirections

* push

* cleanup

* restore files

* fix netlify functions?

* fix netlify functions?

* implement serverless functions for playgrounds with persistent cookie

* move new.docusaurus.io to monorepo packages

* move new.docusaurus.io to monorepo packages

* lockfile

* push

* catch-all redirect

* Translate/Interpolate: add better error message if not used correctly

* Add /docs/playground page

* Add some additional doc
This commit is contained in:
Sébastien Lorber 2021-07-22 21:10:36 +02:00 committed by GitHub
parent 2b5fd2b490
commit 700a82aefe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1426 additions and 129 deletions

View file

@ -1,9 +1,9 @@
# new.docusaurus.io
This is a Netlify deployment that only redirects to the official CodeSandbox template.
This is a Netlify deployment to handle the Docusaurus playgrounds shortcut [new.docusaurus.io](https://new.docusaurus.io).
https://codesandbox.io/s/github/facebook/docusaurus/tree/master/examples/classic
We use serverless functions because we want to persist the latest choice of the user in a cookie, so that it redirects directly to the preferred playground next time user visits this link. This is better to do it server-side with cookies and 302 redirects than with client redirects and localStorage.
The Netlify deployment (Joel can give access): https://app.netlify.com/sites/docusaurus-new/overview
Netlify deployment (Joel can give access): https://app.netlify.com/sites/docusaurus-new/overview
Builds are stopped because we shouldn't need to redeploy the \_redirects file. You can just trigger a manual build if needed.
Builds are stopped because we shouldn't need to redeploy very often. You can just trigger a manual build if needed.

View file

@ -1 +0,0 @@
/* https://codesandbox.io/s/docusaurus

View file

@ -0,0 +1,77 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {HandlerEvent, HandlerResponse} from '@netlify/functions';
const CookieName = 'DocusaurusPlaygroundName';
const PlaygroundConfigs = {
codesandbox: 'https://codesandbox.io/s/docusaurus',
stackblitz: 'https://stackblitz.com/fork/docusaurus',
};
const PlaygroundDocumentationUrl = 'https://docusaurus.io/docs/playground';
export type PlaygroundName = keyof typeof PlaygroundConfigs;
function isValidPlaygroundName(
playgroundName: string,
): playgroundName is PlaygroundName {
return Object.keys(PlaygroundConfigs).includes(playgroundName);
}
export function createPlaygroundDocumentationResponse(): HandlerResponse {
return {
statusCode: 302,
headers: {
Location: PlaygroundDocumentationUrl,
},
};
}
export function createPlaygroundResponse(
playgroundName: PlaygroundName,
): HandlerResponse {
const playgroundUrl = PlaygroundConfigs[playgroundName];
return {
statusCode: 302,
headers: {
Location: playgroundUrl,
'Set-Cookie': `${CookieName}=${playgroundName}`,
},
};
}
// Inspired by https://stackoverflow.com/a/3409200/82609
function parseCookieString(cookieString: string): Record<string, string> {
const result: Record<string, string> = {};
cookieString.split(';').forEach(function (cookie) {
const [name, value] = cookie.split('=');
result[name.trim()] = decodeURI(value);
});
return result;
}
export function readPlaygroundName(
event: HandlerEvent,
): PlaygroundName | undefined {
const parsedCookie: Record<string, string> = event.headers.cookie
? parseCookieString(event.headers.cookie)
: {};
const playgroundName: string | undefined = parsedCookie[CookieName];
if (playgroundName) {
if (isValidPlaygroundName(playgroundName)) {
return playgroundName;
} else {
console.error(
`playgroundName found in cookie was invalid: ${playgroundName}`,
);
}
}
return undefined;
}

View file

@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {Handler} from '@netlify/functions';
import {createPlaygroundResponse} from '../functionUtils/playgroundUtils';
export const handler: Handler = async function (_event, _context) {
return createPlaygroundResponse('codesandbox');
};

View file

@ -0,0 +1,20 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {Handler} from '@netlify/functions';
import {
readPlaygroundName,
createPlaygroundResponse,
createPlaygroundDocumentationResponse,
} from '../functionUtils/playgroundUtils';
export const handler: Handler = async (event, _context) => {
const playgroundName = readPlaygroundName(event);
return playgroundName
? createPlaygroundResponse(playgroundName)
: createPlaygroundDocumentationResponse();
};

View file

@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {Handler} from '@netlify/functions';
import {createPlaygroundResponse} from '../functionUtils/playgroundUtils';
export const handler: Handler = async function (_event, _context) {
return createPlaygroundResponse('stackblitz');
};

View file

@ -0,0 +1,23 @@
[functions]
directory = "functions"
[[redirects]]
from = "/"
to = "/.netlify/functions/index"
status = 200
[[redirects]]
from = "/codesandbox"
to = "/.netlify/functions/codesandbox"
status = 200
[[redirects]]
from = "/stackblitz"
to = "/.netlify/functions/stackblitz"
status = 200
[[redirects]]
from = "/*"
to = "/.netlify/functions/index"
status = 200

View file

@ -0,0 +1,14 @@
{
"name": "new.docusaurus.io",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "netlify dev"
},
"dependencies": {
"@netlify/functions": "^0.7.2"
},
"devDependencies": {
"netlify-cli": "^5.2.2"
}
}

View file

@ -3,7 +3,8 @@
"workspaces": [
"packages/*",
"website",
"packages/docusaurus-init/templates/*"
"packages/docusaurus-init/templates/*",
"admin/new.docusaurus.io"
],
"scripts": {
"start": "yarn build:packages && yarn start:v2",

View file

@ -92,5 +92,11 @@ export default function Interpolate<Str extends string>({
children,
values,
}: InterpolateProps<Str>): ReactNode {
if (typeof children !== 'string') {
console.warn('Illegal <Interpolate> children', children);
throw new Error(
'The Docusaurus <Interpolate> component only accept simple string values',
);
}
return interpolate(children, values);
}

View file

@ -43,6 +43,13 @@ export default function Translate<Str extends string>({
id,
values,
}: TranslateProps<Str>): JSX.Element {
if (typeof children !== 'string') {
console.warn('Illegal <Translate> children', children);
throw new Error(
'The Docusaurus <Translate> component only accept simple string values',
);
}
const localizedMessage: string =
getLocalizedMessage({message: children, id}) ?? children;

View file

@ -0,0 +1,24 @@
# Playground
Playgrounds allow you to run Docusaurus **in your browser, without installing anything**!
They are mostly useful for:
- Testing Docusaurus
- Reporting bugs
Use [new.docusaurus.io](https://new.docusaurus.io) as an easy-to-remember shortcut.
Choose one of the available options below.
```mdx-code-block
import {PlaygroundCardsRow} from '@site/src/components/Playground';
<PlaygroundCardsRow />
```
:::tip
For convenience, we'll remember your choice next time you visit [new.docusaurus.io](https://new.docusaurus.io).
:::

View file

@ -12,7 +12,12 @@ module.exports = {
type: 'category',
label: 'Getting Started',
collapsed: false,
items: ['installation', 'configuration', 'typescript-support'],
items: [
'installation',
'configuration',
'playground',
'typescript-support',
],
},
{
type: 'category',

View file

@ -0,0 +1,81 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import Translate from '@docusaurus/Translate';
import Link from '@docusaurus/Link';
import Image from '@theme/IdealImage';
import clsx from 'clsx';
const Playgrounds = [
{
name: '📦 CodeSandbox',
image: require('@site/static/img/playgrounds/codesandbox.png'),
url: 'https://new.docusaurus.io/codesandbox',
description: (
<Translate id="playground.codesandbox.description">
CodeSandbox is a popular playground solution. Runs Docusaurus in a
remote Docker container.
</Translate>
),
},
{
name: '⚡ StackBlitz 🆕',
image: require('@site/static/img/playgrounds/stackblitz.png'),
url: 'https://new.docusaurus.io/stackblitz',
description: (
<Translate
id="playground.stackblitz.description"
values={{
webContainersLink: (
<Link target="https://blog.stackblitz.com/posts/introducing-webcontainers/">
WebContainers
</Link>
),
}}>
{
'StackBlitz uses a novel {webContainersLink} technology to run Docusaurus directly in your browser.'
}
</Translate>
),
},
];
function PlaygroundCard({name, image, url, description}) {
return (
<div className="col col--6 margin-bottom--lg">
<div className={clsx('card')}>
<div className={clsx('card__image')}>
<Link to={url}>
<Image img={image} alt={`${name}'s image`} />
</Link>
</div>
<div className="card__body">
<h3>{name}</h3>
<p>{description}</p>
</div>
<div className="card__footer">
<div className="button-group button-group--block">
<Link className="button button--secondary" to={url}>
<Translate id="playground.tryItButton">Try it now!</Translate>
</Link>
</div>
</div>
</div>
</div>
);
}
export function PlaygroundCardsRow() {
return (
<div className="row">
{Playgrounds.map((playground) => (
<PlaygroundCard key={playground.name} {...playground} />
))}
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

View file

@ -0,0 +1,24 @@
# Playground
Playgrounds allow you to run Docusaurus **in your browser, without installing anything**!
They are mostly useful for:
- Testing Docusaurus
- Reporting bugs
Use [new.docusaurus.io](https://new.docusaurus.io) as an easy-to-remember shortcut.
Choose one of the available options below.
```mdx-code-block
import {PlaygroundCardsRow} from '@site/src/components/Playground';
<PlaygroundCardsRow />
```
:::tip
For convenience, we'll remember your choice next time you visit [new.docusaurus.io](https://new.docusaurus.io).
:::

View file

@ -17,6 +17,10 @@
"type": "doc",
"id": "version-2.0.0-beta.3/configuration"
},
{
"type": "doc",
"id": "version-2.0.0-beta.3/playground"
},
{
"type": "doc",
"id": "version-2.0.0-beta.3/typescript-support"

1228
yarn.lock

File diff suppressed because it is too large Load diff