@sanity/code-input 2.35.0 → 2.36.0-v2-studio.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +12 -1
- package/{dist/dts → dts}/CodeInput.d.ts +0 -1
- package/{dist/dts → dts}/PreviewCode.d.ts +0 -1
- package/{dist/dts → dts}/config.d.ts +0 -1
- package/{dist/dts → dts}/createHighlightMarkers.d.ts +0 -1
- package/{dist/dts → dts}/deprecatedSchema.d.ts +0 -1
- package/{dist/dts → dts}/editorSupport.d.ts +0 -1
- package/{dist/dts → dts}/getMedia.d.ts +0 -1
- package/dts/groq.d.ts +1 -0
- package/{dist/dts → dts}/schema.d.ts +1 -2
- package/{dist/dts → dts}/types.d.ts +0 -1
- package/lib/@types/css.d.js +2 -1
- package/lib/@types/css.d.js.map +1 -0
- package/lib/CodeInput.js +2 -1
- package/lib/CodeInput.js.map +1 -0
- package/lib/PreviewCode.js +2 -1
- package/lib/PreviewCode.js.map +1 -0
- package/lib/config.js +2 -1
- package/lib/config.js.map +1 -0
- package/lib/createHighlightMarkers.js +2 -1
- package/lib/createHighlightMarkers.js.map +1 -0
- package/lib/deprecatedSchema.js +2 -1
- package/lib/deprecatedSchema.js.map +1 -0
- package/lib/editorSupport.js +2 -1
- package/lib/editorSupport.js.map +1 -0
- package/lib/getMedia.js +2 -1
- package/lib/getMedia.js.map +1 -0
- package/lib/groq.js +5 -1
- package/lib/groq.js.map +1 -0
- package/lib/schema.js +2 -1
- package/lib/schema.js.map +1 -0
- package/lib/types.js +1 -4
- package/lib/types.js.map +1 -0
- package/package.json +54 -21
- package/sanity.json +4 -4
- package/src/@types/css.d.ts +5 -0
- package/src/CodeInput.tsx +447 -0
- package/src/PreviewCode.tsx +88 -0
- package/src/config.ts +45 -0
- package/src/createHighlightMarkers.ts +24 -0
- package/src/deprecatedSchema.ts +19 -0
- package/src/editorSupport.ts +31 -0
- package/src/getMedia.tsx +95 -0
- package/src/groq.ts +629 -0
- package/src/schema.tsx +65 -0
- package/src/types.ts +26 -0
- package/dist/dts/CodeInput.d.ts.map +0 -1
- package/dist/dts/PreviewCode.d.ts.map +0 -1
- package/dist/dts/config.d.ts.map +0 -1
- package/dist/dts/createHighlightMarkers.d.ts.map +0 -1
- package/dist/dts/deprecatedSchema.d.ts.map +0 -1
- package/dist/dts/editorSupport.d.ts.map +0 -1
- package/dist/dts/getMedia.d.ts.map +0 -1
- package/dist/dts/groq.d.ts +0 -376
- package/dist/dts/groq.d.ts.map +0 -1
- package/dist/dts/schema.d.ts.map +0 -1
- package/dist/dts/types.d.ts.map +0 -1
- package/tsconfig.json +0 -26
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import React, {useCallback, useEffect, useImperativeHandle, useRef} from 'react'
|
|
2
|
+
import {FormFieldPresence} from '@sanity/base/presence'
|
|
3
|
+
import {FormField, FormFieldSet} from '@sanity/base/components'
|
|
4
|
+
import {Path} from '@sanity/types'
|
|
5
|
+
import {Card, Select, TextInput} from '@sanity/ui'
|
|
6
|
+
import * as PathUtils from '@sanity/util/paths'
|
|
7
|
+
import {ChangeIndicatorProvider} from '@sanity/base/change-indicators'
|
|
8
|
+
import PatchEvent, {set, unset, setIfMissing} from '@sanity/form-builder/PatchEvent'
|
|
9
|
+
import AceEditor from 'react-ace'
|
|
10
|
+
import styled from 'styled-components'
|
|
11
|
+
import {useId} from '@reach/auto-id'
|
|
12
|
+
import createHighlightMarkers, {highlightMarkersCSS} from './createHighlightMarkers'
|
|
13
|
+
import {CodeInputLanguage, CodeInputType, CodeInputValue} from './types'
|
|
14
|
+
import './editorSupport'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
LANGUAGE_ALIASES,
|
|
18
|
+
ACE_EDITOR_PROPS,
|
|
19
|
+
ACE_SET_OPTIONS,
|
|
20
|
+
SUPPORTED_LANGUAGES,
|
|
21
|
+
SUPPORTED_THEMES,
|
|
22
|
+
DEFAULT_THEME,
|
|
23
|
+
PATH_LANGUAGE,
|
|
24
|
+
PATH_CODE,
|
|
25
|
+
PATH_FILENAME,
|
|
26
|
+
} from './config'
|
|
27
|
+
|
|
28
|
+
const EditorContainer = styled(Card)`
|
|
29
|
+
position: relative;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
z-index: 0;
|
|
33
|
+
|
|
34
|
+
.ace_editor {
|
|
35
|
+
font-family: ${({theme}) => theme.sanity.fonts.code.family};
|
|
36
|
+
font-size: ${({theme}) => theme.sanity.fonts.code.sizes[1]};
|
|
37
|
+
line-height: inherit;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
${highlightMarkersCSS}
|
|
41
|
+
|
|
42
|
+
&:not([disabled]):not([readonly]) {
|
|
43
|
+
&:focus,
|
|
44
|
+
&:focus-within {
|
|
45
|
+
box-shadow: 0 0 0 2px ${({theme}) => theme.sanity.color.base.focusRing};
|
|
46
|
+
background-color: ${({theme}) => theme.sanity.color.base.bg};
|
|
47
|
+
border-color: ${({theme}) => theme.sanity.color.base.focusRing};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`
|
|
51
|
+
|
|
52
|
+
export interface CodeInputProps {
|
|
53
|
+
compareValue?: CodeInputValue
|
|
54
|
+
focusPath: Path
|
|
55
|
+
level: number
|
|
56
|
+
onBlur: () => void
|
|
57
|
+
onChange: (...args: any[]) => void
|
|
58
|
+
onFocus: (path: Path) => void
|
|
59
|
+
presence: FormFieldPresence[]
|
|
60
|
+
readOnly?: boolean
|
|
61
|
+
type: CodeInputType
|
|
62
|
+
value?: CodeInputValue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Returns a string with the mode name if supported (because aliases), otherwise false
|
|
66
|
+
function isSupportedLanguage(mode: string) {
|
|
67
|
+
const alias = LANGUAGE_ALIASES[mode]
|
|
68
|
+
|
|
69
|
+
if (alias) {
|
|
70
|
+
return alias
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const isSupported = SUPPORTED_LANGUAGES.find((lang) => lang.value === mode)
|
|
74
|
+
if (isSupported) {
|
|
75
|
+
return mode
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const CodeInput = React.forwardRef(
|
|
82
|
+
(props: CodeInputProps, ref: React.ForwardedRef<{focus: () => void}>) => {
|
|
83
|
+
const aceEditorRef = useRef<any>()
|
|
84
|
+
const aceEditorId = useId()
|
|
85
|
+
|
|
86
|
+
const {
|
|
87
|
+
onFocus,
|
|
88
|
+
onChange,
|
|
89
|
+
onBlur,
|
|
90
|
+
compareValue,
|
|
91
|
+
value,
|
|
92
|
+
presence,
|
|
93
|
+
type,
|
|
94
|
+
level,
|
|
95
|
+
readOnly,
|
|
96
|
+
focusPath,
|
|
97
|
+
} = props
|
|
98
|
+
|
|
99
|
+
useImperativeHandle(ref, () => ({
|
|
100
|
+
focus: () => {
|
|
101
|
+
aceEditorRef?.current?.editor?.focus()
|
|
102
|
+
},
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
const handleLanguageFocus = useCallback(() => {
|
|
106
|
+
onFocus(PATH_LANGUAGE)
|
|
107
|
+
}, [onFocus])
|
|
108
|
+
|
|
109
|
+
const handleCodeFocus = useCallback(() => {
|
|
110
|
+
onFocus(PATH_CODE)
|
|
111
|
+
}, [onFocus])
|
|
112
|
+
|
|
113
|
+
const handleFilenameFocus = useCallback(() => {
|
|
114
|
+
onFocus(PATH_FILENAME)
|
|
115
|
+
}, [onFocus])
|
|
116
|
+
|
|
117
|
+
const handleFilenameChange = useCallback(
|
|
118
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
119
|
+
const val = event.target.value
|
|
120
|
+
const path = PATH_FILENAME
|
|
121
|
+
|
|
122
|
+
onChange(
|
|
123
|
+
PatchEvent.from([setIfMissing({_type: type.name}), val ? set(val, path) : unset(path)])
|
|
124
|
+
)
|
|
125
|
+
},
|
|
126
|
+
[onChange, type.name]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const getTheme = useCallback(() => {
|
|
130
|
+
const preferredTheme = type.options?.theme
|
|
131
|
+
return preferredTheme && SUPPORTED_THEMES.find((theme) => theme === preferredTheme)
|
|
132
|
+
? preferredTheme
|
|
133
|
+
: DEFAULT_THEME
|
|
134
|
+
}, [type])
|
|
135
|
+
|
|
136
|
+
const handleToggleSelectLine = useCallback(
|
|
137
|
+
(lineNumber: number) => {
|
|
138
|
+
const editorSession = aceEditorRef.current?.editor?.getSession()
|
|
139
|
+
const backgroundMarkers = editorSession?.getMarkers(true)
|
|
140
|
+
const currentHighlightedLines = Object.keys(backgroundMarkers)
|
|
141
|
+
.filter((key) => backgroundMarkers[key].type === 'screenLine')
|
|
142
|
+
.map((key) => backgroundMarkers[key].range.start.row)
|
|
143
|
+
const currentIndex = currentHighlightedLines.indexOf(lineNumber)
|
|
144
|
+
if (currentIndex > -1) {
|
|
145
|
+
// toggle remove
|
|
146
|
+
currentHighlightedLines.splice(currentIndex, 1)
|
|
147
|
+
} else {
|
|
148
|
+
// toggle add
|
|
149
|
+
currentHighlightedLines.push(lineNumber)
|
|
150
|
+
currentHighlightedLines.sort()
|
|
151
|
+
}
|
|
152
|
+
onChange(
|
|
153
|
+
PatchEvent.from(
|
|
154
|
+
set(
|
|
155
|
+
currentHighlightedLines.map(
|
|
156
|
+
(line) =>
|
|
157
|
+
// ace starts at line (row) 0, but we store it starting at line 1
|
|
158
|
+
line + 1
|
|
159
|
+
),
|
|
160
|
+
['highlightedLines']
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
},
|
|
165
|
+
[aceEditorRef, onChange]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const handleGutterMouseDown = useCallback(
|
|
169
|
+
(event: any) => {
|
|
170
|
+
const target = event.domEvent.target
|
|
171
|
+
if (target.classList.contains('ace_gutter-cell')) {
|
|
172
|
+
const row = event.getDocumentPosition().row
|
|
173
|
+
handleToggleSelectLine(row)
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
[handleToggleSelectLine]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const editor = aceEditorRef?.current?.editor
|
|
181
|
+
return () => {
|
|
182
|
+
editor?.session?.removeListener('guttermousedown', handleGutterMouseDown)
|
|
183
|
+
}
|
|
184
|
+
}, [aceEditorRef, handleGutterMouseDown])
|
|
185
|
+
|
|
186
|
+
const handleEditorLoad = useCallback(
|
|
187
|
+
(editor: any) => {
|
|
188
|
+
editor?.on('guttermousedown', handleGutterMouseDown)
|
|
189
|
+
},
|
|
190
|
+
[handleGutterMouseDown]
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const getLanguageAlternatives = useCallback((): {
|
|
194
|
+
title: string
|
|
195
|
+
value: string
|
|
196
|
+
mode?: string
|
|
197
|
+
}[] => {
|
|
198
|
+
const languageAlternatives = type.options?.languageAlternatives
|
|
199
|
+
if (!languageAlternatives) {
|
|
200
|
+
return SUPPORTED_LANGUAGES
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!Array.isArray(languageAlternatives)) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}`
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => {
|
|
210
|
+
const alias = LANGUAGE_ALIASES[val]
|
|
211
|
+
if (alias) {
|
|
212
|
+
// eslint-disable-next-line no-console
|
|
213
|
+
console.warn(
|
|
214
|
+
`'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`,
|
|
215
|
+
val,
|
|
216
|
+
alias,
|
|
217
|
+
alias
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return acc.concat({title, value: alias, mode: mode})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!mode && !SUPPORTED_LANGUAGES.find((lang) => lang.value === val)) {
|
|
224
|
+
// eslint-disable-next-line no-console
|
|
225
|
+
console.warn(
|
|
226
|
+
`'options.languageAlternatives' lists a language which is not supported: "%s", syntax highlighting will be disabled.`,
|
|
227
|
+
val
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return acc.concat({title, value: val, mode})
|
|
232
|
+
}, [])
|
|
233
|
+
}, [type])
|
|
234
|
+
|
|
235
|
+
const handleCodeChange = useCallback(
|
|
236
|
+
(code: string) => {
|
|
237
|
+
const path = PATH_CODE
|
|
238
|
+
const fixedLanguage = type.options?.language
|
|
239
|
+
|
|
240
|
+
onChange(
|
|
241
|
+
PatchEvent.from([
|
|
242
|
+
setIfMissing({_type: type.name, language: fixedLanguage}),
|
|
243
|
+
code ? set(code, path) : unset(path),
|
|
244
|
+
])
|
|
245
|
+
)
|
|
246
|
+
},
|
|
247
|
+
[onChange, type]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
const handleLanguageChange = useCallback(
|
|
251
|
+
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
252
|
+
const val = event.currentTarget.value
|
|
253
|
+
const path = PATH_LANGUAGE
|
|
254
|
+
|
|
255
|
+
onChange(
|
|
256
|
+
PatchEvent.from([setIfMissing({_type: type.name}), val ? set(val, path) : unset(path)])
|
|
257
|
+
)
|
|
258
|
+
},
|
|
259
|
+
[onChange, type.name]
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const languages = getLanguageAlternatives().slice()
|
|
263
|
+
|
|
264
|
+
const selectedLanguage = props?.value?.language
|
|
265
|
+
? languages.find((item: {value: string | undefined}) => item.value === props?.value?.language)
|
|
266
|
+
: undefined
|
|
267
|
+
|
|
268
|
+
const languageField = type.fields.find((field) => field.name === 'language')
|
|
269
|
+
const filenameField = type.fields.find((field) => field.name === 'filename')
|
|
270
|
+
|
|
271
|
+
const languageCompareValue = PathUtils.get(compareValue, PATH_LANGUAGE)
|
|
272
|
+
const codeCompareValue = PathUtils.get(compareValue, PATH_CODE)
|
|
273
|
+
const filenameCompareValue = PathUtils.get(compareValue, PATH_FILENAME)
|
|
274
|
+
|
|
275
|
+
const languagePresence = presence.filter((presenceItem) =>
|
|
276
|
+
PathUtils.startsWith(PATH_LANGUAGE, presenceItem.path)
|
|
277
|
+
)
|
|
278
|
+
const codePresence = presence.filter((presenceItem) =>
|
|
279
|
+
PathUtils.startsWith(PATH_CODE, presenceItem.path)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
const filenamePresence = presence.filter((presenceItem) =>
|
|
283
|
+
PathUtils.startsWith(PATH_FILENAME, presenceItem.path)
|
|
284
|
+
)
|
|
285
|
+
const renderLanguageAlternatives = useCallback(() => {
|
|
286
|
+
return (
|
|
287
|
+
<ChangeIndicatorProvider
|
|
288
|
+
path={PATH_LANGUAGE}
|
|
289
|
+
focusPath={focusPath}
|
|
290
|
+
value={selectedLanguage?.value}
|
|
291
|
+
compareValue={languageCompareValue}
|
|
292
|
+
>
|
|
293
|
+
<FormField
|
|
294
|
+
level={level + 1}
|
|
295
|
+
label={languageField?.title || 'Language'}
|
|
296
|
+
__unstable_presence={languagePresence}
|
|
297
|
+
>
|
|
298
|
+
<Select
|
|
299
|
+
onChange={handleLanguageChange}
|
|
300
|
+
readOnly={readOnly}
|
|
301
|
+
value={selectedLanguage?.value || ''}
|
|
302
|
+
onFocus={handleLanguageFocus}
|
|
303
|
+
onBlur={onBlur}
|
|
304
|
+
>
|
|
305
|
+
{languages.map((lang: {title: string; value: string}) => (
|
|
306
|
+
<option key={lang.value} value={lang.value}>
|
|
307
|
+
{lang.title}
|
|
308
|
+
</option>
|
|
309
|
+
))}
|
|
310
|
+
</Select>
|
|
311
|
+
</FormField>
|
|
312
|
+
</ChangeIndicatorProvider>
|
|
313
|
+
)
|
|
314
|
+
}, [
|
|
315
|
+
focusPath,
|
|
316
|
+
handleLanguageChange,
|
|
317
|
+
handleLanguageFocus,
|
|
318
|
+
languageCompareValue,
|
|
319
|
+
languageField?.title,
|
|
320
|
+
languagePresence,
|
|
321
|
+
languages,
|
|
322
|
+
level,
|
|
323
|
+
onBlur,
|
|
324
|
+
readOnly,
|
|
325
|
+
selectedLanguage?.value,
|
|
326
|
+
])
|
|
327
|
+
|
|
328
|
+
const renderFilenameInput = useCallback(() => {
|
|
329
|
+
return (
|
|
330
|
+
<ChangeIndicatorProvider
|
|
331
|
+
path={PATH_FILENAME}
|
|
332
|
+
focusPath={focusPath}
|
|
333
|
+
value={value?.filename}
|
|
334
|
+
compareValue={filenameCompareValue}
|
|
335
|
+
>
|
|
336
|
+
<FormField
|
|
337
|
+
label={filenameField?.title || 'Filename'}
|
|
338
|
+
level={level + 1}
|
|
339
|
+
__unstable_presence={filenamePresence}
|
|
340
|
+
>
|
|
341
|
+
<TextInput
|
|
342
|
+
name="filename"
|
|
343
|
+
value={value?.filename || ''}
|
|
344
|
+
placeholder={filenameField?.placeholder}
|
|
345
|
+
onChange={handleFilenameChange}
|
|
346
|
+
onFocus={handleFilenameFocus}
|
|
347
|
+
onBlur={onBlur}
|
|
348
|
+
/>
|
|
349
|
+
</FormField>
|
|
350
|
+
</ChangeIndicatorProvider>
|
|
351
|
+
)
|
|
352
|
+
}, [
|
|
353
|
+
filenameCompareValue,
|
|
354
|
+
filenameField?.placeholder,
|
|
355
|
+
filenameField?.title,
|
|
356
|
+
filenamePresence,
|
|
357
|
+
focusPath,
|
|
358
|
+
handleFilenameChange,
|
|
359
|
+
handleFilenameFocus,
|
|
360
|
+
level,
|
|
361
|
+
onBlur,
|
|
362
|
+
value?.filename,
|
|
363
|
+
])
|
|
364
|
+
|
|
365
|
+
const renderEditor = useCallback(() => {
|
|
366
|
+
const fixedLanguage = type.options?.language
|
|
367
|
+
|
|
368
|
+
const language = value?.language || fixedLanguage
|
|
369
|
+
|
|
370
|
+
// the language config from the schema
|
|
371
|
+
const configured = languages.find((entry) => entry.value === language)
|
|
372
|
+
|
|
373
|
+
// is the language officially supported (e.g. we import the mode by default)
|
|
374
|
+
const supported = language && isSupportedLanguage(language)
|
|
375
|
+
|
|
376
|
+
const mode = configured?.mode || (supported ? supported : 'text')
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<ChangeIndicatorProvider
|
|
380
|
+
path={PATH_CODE}
|
|
381
|
+
focusPath={focusPath}
|
|
382
|
+
value={value?.code}
|
|
383
|
+
compareValue={codeCompareValue}
|
|
384
|
+
>
|
|
385
|
+
<FormField label="Code" level={level + 1} __unstable_presence={codePresence}>
|
|
386
|
+
<EditorContainer radius={1} shadow={1} readOnly={readOnly}>
|
|
387
|
+
<AceEditor
|
|
388
|
+
ref={aceEditorRef}
|
|
389
|
+
mode={mode}
|
|
390
|
+
theme={getTheme()}
|
|
391
|
+
width="100%"
|
|
392
|
+
onChange={handleCodeChange}
|
|
393
|
+
name={`editor-${aceEditorId}`}
|
|
394
|
+
value={(value && value.code) || ''}
|
|
395
|
+
markers={
|
|
396
|
+
value && value.highlightedLines
|
|
397
|
+
? createHighlightMarkers(value.highlightedLines)
|
|
398
|
+
: undefined
|
|
399
|
+
}
|
|
400
|
+
onLoad={handleEditorLoad}
|
|
401
|
+
readOnly={readOnly}
|
|
402
|
+
tabSize={2}
|
|
403
|
+
wrapEnabled
|
|
404
|
+
setOptions={ACE_SET_OPTIONS}
|
|
405
|
+
editorProps={ACE_EDITOR_PROPS}
|
|
406
|
+
onFocus={handleCodeFocus}
|
|
407
|
+
onBlur={onBlur}
|
|
408
|
+
/>
|
|
409
|
+
</EditorContainer>
|
|
410
|
+
</FormField>
|
|
411
|
+
</ChangeIndicatorProvider>
|
|
412
|
+
)
|
|
413
|
+
}, [
|
|
414
|
+
type.options?.language,
|
|
415
|
+
value,
|
|
416
|
+
languages,
|
|
417
|
+
focusPath,
|
|
418
|
+
codeCompareValue,
|
|
419
|
+
level,
|
|
420
|
+
codePresence,
|
|
421
|
+
readOnly,
|
|
422
|
+
getTheme,
|
|
423
|
+
handleCodeChange,
|
|
424
|
+
aceEditorId,
|
|
425
|
+
handleEditorLoad,
|
|
426
|
+
handleCodeFocus,
|
|
427
|
+
onBlur,
|
|
428
|
+
])
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
<FormFieldSet
|
|
432
|
+
title={type.title}
|
|
433
|
+
description={type.description}
|
|
434
|
+
level={level}
|
|
435
|
+
__unstable_changeIndicator={false}
|
|
436
|
+
>
|
|
437
|
+
{!type.options?.language && renderLanguageAlternatives()}
|
|
438
|
+
{type?.options?.withFilename && renderFilenameInput()}
|
|
439
|
+
{renderEditor()}
|
|
440
|
+
</FormFieldSet>
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
CodeInput.displayName = 'CodeInput'
|
|
446
|
+
|
|
447
|
+
export default CodeInput
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React, {useCallback, useEffect, useRef} from 'react'
|
|
2
|
+
import AceEditor from 'react-ace'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import {Box} from '@sanity/ui'
|
|
5
|
+
import {ACE_EDITOR_PROPS, ACE_SET_OPTIONS} from './config'
|
|
6
|
+
import createHighlightMarkers from './createHighlightMarkers'
|
|
7
|
+
import {CodeInputType, CodeInputValue} from './types'
|
|
8
|
+
import './editorSupport'
|
|
9
|
+
|
|
10
|
+
const PreviewContainer = styled(Box)`
|
|
11
|
+
position: relative;
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const PreviewInner = styled(Box)`
|
|
15
|
+
background-color: #272822;
|
|
16
|
+
|
|
17
|
+
.ace_editor {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
cursor: default;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ace_content {
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
export interface PreviewCodeProps {
|
|
30
|
+
type?: CodeInputType
|
|
31
|
+
value?: CodeInputValue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function PreviewCode(props: PreviewCodeProps) {
|
|
35
|
+
const aceEditorRef = useRef<any>()
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!aceEditorRef?.current) return
|
|
39
|
+
|
|
40
|
+
const editor = aceEditorRef.current?.editor
|
|
41
|
+
|
|
42
|
+
if (editor) {
|
|
43
|
+
// Avoid cursor and focus tracking by Ace
|
|
44
|
+
editor.renderer.$cursorLayer.element.style.opacity = 0
|
|
45
|
+
editor.textInput.getElement().disabled = true
|
|
46
|
+
}
|
|
47
|
+
}, [])
|
|
48
|
+
|
|
49
|
+
const handleEditorChange = useCallback(() => {
|
|
50
|
+
// do nothing when the editor changes
|
|
51
|
+
}, [])
|
|
52
|
+
|
|
53
|
+
const {value, type} = props
|
|
54
|
+
const fixedLanguage = type?.options?.language
|
|
55
|
+
|
|
56
|
+
const mode = value?.language || fixedLanguage || 'text'
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<PreviewContainer>
|
|
60
|
+
<PreviewInner padding={4}>
|
|
61
|
+
<AceEditor
|
|
62
|
+
ref={aceEditorRef}
|
|
63
|
+
focus={false}
|
|
64
|
+
mode={mode}
|
|
65
|
+
theme="monokai"
|
|
66
|
+
width="100%"
|
|
67
|
+
onChange={handleEditorChange}
|
|
68
|
+
maxLines={200}
|
|
69
|
+
readOnly
|
|
70
|
+
wrapEnabled
|
|
71
|
+
showPrintMargin={false}
|
|
72
|
+
highlightActiveLine={false}
|
|
73
|
+
cursorStart={-1}
|
|
74
|
+
value={(value && value.code) || ''}
|
|
75
|
+
markers={
|
|
76
|
+
value && value.highlightedLines
|
|
77
|
+
? createHighlightMarkers(value.highlightedLines)
|
|
78
|
+
: undefined
|
|
79
|
+
}
|
|
80
|
+
tabSize={2}
|
|
81
|
+
showGutter={false}
|
|
82
|
+
setOptions={ACE_SET_OPTIONS}
|
|
83
|
+
editorProps={ACE_EDITOR_PROPS}
|
|
84
|
+
/>
|
|
85
|
+
</PreviewInner>
|
|
86
|
+
</PreviewContainer>
|
|
87
|
+
)
|
|
88
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {CodeInputLanguage} from './types'
|
|
2
|
+
|
|
3
|
+
// NOTE: MAKE SURE THESE ALIGN WITH IMPORTS IN ./editorSupport
|
|
4
|
+
export const SUPPORTED_LANGUAGES: CodeInputLanguage[] = [
|
|
5
|
+
{title: 'Batch file', value: 'batchfile'},
|
|
6
|
+
{title: 'C#', value: 'csharp'},
|
|
7
|
+
{title: 'CSS', value: 'css'},
|
|
8
|
+
{title: 'Go', value: 'golang'},
|
|
9
|
+
{title: 'GROQ', value: 'groq'},
|
|
10
|
+
{title: 'HTML', value: 'html'},
|
|
11
|
+
{title: 'Java', value: 'java'},
|
|
12
|
+
{title: 'JavaScript', value: 'javascript'},
|
|
13
|
+
{title: 'JSON', value: 'json'},
|
|
14
|
+
{title: 'JSX', value: 'jsx'},
|
|
15
|
+
{title: 'Markdown', value: 'markdown'},
|
|
16
|
+
{title: 'MySQL', value: 'mysql'},
|
|
17
|
+
{title: 'PHP', value: 'php'},
|
|
18
|
+
{title: 'Plain text', value: 'text'},
|
|
19
|
+
{title: 'Python', value: 'python'},
|
|
20
|
+
{title: 'Ruby', value: 'ruby'},
|
|
21
|
+
{title: 'SASS', value: 'sass'},
|
|
22
|
+
{title: 'SCSS', value: 'scss'},
|
|
23
|
+
{title: 'sh', value: 'sh'},
|
|
24
|
+
{title: 'TSX', value: 'tsx'},
|
|
25
|
+
{title: 'TypeScript', value: 'typescript'},
|
|
26
|
+
{title: 'XML', value: 'xml'},
|
|
27
|
+
{title: 'YAML', value: 'yaml'},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const LANGUAGE_ALIASES: Record<string, string | undefined> = {js: 'javascript'}
|
|
31
|
+
|
|
32
|
+
export const SUPPORTED_THEMES = ['github', 'monokai', 'terminal', 'tomorrow']
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_THEME = 'tomorrow'
|
|
35
|
+
|
|
36
|
+
export const ACE_SET_OPTIONS = {
|
|
37
|
+
useSoftTabs: true,
|
|
38
|
+
navigateWithinSoftTabs: true /* note only supported by ace v1.2.7 or higher */,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ACE_EDITOR_PROPS = {$blockScrolling: true}
|
|
42
|
+
|
|
43
|
+
export const PATH_LANGUAGE = ['language']
|
|
44
|
+
export const PATH_CODE = ['code']
|
|
45
|
+
export const PATH_FILENAME = ['filename']
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {IMarker} from 'react-ace'
|
|
2
|
+
import {css} from 'styled-components'
|
|
3
|
+
|
|
4
|
+
export const highlightMarkersCSS = css`
|
|
5
|
+
.ace_editor_markers_highlight {
|
|
6
|
+
position: absolute;
|
|
7
|
+
background-color: ${({theme}) => theme.sanity.color.solid.primary.enabled.bg};
|
|
8
|
+
opacity: 0.2;
|
|
9
|
+
width: 100% !important;
|
|
10
|
+
border-radius: 0 !important;
|
|
11
|
+
}
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
export default function createHighlightMarkers(rows: number[]): IMarker[] {
|
|
15
|
+
return rows.map((row) => ({
|
|
16
|
+
startRow: Number(row) - 1,
|
|
17
|
+
startCol: 0,
|
|
18
|
+
endRow: Number(row) - 1,
|
|
19
|
+
endCol: +Infinity,
|
|
20
|
+
className: 'ace_editor_markers_highlight',
|
|
21
|
+
type: 'screenLine',
|
|
22
|
+
inFront: true,
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default (() => {
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
console.warn('@sanity/code-input has been upgraded to automatically register its schema type.')
|
|
4
|
+
console.warn('Please remove the explicit import of `part:@sanity/base/schema-type`')
|
|
5
|
+
/* eslint-enable no-console */
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
name: 'oldDeprecatedCodeTypeWhichYouShouldRemove',
|
|
9
|
+
type: 'object',
|
|
10
|
+
title: 'Deprecated code type',
|
|
11
|
+
fields: [
|
|
12
|
+
{
|
|
13
|
+
title: 'Code',
|
|
14
|
+
name: 'code',
|
|
15
|
+
type: 'text',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
})()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// NOTE: MAKE SURE THESE ALIGN WITH SUPPORTED_LANGUAGES in ./config
|
|
2
|
+
import './groq'
|
|
3
|
+
|
|
4
|
+
import 'ace-builds/src-noconflict/mode-batchfile'
|
|
5
|
+
import 'ace-builds/src-noconflict/mode-csharp'
|
|
6
|
+
import 'ace-builds/src-noconflict/mode-css'
|
|
7
|
+
import 'ace-builds/src-noconflict/mode-golang'
|
|
8
|
+
import 'ace-builds/src-noconflict/mode-html'
|
|
9
|
+
import 'ace-builds/src-noconflict/mode-java'
|
|
10
|
+
import 'ace-builds/src-noconflict/mode-javascript'
|
|
11
|
+
import 'ace-builds/src-noconflict/mode-json'
|
|
12
|
+
import 'ace-builds/src-noconflict/mode-jsx'
|
|
13
|
+
import 'ace-builds/src-noconflict/mode-markdown'
|
|
14
|
+
import 'ace-builds/src-noconflict/mode-mysql'
|
|
15
|
+
import 'ace-builds/src-noconflict/mode-php'
|
|
16
|
+
import 'ace-builds/src-noconflict/mode-python'
|
|
17
|
+
import 'ace-builds/src-noconflict/mode-ruby'
|
|
18
|
+
import 'ace-builds/src-noconflict/mode-sass'
|
|
19
|
+
import 'ace-builds/src-noconflict/mode-scss'
|
|
20
|
+
import 'ace-builds/src-noconflict/mode-sh'
|
|
21
|
+
import 'ace-builds/src-noconflict/mode-text'
|
|
22
|
+
import 'ace-builds/src-noconflict/mode-tsx'
|
|
23
|
+
import 'ace-builds/src-noconflict/mode-typescript'
|
|
24
|
+
import 'ace-builds/src-noconflict/mode-xml'
|
|
25
|
+
import 'ace-builds/src-noconflict/mode-yaml'
|
|
26
|
+
|
|
27
|
+
// Themes
|
|
28
|
+
import 'ace-builds/src-noconflict/theme-github'
|
|
29
|
+
import 'ace-builds/src-noconflict/theme-monokai'
|
|
30
|
+
import 'ace-builds/src-noconflict/theme-terminal'
|
|
31
|
+
import 'ace-builds/src-noconflict/theme-tomorrow'
|