import { Button, ClipboardButton } from "./buttons.jsx"; import getCodeSample from "./codeSamples.mjs"; import generateDummyId from "./dummyId.js"; import EditorState from "./EditorState.js"; import { shallowEqual } from "./helpers.js"; import formatMarkdown from "./markdown.js"; import { DebugPanel, InputPanel, OutputPanel } from "./panels.jsx"; import PrettierFormat from "./PrettierFormat.js"; import { Sidebar, SidebarCategory } from "./sidebar/components.jsx"; import { Checkbox } from "./sidebar/inputs.jsx"; import Option from "./sidebar/options.jsx"; import SidebarOptions from "./sidebar/SidebarOptions.jsx"; import * as urlHash from "./urlHash.js"; import * as util from "./util.js"; const { React } = window; const CATEGORIES_ORDER = [ "Global", "Common", "JavaScript", "Markdown", "HTML", "Special", ]; const ISSUES_URL = "https://github.com/prettier/prettier/issues/new?body="; const MAX_LENGTH = 8000 - ISSUES_URL.length; // it seems that GitHub limit is 8195 const COPY_MESSAGE = "\n"; const ENABLED_OPTIONS = [ "parser", "printWidth", "tabWidth", "useTabs", "semi", "singleQuote", "bracketSpacing", "jsxSingleQuote", "objectWrap", "quoteProps", "arrowParens", "trailingComma", "proseWrap", "htmlWhitespaceSensitivity", "insertPragma", "requirePragma", // TODO: Enable this after release 3.6 // "checkIgnorePragma", "vueIndentScriptAndStyle", "embeddedLanguageFormatting", "bracketSameLine", "singleAttributePerLine", "experimentalTernaries", "experimentalOperatorPosition", ]; class Playground extends React.Component { constructor(props) { super(); const original = urlHash.read(); const defaultOptions = util.getDefaults( props.availableOptions, ENABLED_OPTIONS, ); const options = Object.assign(defaultOptions, original.options); // 0.0.0 ~ 0.0.10 if (options.parser === "babylon") { options.parser = "babel"; } // 0.0.0 ~ 0.0.10 if (options.useFlowParser) { options.parser ??= "flow"; } // 1.8.2 ~ 1.9.0 if (typeof options.proseWrap === "boolean") { options.proseWrap = options.proseWrap ? "always" : "never"; } // 0.0.0 ~ 1.9.0 if (typeof options.trailingComma === "boolean") { options.trailingComma = options.trailingComma ? "es5" : "none"; } // 0.17.0 ~ 2.4.0 if (options.jsxBracketSameLine) { delete options.jsxBracketSameLine; options.bracketSameLine ??= options.jsxBracketSameLine; } const codeSample = getCodeSample(options.parser); const content = original.content || codeSample; const isDocExplorer = options.parser === "doc-explorer"; const selection = {}; this.state = { content, options, selection }; this.handleOptionValueChange = this.handleOptionValueChange.bind(this); this.setContent = (content) => this.setState({ content }); this.clearContent = this.setContent.bind(this, ""); this.resetOptions = () => this.setState({ options: defaultOptions }); this.setSelection = (selection) => { this.setState({ selection }); if (this.state.trackCursorOffset) { this.handleOptionValueChange( this.cursorOffsetOption, util.convertSelectionToRange(selection, this.state.content)[0], ); } }; this.setSelectionAsRange = () => { const { selection, content, options } = this.state; const [rangeStart, rangeEnd] = util.convertSelectionToRange( selection, content, ); const updatedOptions = { ...options, rangeStart, rangeEnd }; if (rangeStart === rangeEnd) { delete updatedOptions.rangeStart; delete updatedOptions.rangeEnd; } this.setState({ options: updatedOptions }); }; this.enabledOptions = orderOptions(props.availableOptions, ENABLED_OPTIONS); this.rangeStartOption = props.availableOptions.find( (opt) => opt.name === "rangeStart", ); this.rangeEndOption = props.availableOptions.find( (opt) => opt.name === "rangeEnd", ); this.cursorOffsetOption = props.availableOptions.find( (opt) => opt.name === "cursorOffset", ); this.formatInput = this.formatInput.bind(this); this.insertDummyId = this.insertDummyId.bind(this); } componentDidUpdate(_, prevState) { const { content, options } = this.state; if ( !shallowEqual(prevState.options, this.state.options) || prevState.content !== content ) { urlHash.replace({ content, options }); } } handleOptionValueChange(option, value) { this.setState((state) => { const options = { ...state.options }; if (option.type === "int" && Number.isNaN(value)) { delete options[option.name]; } else { options[option.name] = value; } const content = state.content === "" || state.content === getCodeSample(state.options.parser) ? getCodeSample(options.parser) : state.content; return { options, content }; }); } getMarkdown({ formatted, reformatted, full, doc }) { const { content, options } = this.state; const { availableOptions, version } = this.props; const orderedOptions = orderOptions(availableOptions, [ ...ENABLED_OPTIONS, "rangeStart", "rangeEnd", "cursorOffset", ]); const cliOptions = util.buildCliArgs(orderedOptions, options); return formatMarkdown({ input: content, output: formatted, output2: reformatted, doc, version, url: window.location.href, options, cliOptions, full, }); } formatInput() { if (this.state.options.parser !== "doc-explorer") { return; } const { content, selection } = this.state; return this.props.worker .format(content, { parser: "__js_expression", cursorOffset: util.convertSelectionToRange(selection, content)[0], }) .then(({ error, formatted, cursorOffset }) => { if (error) { return; } this.setState({ content: formatted, selection: util.convertOffsetToSelection(cursorOffset, formatted), }); }); } insertDummyId() { const { content, selection } = this.state; const dummyId = generateDummyId(); const range = util.convertSelectionToRange(selection, content); const modifiedContent = content.slice(0, range[0]) + dummyId + content.slice(range[1]); this.setState({ content: modifiedContent, selection: util.convertOffsetToSelection( range[0] + dummyId.length, modifiedContent, ), }); } render() { const { worker } = this.props; const { content, options } = this.state; return ( {(editorState) => ( {({ formatted, debug, cursorOffset }) => { const fullReport = this.getMarkdown({ formatted, reformatted: debug.reformatted, full: true, }); const showFullReport = encodeURIComponent(fullReport).length < MAX_LENGTH; const isDocExplorer = options.parser === "doc-explorer"; return ( <>
{isDocExplorer ? null : ( )} {isDocExplorer ? null : ( )} {isDocExplorer ? null : ( )} {isDocExplorer ? null : ( )} {isDocExplorer ? null : ( )} {isDocExplorer ? null : ( )} {isDocExplorer ? null : ( )} {editorState.showDoc && !isDocExplorer ? ( this.getMarkdown({ doc: debug.doc })} disabled={!debug.doc} > Copy doc ) : null}
{editorState.showInput ? ( ) : null} {editorState.showAst ? ( ) : null} {editorState.showPreprocessedAst && !isDocExplorer ? ( ) : null} {editorState.showDoc && !isDocExplorer ? ( ) : null} {editorState.showComments && !isDocExplorer ? ( ) : null} {editorState.showOutput ? ( ) : null} {editorState.showSecondFormat && !isDocExplorer ? ( ) : null}
Copy config JSON
Copy link this.getMarkdown({ formatted, reformatted: debug.reformatted, }) } > Copy markdown (showFullReport ? "" : fullReport)} > Report issue
); }}
)}
); } } function orderOptions(availableOptions, order) { const optionsByName = {}; for (const option of availableOptions) { optionsByName[option.name] = option; } return order.map((name) => optionsByName[name]); } function getReportLink(reportBody) { return `${ISSUES_URL}${encodeURIComponent(reportBody)}`; } function getSecondFormat(formatted, reformatted) { return formatted === "" ? "" : formatted === reformatted ? "✓ Second format is unchanged." : reformatted; } export default Playground;