UNPKG

@sanity/code-input

Version:

Sanity input component for code, powered by CodeMirror

430 lines (429 loc) • 18.2 kB
"use strict"; 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