update
parent
243c99b821
commit
4cee59a9cf
|
@ -1,357 +1,425 @@
|
||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
/* eslint no-var: off, strict: off, prefer-arrow-callback: off */
|
/* eslint no-var: off, strict: off, prefer-arrow-callback: off */
|
||||||
/* global Clipboard CodeMirror formatMarkdown LZString */
|
/* global Clipboard CodeMirror formatMarkdown LZString preact */
|
||||||
|
|
||||||
var prettierVersion = "?";
|
var h = preact.h;
|
||||||
var inputEditor;
|
|
||||||
var docEditor;
|
|
||||||
var astEditor;
|
|
||||||
var outputEditor;
|
|
||||||
var output2Editor;
|
|
||||||
|
|
||||||
|
var CATEGORIES = ["Global", "JavaScript", "Markdown", "Special"];
|
||||||
var OPTIONS = [
|
var OPTIONS = [
|
||||||
|
"parser",
|
||||||
"printWidth",
|
"printWidth",
|
||||||
"tabWidth",
|
"tabWidth",
|
||||||
"singleQuote",
|
|
||||||
"trailingComma",
|
|
||||||
"bracketSpacing",
|
|
||||||
"jsxBracketSameLine",
|
|
||||||
"parser",
|
|
||||||
"semi",
|
|
||||||
"useTabs",
|
"useTabs",
|
||||||
"insertPragma",
|
{ name: "semi", inverted: true },
|
||||||
"requirePragma",
|
"singleQuote",
|
||||||
"proseWrap",
|
{ name: "bracketSpacing", inverted: true },
|
||||||
|
"jsxBracketSameLine",
|
||||||
"arrowParens",
|
"arrowParens",
|
||||||
"rangeStart",
|
"trailingComma",
|
||||||
"rangeEnd",
|
"proseWrap",
|
||||||
"doc",
|
"insertPragma",
|
||||||
"ast",
|
"requirePragma"
|
||||||
"output2"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var IDEMPOTENT_MESSAGE = "✓ Second format is unchanged.";
|
// ---------- Options components -----------
|
||||||
|
|
||||||
var worker = new Worker("/worker.js");
|
function BooleanOption(props) {
|
||||||
|
function maybeInvert(value) {
|
||||||
const DEFAULT_OPTIONS = {
|
return props.option.inverted ? !value : value;
|
||||||
options: undefined,
|
|
||||||
content: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
function createRangeOverlay(rangeStartLocation, rangeEndLocation) {
|
|
||||||
var rangeStartLine = rangeStartLocation.line;
|
|
||||||
var rangeEndLine = rangeEndLocation.line;
|
|
||||||
var rangeStartPos = rangeStartLocation.pos;
|
|
||||||
var rangeEndPos = rangeEndLocation.pos;
|
|
||||||
var isEndOnSameLineAsStart = rangeStartLine === rangeEndLine;
|
|
||||||
var highlightedToken = "searching";
|
|
||||||
|
|
||||||
return {
|
|
||||||
token: function(stream) {
|
|
||||||
var currentLine = stream.lineOracle.line;
|
|
||||||
|
|
||||||
// we are on the line containing rangeStart
|
|
||||||
if (currentLine === rangeStartLine) {
|
|
||||||
// on the same line as, but not reached rangeStart yet,
|
|
||||||
// jump straight to it
|
|
||||||
if (rangeStartPos > stream.pos) {
|
|
||||||
stream.pos = rangeStartPos;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// the rangeEnd is on the same line as the rangeStart
|
return h(
|
||||||
if (isEndOnSameLineAsStart) {
|
"label",
|
||||||
// we are still within the range,
|
null,
|
||||||
// keep iterating along the string stream,
|
h("input", {
|
||||||
// marking it as highlighted
|
type: "checkbox",
|
||||||
if (stream.pos < rangeEndPos) {
|
checked: maybeInvert(props.value),
|
||||||
stream.pos += 1;
|
onChange: function(ev) {
|
||||||
return highlightedToken;
|
props.onOptionChange(props.option, maybeInvert(ev.target.checked));
|
||||||
}
|
}
|
||||||
// we've moved outside of the range
|
}),
|
||||||
// just skip to the end
|
" ",
|
||||||
return stream.skipToEnd();
|
props.option.cliName
|
||||||
}
|
|
||||||
// keep iterating along the string stream,
|
|
||||||
// marking it as highlighted
|
|
||||||
stream.pos += 1;
|
|
||||||
return highlightedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we are on the line containing rangeEnd
|
|
||||||
if (currentLine === rangeEndLine) {
|
|
||||||
// keep iterating along the string stream,
|
|
||||||
// marking it as highlighted
|
|
||||||
if (rangeEndPos > stream.pos) {
|
|
||||||
stream.pos += 1;
|
|
||||||
return highlightedToken;
|
|
||||||
}
|
|
||||||
// we've moved outside of the range
|
|
||||||
// just skip to the end
|
|
||||||
return stream.skipToEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
// we are on a line which is completely included
|
|
||||||
// within the range, mark it all as highlighted
|
|
||||||
if (currentLine > rangeStartLine && currentLine < rangeEndLine) {
|
|
||||||
stream.skipToEnd();
|
|
||||||
return highlightedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no action can be required on the current line
|
|
||||||
// as it must fall outside of the range,
|
|
||||||
// so just skip to the end
|
|
||||||
return stream.skipToEnd();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function indexToEditorLocation(editorContent, index) {
|
|
||||||
var line = 0;
|
|
||||||
var count = 0;
|
|
||||||
var startIndex = 0;
|
|
||||||
for (var c, i = 0; count < index && i < editorContent.length; i++) {
|
|
||||||
count++;
|
|
||||||
c = editorContent[i];
|
|
||||||
if (c === "\n") {
|
|
||||||
line++;
|
|
||||||
startIndex = count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
line: line,
|
|
||||||
pos: count - startIndex
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = function() {
|
|
||||||
var state = (function loadState(hash) {
|
|
||||||
var parsed;
|
|
||||||
try {
|
|
||||||
// providing backwards support for old json encoded URIComponent
|
|
||||||
if (hash.indexOf("%7B%22") !== -1) {
|
|
||||||
parsed = JSON.parse(decodeURIComponent(hash));
|
|
||||||
} else {
|
|
||||||
parsed = JSON.parse(LZString.decompressFromEncodedURIComponent(hash));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return DEFAULT_OPTIONS;
|
|
||||||
}
|
|
||||||
// Support old links with the deprecated "postcss" value for the parser option.
|
|
||||||
if (parsed && parsed.options && parsed.options.parser === "postcss") {
|
|
||||||
parsed.options.parser = "css";
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed || DEFAULT_OPTIONS;
|
|
||||||
})(location.hash.slice(1));
|
|
||||||
|
|
||||||
worker.onmessage = function(message) {
|
|
||||||
if (prettierVersion === "?") {
|
|
||||||
prettierVersion = message.data.version;
|
|
||||||
|
|
||||||
var link = document.createElement("a");
|
|
||||||
var match = prettierVersion.match(/^\d+\.\d+\.\d+-pr.(\d+)$/);
|
|
||||||
if (match) {
|
|
||||||
link.href = "https://github.com/prettier/prettier/pull/" + match[1];
|
|
||||||
link.textContent = "PR #" + match[1];
|
|
||||||
prettierVersion = "pr-" + match[1];
|
|
||||||
} else {
|
|
||||||
if (prettierVersion.match(/\.0$/)) {
|
|
||||||
link.href =
|
|
||||||
"https://github.com/prettier/prettier/releases/tag/" +
|
|
||||||
prettierVersion;
|
|
||||||
} else {
|
|
||||||
link.href =
|
|
||||||
"https://github.com/prettier/prettier/blob/master/CHANGELOG.md#" +
|
|
||||||
prettierVersion.replace(/\./g, "");
|
|
||||||
}
|
|
||||||
link.textContent = "v" + prettierVersion;
|
|
||||||
}
|
|
||||||
document.getElementById("version").appendChild(link);
|
|
||||||
}
|
|
||||||
if (outputEditor && docEditor && astEditor) {
|
|
||||||
outputEditor.setValue(message.data.formatted);
|
|
||||||
docEditor.setValue(message.data.doc || "");
|
|
||||||
astEditor.setValue(message.data.ast || "");
|
|
||||||
output2Editor.setValue(
|
|
||||||
message.data.formatted === ""
|
|
||||||
? ""
|
|
||||||
: message.data.formatted2 === message.data.formatted
|
|
||||||
? IDEMPOTENT_MESSAGE
|
|
||||||
: message.data.formatted2 || ""
|
|
||||||
);
|
);
|
||||||
document.getElementById("button-report-issue").search =
|
|
||||||
"body=" + encodeURIComponent(createMarkdown(true));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Warm up the worker (load the current parser while CodeMirror loads)
|
function ChoiceOption(props) {
|
||||||
worker.postMessage({ text: "", options: state.options });
|
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
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
state.options && setOptions(state.options);
|
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));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var editorOptions = {
|
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) {
|
||||||
|
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,
|
lineNumbers: true,
|
||||||
keyMap: "sublime",
|
keyMap: "sublime",
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: true,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
showCursorWhenSelecting: true,
|
showCursorWhenSelecting: true,
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
mode: "jsx"
|
mode: props.mode
|
||||||
};
|
},
|
||||||
inputEditor = CodeMirror.fromTextArea(
|
value: props.value,
|
||||||
document.getElementById("input-editor"),
|
onChange: props.onChange
|
||||||
editorOptions
|
|
||||||
);
|
|
||||||
docEditor = CodeMirror.fromTextArea(document.getElementById("doc-editor"), {
|
|
||||||
readOnly: true,
|
|
||||||
lineNumbers: false,
|
|
||||||
mode: "jsx"
|
|
||||||
});
|
});
|
||||||
astEditor = CodeMirror.fromTextArea(document.getElementById("ast-editor"), {
|
}
|
||||||
readOnly: true,
|
|
||||||
lineNumbers: false,
|
function OutputEditor(props) {
|
||||||
mode: "jsx"
|
return h(Editor, {
|
||||||
});
|
options: {
|
||||||
outputEditor = CodeMirror.fromTextArea(
|
|
||||||
document.getElementById("output-editor"),
|
|
||||||
{
|
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
mode: "jsx"
|
mode: props.mode
|
||||||
|
},
|
||||||
|
value: props.value
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
output2Editor = CodeMirror.fromTextArea(
|
function Button(props) {
|
||||||
document.getElementById("output2-editor"),
|
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",
|
||||||
{
|
{
|
||||||
readOnly: true,
|
href:
|
||||||
lineNumbers: true,
|
"https://github.com/prettier/prettier/issues/new?body=" +
|
||||||
mode: "jsx"
|
encodeURIComponent(props.reportBody),
|
||||||
}
|
target: "_blank",
|
||||||
|
rel: "noopener",
|
||||||
|
class: "btn"
|
||||||
|
},
|
||||||
|
"Report issue"
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
var editors = document.querySelectorAll(".editor");
|
|
||||||
for (var i = 0; i < editors.length; i++) {
|
|
||||||
editors[i].classList.remove("loading");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditorStyles();
|
var App = createClass({
|
||||||
|
state: Object.assign({ loaded: false, formatted: "" }, loadPersistedState()),
|
||||||
|
|
||||||
inputEditor.setValue(state.content);
|
_format: function() {
|
||||||
inputEditor.on("change", formatAsync);
|
this._worker.postMessage(
|
||||||
formatAsync();
|
{ type: "format", code: this.state.content, options: this.state.options },
|
||||||
|
function(message) {
|
||||||
|
this.setState({ formatted: message.formatted });
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
persistState(this.state);
|
||||||
|
},
|
||||||
|
|
||||||
document.querySelector(".options-container").onchange = formatAsync;
|
handleOptionChange: function(option, value) {
|
||||||
|
this.setState(function(state) {
|
||||||
document.getElementById("button-clear").onclick = function() {
|
return {
|
||||||
inputEditor.setValue("");
|
options: Object.assign({}, state.options, { [option.name]: value })
|
||||||
};
|
};
|
||||||
|
}, this._format.bind(this));
|
||||||
var optionsElement = document.getElementById("options-details");
|
},
|
||||||
document.getElementById("button-options").onclick = function() {
|
handleOptionsOpen: function() {
|
||||||
var classes = optionsElement.classList;
|
this.setState(
|
||||||
if (classes.contains("open")) {
|
function(state) {
|
||||||
classes.remove("open");
|
return {
|
||||||
this.innerHTML = "Show options";
|
editorState: Object.assign({}, state.editorState, {
|
||||||
} else {
|
optionsOpen: !state.editorState.optionsOpen
|
||||||
classes.add("open");
|
|
||||||
this.innerHTML = "Hide options";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var clipboard = new Clipboard("#button-copy-link, #button-copy-markdown", {
|
|
||||||
text: function(trigger) {
|
|
||||||
switch (trigger.id) {
|
|
||||||
case "button-copy-link":
|
|
||||||
return window.location.href;
|
|
||||||
case "button-copy-markdown":
|
|
||||||
return createMarkdown();
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clipboard.on("success", function(e) {
|
|
||||||
showTooltip(e.trigger, "Copied!");
|
|
||||||
});
|
|
||||||
clipboard.on("error", function(e) {
|
|
||||||
showTooltip(e.trigger, "Press ctrl+c to copy");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function setOptions(options) {
|
|
||||||
OPTIONS.forEach(function(option) {
|
|
||||||
var elem = document.getElementById(option);
|
|
||||||
if (elem.tagName === "SELECT") {
|
|
||||||
elem.value = options[option];
|
|
||||||
} else if (elem.type === "number") {
|
|
||||||
elem.value = options[option];
|
|
||||||
} else {
|
|
||||||
var isInverted = elem.hasAttribute("data-inverted");
|
|
||||||
elem.checked = isInverted ? !options[option] : options[option];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOptions() {
|
|
||||||
var options = {};
|
|
||||||
OPTIONS.forEach(function(option) {
|
|
||||||
var elem = document.getElementById(option);
|
|
||||||
if (elem.tagName === "SELECT") {
|
|
||||||
options[option] = elem.value;
|
|
||||||
} else if (elem.type === "number") {
|
|
||||||
if (elem.value !== "") {
|
|
||||||
options[option] = Number(elem.value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var isInverted = elem.hasAttribute("data-inverted");
|
|
||||||
options[option] = isInverted ? !elem.checked : elem.checked;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCLIOptions() {
|
|
||||||
return OPTIONS.sort()
|
|
||||||
.map(function(option) {
|
|
||||||
var elem = document.getElementById(option);
|
|
||||||
var match = elem.parentNode.textContent.match(/--\S+/);
|
|
||||||
if (!match) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var name = match[0];
|
|
||||||
if (elem.tagName === "SELECT") {
|
|
||||||
if (elem.value === elem.options[0].value) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [name, elem.value];
|
|
||||||
} else if (elem.type === "number") {
|
|
||||||
if (elem.value === elem.getAttribute("value")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [name, elem.value];
|
|
||||||
} else if (elem.type === "checkbox") {
|
|
||||||
if (!elem.checked) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [name, true];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
};
|
||||||
|
},
|
||||||
|
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-container" },
|
||||||
|
h(Options, {
|
||||||
|
availableOptions: state.availableOptions,
|
||||||
|
options: state.options,
|
||||||
|
open: editorState.optionsOpen,
|
||||||
|
onOptionChange: this.handleOptionChange.bind(this)
|
||||||
|
}),
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
return cliOptions;
|
||||||
|
}, [])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceHash(hash) {
|
return { postMessage: postMessage };
|
||||||
if (
|
}
|
||||||
typeof URL === "function" &&
|
|
||||||
typeof history === "object" &&
|
// -------- UTILITY FUNCTIONS --------
|
||||||
typeof history.replaceState === "function"
|
|
||||||
) {
|
function loadPersistedState() {
|
||||||
var url = new URL(location);
|
var editorState = { optionsOpen: false };
|
||||||
url.hash = hash;
|
try {
|
||||||
history.replaceState(null, null, url);
|
Object.assign(
|
||||||
} else {
|
editorState,
|
||||||
location.hash = hash;
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,104 +436,62 @@ function getCodemirrorMode(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputEditorOverlay;
|
function getOptions(supportedOptions) {
|
||||||
|
supportedOptions = supportedOptions.reduce(function(acc, opt) {
|
||||||
|
acc[opt.name] = opt;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
function formatAsync() {
|
return OPTIONS.reduce(function(options, opt) {
|
||||||
var options = getOptions();
|
opt = typeof opt === "string" ? { name: opt } : opt;
|
||||||
setEditorStyles();
|
if (!supportedOptions[opt.name]) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
var value = LZString.compressToEncodedURIComponent(
|
var modified = Object.assign({}, opt, supportedOptions[opt.name]);
|
||||||
JSON.stringify(
|
modified.cliName =
|
||||||
Object.assign({ content: inputEditor.getValue(), options: options })
|
"--" +
|
||||||
)
|
(opt.inverted ? "no-" : "") +
|
||||||
);
|
opt.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||||
replaceHash(value);
|
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 (
|
if (
|
||||||
typeof options.rangeStart === "number" &&
|
typeof URL === "function" &&
|
||||||
typeof options.rangeEnd === "number"
|
typeof history === "object" &&
|
||||||
|
typeof history.replaceState === "function"
|
||||||
) {
|
) {
|
||||||
var rangeStartLocation = indexToEditorLocation(
|
var url = new URL(location);
|
||||||
inputEditor.getValue(),
|
url.hash = hash;
|
||||||
options.rangeStart
|
history.replaceState(null, null, url);
|
||||||
);
|
} else {
|
||||||
var rangeEndLocation = indexToEditorLocation(
|
location.hash = hash;
|
||||||
inputEditor.getValue(),
|
}
|
||||||
options.rangeEnd
|
|
||||||
);
|
|
||||||
inputEditor.removeOverlay(inputEditorOverlay);
|
|
||||||
inputEditorOverlay = createRangeOverlay(
|
|
||||||
rangeStartLocation,
|
|
||||||
rangeEndLocation
|
|
||||||
);
|
|
||||||
inputEditor.addOverlay(inputEditorOverlay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.postMessage({
|
function getCodeExample(parser) {
|
||||||
text: inputEditor.getValue() || getExample(options.parser),
|
|
||||||
options: options,
|
|
||||||
ast: options.ast,
|
|
||||||
doc: options.doc,
|
|
||||||
formatted2: options.output2
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEditorStyles() {
|
|
||||||
var options = getOptions();
|
|
||||||
|
|
||||||
inputEditor.setOption("placeholder", getExample(options.parser));
|
|
||||||
|
|
||||||
var mode = getCodemirrorMode(options);
|
|
||||||
inputEditor.setOption("mode", mode);
|
|
||||||
outputEditor.setOption("mode", mode);
|
|
||||||
output2Editor.setOption("mode", mode);
|
|
||||||
|
|
||||||
inputEditor.setOption("rulers", [
|
|
||||||
{ column: options.printWidth, color: "#eeeeee" }
|
|
||||||
]);
|
|
||||||
|
|
||||||
[outputEditor, output2Editor].forEach(function(editor) {
|
|
||||||
editor.setOption("rulers", [
|
|
||||||
{ column: options.printWidth, color: "#444444" }
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
document.querySelector(".ast").style.display = options.ast ? "" : "none";
|
|
||||||
document.querySelector(".doc").style.display = options.doc ? "" : "none";
|
|
||||||
document.querySelector(".output2").style.display = options.output2
|
|
||||||
? ""
|
|
||||||
: "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMarkdown(full) {
|
|
||||||
var output = outputEditor.getValue();
|
|
||||||
var output2 = output2Editor.getValue();
|
|
||||||
var options = getOptions();
|
|
||||||
var input = inputEditor.getValue() || getExample(options.parser);
|
|
||||||
var cliOptions = getCLIOptions();
|
|
||||||
var markdown = formatMarkdown(
|
|
||||||
input,
|
|
||||||
output,
|
|
||||||
output2 === IDEMPOTENT_MESSAGE ? "" : output2,
|
|
||||||
prettierVersion,
|
|
||||||
window.location.href,
|
|
||||||
options,
|
|
||||||
cliOptions,
|
|
||||||
full
|
|
||||||
);
|
|
||||||
return markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTooltip(elem, text) {
|
|
||||||
var tooltip = document.createElement("span");
|
|
||||||
tooltip.className = "tooltip";
|
|
||||||
tooltip.textContent = text;
|
|
||||||
elem.appendChild(tooltip);
|
|
||||||
window.setTimeout(function() {
|
|
||||||
elem.removeChild(tooltip);
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getExample(parser) {
|
|
||||||
switch (parser) {
|
switch (parser) {
|
||||||
case "babylon":
|
case "babylon":
|
||||||
return [
|
return [
|
||||||
|
@ -653,3 +679,15 @@ function getExample(parser) {
|
||||||
return "";
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,655 @@
|
||||||
|
/* eslint-env browser */
|
||||||
|
/* eslint no-var: off, strict: off, prefer-arrow-callback: off */
|
||||||
|
/* global Clipboard CodeMirror formatMarkdown LZString */
|
||||||
|
|
||||||
|
var prettierVersion = "?";
|
||||||
|
var inputEditor;
|
||||||
|
var docEditor;
|
||||||
|
var astEditor;
|
||||||
|
var outputEditor;
|
||||||
|
var output2Editor;
|
||||||
|
|
||||||
|
var OPTIONS = [
|
||||||
|
"printWidth",
|
||||||
|
"tabWidth",
|
||||||
|
"singleQuote",
|
||||||
|
"trailingComma",
|
||||||
|
"bracketSpacing",
|
||||||
|
"jsxBracketSameLine",
|
||||||
|
"parser",
|
||||||
|
"semi",
|
||||||
|
"useTabs",
|
||||||
|
"insertPragma",
|
||||||
|
"requirePragma",
|
||||||
|
"proseWrap",
|
||||||
|
"arrowParens",
|
||||||
|
"rangeStart",
|
||||||
|
"rangeEnd",
|
||||||
|
"doc",
|
||||||
|
"ast",
|
||||||
|
"output2"
|
||||||
|
];
|
||||||
|
|
||||||
|
var IDEMPOTENT_MESSAGE = "✓ Second format is unchanged.";
|
||||||
|
|
||||||
|
var worker = new Worker("/worker.js");
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
options: undefined,
|
||||||
|
content: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
function createRangeOverlay(rangeStartLocation, rangeEndLocation) {
|
||||||
|
var rangeStartLine = rangeStartLocation.line;
|
||||||
|
var rangeEndLine = rangeEndLocation.line;
|
||||||
|
var rangeStartPos = rangeStartLocation.pos;
|
||||||
|
var rangeEndPos = rangeEndLocation.pos;
|
||||||
|
var isEndOnSameLineAsStart = rangeStartLine === rangeEndLine;
|
||||||
|
var highlightedToken = "searching";
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: function(stream) {
|
||||||
|
var currentLine = stream.lineOracle.line;
|
||||||
|
|
||||||
|
// we are on the line containing rangeStart
|
||||||
|
if (currentLine === rangeStartLine) {
|
||||||
|
// on the same line as, but not reached rangeStart yet,
|
||||||
|
// jump straight to it
|
||||||
|
if (rangeStartPos > stream.pos) {
|
||||||
|
stream.pos = rangeStartPos;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// the rangeEnd is on the same line as the rangeStart
|
||||||
|
if (isEndOnSameLineAsStart) {
|
||||||
|
// we are still within the range,
|
||||||
|
// keep iterating along the string stream,
|
||||||
|
// marking it as highlighted
|
||||||
|
if (stream.pos < rangeEndPos) {
|
||||||
|
stream.pos += 1;
|
||||||
|
return highlightedToken;
|
||||||
|
}
|
||||||
|
// we've moved outside of the range
|
||||||
|
// just skip to the end
|
||||||
|
return stream.skipToEnd();
|
||||||
|
}
|
||||||
|
// keep iterating along the string stream,
|
||||||
|
// marking it as highlighted
|
||||||
|
stream.pos += 1;
|
||||||
|
return highlightedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are on the line containing rangeEnd
|
||||||
|
if (currentLine === rangeEndLine) {
|
||||||
|
// keep iterating along the string stream,
|
||||||
|
// marking it as highlighted
|
||||||
|
if (rangeEndPos > stream.pos) {
|
||||||
|
stream.pos += 1;
|
||||||
|
return highlightedToken;
|
||||||
|
}
|
||||||
|
// we've moved outside of the range
|
||||||
|
// just skip to the end
|
||||||
|
return stream.skipToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are on a line which is completely included
|
||||||
|
// within the range, mark it all as highlighted
|
||||||
|
if (currentLine > rangeStartLine && currentLine < rangeEndLine) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return highlightedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no action can be required on the current line
|
||||||
|
// as it must fall outside of the range,
|
||||||
|
// so just skip to the end
|
||||||
|
return stream.skipToEnd();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexToEditorLocation(editorContent, index) {
|
||||||
|
var line = 0;
|
||||||
|
var count = 0;
|
||||||
|
var startIndex = 0;
|
||||||
|
for (var c, i = 0; count < index && i < editorContent.length; i++) {
|
||||||
|
count++;
|
||||||
|
c = editorContent[i];
|
||||||
|
if (c === "\n") {
|
||||||
|
line++;
|
||||||
|
startIndex = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
line: line,
|
||||||
|
pos: count - startIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var state = (function loadState(hash) {
|
||||||
|
var parsed;
|
||||||
|
try {
|
||||||
|
// providing backwards support for old json encoded URIComponent
|
||||||
|
if (hash.indexOf("%7B%22") !== -1) {
|
||||||
|
parsed = JSON.parse(decodeURIComponent(hash));
|
||||||
|
} else {
|
||||||
|
parsed = JSON.parse(LZString.decompressFromEncodedURIComponent(hash));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return DEFAULT_OPTIONS;
|
||||||
|
}
|
||||||
|
// Support old links with the deprecated "postcss" value for the parser option.
|
||||||
|
if (parsed && parsed.options && parsed.options.parser === "postcss") {
|
||||||
|
parsed.options.parser = "css";
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed || DEFAULT_OPTIONS;
|
||||||
|
})(location.hash.slice(1));
|
||||||
|
|
||||||
|
worker.onmessage = function(message) {
|
||||||
|
if (prettierVersion === "?") {
|
||||||
|
prettierVersion = message.data.version;
|
||||||
|
|
||||||
|
var link = document.createElement("a");
|
||||||
|
var match = prettierVersion.match(/^\d+\.\d+\.\d+-pr.(\d+)$/);
|
||||||
|
if (match) {
|
||||||
|
link.href = "https://github.com/prettier/prettier/pull/" + match[1];
|
||||||
|
link.textContent = "PR #" + match[1];
|
||||||
|
prettierVersion = "pr-" + match[1];
|
||||||
|
} else {
|
||||||
|
if (prettierVersion.match(/\.0$/)) {
|
||||||
|
link.href =
|
||||||
|
"https://github.com/prettier/prettier/releases/tag/" +
|
||||||
|
prettierVersion;
|
||||||
|
} else {
|
||||||
|
link.href =
|
||||||
|
"https://github.com/prettier/prettier/blob/master/CHANGELOG.md#" +
|
||||||
|
prettierVersion.replace(/\./g, "");
|
||||||
|
}
|
||||||
|
link.textContent = "v" + prettierVersion;
|
||||||
|
}
|
||||||
|
document.getElementById("version").appendChild(link);
|
||||||
|
}
|
||||||
|
if (outputEditor && docEditor && astEditor) {
|
||||||
|
outputEditor.setValue(message.data.formatted);
|
||||||
|
docEditor.setValue(message.data.doc || "");
|
||||||
|
astEditor.setValue(message.data.ast || "");
|
||||||
|
output2Editor.setValue(
|
||||||
|
message.data.formatted === ""
|
||||||
|
? ""
|
||||||
|
: message.data.formatted2 === message.data.formatted
|
||||||
|
? IDEMPOTENT_MESSAGE
|
||||||
|
: message.data.formatted2 || ""
|
||||||
|
);
|
||||||
|
document.getElementById("button-report-issue").search =
|
||||||
|
"body=" + encodeURIComponent(createMarkdown(true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Warm up the worker (load the current parser while CodeMirror loads)
|
||||||
|
worker.postMessage({ text: "", options: state.options });
|
||||||
|
|
||||||
|
state.options && setOptions(state.options);
|
||||||
|
|
||||||
|
var editorOptions = {
|
||||||
|
lineNumbers: true,
|
||||||
|
keyMap: "sublime",
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
showCursorWhenSelecting: true,
|
||||||
|
tabWidth: 2,
|
||||||
|
mode: "jsx"
|
||||||
|
};
|
||||||
|
inputEditor = CodeMirror.fromTextArea(
|
||||||
|
document.getElementById("input-editor"),
|
||||||
|
editorOptions
|
||||||
|
);
|
||||||
|
docEditor = CodeMirror.fromTextArea(document.getElementById("doc-editor"), {
|
||||||
|
readOnly: true,
|
||||||
|
lineNumbers: false,
|
||||||
|
mode: "jsx"
|
||||||
|
});
|
||||||
|
astEditor = CodeMirror.fromTextArea(document.getElementById("ast-editor"), {
|
||||||
|
readOnly: true,
|
||||||
|
lineNumbers: false,
|
||||||
|
mode: "jsx"
|
||||||
|
});
|
||||||
|
outputEditor = CodeMirror.fromTextArea(
|
||||||
|
document.getElementById("output-editor"),
|
||||||
|
{
|
||||||
|
readOnly: true,
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: "jsx"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
output2Editor = CodeMirror.fromTextArea(
|
||||||
|
document.getElementById("output2-editor"),
|
||||||
|
{
|
||||||
|
readOnly: true,
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: "jsx"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var editors = document.querySelectorAll(".editor");
|
||||||
|
for (var i = 0; i < editors.length; i++) {
|
||||||
|
editors[i].classList.remove("loading");
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditorStyles();
|
||||||
|
|
||||||
|
inputEditor.setValue(state.content);
|
||||||
|
inputEditor.on("change", formatAsync);
|
||||||
|
formatAsync();
|
||||||
|
|
||||||
|
document.querySelector(".options-container").onchange = formatAsync;
|
||||||
|
|
||||||
|
document.getElementById("button-clear").onclick = function() {
|
||||||
|
inputEditor.setValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
var optionsElement = document.getElementById("options-details");
|
||||||
|
document.getElementById("button-options").onclick = function() {
|
||||||
|
var classes = optionsElement.classList;
|
||||||
|
if (classes.contains("open")) {
|
||||||
|
classes.remove("open");
|
||||||
|
this.innerHTML = "Show options";
|
||||||
|
} else {
|
||||||
|
classes.add("open");
|
||||||
|
this.innerHTML = "Hide options";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var clipboard = new Clipboard("#button-copy-link, #button-copy-markdown", {
|
||||||
|
text: function(trigger) {
|
||||||
|
switch (trigger.id) {
|
||||||
|
case "button-copy-link":
|
||||||
|
return window.location.href;
|
||||||
|
case "button-copy-markdown":
|
||||||
|
return createMarkdown();
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
clipboard.on("success", function(e) {
|
||||||
|
showTooltip(e.trigger, "Copied!");
|
||||||
|
});
|
||||||
|
clipboard.on("error", function(e) {
|
||||||
|
showTooltip(e.trigger, "Press ctrl+c to copy");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function setOptions(options) {
|
||||||
|
OPTIONS.forEach(function(option) {
|
||||||
|
var elem = document.getElementById(option);
|
||||||
|
if (elem.tagName === "SELECT") {
|
||||||
|
elem.value = options[option];
|
||||||
|
} else if (elem.type === "number") {
|
||||||
|
elem.value = options[option];
|
||||||
|
} else {
|
||||||
|
var isInverted = elem.hasAttribute("data-inverted");
|
||||||
|
elem.checked = isInverted ? !options[option] : options[option];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions() {
|
||||||
|
var options = {};
|
||||||
|
OPTIONS.forEach(function(option) {
|
||||||
|
var elem = document.getElementById(option);
|
||||||
|
if (elem.tagName === "SELECT") {
|
||||||
|
options[option] = elem.value;
|
||||||
|
} else if (elem.type === "number") {
|
||||||
|
if (elem.value !== "") {
|
||||||
|
options[option] = Number(elem.value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var isInverted = elem.hasAttribute("data-inverted");
|
||||||
|
options[option] = isInverted ? !elem.checked : elem.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCLIOptions() {
|
||||||
|
return OPTIONS.sort()
|
||||||
|
.map(function(option) {
|
||||||
|
var elem = document.getElementById(option);
|
||||||
|
var match = elem.parentNode.textContent.match(/--\S+/);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var name = match[0];
|
||||||
|
if (elem.tagName === "SELECT") {
|
||||||
|
if (elem.value === elem.options[0].value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [name, elem.value];
|
||||||
|
} else if (elem.type === "number") {
|
||||||
|
if (elem.value === elem.getAttribute("value")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [name, elem.value];
|
||||||
|
} else if (elem.type === "checkbox") {
|
||||||
|
if (!elem.checked) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [name, true];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceHash(hash) {
|
||||||
|
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 getCodemirrorMode(options) {
|
||||||
|
switch (options.parser) {
|
||||||
|
case "css":
|
||||||
|
case "less":
|
||||||
|
case "scss":
|
||||||
|
return "css";
|
||||||
|
case "markdown":
|
||||||
|
return "markdown";
|
||||||
|
default:
|
||||||
|
return "jsx";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputEditorOverlay;
|
||||||
|
|
||||||
|
function formatAsync() {
|
||||||
|
var options = getOptions();
|
||||||
|
setEditorStyles();
|
||||||
|
|
||||||
|
var value = LZString.compressToEncodedURIComponent(
|
||||||
|
JSON.stringify(
|
||||||
|
Object.assign({ content: inputEditor.getValue(), options: options })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
replaceHash(value);
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof options.rangeStart === "number" &&
|
||||||
|
typeof options.rangeEnd === "number"
|
||||||
|
) {
|
||||||
|
var rangeStartLocation = indexToEditorLocation(
|
||||||
|
inputEditor.getValue(),
|
||||||
|
options.rangeStart
|
||||||
|
);
|
||||||
|
var rangeEndLocation = indexToEditorLocation(
|
||||||
|
inputEditor.getValue(),
|
||||||
|
options.rangeEnd
|
||||||
|
);
|
||||||
|
inputEditor.removeOverlay(inputEditorOverlay);
|
||||||
|
inputEditorOverlay = createRangeOverlay(
|
||||||
|
rangeStartLocation,
|
||||||
|
rangeEndLocation
|
||||||
|
);
|
||||||
|
inputEditor.addOverlay(inputEditorOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
text: inputEditor.getValue() || getExample(options.parser),
|
||||||
|
options: options,
|
||||||
|
ast: options.ast,
|
||||||
|
doc: options.doc,
|
||||||
|
formatted2: options.output2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEditorStyles() {
|
||||||
|
var options = getOptions();
|
||||||
|
|
||||||
|
inputEditor.setOption("placeholder", getExample(options.parser));
|
||||||
|
|
||||||
|
var mode = getCodemirrorMode(options);
|
||||||
|
inputEditor.setOption("mode", mode);
|
||||||
|
outputEditor.setOption("mode", mode);
|
||||||
|
output2Editor.setOption("mode", mode);
|
||||||
|
|
||||||
|
inputEditor.setOption("rulers", [
|
||||||
|
{ column: options.printWidth, color: "#eeeeee" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
[outputEditor, output2Editor].forEach(function(editor) {
|
||||||
|
editor.setOption("rulers", [
|
||||||
|
{ column: options.printWidth, color: "#444444" }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
document.querySelector(".ast").style.display = options.ast ? "" : "none";
|
||||||
|
document.querySelector(".doc").style.display = options.doc ? "" : "none";
|
||||||
|
document.querySelector(".output2").style.display = options.output2
|
||||||
|
? ""
|
||||||
|
: "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMarkdown(full) {
|
||||||
|
var output = outputEditor.getValue();
|
||||||
|
var output2 = output2Editor.getValue();
|
||||||
|
var options = getOptions();
|
||||||
|
var input = inputEditor.getValue() || getExample(options.parser);
|
||||||
|
var cliOptions = getCLIOptions();
|
||||||
|
var markdown = formatMarkdown(
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
output2 === IDEMPOTENT_MESSAGE ? "" : output2,
|
||||||
|
prettierVersion,
|
||||||
|
window.location.href,
|
||||||
|
options,
|
||||||
|
cliOptions,
|
||||||
|
full
|
||||||
|
);
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltip(elem, text) {
|
||||||
|
var tooltip = document.createElement("span");
|
||||||
|
tooltip.className = "tooltip";
|
||||||
|
tooltip.textContent = text;
|
||||||
|
elem.appendChild(tooltip);
|
||||||
|
window.setTimeout(function() {
|
||||||
|
elem.removeChild(tooltip);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExample(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 <div className='HelloWorld' title={`You are visitor number ${ num }`} onMouseOver={onMouseOver}>",
|
||||||
|
"",
|
||||||
|
" <strong>{ greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() }</strong>",
|
||||||
|
' {greeting.endsWith(",") ? " " : <span style={{color: \'\\grey\'}}>", "</span> }',
|
||||||
|
" <em>",
|
||||||
|
"\t{ greeted }",
|
||||||
|
"\t</em>",
|
||||||
|
" { (silent)",
|
||||||
|
' ? "."',
|
||||||
|
' : "!"}',
|
||||||
|
"",
|
||||||
|
" </div>;",
|
||||||
|
"",
|
||||||
|
"}"
|
||||||
|
].join("\n");
|
||||||
|
case "flow":
|
||||||
|
return [
|
||||||
|
"declare export function graphql<Props, Variables, Component: React$ComponentType<Props>>",
|
||||||
|
" (query: GQLDocument, config?: Config<Props, QueryConfigOptions<Variables>>):",
|
||||||
|
" (Component: Component) => React$ComponentType<$Diff<React$ElementConfig<Component>, {",
|
||||||
|
" 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<number>,",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"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 [
|
||||||
|
"<template>",
|
||||||
|
" <p>Templates are not formatted yet ...",
|
||||||
|
" </p>",
|
||||||
|
"</template>",
|
||||||
|
"",
|
||||||
|
"<script>",
|
||||||
|
"let Prettier = format => { your.js('though') }",
|
||||||
|
"</script>",
|
||||||
|
"",
|
||||||
|
"<style>",
|
||||||
|
".and { css: too! important }",
|
||||||
|
"</style>"
|
||||||
|
].join("\n");
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,693 +0,0 @@
|
||||||
/* eslint-env browser */
|
|
||||||
/* eslint no-var: off, strict: off, prefer-arrow-callback: off */
|
|
||||||
/* global Clipboard CodeMirror formatMarkdown LZString preact */
|
|
||||||
|
|
||||||
var h = preact.h;
|
|
||||||
|
|
||||||
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"
|
|
||||||
];
|
|
||||||
|
|
||||||
// ---------- 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) {
|
|
||||||
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" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "editors-container" },
|
|
||||||
h(Options, {
|
|
||||||
availableOptions: state.availableOptions,
|
|
||||||
options: state.options,
|
|
||||||
open: editorState.optionsOpen,
|
|
||||||
onOptionChange: this.handleOptionChange.bind(this)
|
|
||||||
}),
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
return cliOptions;
|
|
||||||
}, [])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 <div className='HelloWorld' title={`You are visitor number ${ num }`} onMouseOver={onMouseOver}>",
|
|
||||||
"",
|
|
||||||
" <strong>{ greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() }</strong>",
|
|
||||||
' {greeting.endsWith(",") ? " " : <span style={{color: \'\\grey\'}}>", "</span> }',
|
|
||||||
" <em>",
|
|
||||||
"\t{ greeted }",
|
|
||||||
"\t</em>",
|
|
||||||
" { (silent)",
|
|
||||||
' ? "."',
|
|
||||||
' : "!"}',
|
|
||||||
"",
|
|
||||||
" </div>;",
|
|
||||||
"",
|
|
||||||
"}"
|
|
||||||
].join("\n");
|
|
||||||
case "flow":
|
|
||||||
return [
|
|
||||||
"declare export function graphql<Props, Variables, Component: React$ComponentType<Props>>",
|
|
||||||
" (query: GQLDocument, config?: Config<Props, QueryConfigOptions<Variables>>):",
|
|
||||||
" (Component: Component) => React$ComponentType<$Diff<React$ElementConfig<Component>, {",
|
|
||||||
" 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<number>,",
|
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
"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 [
|
|
||||||
"<template>",
|
|
||||||
" <p>Templates are not formatted yet ...",
|
|
||||||
" </p>",
|
|
||||||
"</template>",
|
|
||||||
"",
|
|
||||||
"<script>",
|
|
||||||
"let Prettier = format => { your.js('though') }",
|
|
||||||
"</script>",
|
|
||||||
"",
|
|
||||||
"<style>",
|
|
||||||
".and { css: too! important }",
|
|
||||||
"</style>"
|
|
||||||
].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);
|
|
||||||
}
|
|
Loading…
Reference in New Issue