diff --git a/website/package.json b/website/package.json
index ad4e3188..358d06da 100644
--- a/website/package.json
+++ b/website/package.json
@@ -7,14 +7,21 @@
"svgo": "svgo --pretty --indent=2 --config=svgo.yml",
"svgo-all": "find static | grep '\\.svg$' | xargs -Iz -n 1 yarn svgo z"
},
+ "dependencies": {
+ "codemirror": "5.36.0",
+ "lz-string": "1.4.4",
+ "react": "16.3.1",
+ "react-dom": "16.3.1",
+ "sw-toolbox": "3.6.0"
+ },
"devDependencies": {
"@sandhose/prettier-animated-logo": "1.0.3",
"babel-loader": "7.1.4",
"babel-preset-env": "1.6.1",
+ "babel-preset-react": "6.24.1",
"docusaurus": "1.0.5",
"js-yaml": "3.10.0",
"svgo": "1.0.4",
- "sw-toolbox": "3.6.0",
"webpack": "4.5.0",
"webpack-cli": "2.0.14"
}
diff --git a/website/pages/playground/index.html b/website/pages/playground/index.html
index fcf40598..8f074341 100644
--- a/website/pages/playground/index.html
+++ b/website/pages/playground/index.html
@@ -23,7 +23,6 @@
-
@@ -41,9 +40,8 @@
-
-
-
+
+
@@ -77,6 +75,8 @@
+
+
-
-
diff --git a/website/playground/Button.js b/website/playground/Button.js
new file mode 100644
index 00000000..dbc1d56e
--- /dev/null
+++ b/website/playground/Button.js
@@ -0,0 +1,5 @@
+import React from "react";
+
+export default function(props) {
+ return ;
+}
diff --git a/website/playground/EditorState.js b/website/playground/EditorState.js
new file mode 100644
index 00000000..c70c3182
--- /dev/null
+++ b/website/playground/EditorState.js
@@ -0,0 +1,34 @@
+import React from "react";
+
+import { stateToggler, shallowEqual } from "./helpers";
+import * as storage from "./storage";
+
+export default class extends React.Component {
+ constructor() {
+ super();
+ this.state = Object.assign(
+ {
+ sidebarExpanded: false,
+ astPanelVisible: false,
+ docPanelVisible: false,
+ toggleAstPanelVisible: () =>
+ this.setState(stateToggler("astPanelVisible")),
+ toggleDocPanelVisible: () =>
+ this.setState(stateToggler("docPanelVisible")),
+ toggleSidebarExpanded: () =>
+ this.setState(stateToggler("sidebarExpanded"))
+ },
+ storage.get("editor_state")
+ );
+ }
+
+ componentDidUpdate(_, prevState) {
+ if (!shallowEqual(this.state, prevState)) {
+ storage.set("editor_state", this.state);
+ }
+ }
+
+ render() {
+ return this.props.children(this.state);
+ }
+}
diff --git a/website/playground/Option.js b/website/playground/Option.js
new file mode 100644
index 00000000..d1086dae
--- /dev/null
+++ b/website/playground/Option.js
@@ -0,0 +1,61 @@
+import React from "react";
+
+export function BooleanOption({ option, value, onChange }) {
+ function maybeInvert(value) {
+ return option.inverted ? !value : value;
+ }
+ return (
+
+ );
+}
+
+export function ChoiceOption({ option, value, onChange }) {
+ return (
+
+ );
+}
+
+export function NumberOption({ option, value, onChange }) {
+ return (
+
+ );
+}
+
+export default function(props) {
+ switch (props.option.type) {
+ case "boolean":
+ return ;
+ case "int":
+ return ;
+ case "choice":
+ return ;
+ default:
+ throw new Error("unsupported type");
+ }
+}
diff --git a/website/playground/Playground.js b/website/playground/Playground.js
new file mode 100644
index 00000000..349b3453
--- /dev/null
+++ b/website/playground/Playground.js
@@ -0,0 +1,172 @@
+import React from "react";
+
+import Button from "./Button";
+import EditorState from "./EditorState";
+import { DebugPanel, InputPanel, OutputPanel } from "./panels";
+import SidebarOptions from "./SidebarOptions";
+import WorkerApi from "./WorkerApi";
+
+const ENABLED_OPTIONS = [
+ "parser",
+ "printWidth",
+ "tabWidth",
+ "useTabs",
+ { name: "semi", inverted: true },
+ "singleQuote",
+ { name: "bracketSpacing", inverted: true },
+ "jsxBracketSameLine",
+ "arrowParens",
+ "trailingComma",
+ "proseWrap",
+ "insertPragma",
+ "requirePragma"
+].map(option => (typeof option === "string" ? { name: option } : option));
+
+class Playground extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ content: "",
+ loaded: false,
+ options: null
+ };
+ this.handleOptionValueChange = this.handleOptionValueChange.bind(this);
+
+ this.setContent = content => this.setState({ content });
+ this.clearContent = () => this.setState({ content: "" });
+ }
+
+ componentDidMount() {
+ this._worker = new WorkerApi("/worker.js");
+
+ this._worker
+ .postMessage({ type: "meta" })
+ .then(({ supportInfo, version }) => {
+ const availableOptions = parsePrettierOptions(supportInfo);
+ const options =
+ this.state.options ||
+ availableOptions.reduce((acc, option) => {
+ acc[option.name] = option.default;
+ return acc;
+ }, {});
+
+ this.setState({ loaded: true, availableOptions, options, version });
+ });
+ }
+
+ handleOptionValueChange(option, value) {
+ this.setState(state => ({
+ options: Object.assign({}, state.options, { [option.name]: value })
+ }));
+ }
+
+ componentDidUpdate(_, prevState) {
+ if (
+ prevState.content !== this.state.content ||
+ prevState.options !== this.state.options
+ ) {
+ this._format();
+ }
+ }
+
+ _format() {
+ const { content, options } = this.state;
+ this._worker
+ .postMessage({
+ type: "format",
+ code: content,
+ options
+ })
+ .then(({ formatted }) => this.setState({ formatted }));
+ }
+
+ render() {
+ const {
+ availableOptions,
+ content,
+ formatted,
+ loaded,
+ options,
+ sidebarExpanded
+ } = this.state;
+
+ if (!loaded) return "Loading...";
+
+ return (
+
+ {editorState => (
+
+
+
+
+
+ {editorState.astPanelVisible ? (
+
+ ) : null}
+ {editorState.docPanelVisible ? (
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+ }
+}
+
+function parsePrettierOptions(supportInfo) {
+ const supportedOptions = supportInfo.options.reduce((acc, option) => {
+ acc[option.name] = option;
+ return acc;
+ }, {});
+
+ return ENABLED_OPTIONS.reduce((optionsList, optionConfig) => {
+ if (!supportedOptions[optionConfig.name]) return optionsList;
+
+ const option = Object.assign(
+ {},
+ optionConfig,
+ supportedOptions[optionConfig.name]
+ );
+ option.cliName =
+ "--" +
+ (option.inverted ? "no-" : "") +
+ option.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
+
+ optionsList.push(option);
+ return optionsList;
+ }, []);
+}
+
+export default Playground;
diff --git a/website/playground/SidebarOptions.js b/website/playground/SidebarOptions.js
new file mode 100644
index 00000000..e82051ee
--- /dev/null
+++ b/website/playground/SidebarOptions.js
@@ -0,0 +1,33 @@
+import React from "react";
+
+import Option from "./Option";
+
+const CATEGORIES_ORDER = ["Global", "JavaScript", "Markdown", "Special"];
+
+export default function({
+ availableOptions,
+ currentOptions,
+ onOptionValueChange
+}) {
+ const optionsByCategory = availableOptions.reduce((acc, option) => {
+ let options;
+ acc[option.category] = options = acc[option.category] || [];
+ options.push(option);
+ return acc;
+ }, {});
+
+ return CATEGORIES_ORDER.map(category => (
+
+ {category}
+
+ {(optionsByCategory[category] || []).map(option => (
+
+ ))}
+
+ ));
+}
diff --git a/website/playground/WorkerApi.js b/website/playground/WorkerApi.js
new file mode 100644
index 00000000..02e75f43
--- /dev/null
+++ b/website/playground/WorkerApi.js
@@ -0,0 +1,30 @@
+export default function(source) {
+ const worker = new Worker(source);
+ let counter = 0;
+ let handlers = {};
+
+ worker.addEventListener("message", event => {
+ const { uid, message, error } = event.data;
+
+ if (!handlers[uid]) return;
+
+ const [resolve, reject] = handlers[uid];
+ delete handlers[uid];
+
+ if (error) {
+ reject(error);
+ } else {
+ resolve(message);
+ }
+ });
+
+ return {
+ postMessage(message) {
+ const uid = ++counter;
+ return new Promise((resolve, reject) => {
+ handlers[uid] = [resolve, reject];
+ worker.postMessage({ uid, message });
+ });
+ }
+ };
+}
diff --git a/website/playground/codeSamples.js b/website/playground/codeSamples.js
new file mode 100644
index 00000000..81d5b63e
--- /dev/null
+++ b/website/playground/codeSamples.js
@@ -0,0 +1,188 @@
+export default function getCodeExample(parser) {
+ switch (parser) {
+ case "babylon":
+ return [
+ 'function HelloWorld({greeting = "hello", greeted = \'"World"\', silent = false, onMouseOver,}) {',
+ "",
+ " if(!greeting){return null};",
+ "",
+ " // TODO: Don't use random in render",
+ ' let num = Math.floor (Math.random() * 1E+7).toString().replace(/\\.\\d+/ig, "")',
+ "",
+ " return ",
+ "",
+ " { greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() }",
+ ' {greeting.endsWith(",") ? " " : ", " }',
+ " ",
+ "\t{ greeted }",
+ "\t",
+ " { (silent)",
+ ' ? "."',
+ ' : "!"}',
+ "",
+ "
;",
+ "",
+ "}"
+ ].join("\n");
+ case "flow":
+ return [
+ "declare export function graphql>",
+ " (query: GQLDocument, config?: Config>):",
+ " (Component: Component) => React$ComponentType<$Diff, {",
+ " data: Object|void,",
+ " mutate: Function|void",
+ " }>>",
+ "",
+ 'declare type FetchPolicy = "cache-first" | "cache-and-network" | "network-only" | "cache-only"'
+ ].join("\n");
+ case "typescript":
+ return [
+ "interface MyInterface {",
+ " foo(): string,",
+ " bar: Array,",
+ "}",
+ "",
+ "export abstract class Foo implements MyInterface {",
+ " foo() {",
+ " // TODO: return an actual value here",
+ " return 'hello'",
+ " }",
+ " get bar() {",
+ " return [ 1,",
+ "",
+ " 2, 3,",
+ " ]",
+ " }",
+ "}",
+ "",
+ "type RequestType = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'OPTIONS' | 'CONNECT' | 'DELETE' | 'TRACE'"
+ ].join("\n");
+ case "css":
+ // Excerpted from the Bootstrap source, which is licensed under the MIT license:
+ // https://github.com/twbs/bootstrap/blob/v4.0.0-beta.3/LICENSE
+ return [
+ "@media (max-width: 480px) {",
+ " .bd-examples {margin-right: -.75rem;margin-left: -.75rem",
+ " }",
+ " ",
+ ' .bd-examples>[class^="col-"] {',
+ " padding-right: .75rem;",
+ " padding-left: .75rem;",
+ " ",
+ " }",
+ "}"
+ ].join("\n");
+ case "scss":
+ // Excerpted from the Bootstrap source, which is licensed under the MIT license:
+ // https://github.com/twbs/bootstrap/blob/v4.0.0-beta.3/LICENSE
+ return [
+ "@function color-yiq($color) {",
+ " $r: red($color);$g: green($color);$b: blue($color);",
+ "",
+ " $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;",
+ "",
+ " @if ($yiq >= $yiq-contrasted-threshold) {",
+ " @return $yiq-text-dark;",
+ "} @else {",
+ " @return $yiq-text-light;",
+ " }",
+ "}",
+ "",
+ "@each $color, $value in $colors {",
+ " .swatch-#{$color} {",
+ " color: color-yiq($value);",
+ " background-color: #{$value};",
+ " }",
+ "}"
+ ].join("\n");
+ case "less":
+ // Copied from http://lesscss.org/features/#detached-rulesets-feature
+ return [
+ "@my-ruleset: {",
+ " .my-selector {",
+ " @media tv {",
+ " background-color: black;",
+ " }",
+ " }",
+ " };",
+ "@media (orientation:portrait) {",
+ " @my-ruleset();",
+ "}"
+ ].join("\n");
+ case "json":
+ // Excerpted & adapted from Wikipedia, under the Creative Commons Attribution-ShareAlike License
+ // https://en.wikipedia.org/wiki/JSON#Example
+ return [
+ '{"allOn": "Single", "Line": "example",',
+ '"noSpace":true,',
+ ' "quote": {',
+ " 'singleQuote': 'example',",
+ ' "indented": true,',
+ " },",
+ ' "phoneNumbers": [',
+ ' {"type": "home",',
+ ' "number": "212 555-1234"},',
+ ' {"type": "office",',
+ ' "trailing": "commas by accident"},',
+ " ],",
+ "}"
+ ].join("\n");
+ case "graphql":
+ return [
+ "query Browse($offset: Int, $limit: Int, $categories: [String!], $search: String) {",
+ " browse(limit: $limit, offset: $offset, categories: $categories, search: $search) {",
+ " total,",
+ " results {",
+ " title",
+ " price",
+ " }",
+ " }",
+ "}"
+ ].join("\n");
+ case "markdown":
+ return [
+ "Header",
+ "======",
+ "",
+ "_Look,_ code blocks are formatted *too!*",
+ "",
+ "``` js",
+ "function identity(x) { return x }",
+ "```",
+ "",
+ "Pilot|Airport|Hours",
+ "--|:--:|--:",
+ "John Doe|SKG|1338",
+ "Jane Roe|JFK|314",
+ "",
+ "- - - - - - - - - - - - - - -",
+ "",
+ "+ List",
+ " + with a [link] (/to/somewhere)",
+ "+ and [another one]",
+ "",
+ "",
+ " [another one]: http://example.com 'Example title'",
+ "",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ "Curabitur consectetur maximus risus, sed maximus tellus tincidunt et."
+ ].join("\n");
+ case "vue":
+ return [
+ "",
+ " Templates are not formatted yet ...",
+ "
",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ].join("\n");
+ default:
+ return "";
+ }
+}
diff --git a/website/playground/helpers.js b/website/playground/helpers.js
new file mode 100644
index 00000000..520f0dff
--- /dev/null
+++ b/website/playground/helpers.js
@@ -0,0 +1,46 @@
+export function stateToggler(key) {
+ return state => ({ [key]: !Boolean(state[key]) });
+}
+
+const hasOwnProperty = Object.prototype.hasOwnProperty;
+
+function is(x, y) {
+ // SameValue algorithm
+ if (x === y) {
+ // Steps 1-5, 7-10
+ // Steps 6.b-6.e: +0 != -0
+ return x !== 0 || 1 / x === 1 / y;
+ } else {
+ // Step 6.a: NaN == NaN
+ return x !== x && y !== y;
+ }
+}
+
+export function shallowEqual(objA, objB) {
+ if (is(objA, objB)) return true;
+
+ if (
+ typeof objA !== "object" ||
+ objA === null ||
+ typeof objB !== "object" ||
+ objB === null
+ ) {
+ return false;
+ }
+
+ const keysA = Object.keys(objA);
+ const keysB = Object.keys(objB);
+
+ if (keysA.length !== keysB.length) return false;
+
+ for (let i = 0; i <= keysA.length; i++) {
+ if (
+ !hasOwnProperty.call(objB, keysA[i]) ||
+ !is(objA[keysA[i]], objB[keysA[i]])
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/website/playground/index.js b/website/playground/index.js
index 72fb64dc..12163cd4 100644
--- a/website/playground/index.js
+++ b/website/playground/index.js
@@ -1,693 +1,240 @@
-/* eslint-env browser */
-/* eslint no-var: off, strict: off, prefer-arrow-callback: off */
-/* global Clipboard CodeMirror formatMarkdown LZString preact */
+import React from "react";
+import ReactDOM from "react-dom";
-var h = preact.h;
+import Playground from "./Playground";
-var CATEGORIES = ["Global", "JavaScript", "Markdown", "Special"];
-var OPTIONS = [
- "parser",
- "printWidth",
- "tabWidth",
- "useTabs",
- { name: "semi", inverted: true },
- "singleQuote",
- { name: "bracketSpacing", inverted: true },
- "jsxBracketSameLine",
- "arrowParens",
- "trailingComma",
- "proseWrap",
- "insertPragma",
- "requirePragma"
-];
+ReactDOM.render(, document.getElementById("root"));
-// ---------- Options components -----------
+/******************************************/
-function BooleanOption(props) {
- function maybeInvert(value) {
- return props.option.inverted ? !value : value;
- }
- return h(
- "label",
- null,
- h("input", {
- type: "checkbox",
- checked: maybeInvert(props.value),
- onChange: function(ev) {
- props.onOptionChange(props.option, maybeInvert(ev.target.checked));
- }
- }),
- " ",
- props.option.cliName
- );
-}
-
-function ChoiceOption(props) {
- return h(
- "label",
- null,
- props.option.cliName,
- " ",
- h(
- "select",
- {
- onChange: function(ev) {
- props.onOptionChange(props.option, ev.target.value);
- }
- },
- props.option.choices.map(function(choice) {
- return h(
- "option",
- { value: choice.value, selected: choice.value === props.value },
- choice.value
- );
- })
- )
- );
-}
-
-function NumberOption(props) {
- return h(
- "label",
- null,
- props.option.cliName,
- " ",
- h("input", {
- type: "number",
- min: props.option.range.start,
- max: props.option.range.end,
- step: props.option.range.step,
- value: props.value,
- onChange: function(ev) {
- props.onOptionChange(props.option, parseInt(ev.target.value, 10));
- }
- })
- );
-}
-
-function Options(props) {
- var optionsByCategory = props.availableOptions.reduce(function(acc, opt) {
- acc[opt.category] = opts = acc[opt.category] || [];
- opts.push(opt);
- return acc;
- }, {});
-
- return h(
- "div",
- { className: "options-container" + (props.open ? " open" : "") },
- h(
- "div",
- { className: "options" },
- CATEGORIES.map(function(category) {
- return h(
- "details",
- { className: "sub-options", open: "true" },
- h("summary", null, category),
- (optionsByCategory[category] || []).map(function(opt) {
- return h(getComponentByOptionType(opt), {
- option: opt,
- value: props.options[opt.name],
- onOptionChange: props.onOptionChange
- });
- })
- );
- })
- )
- );
-}
-
-// ---------- Editor components -----------
-
-var Editor = createClass({
- componentDidMount: function() {
- this._codeMirror = CodeMirror.fromTextArea(this._ref, this.props.options);
- this._codeMirror.on("change", this.handleChange.bind(this));
- this._codeMirror.setValue(this.props.value || "");
- },
- handleChange: function(doc, change) {
- if (change.origin !== "setValue") {
- this.props.onChange(doc.getValue());
- }
- },
- componentWillReceiveProps(nextProps) {
- if (
- nextProps.value &&
- nextProps.value !== this.props.value &&
- this._codeMirror.getValue() !== nextProps.value
- ) {
- this._codeMirror.setValue(nextProps.value);
- } else if (!nextProps.value) {
- this._codeMirror.setValue("");
- }
- },
- render: function(props) {
+(function() {
+ function BottomBar(props) {
return h(
"div",
- { className: "editor input" },
- h("textarea", {
- ref: function(ref) {
- this._ref = ref;
- }.bind(this)
- })
- );
- }
-});
-
-function InputEditor(props) {
- return h(Editor, {
- options: {
- lineNumbers: true,
- keyMap: "sublime",
- autoCloseBrackets: true,
- matchBrackets: true,
- showCursorWhenSelecting: true,
- tabWidth: 2,
- mode: props.mode
- },
- value: props.value,
- onChange: props.onChange
- });
-}
-
-function OutputEditor(props) {
- return h(Editor, {
- options: {
- readOnly: true,
- lineNumbers: true,
- mode: props.mode
- },
- value: props.value
- });
-}
-
-function Button(props) {
- return h("button", Object.assign({ type: "button", class: "btn" }, props));
-}
-
-function BottomBar(props) {
- return h(
- "div",
- { class: "bottom-bar" },
- h(
- "div",
- { class: "bottom-bar-buttons" },
- h(
- Button,
- { onClick: props.onShowOptionsClick },
- props.optionsOpen ? "Hide options" : "Show options"
- ),
- h(Button, { onClick: props.onClearClick }, "Clear")
- ),
- h(
- "div",
- { class: "bottom-bar-buttons bottom-bar-buttons-right" },
- h(Button, null, "Copy Link"),
- h(Button, null, "Copy markdown"),
- h(
- "a",
- {
- href:
- "https://github.com/prettier/prettier/issues/new?body=" +
- encodeURIComponent(props.reportBody),
- target: "_blank",
- rel: "noopener",
- class: "btn"
- },
- "Report issue"
- )
- )
- );
-}
-
-var App = createClass({
- state: Object.assign({ loaded: false, formatted: "" }, loadPersistedState()),
-
- _format: function() {
- this._worker.postMessage(
- { type: "format", code: this.state.content, options: this.state.options },
- function(message) {
- this.setState({ formatted: message.formatted });
- }.bind(this)
- );
- persistState(this.state);
- },
-
- handleOptionChange: function(option, value) {
- this.setState(function(state) {
- return {
- options: Object.assign({}, state.options, { [option.name]: value })
- };
- }, this._format.bind(this));
- },
- handleOptionsOpen: function() {
- this.setState(
- function(state) {
- return {
- editorState: Object.assign({}, state.editorState, {
- optionsOpen: !state.editorState.optionsOpen
- })
- };
- },
- function() {
- persistState(this.state);
- }
- );
- },
- handleContentChange: function(value) {
- this.setState({ content: value }, this._format.bind(this));
- },
-
- componentDidMount: function() {
- this._worker = new WorkerApi("/worker2.js");
-
- this._worker.postMessage(
- { type: "meta" },
- function(message) {
- var availableOptions = getOptions(message.supportInfo.options);
- var options =
- this.state.options ||
- availableOptions.reduce(function(options, opt) {
- options[opt.name] = opt.default;
- return options;
- }, {});
-
- this.setState({
- loaded: true,
- version: message.version,
- availableOptions: availableOptions,
- options: options
- });
- }.bind(this)
- );
-
- this._format();
- },
-
- render: function(props, state) {
- if (!state.loaded) {
- return "Loading...";
- }
- var editorState = state.editorState;
- return h(
- "div",
- { className: "playground-container" },
+ { class: "bottom-bar" },
h(
"div",
- { className: "editors-container" },
- h(Options, {
- availableOptions: state.availableOptions,
- options: state.options,
- open: editorState.optionsOpen,
- onOptionChange: this.handleOptionChange.bind(this)
- }),
+ { class: "bottom-bar-buttons" },
+ h(
+ Button,
+ { onClick: props.onShowOptionsClick },
+ props.optionsOpen ? "Hide options" : "Show options"
+ ),
+ h(Button, { onClick: props.onClearClick }, "Clear")
+ ),
+ h(
+ "div",
+ { class: "bottom-bar-buttons bottom-bar-buttons-right" },
+ h(Button, null, "Copy Link"),
+ h(Button, null, "Copy markdown"),
+ h(
+ "a",
+ {
+ href:
+ "https://github.com/prettier/prettier/issues/new?body=" +
+ encodeURIComponent(props.reportBody),
+ target: "_blank",
+ rel: "noopener",
+ class: "btn"
+ },
+ "Report issue"
+ )
+ )
+ );
+ }
+
+ var App = createClass({
+ state: Object.assign(
+ { loaded: false, formatted: "" },
+ loadPersistedState()
+ ),
+
+ handleOptionChange: function(option, value) {
+ this.setState(function(state) {
+ return {
+ options: Object.assign({}, state.options, { [option.name]: value })
+ };
+ }, this._format.bind(this));
+ },
+ handleOptionsOpen: function() {
+ this.setState(
+ function(state) {
+ return {
+ editorState: Object.assign({}, state.editorState, {
+ optionsOpen: !state.editorState.optionsOpen
+ })
+ };
+ },
+ function() {
+ persistState(this.state);
+ }
+ );
+ },
+ handleContentChange: function(value) {
+ this.setState({ content: value }, this._format.bind(this));
+ },
+
+ componentDidMount: function() {
+ this._worker = new WorkerApi("/worker2.js");
+
+ this._worker.postMessage(
+ { type: "meta" },
+ function(message) {
+ var availableOptions = getOptions(message.supportInfo.options);
+ var options =
+ this.state.options ||
+ availableOptions.reduce(function(options, opt) {
+ options[opt.name] = opt.default;
+ return options;
+ }, {});
+
+ this.setState({
+ loaded: true,
+ version: message.version,
+ availableOptions: availableOptions,
+ options: options
+ });
+ }.bind(this)
+ );
+
+ this._format();
+ },
+
+ render: function(props, state) {
+ if (!state.loaded) {
+ return "Loading...";
+ }
+ var editorState = state.editorState;
+ return h(
+ "div",
+ { className: "playground-container" },
h(
"div",
- { className: "editors" },
- h(InputEditor, {
- mode: getCodemirrorMode(state.options),
- value: state.content,
- onChange: this.handleContentChange.bind(this)
+ { className: "editors-container" },
+ h(Options, {
+ availableOptions: state.availableOptions,
+ options: state.options,
+ open: editorState.optionsOpen,
+ onOptionChange: this.handleOptionChange.bind(this)
}),
- h(OutputEditor, {
- mode: getCodemirrorMode(state.options),
- value: state.formatted
- })
- )
- ),
- h(BottomBar, {
- optionsOpen: editorState.optionsOpen,
- onShowOptionsClick: this.handleOptionsOpen.bind(this),
- onClearClick: function() {
- this.handleContentChange("");
- }.bind(this),
- reportBody: formatMarkdown(
- state.content,
- state.formatted,
- "",
- state.version,
- window.location.href,
- state.options,
- state.availableOptions.reduce(function(cliOptions, option) {
- var value = state.options[option.name];
- if (option.type === "boolean") {
- if ((value && !option.inverted) || (!value && option.inverted)) {
- cliOptions.push([option.cliName, true]);
+ h(
+ "div",
+ { className: "editors" },
+ h(InputEditor, {
+ mode: getCodemirrorMode(state.options),
+ value: state.content,
+ onChange: this.handleContentChange.bind(this)
+ }),
+ h(OutputEditor, {
+ mode: getCodemirrorMode(state.options),
+ value: state.formatted
+ })
+ )
+ ),
+ h(BottomBar, {
+ optionsOpen: editorState.optionsOpen,
+ onShowOptionsClick: this.handleOptionsOpen.bind(this),
+ onClearClick: function() {
+ this.handleContentChange("");
+ }.bind(this),
+ reportBody: formatMarkdown(
+ state.content,
+ state.formatted,
+ "",
+ state.version,
+ window.location.href,
+ state.options,
+ state.availableOptions.reduce(function(cliOptions, option) {
+ var value = state.options[option.name];
+ if (option.type === "boolean") {
+ if (
+ (value && !option.inverted) ||
+ (!value && option.inverted)
+ ) {
+ cliOptions.push([option.cliName, true]);
+ }
+ } else if (value !== option.default) {
+ cliOptions.push([option.cliName, value]);
}
- } else if (value !== option.default) {
- cliOptions.push([option.cliName, value]);
- }
- return cliOptions;
- }, [])
- )
- })
- );
+ return cliOptions;
+ }, [])
+ )
+ })
+ );
+ }
+ });
+
+ // -------- UTILITY FUNCTIONS --------
+
+ function loadPersistedState() {
+ var editorState = { optionsOpen: false };
+ try {
+ Object.assign(
+ editorState,
+ JSON.parse(window.localStorage.getItem("editorState"))
+ );
+ } catch (error) {
+ // noop
+ }
+ var queryState = parseQuery() || {};
+ return {
+ editorState: editorState,
+ content: typeof queryState.content === "string" ? queryState.content : "",
+ options: queryState.options
+ };
+ }
+
+ function persistState(state) {
+ try {
+ window.localStorage.setItem(
+ "editorState",
+ JSON.stringify(state.editorState)
+ );
+ } catch (_) {
+ // noop
+ }
+ saveQuery({ content: state.content, options: state.options });
+ }
+
+ function getCodemirrorMode(options) {
+ switch (options.parser) {
+ case "css":
+ case "less":
+ case "scss":
+ return "css";
+ case "markdown":
+ return "markdown";
+ default:
+ return "jsx";
+ }
+ }
+
+ // Parse the URL hash as a config object
+ function parseQuery() {
+ var hash = document.location.hash.slice(1);
+ try {
+ // providing backwards support for old json encoded URIComponent
+ if (hash.indexOf("%7B%22") !== -1) {
+ return JSON.parse(decodeURIComponent(hash));
+ }
+ return JSON.parse(LZString.decompressFromEncodedURIComponent(hash));
+ } catch (error) {
+ return {};
+ }
+ }
+
+ function saveQuery(state) {
+ var hash = LZString.compressToEncodedURIComponent(JSON.stringify(state));
+ if (
+ typeof URL === "function" &&
+ typeof history === "object" &&
+ typeof history.replaceState === "function"
+ ) {
+ var url = new URL(location);
+ url.hash = hash;
+ history.replaceState(null, null, url);
+ } else {
+ location.hash = hash;
+ }
}
});
-
-window.onload = function() {
- preact.render(h(App), document.getElementById("root"));
-};
-
-// -------- Worker API -------------
-
-function WorkerApi(source) {
- var worker = new Worker(source);
- var counter = 0;
- var handlers = {};
-
- worker.onmessage = function(event) {
- var uid = event.data.uid;
- var message = event.data.message;
- if (handlers[uid]) {
- var handler = handlers[uid];
- delete handlers[uid];
-
- handler(message);
- }
- };
-
- function postMessage(message, handler) {
- var uid = ++counter;
- handlers[uid] = handler;
- worker.postMessage({
- uid: uid,
- message: message
- });
- }
-
- return { postMessage: postMessage };
-}
-
-// -------- UTILITY FUNCTIONS --------
-
-function loadPersistedState() {
- var editorState = { optionsOpen: false };
- try {
- Object.assign(
- editorState,
- JSON.parse(window.localStorage.getItem("editorState"))
- );
- } catch (error) {
- // noop
- }
- var queryState = parseQuery() || {};
- return {
- editorState: editorState,
- content: typeof queryState.content === "string" ? queryState.content : "",
- options: queryState.options
- };
-}
-
-function persistState(state) {
- try {
- window.localStorage.setItem(
- "editorState",
- JSON.stringify(state.editorState)
- );
- } catch (_) {
- // noop
- }
- saveQuery({ content: state.content, options: state.options });
-}
-
-function getComponentByOptionType(option) {
- switch (option.type) {
- case "boolean":
- return BooleanOption;
- case "int":
- return NumberOption;
- case "choice":
- return ChoiceOption;
- default:
- throw new Error("unsupported type");
- }
-}
-
-function getCodemirrorMode(options) {
- switch (options.parser) {
- case "css":
- case "less":
- case "scss":
- return "css";
- case "markdown":
- return "markdown";
- default:
- return "jsx";
- }
-}
-
-function getOptions(supportedOptions) {
- supportedOptions = supportedOptions.reduce(function(acc, opt) {
- acc[opt.name] = opt;
- return acc;
- }, {});
-
- return OPTIONS.reduce(function(options, opt) {
- opt = typeof opt === "string" ? { name: opt } : opt;
- if (!supportedOptions[opt.name]) {
- return options;
- }
-
- var modified = Object.assign({}, opt, supportedOptions[opt.name]);
- modified.cliName =
- "--" +
- (opt.inverted ? "no-" : "") +
- opt.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
- if (modified.type === "boolean" && opt.inverted) {
- modified.default = !modified.default;
- }
-
- options.push(modified);
- return options;
- }, []);
-}
-
-// Parse the URL hash as a config object
-function parseQuery() {
- var hash = document.location.hash.slice(1);
- try {
- // providing backwards support for old json encoded URIComponent
- if (hash.indexOf("%7B%22") !== -1) {
- return JSON.parse(decodeURIComponent(hash));
- }
- return JSON.parse(LZString.decompressFromEncodedURIComponent(hash));
- } catch (error) {
- return {};
- }
-}
-
-function saveQuery(state) {
- var hash = LZString.compressToEncodedURIComponent(JSON.stringify(state));
- if (
- typeof URL === "function" &&
- typeof history === "object" &&
- typeof history.replaceState === "function"
- ) {
- var url = new URL(location);
- url.hash = hash;
- history.replaceState(null, null, url);
- } else {
- location.hash = hash;
- }
-}
-
-function getCodeExample(parser) {
- switch (parser) {
- case "babylon":
- return [
- 'function HelloWorld({greeting = "hello", greeted = \'"World"\', silent = false, onMouseOver,}) {',
- "",
- " if(!greeting){return null};",
- "",
- " // TODO: Don't use random in render",
- ' let num = Math.floor (Math.random() * 1E+7).toString().replace(/\\.\\d+/ig, "")',
- "",
- " return ",
- "",
- " { greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() }",
- ' {greeting.endsWith(",") ? " " : ", " }',
- " ",
- "\t{ greeted }",
- "\t",
- " { (silent)",
- ' ? "."',
- ' : "!"}',
- "",
- "
;",
- "",
- "}"
- ].join("\n");
- case "flow":
- return [
- "declare export function graphql>",
- " (query: GQLDocument, config?: Config>):",
- " (Component: Component) => React$ComponentType<$Diff, {",
- " data: Object|void,",
- " mutate: Function|void",
- " }>>",
- "",
- 'declare type FetchPolicy = "cache-first" | "cache-and-network" | "network-only" | "cache-only"'
- ].join("\n");
- case "typescript":
- return [
- "interface MyInterface {",
- " foo(): string,",
- " bar: Array,",
- "}",
- "",
- "export abstract class Foo implements MyInterface {",
- " foo() {",
- " // TODO: return an actual value here",
- " return 'hello'",
- " }",
- " get bar() {",
- " return [ 1,",
- "",
- " 2, 3,",
- " ]",
- " }",
- "}",
- "",
- "type RequestType = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'OPTIONS' | 'CONNECT' | 'DELETE' | 'TRACE'"
- ].join("\n");
- case "css":
- // Excerpted from the Bootstrap source, which is licensed under the MIT license:
- // https://github.com/twbs/bootstrap/blob/v4.0.0-beta.3/LICENSE
- return [
- "@media (max-width: 480px) {",
- " .bd-examples {margin-right: -.75rem;margin-left: -.75rem",
- " }",
- " ",
- ' .bd-examples>[class^="col-"] {',
- " padding-right: .75rem;",
- " padding-left: .75rem;",
- " ",
- " }",
- "}"
- ].join("\n");
- case "scss":
- // Excerpted from the Bootstrap source, which is licensed under the MIT license:
- // https://github.com/twbs/bootstrap/blob/v4.0.0-beta.3/LICENSE
- return [
- "@function color-yiq($color) {",
- " $r: red($color);$g: green($color);$b: blue($color);",
- "",
- " $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;",
- "",
- " @if ($yiq >= $yiq-contrasted-threshold) {",
- " @return $yiq-text-dark;",
- "} @else {",
- " @return $yiq-text-light;",
- " }",
- "}",
- "",
- "@each $color, $value in $colors {",
- " .swatch-#{$color} {",
- " color: color-yiq($value);",
- " background-color: #{$value};",
- " }",
- "}"
- ].join("\n");
- case "less":
- // Copied from http://lesscss.org/features/#detached-rulesets-feature
- return [
- "@my-ruleset: {",
- " .my-selector {",
- " @media tv {",
- " background-color: black;",
- " }",
- " }",
- " };",
- "@media (orientation:portrait) {",
- " @my-ruleset();",
- "}"
- ].join("\n");
- case "json":
- // Excerpted & adapted from Wikipedia, under the Creative Commons Attribution-ShareAlike License
- // https://en.wikipedia.org/wiki/JSON#Example
- return [
- '{"allOn": "Single", "Line": "example",',
- '"noSpace":true,',
- ' "quote": {',
- " 'singleQuote': 'example',",
- ' "indented": true,',
- " },",
- ' "phoneNumbers": [',
- ' {"type": "home",',
- ' "number": "212 555-1234"},',
- ' {"type": "office",',
- ' "trailing": "commas by accident"},',
- " ],",
- "}"
- ].join("\n");
- case "graphql":
- return [
- "query Browse($offset: Int, $limit: Int, $categories: [String!], $search: String) {",
- " browse(limit: $limit, offset: $offset, categories: $categories, search: $search) {",
- " total,",
- " results {",
- " title",
- " price",
- " }",
- " }",
- "}"
- ].join("\n");
- case "markdown":
- return [
- "Header",
- "======",
- "",
- "_Look,_ code blocks are formatted *too!*",
- "",
- "``` js",
- "function identity(x) { return x }",
- "```",
- "",
- "Pilot|Airport|Hours",
- "--|:--:|--:",
- "John Doe|SKG|1338",
- "Jane Roe|JFK|314",
- "",
- "- - - - - - - - - - - - - - -",
- "",
- "+ List",
- " + with a [link] (/to/somewhere)",
- "+ and [another one]",
- "",
- "",
- " [another one]: http://example.com 'Example title'",
- "",
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
- "Curabitur consectetur maximus risus, sed maximus tellus tincidunt et."
- ].join("\n");
- case "vue":
- return [
- "",
- " Templates are not formatted yet ...",
- "
",
- "",
- "",
- "",
- "",
- ""
- ].join("\n");
- default:
- return "";
- }
-}
-
-// Preact without ES6 classes
-function createClass(obj) {
- function F() {
- preact.Component.call(this);
- }
- var p = (F.prototype = new preact.Component());
- for (var i in obj) {
- p[i] = obj[i];
- }
- return (p.constructor = F);
-}
diff --git a/website/playground/panels.js b/website/playground/panels.js
new file mode 100644
index 00000000..bc058860
--- /dev/null
+++ b/website/playground/panels.js
@@ -0,0 +1,84 @@
+import CodeMirror from "codemirror";
+import React from "react";
+
+class CodeMirrorPanel extends React.Component {
+ constructor() {
+ super();
+ this._textareaRef = React.createRef();
+ this._codeMirror = null;
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ componentDidMount() {
+ this._codeMirror = CodeMirror.fromTextArea(
+ this._textareaRef.current,
+ this.props.options
+ );
+ this._codeMirror.on("change", this.handleChange);
+ this._codeMirror.setValue(this.props.value || "");
+ }
+
+ componentWillUnmount() {
+ this._codeMirror && this._codeMirror.toTextArea();
+ }
+
+ componentDidUpdate() {
+ if (this.props.value !== this._codeMirror.getValue()) {
+ this._codeMirror.setValue(this.props.value);
+ }
+ }
+
+ handleChange(doc, change) {
+ if (change.origin !== "setValue") {
+ this.props.onChange(doc.getValue());
+ }
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+export function InputPanel({ mode, value, onChange }) {
+ return (
+
+ );
+}
+
+export function OutputPanel({ mode, value }) {
+ return (
+
+ );
+}
+
+export function DebugPanel({ value }) {
+ return (
+
+ );
+}
diff --git a/website/playground/storage.js b/website/playground/storage.js
new file mode 100644
index 00000000..df4c96c7
--- /dev/null
+++ b/website/playground/storage.js
@@ -0,0 +1,15 @@
+export function get(key) {
+ try {
+ return JSON.parse(window.localStorage.getItem(key));
+ } catch (_) {
+ // noop
+ }
+}
+
+export function set(key, value) {
+ try {
+ window.localStorage.setItem(key, JSON.stringify(value));
+ } catch (_) {
+ // noop
+ }
+}
diff --git a/website/static/worker2.js b/website/static/old-worker.js
similarity index 59%
rename from website/static/worker2.js
rename to website/static/old-worker.js
index dd612883..85c2b770 100644
--- a/website/static/worker2.js
+++ b/website/static/old-worker.js
@@ -4,6 +4,7 @@
var parsersLoaded = {};
// "Polyfills" in order for all the code to run
+/* eslint-disable */
self.global = self;
self.util = {};
self.path = {};
@@ -24,7 +25,7 @@ self.fs = { readFile: function() {} };
os.homedir = function() {
return "/home/prettier";
};
-os.EOL = "\n";
+os.EOL = '\n';
self.process = {
argv: [],
env: { PRETTIER_DEBUG: true },
@@ -57,6 +58,8 @@ self.require = function require(path) {
return self[path];
};
+/* eslint-enable */
+
var prettier;
importScripts("lib/index.js");
if (typeof prettier === "undefined") {
@@ -66,36 +69,60 @@ if (typeof prettier === "undefined") {
prettier = index; // eslint-disable-line
}
-self.onmessage = function(event) {
- var uid = event.data.uid;
- var message = event.data.message;
- switch (message.type) {
- case "meta":
- self.postMessage({
- uid: uid,
- message: {
- type: "meta",
- supportInfo: JSON.parse(JSON.stringify(prettier.getSupportInfo())),
- version: prettier.version
- }
- });
- break;
+self.onmessage = function(message) {
+ var options = message.data.options || {};
+ options.parser = options.parser || "babylon";
- case "format":
- var options = message.options || {};
+ delete options.ast;
+ delete options.doc;
+ delete options.output2;
- delete options.ast;
- delete options.doc;
- delete options.output2;
+ var formatted = formatCode(message.data.text, options);
+ var doc;
+ var ast;
+ var formatted2;
- self.postMessage({
- uid: uid,
- message: {
- formatted: formatCode(message.code, options)
- }
- });
- break;
+ if (message.data.ast) {
+ var actualAst;
+ var errored = false;
+ try {
+ actualAst = prettier.__debug.parse(message.data.text, options).ast;
+ ast = JSON.stringify(actualAst);
+ } catch (e) {
+ errored = true;
+ ast = String(e);
+ }
+ if (!errored) {
+ try {
+ ast = formatCode(ast, { parser: "json" });
+ } catch (e) {
+ ast = JSON.stringify(actualAst, null, 2);
+ }
+ }
}
+
+ if (message.data.doc) {
+ try {
+ doc = prettier.__debug.formatDoc(
+ prettier.__debug.printToDoc(message.data.text, options),
+ { parser: "babylon" }
+ );
+ } catch (e) {
+ doc = String(e);
+ }
+ }
+
+ if (message.data.formatted2) {
+ formatted2 = formatCode(formatted, options);
+ }
+
+ self.postMessage({
+ formatted: formatted,
+ doc: doc,
+ ast: ast,
+ formatted2: formatted2,
+ version: prettier.version
+ });
};
function formatCode(text, options) {
diff --git a/website/static/worker.js b/website/static/worker.js
index 85c2b770..dd612883 100644
--- a/website/static/worker.js
+++ b/website/static/worker.js
@@ -4,7 +4,6 @@
var parsersLoaded = {};
// "Polyfills" in order for all the code to run
-/* eslint-disable */
self.global = self;
self.util = {};
self.path = {};
@@ -25,7 +24,7 @@ self.fs = { readFile: function() {} };
os.homedir = function() {
return "/home/prettier";
};
-os.EOL = '\n';
+os.EOL = "\n";
self.process = {
argv: [],
env: { PRETTIER_DEBUG: true },
@@ -58,8 +57,6 @@ self.require = function require(path) {
return self[path];
};
-/* eslint-enable */
-
var prettier;
importScripts("lib/index.js");
if (typeof prettier === "undefined") {
@@ -69,60 +66,36 @@ if (typeof prettier === "undefined") {
prettier = index; // eslint-disable-line
}
-self.onmessage = function(message) {
- var options = message.data.options || {};
- options.parser = options.parser || "babylon";
+self.onmessage = function(event) {
+ var uid = event.data.uid;
+ var message = event.data.message;
+ switch (message.type) {
+ case "meta":
+ self.postMessage({
+ uid: uid,
+ message: {
+ type: "meta",
+ supportInfo: JSON.parse(JSON.stringify(prettier.getSupportInfo())),
+ version: prettier.version
+ }
+ });
+ break;
- delete options.ast;
- delete options.doc;
- delete options.output2;
+ case "format":
+ var options = message.options || {};
- var formatted = formatCode(message.data.text, options);
- var doc;
- var ast;
- var formatted2;
+ delete options.ast;
+ delete options.doc;
+ delete options.output2;
- if (message.data.ast) {
- var actualAst;
- var errored = false;
- try {
- actualAst = prettier.__debug.parse(message.data.text, options).ast;
- ast = JSON.stringify(actualAst);
- } catch (e) {
- errored = true;
- ast = String(e);
- }
- if (!errored) {
- try {
- ast = formatCode(ast, { parser: "json" });
- } catch (e) {
- ast = JSON.stringify(actualAst, null, 2);
- }
- }
+ self.postMessage({
+ uid: uid,
+ message: {
+ formatted: formatCode(message.code, options)
+ }
+ });
+ break;
}
-
- if (message.data.doc) {
- try {
- doc = prettier.__debug.formatDoc(
- prettier.__debug.printToDoc(message.data.text, options),
- { parser: "babylon" }
- );
- } catch (e) {
- doc = String(e);
- }
- }
-
- if (message.data.formatted2) {
- formatted2 = formatCode(formatted, options);
- }
-
- self.postMessage({
- formatted: formatted,
- doc: doc,
- ast: ast,
- formatted2: formatted2,
- version: prettier.version
- });
};
function formatCode(text, options) {
diff --git a/website/webpack.config.js b/website/webpack.config.js
index ac7d0574..2de88e38 100644
--- a/website/webpack.config.js
+++ b/website/webpack.config.js
@@ -15,9 +15,14 @@ module.exports = {
exclude: /node_modules/,
loader: "babel-loader",
options: {
- presets: ["env"]
+ presets: ["env", "react"]
}
}
]
+ },
+ externals: {
+ codemirror: "CodeMirror",
+ react: "React",
+ "react-dom": "ReactDOM"
}
};
diff --git a/website/yarn.lock b/website/yarn.lock
index 42fb01db..7ffd07b9 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -884,7 +884,7 @@ babel-preset-flow@^6.23.0:
dependencies:
babel-plugin-transform-flow-strip-types "^6.22.0"
-babel-preset-react@^6.24.1:
+babel-preset-react@6.24.1, babel-preset-react@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380"
dependencies:
@@ -1436,6 +1436,10 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+codemirror@5.36.0:
+ version "5.36.0"
+ resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.36.0.tgz#1172ad9dc298056c06e0b34e5ccd23825ca15b40"
+
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@@ -3428,6 +3432,10 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
+lz-string@1.4.4:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
+
make-dir@^1.0.0, make-dir@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
@@ -4285,6 +4293,15 @@ react-dom-factories@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.2.tgz#eb7705c4db36fb501b3aa38ff759616aa0ff96e0"
+react-dom@16.3.1:
+ version "16.3.1"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.1.tgz#6a3c90a4fb62f915bdbcf6204422d93a7d4ca573"
+ dependencies:
+ fbjs "^0.8.16"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ prop-types "^15.6.0"
+
react-dom@^15.5.4:
version "15.6.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470"
@@ -4303,6 +4320,15 @@ react-dom@^16.2.0:
object-assign "^4.1.1"
prop-types "^15.6.0"
+react@16.3.1:
+ version "16.3.1"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.3.1.tgz#4a2da433d471251c69b6033ada30e2ed1202cfd8"
+ dependencies:
+ fbjs "^0.8.16"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ prop-types "^15.6.0"
+
react@^15.5.4:
version "15.6.1"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"