@sanity/code-input
Version:
Sanity input component for code, powered by CodeMirror
430 lines (429 loc) • 18.2 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from == "object" || typeof from == "function")
for (let key of __getOwnPropNames(from))
!__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
mod
));
var jsxRuntime = require("react/jsx-runtime"), view = require("@codemirror/view"), ui = require("@sanity/ui"), CodeMirror = require("@uiw/react-codemirror"), react = require("react"), index = require("./index.cjs"), language = require("@codemirror/language"), state = require("@codemirror/state"), theme = require("@sanity/ui/theme"), highlight = require("@lezer/highlight"), codemirrorThemes = require("@uiw/codemirror-themes");
function _interopDefaultCompat(e) {
return e && typeof e == "object" && "default" in e ? e : { default: e };
}
var CodeMirror__default = /* @__PURE__ */ _interopDefaultCompat(CodeMirror);
const defaultCodeModes = [
{
name: "groq",
loader: () => import("@codemirror/lang-javascript").then(({ javascriptLanguage }) => javascriptLanguage)
},
{
name: "javascript",
loader: () => import("@codemirror/lang-javascript").then(({ javascript }) => javascript({ jsx: !1 }))
},
{
name: "jsx",
loader: () => import("@codemirror/lang-javascript").then(({ javascript }) => javascript({ jsx: !0 }))
},
{
name: "typescript",
loader: () => import("@codemirror/lang-javascript").then(
({ javascript }) => javascript({ jsx: !1, typescript: !0 })
)
},
{
name: "tsx",
loader: () => import("@codemirror/lang-javascript").then(
({ javascript }) => javascript({ jsx: !0, typescript: !0 })
)
},
{ name: "php", loader: () => import("@codemirror/lang-php").then(({ php }) => php()) },
{ name: "sql", loader: () => import("@codemirror/lang-sql").then(({ sql }) => sql()) },
{
name: "mysql",
loader: () => import("@codemirror/lang-sql").then(({ sql, MySQL }) => sql({ dialect: MySQL }))
},
{ name: "json", loader: () => import("@codemirror/lang-json").then(({ json }) => json()) },
{
name: "markdown",
loader: () => import("@codemirror/lang-markdown").then(({ markdown }) => markdown())
},
{ name: "java", loader: () => import("@codemirror/lang-java").then(({ java }) => java()) },
{ name: "html", loader: () => import("@codemirror/lang-html").then(({ html }) => html()) },
{
name: "csharp",
loader: () => import("@codemirror/legacy-modes/mode/clike").then(
({ csharp }) => language.StreamLanguage.define(csharp)
)
},
{
name: "sh",
loader: () => import("@codemirror/legacy-modes/mode/shell").then(({ shell }) => language.StreamLanguage.define(shell))
},
{
name: "css",
loader: () => import("@codemirror/legacy-modes/mode/css").then(({ css }) => language.StreamLanguage.define(css))
},
{
name: "scss",
loader: () => import("@codemirror/legacy-modes/mode/css").then(({ css }) => language.StreamLanguage.define(css))
},
{
name: "sass",
loader: () => import("@codemirror/legacy-modes/mode/sass").then(({ sass }) => language.StreamLanguage.define(sass))
},
{
name: "ruby",
loader: () => import("@codemirror/legacy-modes/mode/ruby").then(({ ruby }) => language.StreamLanguage.define(ruby))
},
{
name: "python",
loader: () => import("@codemirror/legacy-modes/mode/python").then(
({ python }) => language.StreamLanguage.define(python)
)
},
{
name: "xml",
loader: () => import("@codemirror/legacy-modes/mode/xml").then(({ xml }) => language.StreamLanguage.define(xml))
},
{
name: "yaml",
loader: () => import("@codemirror/legacy-modes/mode/yaml").then(({ yaml }) => language.StreamLanguage.define(yaml))
},
{
name: "golang",
loader: () => import("@codemirror/legacy-modes/mode/go").then(({ go }) => language.StreamLanguage.define(go))
},
{ name: "text", loader: () => {
} },
{ name: "batch", loader: () => {
} }
];
function getBackwardsCompatibleTone(themeCtx) {
return themeCtx.tone !== "neutral" && themeCtx.tone !== "suggest" ? themeCtx.tone : themeCtx.tone === "neutral" ? "default" : "primary";
}
const highlightLineClass = "cm-highlight-line", addLineHighlight = state.StateEffect.define(), removeLineHighlight = state.StateEffect.define(), lineHighlightField = state.StateField.define({
create() {
return view.Decoration.none;
},
update(lines, tr) {
lines = lines.map(tr.changes);
for (const e of tr.effects)
e.is(addLineHighlight) && (lines = lines.update({ add: [lineHighlightMark.range(e.value)] })), e.is(removeLineHighlight) && (lines = lines.update({
filter: (from) => from !== e.value
}));
return lines;
},
toJSON(value, state2) {
const highlightLines = [], iter = value.iter();
for (; iter.value; ) {
const lineNumber = state2.doc.lineAt(iter.from).number;
highlightLines.includes(lineNumber) || highlightLines.push(lineNumber), iter.next();
}
return highlightLines;
},
fromJSON(value, state2) {
const lines = state2.doc.lines, highlights = value.filter((line) => line <= lines).map((line) => lineHighlightMark.range(state2.doc.line(line).from));
highlights.sort((a, b) => a.from - b.from);
try {
return view.Decoration.none.update({
add: highlights
});
} catch (e) {
return console.error(e), view.Decoration.none;
}
},
provide: (f) => view.EditorView.decorations.from(f)
}), lineHighlightMark = view.Decoration.line({
class: highlightLineClass
}), highlightState = {
highlight: lineHighlightField
};
function createCodeMirrorTheme(options) {
const { themeCtx } = options, fallbackTone = getBackwardsCompatibleTone(themeCtx), dark = { color: themeCtx.theme.color.dark[fallbackTone] }, light = { color: themeCtx.theme.color.light[fallbackTone] };
return view.EditorView.baseTheme({
".cm-lineNumbers": {
cursor: "default"
},
".cm-line.cm-line": {
position: "relative"
},
// need set background with pseudoelement so it does not render over selection color
[`.${highlightLineClass}::before`]: {
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: -3,
content: "''",
boxSizing: "border-box"
},
[`&dark .${highlightLineClass}::before`]: {
background: theme.rgba(dark.color.muted.caution.pressed.bg, 0.5)
},
[`&light .${highlightLineClass}::before`]: {
background: theme.rgba(light.color.muted.caution.pressed.bg, 0.75)
}
});
}
const highlightLine = (config) => {
const highlightTheme = createCodeMirrorTheme({ themeCtx: config.theme });
return [
lineHighlightField,
config.readOnly ? [] : view.lineNumbers({
domEventHandlers: {
mousedown: (editorView, lineInfo) => {
const line = editorView.state.doc.lineAt(lineInfo.from);
let isHighlighted = !1;
return editorView.state.field(lineHighlightField).between(line.from, line.to, (from, to, value) => {
if (value)
return isHighlighted = !0, !1;
}), isHighlighted ? editorView.dispatch({ effects: removeLineHighlight.of(line.from) }) : editorView.dispatch({ effects: addLineHighlight.of(line.from) }), config != null && config.onHighlightChange && config.onHighlightChange(editorView.state.toJSON(highlightState).highlight), !0;
}
}
}),
highlightTheme
];
};
function setHighlightedLines(view2, highlightLines) {
const doc = view2.state.doc, lines = doc.lines, allLineNumbers = Array.from({ length: lines }, (x, i) => i + 1);
view2.dispatch({
effects: allLineNumbers.map((lineNumber) => {
const line = doc.line(lineNumber);
return highlightLines != null && highlightLines.includes(lineNumber) ? addLineHighlight.of(line.from) : removeLineHighlight.of(line.from);
})
});
}
function useThemeExtension() {
const themeCtx = ui.useRootTheme();
return react.useMemo(() => {
const fallbackTone = getBackwardsCompatibleTone(themeCtx), dark = { color: themeCtx.theme.color.dark[fallbackTone] }, light = { color: themeCtx.theme.color.light[fallbackTone] };
return view.EditorView.baseTheme({
"&.cm-editor": {
height: "100%"
},
"&.cm-editor.cm-focused": {
outline: "none"
},
// Matching brackets
"&.cm-editor.cm-focused .cm-matchingBracket": {
backgroundColor: "transparent"
},
"&.cm-editor.cm-focused .cm-nonmatchingBracket": {
backgroundColor: "transparent"
},
"&dark.cm-editor.cm-focused .cm-matchingBracket": {
outline: `1px solid ${dark.color.base.border}`
},
"&dark.cm-editor.cm-focused .cm-nonmatchingBracket": {
outline: `1px solid ${dark.color.base.border}`
},
"&light.cm-editor.cm-focused .cm-matchingBracket": {
outline: `1px solid ${light.color.base.border}`
},
"&light.cm-editor.cm-focused .cm-nonmatchingBracket": {
outline: `1px solid ${light.color.base.border}`
},
// Size and padding of gutter
"& .cm-lineNumbers .cm-gutterElement": {
minWidth: "32px !important",
padding: "0 8px !important"
},
"& .cm-gutter.cm-foldGutter": {
width: "0px !important"
},
// Color of gutter
"&dark .cm-gutters": {
color: `${theme.rgba(dark.color.card.enabled.code.fg, 0.5)} !important`,
borderRight: `1px solid ${theme.rgba(dark.color.base.border, 0.5)}`
},
"&light .cm-gutters": {
color: `${theme.rgba(light.color.card.enabled.code.fg, 0.5)} !important`,
borderRight: `1px solid ${theme.rgba(light.color.base.border, 0.5)}`
}
});
}, [themeCtx]);
}
function useCodeMirrorTheme() {
const theme$1 = ui.useTheme();
return react.useMemo(() => {
const { code: codeFont } = theme$1.sanity.fonts, { base, card, dark, syntax } = theme$1.sanity.color;
return codemirrorThemes.createTheme({
theme: dark ? "dark" : "light",
settings: {
background: card.enabled.bg,
foreground: card.enabled.code.fg,
lineHighlight: card.enabled.bg,
fontFamily: codeFont.family,
caret: base.focusRing,
selection: theme.rgba(base.focusRing, 0.2),
selectionMatch: theme.rgba(base.focusRing, 0.4),
gutterBackground: card.disabled.bg,
gutterForeground: card.disabled.code.fg,
gutterActiveForeground: card.enabled.fg
},
styles: [
{
tag: [highlight.tags.heading, highlight.tags.heading2, highlight.tags.heading3, highlight.tags.heading4, highlight.tags.heading5, highlight.tags.heading6],
color: card.enabled.fg
},
{ tag: highlight.tags.angleBracket, color: card.enabled.code.fg },
{ tag: highlight.tags.atom, color: syntax.keyword },
{ tag: highlight.tags.attributeName, color: syntax.attrName },
{ tag: highlight.tags.bool, color: syntax.boolean },
{ tag: highlight.tags.bracket, color: card.enabled.code.fg },
{ tag: highlight.tags.className, color: syntax.className },
{ tag: highlight.tags.comment, color: syntax.comment },
{ tag: highlight.tags.definition(highlight.tags.typeName), color: syntax.function },
{
tag: [
highlight.tags.definition(highlight.tags.variableName),
highlight.tags.function(highlight.tags.variableName),
highlight.tags.className,
highlight.tags.attributeName
],
color: syntax.function
},
{ tag: [highlight.tags.function(highlight.tags.propertyName), highlight.tags.propertyName], color: syntax.function },
{ tag: highlight.tags.keyword, color: syntax.keyword },
{ tag: highlight.tags.null, color: syntax.number },
{ tag: highlight.tags.number, color: syntax.number },
{ tag: highlight.tags.meta, color: card.enabled.code.fg },
{ tag: highlight.tags.operator, color: syntax.operator },
{ tag: highlight.tags.propertyName, color: syntax.property },
{ tag: [highlight.tags.string, highlight.tags.special(highlight.tags.brace)], color: syntax.string },
{ tag: highlight.tags.tagName, color: syntax.className },
{ tag: highlight.tags.typeName, color: syntax.keyword }
]
});
}, [theme$1]);
}
function useFontSizeExtension(props) {
const { fontSize: fontSizeProp } = props, theme2 = ui.useTheme();
return react.useMemo(() => {
const { code: codeFont } = theme2.sanity.fonts, { fontSize, lineHeight } = codeFont.sizes[fontSizeProp] || codeFont.sizes[2];
return view.EditorView.baseTheme({
"&": {
fontSize: ui.rem(fontSize)
},
"& .cm-scroller": {
lineHeight: `${lineHeight / fontSize} !important`
}
});
}, [fontSizeProp, theme2]);
}
var __defProp2 = Object.defineProperty, __defProps = Object.defineProperties, __getOwnPropDescs = Object.getOwnPropertyDescriptors, __getOwnPropSymbols = Object.getOwnPropertySymbols, __hasOwnProp2 = Object.prototype.hasOwnProperty, __propIsEnum = Object.prototype.propertyIsEnumerable, __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
__hasOwnProp2.call(b, prop) && __defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b))
__propIsEnum.call(b, prop) && __defNormalProp(a, prop, b[prop]);
return a;
}, __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)), __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
__hasOwnProp2.call(source, prop) && exclude.indexOf(prop) < 0 && (target[prop] = source[prop]);
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source))
exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop) && (target[prop] = source[prop]);
return target;
};
const CodeMirrorProxy = react.forwardRef(
function(props, ref) {
const _a = props, {
basicSetup: basicSetupProp,
highlightLines,
languageMode,
onHighlightChange,
readOnly,
value
} = _a, codeMirrorProps = __objRest(_a, [
"basicSetup",
"highlightLines",
"languageMode",
"onHighlightChange",
"readOnly",
"value"
]), themeCtx = ui.useRootTheme(), codeMirrorTheme = useCodeMirrorTheme(), [editorView, setEditorView] = react.useState(void 0), themeExtension = useThemeExtension(), fontSizeExtension = useFontSizeExtension({ fontSize: 1 }), languageExtension = useLanguageExtension(languageMode), highlightLineExtension = react.useMemo(
() => highlightLine({
onHighlightChange,
readOnly,
theme: themeCtx
}),
[onHighlightChange, readOnly, themeCtx]
), extensions = react.useMemo(() => {
const baseExtensions = [
themeExtension,
fontSizeExtension,
highlightLineExtension,
view.EditorView.lineWrapping
];
return languageExtension ? [...baseExtensions, languageExtension] : baseExtensions;
}, [fontSizeExtension, highlightLineExtension, languageExtension, themeExtension]);
react.useEffect(() => {
editorView && setHighlightedLines(editorView, highlightLines != null ? highlightLines : []);
}, [editorView, highlightLines, value]);
const initialState = react.useMemo(() => ({
json: {
doc: value != null ? value : "",
selection: {
main: 0,
ranges: [{ anchor: 0, head: 0 }]
},
highlight: highlightLines != null ? highlightLines : []
},
fields: highlightState
}), []), handleCreateEditor = react.useCallback((view2) => {
setEditorView(view2);
}, []), basicSetup = react.useMemo(
() => basicSetupProp != null ? basicSetupProp : {
highlightActiveLine: !1
},
[basicSetupProp]
);
return /* @__PURE__ */ jsxRuntime.jsx(
CodeMirror__default.default,
__spreadProps(__spreadValues({}, codeMirrorProps), {
value,
ref,
extensions,
theme: codeMirrorTheme,
onCreateEditor: handleCreateEditor,
initialState,
basicSetup
})
);
}
);
function useLanguageExtension(mode) {
const codeConfig = react.useContext(index.CodeInputConfigContext), [languageExtension, setLanguageExtension] = react.useState();
return react.useEffect(() => {
var _a;
const codeMode = [...(_a = codeConfig == null ? void 0 : codeConfig.codeModes) != null ? _a : [], ...defaultCodeModes].find((m) => m.name === mode);
codeMode != null && codeMode.loader || console.warn(
`Found no codeMode for language mode ${mode}, syntax highlighting will be disabled.`
);
let active = !0;
return Promise.resolve(codeMode == null ? void 0 : codeMode.loader()).then((extension) => {
active && setLanguageExtension(extension);
}).catch((e) => {
console.error(`Failed to load language mode ${mode}`, e), active && setLanguageExtension(void 0);
}), () => {
active = !1;
};
}, [mode, codeConfig]), languageExtension;
}
exports.default = CodeMirrorProxy;
//# sourceMappingURL=CodeMirrorProxy.cjs.map