@sanity/code-input 2.25.4 → 2.26.1-purple-unicorn.560

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.
Files changed (86) hide show
  1. package/CodeInput.js +7 -0
  2. package/deprecatedSchema.js +7 -0
  3. package/lib/CodeInput.cjs +42 -0
  4. package/lib/CodeInput.cjs.map +1 -0
  5. package/lib/CodeInput.js +36 -333
  6. package/lib/CodeInput.js.map +1 -0
  7. package/lib/_CodeInput-9d891d80.js +893 -0
  8. package/lib/_CodeInput-9d891d80.js.map +1 -0
  9. package/lib/_CodeInput-dd37fa16.cjs +923 -0
  10. package/lib/_CodeInput-dd37fa16.cjs.map +1 -0
  11. package/lib/_reExport.js +19 -0
  12. package/lib/deprecatedSchema.cjs +23 -0
  13. package/lib/deprecatedSchema.cjs.map +1 -0
  14. package/lib/deprecatedSchema.js +19 -24
  15. package/lib/deprecatedSchema.js.map +1 -0
  16. package/lib/dts/src/CodeInput.d.ts +19 -0
  17. package/lib/dts/src/CodeInput.d.ts.map +1 -0
  18. package/{dist/dts → lib/dts/src}/PreviewCode.d.ts +8 -8
  19. package/lib/dts/src/PreviewCode.d.ts.map +1 -0
  20. package/lib/dts/src/__workshop__/dev.d.ts +3 -0
  21. package/lib/dts/src/__workshop__/dev.d.ts.map +1 -0
  22. package/lib/dts/src/__workshop__/index.d.ts +3 -0
  23. package/lib/dts/src/__workshop__/index.d.ts.map +1 -0
  24. package/{dist/dts → lib/dts/src}/config.d.ts +15 -15
  25. package/lib/dts/src/config.d.ts.map +1 -0
  26. package/{dist/dts → lib/dts/src}/createHighlightMarkers.d.ts +3 -3
  27. package/lib/dts/src/createHighlightMarkers.d.ts.map +1 -0
  28. package/{dist/dts → lib/dts/src}/deprecatedSchema.d.ts +11 -11
  29. package/lib/dts/src/deprecatedSchema.d.ts.map +1 -0
  30. package/{dist/dts → lib/dts/src}/editorSupport.d.ts +27 -27
  31. package/lib/dts/src/editorSupport.d.ts.map +1 -0
  32. package/{dist/dts → lib/dts/src}/getMedia.d.ts +2 -2
  33. package/lib/dts/src/getMedia.d.ts.map +1 -0
  34. package/lib/dts/src/groq.d.ts +2 -0
  35. package/lib/dts/src/groq.d.ts.map +1 -0
  36. package/lib/dts/src/index.d.ts +3 -0
  37. package/lib/dts/src/index.d.ts.map +1 -0
  38. package/lib/dts/src/schema.d.ts +44 -0
  39. package/lib/dts/src/schema.d.ts.map +1 -0
  40. package/{dist/dts → lib/dts/src}/types.d.ts +28 -28
  41. package/lib/dts/src/types.d.ts.map +1 -0
  42. package/lib/index.cjs +50 -0
  43. package/lib/index.cjs.map +1 -0
  44. package/lib/index.js +48 -0
  45. package/lib/index.js.map +1 -0
  46. package/lib/schema.cjs +177 -0
  47. package/lib/schema.cjs.map +1 -0
  48. package/lib/schema.js +162 -62
  49. package/lib/schema.js.map +1 -0
  50. package/package.json +49 -10
  51. package/schema.js +7 -0
  52. package/src/CodeInput.tsx +425 -0
  53. package/src/PreviewCode.tsx +89 -0
  54. package/src/__workshop__/dev.tsx +35 -0
  55. package/src/__workshop__/index.ts +10 -0
  56. package/src/config.ts +45 -0
  57. package/src/createHighlightMarkers.ts +24 -0
  58. package/src/deprecatedSchema.ts +19 -0
  59. package/src/editorSupport.ts +33 -0
  60. package/src/getMedia.tsx +95 -0
  61. package/src/groq.ts +630 -0
  62. package/src/index.ts +9 -0
  63. package/src/schema.tsx +65 -0
  64. package/src/types.ts +26 -0
  65. package/dist/dts/CodeInput.d.ts +0 -23
  66. package/dist/dts/CodeInput.d.ts.map +0 -1
  67. package/dist/dts/PreviewCode.d.ts.map +0 -1
  68. package/dist/dts/config.d.ts.map +0 -1
  69. package/dist/dts/createHighlightMarkers.d.ts.map +0 -1
  70. package/dist/dts/deprecatedSchema.d.ts.map +0 -1
  71. package/dist/dts/editorSupport.d.ts.map +0 -1
  72. package/dist/dts/getMedia.d.ts.map +0 -1
  73. package/dist/dts/groq.d.ts +0 -376
  74. package/dist/dts/groq.d.ts.map +0 -1
  75. package/dist/dts/schema.d.ts +0 -44
  76. package/dist/dts/schema.d.ts.map +0 -1
  77. package/dist/dts/types.d.ts.map +0 -1
  78. package/lib/@types/css.d.js +0 -1
  79. package/lib/PreviewCode.js +0 -79
  80. package/lib/config.js +0 -103
  81. package/lib/createHighlightMarkers.js +0 -31
  82. package/lib/editorSupport.js +0 -55
  83. package/lib/getMedia.js +0 -110
  84. package/lib/groq.js +0 -414
  85. package/lib/types.js +0 -5
  86. package/tsconfig.json +0 -26
package/package.json CHANGED
@@ -1,13 +1,44 @@
1
1
  {
2
2
  "name": "@sanity/code-input",
3
- "version": "2.25.4",
3
+ "version": "2.26.1-purple-unicorn.560+8672b3b5e3",
4
4
  "description": "Ace editor for editing code",
5
- "main": "./lib/index.js",
6
- "types": "./dist/dts",
5
+ "source": "./src/index.ts",
6
+ "main": "./lib/index.cjs",
7
+ "module": "./lib/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "source": "./src/index.ts",
11
+ "require": "./lib/index.cjs",
12
+ "default": "./lib/index.js"
13
+ },
14
+ "./CodeInput": {
15
+ "source": "./src/CodeInput.tsx",
16
+ "require": "./lib/CodeInput.cjs",
17
+ "default": "./lib/CodeInput.js"
18
+ },
19
+ "./schema": {
20
+ "source": "./src/schema.tsx",
21
+ "require": "./lib/schema.cjs",
22
+ "default": "./lib/schema.js"
23
+ },
24
+ "./deprecatedSchema": {
25
+ "source": "./src/deprecatedSchema.ts",
26
+ "require": "./lib/deprecatedSchema.cjs",
27
+ "default": "./lib/deprecatedSchema.js"
28
+ }
29
+ },
30
+ "types": "./lib/dts/src/index.d.ts",
7
31
  "author": "Sanity.io <hello@sanity.io>",
8
32
  "license": "MIT",
9
33
  "scripts": {
10
- "clean": "rimraf lib dest"
34
+ "build": "../../../bin/pkg-utils bundle",
35
+ "clean": "rimraf lib",
36
+ "exports:check": "../../../bin/pkg-utils exports-check",
37
+ "exports:clean": "../../../bin/pkg-utils exports-clean",
38
+ "exports:generate": "../../../bin/pkg-utils exports-generate",
39
+ "postbuild": "run-s exports:generate",
40
+ "prebuild": "yarn clean",
41
+ "watch": "../../../bin/pkg-utils bundle --watch"
11
42
  },
12
43
  "keywords": [
13
44
  "sanity",
@@ -21,18 +52,26 @@
21
52
  ],
22
53
  "dependencies": {
23
54
  "@reach/auto-id": "^0.13.2",
24
- "@sanity/base": "2.25.4",
25
55
  "@sanity/icons": "^1.2.1",
26
- "@sanity/types": "2.24.0",
27
- "@sanity/ui": "^0.37.2",
28
- "@sanity/util": "2.24.0",
56
+ "@sanity/ui": "^0.37.5",
29
57
  "ace-builds": "^1.4.13",
30
58
  "react-ace": "^9.5.0"
31
59
  },
32
60
  "devDependencies": {
33
- "rimraf": "^2.7.1"
61
+ "@sanity/base": "2.26.1-purple-unicorn.560+8672b3b5e3",
62
+ "@sanity/form-builder": "2.26.1-purple-unicorn.560+8672b3b5e3",
63
+ "@sanity/types": "2.26.1-purple-unicorn.560+8672b3b5e3",
64
+ "@sanity/ui-workshop": "^0.3.15",
65
+ "@sanity/util": "2.26.1-purple-unicorn.560+8672b3b5e3",
66
+ "react": "17.0.2",
67
+ "rimraf": "^3.0.2",
68
+ "styled-components": "^5.2.0"
34
69
  },
35
70
  "peerDependencies": {
71
+ "@sanity/base": "^2.14",
72
+ "@sanity/form-builder": "^2.27",
73
+ "@sanity/types": "^2.14",
74
+ "@sanity/util": "2.27",
36
75
  "prop-types": "^15.6 || ^16",
37
76
  "react": "^16.9 || ^17",
38
77
  "styled-components": "^5.2.0"
@@ -46,5 +85,5 @@
46
85
  "url": "git+https://github.com/sanity-io/sanity.git",
47
86
  "directory": "packages/@sanity/code-input"
48
87
  },
49
- "gitHead": "300fe66daba470c332a976cb3d78ced2d4a89292"
88
+ "gitHead": "8672b3b5e39b2aeea500d16ac197b068ba7c943c"
50
89
  }
package/schema.js ADDED
@@ -0,0 +1,7 @@
1
+ /* eslint-disable prettier/prettier, strict */
2
+ 'use strict'
3
+
4
+ // AUTO-GENERATED – DO NOT EDIT
5
+ // This is a legacy package export
6
+ // prettier-ignore
7
+ require('./lib/_reExport')(module.exports, require('./lib/schema.cjs'))
@@ -0,0 +1,425 @@
1
+ import React, {useCallback, useEffect, useImperativeHandle, useRef} from 'react'
2
+ import {FormField, FormFieldSet, ChangeIndicatorProvider} from '@sanity/base/components'
3
+ import {PatchEvent, set, unset, setIfMissing, FormInputProps} from '@sanity/base/form'
4
+ import {ObjectSchemaType} from '@sanity/types'
5
+ import {Card, Select, Stack, TextInput} from '@sanity/ui'
6
+ import * as PathUtils from '@sanity/util/paths'
7
+ import AceEditor from 'react-ace'
8
+ import styled from 'styled-components'
9
+ import {useId} from '@reach/auto-id'
10
+ import createHighlightMarkers, {highlightMarkersCSS} from './createHighlightMarkers'
11
+ import {CodeInputLanguage, CodeInputValue} from './types'
12
+ /* eslint-disable-next-line import/no-unassigned-import */
13
+ import './editorSupport'
14
+
15
+ import {
16
+ ACE_EDITOR_PROPS,
17
+ ACE_SET_OPTIONS,
18
+ DEFAULT_THEME,
19
+ LANGUAGE_ALIASES,
20
+ PATH_CODE,
21
+ PATH_FILENAME,
22
+ PATH_LANGUAGE,
23
+ SUPPORTED_LANGUAGES,
24
+ SUPPORTED_THEMES,
25
+ } from './config'
26
+
27
+ const EditorContainer = styled(Card)`
28
+ position: relative;
29
+ box-sizing: border-box;
30
+ overflow: hidden;
31
+ z-index: 0;
32
+
33
+ .ace_editor {
34
+ font-family: ${({theme}) => theme.sanity.fonts.code.family};
35
+ font-size: ${({theme}) => theme.sanity.fonts.code.sizes[1]};
36
+ line-height: inherit;
37
+ }
38
+
39
+ ${highlightMarkersCSS}
40
+
41
+ &:not([disabled]):not([readonly]) {
42
+ &:focus,
43
+ &:focus-within {
44
+ box-shadow: 0 0 0 2px ${({theme}) => theme.sanity.color.base.focusRing};
45
+ background-color: ${({theme}) => theme.sanity.color.base.bg};
46
+ border-color: ${({theme}) => theme.sanity.color.base.focusRing};
47
+ }
48
+ }
49
+ `
50
+
51
+ export type CodeSchemaType = Omit<ObjectSchemaType, 'options'> & {
52
+ options: {
53
+ theme?: string
54
+ languageAlternatives: CodeInputLanguage[]
55
+ language: string
56
+ withFilename?: boolean
57
+ }
58
+ }
59
+
60
+ export type CodeInputProps = FormInputProps<CodeInputValue, CodeSchemaType>
61
+
62
+ // Returns a string with the mode name if supported (because aliases), otherwise false
63
+ function isSupportedLanguage(mode: string) {
64
+ const alias = LANGUAGE_ALIASES[mode]
65
+
66
+ if (alias) {
67
+ return alias
68
+ }
69
+
70
+ const isSupported = SUPPORTED_LANGUAGES.find((lang) => lang.value === mode)
71
+ if (isSupported) {
72
+ return mode
73
+ }
74
+
75
+ return false
76
+ }
77
+
78
+ const CodeInput = React.forwardRef(
79
+ (props: CodeInputProps, ref: React.ForwardedRef<{focus: () => void}>) => {
80
+ const aceEditorRef = useRef<any>()
81
+ const aceEditorId = useId()
82
+
83
+ const {
84
+ onFocus,
85
+ onChange,
86
+ onBlur,
87
+ compareValue,
88
+ value,
89
+ presence,
90
+ type,
91
+ level = 0,
92
+ readOnly,
93
+ focusPath,
94
+ } = props
95
+
96
+ useImperativeHandle(ref, () => ({
97
+ focus: () => {
98
+ aceEditorRef?.current?.editor?.focus()
99
+ },
100
+ }))
101
+
102
+ const handleLanguageFocus = useCallback(() => {
103
+ onFocus(PATH_LANGUAGE)
104
+ }, [onFocus])
105
+
106
+ const handleCodeFocus = useCallback(() => {
107
+ onFocus(PATH_CODE)
108
+ }, [onFocus])
109
+
110
+ const handleFilenameFocus = useCallback(() => {
111
+ onFocus(PATH_FILENAME)
112
+ }, [onFocus])
113
+
114
+ const handleFilenameChange = useCallback(
115
+ (event: React.ChangeEvent<HTMLInputElement>) => {
116
+ const val = event.target.value
117
+ const path = PATH_FILENAME
118
+
119
+ onChange(
120
+ PatchEvent.from([setIfMissing({_type: type.name}), val ? set(val, path) : unset(path)])
121
+ )
122
+ },
123
+ [onChange, type.name]
124
+ )
125
+
126
+ const getTheme = useCallback(() => {
127
+ const preferredTheme = type.options?.theme
128
+ return preferredTheme && SUPPORTED_THEMES.find((theme) => theme === preferredTheme)
129
+ ? preferredTheme
130
+ : DEFAULT_THEME
131
+ }, [type])
132
+
133
+ const handleToggleSelectLine = useCallback(
134
+ (lineNumber: number) => {
135
+ const editorSession = aceEditorRef.current?.editor?.getSession()
136
+ const backgroundMarkers = editorSession?.getMarkers(true)
137
+ const currentHighlightedLines = Object.keys(backgroundMarkers)
138
+ .filter((key) => backgroundMarkers[key].type === 'screenLine')
139
+ .map((key) => backgroundMarkers[key].range.start.row)
140
+ const currentIndex = currentHighlightedLines.indexOf(lineNumber)
141
+ if (currentIndex > -1) {
142
+ // toggle remove
143
+ currentHighlightedLines.splice(currentIndex, 1)
144
+ } else {
145
+ // toggle add
146
+ currentHighlightedLines.push(lineNumber)
147
+ currentHighlightedLines.sort()
148
+ }
149
+ onChange(
150
+ PatchEvent.from(
151
+ set(
152
+ currentHighlightedLines.map(
153
+ (line) =>
154
+ // ace starts at line (row) 0, but we store it starting at line 1
155
+ line + 1
156
+ ),
157
+ ['highlightedLines']
158
+ )
159
+ )
160
+ )
161
+ },
162
+ [aceEditorRef, onChange]
163
+ )
164
+
165
+ const handleGutterMouseDown = useCallback(
166
+ (event: any) => {
167
+ const target = event.domEvent.target
168
+ if (target.classList.contains('ace_gutter-cell')) {
169
+ const row = event.getDocumentPosition().row
170
+ handleToggleSelectLine(row)
171
+ }
172
+ },
173
+ [handleToggleSelectLine]
174
+ )
175
+
176
+ useEffect(() => {
177
+ const editor = aceEditorRef?.current?.editor
178
+ return () => {
179
+ editor?.session?.removeListener('guttermousedown', handleGutterMouseDown)
180
+ }
181
+ }, [aceEditorRef, handleGutterMouseDown])
182
+
183
+ const handleEditorLoad = useCallback(
184
+ (editor: any) => {
185
+ editor?.on('guttermousedown', handleGutterMouseDown)
186
+ },
187
+ [handleGutterMouseDown]
188
+ )
189
+
190
+ const getLanguageAlternatives = useCallback((): {
191
+ title: string
192
+ value: string
193
+ mode?: string
194
+ }[] => {
195
+ const languageAlternatives = type.options?.languageAlternatives
196
+ if (!languageAlternatives) {
197
+ return SUPPORTED_LANGUAGES
198
+ }
199
+
200
+ if (!Array.isArray(languageAlternatives)) {
201
+ throw new Error(
202
+ `'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}`
203
+ )
204
+ }
205
+
206
+ return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => {
207
+ const alias = LANGUAGE_ALIASES[val]
208
+ if (alias) {
209
+ // eslint-disable-next-line no-console
210
+ console.warn(
211
+ `'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`,
212
+ val,
213
+ alias,
214
+ alias
215
+ )
216
+
217
+ return acc.concat({title, value: alias, mode: mode})
218
+ }
219
+
220
+ if (!mode && !SUPPORTED_LANGUAGES.find((lang) => lang.value === val)) {
221
+ // eslint-disable-next-line no-console
222
+ console.warn(
223
+ `'options.languageAlternatives' lists a language which is not supported: "%s", syntax highlighting will be disabled.`,
224
+ val
225
+ )
226
+ }
227
+
228
+ return acc.concat({title, value: val, mode})
229
+ }, [])
230
+ }, [type])
231
+
232
+ const handleCodeChange = useCallback(
233
+ (code: string) => {
234
+ const path = PATH_CODE
235
+ const fixedLanguage = type.options?.language
236
+
237
+ onChange(
238
+ PatchEvent.from([
239
+ setIfMissing({_type: type.name, language: fixedLanguage}),
240
+ code ? set(code, path) : unset(path),
241
+ ])
242
+ )
243
+ },
244
+ [onChange, type]
245
+ )
246
+
247
+ const handleLanguageChange = useCallback(
248
+ (event: React.ChangeEvent<HTMLSelectElement>) => {
249
+ const val = event.currentTarget.value
250
+ const path = PATH_LANGUAGE
251
+
252
+ onChange(
253
+ PatchEvent.from([setIfMissing({_type: type.name}), val ? set(val, path) : unset(path)])
254
+ )
255
+ },
256
+ [onChange, type.name]
257
+ )
258
+
259
+ const languages = getLanguageAlternatives().slice()
260
+
261
+ const selectedLanguage = props?.value?.language
262
+ ? languages.find((item: {value: string | undefined}) => item.value === props?.value?.language)
263
+ : undefined
264
+
265
+ const languageField = type.fields.find((field) => field.name === 'language')
266
+ const filenameField = type.fields.find((field) => field.name === 'filename')
267
+
268
+ const languageCompareValue = PathUtils.get(compareValue, PATH_LANGUAGE)
269
+ const codeCompareValue = PathUtils.get(compareValue, PATH_CODE)
270
+ const filenameCompareValue = PathUtils.get(compareValue, PATH_FILENAME)
271
+
272
+ const languagePresence = presence.filter((presenceItem) =>
273
+ PathUtils.startsWith(PATH_LANGUAGE, presenceItem.path)
274
+ )
275
+ const codePresence = presence.filter((presenceItem) =>
276
+ PathUtils.startsWith(PATH_CODE, presenceItem.path)
277
+ )
278
+
279
+ const filenamePresence = presence.filter((presenceItem) =>
280
+ PathUtils.startsWith(PATH_FILENAME, presenceItem.path)
281
+ )
282
+
283
+ const renderEditor = useCallback(() => {
284
+ const fixedLanguage = type.options?.language
285
+
286
+ const language = value?.language || fixedLanguage
287
+
288
+ // the language config from the schema
289
+ const configured = languages.find((entry) => entry.value === language)
290
+
291
+ // is the language officially supported (e.g. we import the mode by default)
292
+ const supported = language && isSupportedLanguage(language)
293
+
294
+ const mode = configured?.mode || (supported ? language : 'text')
295
+
296
+ return (
297
+ <EditorContainer radius={1} shadow={1} readOnly={readOnly}>
298
+ <AceEditor
299
+ ref={aceEditorRef}
300
+ mode={mode}
301
+ theme={getTheme()}
302
+ width="100%"
303
+ onChange={handleCodeChange}
304
+ name={`editor-${aceEditorId}`}
305
+ value={(value && value.code) || ''}
306
+ markers={
307
+ value && value.highlightedLines
308
+ ? createHighlightMarkers(value.highlightedLines)
309
+ : undefined
310
+ }
311
+ onLoad={handleEditorLoad}
312
+ readOnly={readOnly}
313
+ tabSize={2}
314
+ wrapEnabled
315
+ setOptions={ACE_SET_OPTIONS}
316
+ editorProps={ACE_EDITOR_PROPS}
317
+ onFocus={handleCodeFocus}
318
+ onBlur={onBlur}
319
+ />
320
+ </EditorContainer>
321
+ )
322
+ }, [
323
+ aceEditorId,
324
+ getTheme,
325
+ handleCodeChange,
326
+ handleCodeFocus,
327
+ handleEditorLoad,
328
+ languages,
329
+ onBlur,
330
+ readOnly,
331
+ type,
332
+ value,
333
+ ])
334
+
335
+ if (type.options?.language) {
336
+ return (
337
+ <ChangeIndicatorProvider
338
+ path={PATH_CODE}
339
+ focusPath={focusPath}
340
+ value={value?.code}
341
+ compareValue={codeCompareValue}
342
+ >
343
+ <FormFieldSet title={type.title} description={type.description} level={level}>
344
+ {renderEditor()}
345
+ </FormFieldSet>
346
+ </ChangeIndicatorProvider>
347
+ )
348
+ }
349
+
350
+ return (
351
+ <FormFieldSet
352
+ title={type.title}
353
+ description={type.description}
354
+ level={level}
355
+ __unstable_changeIndicator={false}
356
+ >
357
+ <Stack space={3}>
358
+ <ChangeIndicatorProvider
359
+ path={PATH_LANGUAGE}
360
+ focusPath={focusPath}
361
+ value={selectedLanguage?.value}
362
+ compareValue={languageCompareValue}
363
+ >
364
+ <FormField
365
+ level={level + 1}
366
+ label={languageField?.type.title || 'Language'}
367
+ __unstable_presence={languagePresence}
368
+ >
369
+ <Select
370
+ onChange={handleLanguageChange}
371
+ readOnly={readOnly}
372
+ value={selectedLanguage?.value || ''}
373
+ onFocus={handleLanguageFocus}
374
+ onBlur={onBlur}
375
+ >
376
+ {languages.map((lang: {title: string; value: string}) => (
377
+ <option key={lang.value} value={lang.value}>
378
+ {lang.title}
379
+ </option>
380
+ ))}
381
+ </Select>
382
+ </FormField>
383
+ </ChangeIndicatorProvider>
384
+ {type.options?.withFilename && (
385
+ <ChangeIndicatorProvider
386
+ path={PATH_FILENAME}
387
+ focusPath={focusPath}
388
+ value={value?.filename}
389
+ compareValue={filenameCompareValue}
390
+ >
391
+ <FormField
392
+ label={filenameField?.type.title || 'Filename'}
393
+ level={level + 1}
394
+ __unstable_presence={filenamePresence}
395
+ >
396
+ <TextInput
397
+ name="filename"
398
+ value={value?.filename || ''}
399
+ placeholder={filenameField?.type.placeholder}
400
+ onChange={handleFilenameChange}
401
+ onFocus={handleFilenameFocus}
402
+ onBlur={onBlur}
403
+ />
404
+ </FormField>
405
+ </ChangeIndicatorProvider>
406
+ )}
407
+ <ChangeIndicatorProvider
408
+ path={PATH_CODE}
409
+ focusPath={focusPath}
410
+ value={value?.code}
411
+ compareValue={codeCompareValue}
412
+ >
413
+ <FormField label="Code" level={level + 1} __unstable_presence={codePresence}>
414
+ {renderEditor()}
415
+ </FormField>
416
+ </ChangeIndicatorProvider>
417
+ </Stack>
418
+ </FormFieldSet>
419
+ )
420
+ }
421
+ )
422
+
423
+ CodeInput.displayName = 'CodeInput'
424
+
425
+ export default CodeInput
@@ -0,0 +1,89 @@
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
+ /* eslint-disable-next-line import/no-unassigned-import */
9
+ import './editorSupport'
10
+
11
+ const PreviewContainer = styled(Box)`
12
+ position: relative;
13
+ `
14
+
15
+ const PreviewInner = styled(Box)`
16
+ background-color: #272822;
17
+
18
+ .ace_editor {
19
+ box-sizing: border-box;
20
+ cursor: default;
21
+ pointer-events: none;
22
+ }
23
+
24
+ .ace_content {
25
+ box-sizing: border-box;
26
+ overflow: hidden;
27
+ }
28
+ `
29
+
30
+ export interface PreviewCodeProps {
31
+ type?: CodeInputType
32
+ value?: CodeInputValue
33
+ }
34
+
35
+ export default function PreviewCode(props: PreviewCodeProps) {
36
+ const aceEditorRef = useRef<any>()
37
+
38
+ useEffect(() => {
39
+ if (!aceEditorRef?.current) return
40
+
41
+ const editor = aceEditorRef.current?.editor
42
+
43
+ if (editor) {
44
+ // Avoid cursor and focus tracking by Ace
45
+ editor.renderer.$cursorLayer.element.style.opacity = 0
46
+ editor.textInput.getElement().disabled = true
47
+ }
48
+ }, [])
49
+
50
+ const handleEditorChange = useCallback(() => {
51
+ // do nothing when the editor changes
52
+ }, [])
53
+
54
+ const {value, type} = props
55
+ const fixedLanguage = type?.options?.language
56
+
57
+ const mode = value?.language || fixedLanguage || 'text'
58
+
59
+ return (
60
+ <PreviewContainer>
61
+ <PreviewInner padding={4}>
62
+ <AceEditor
63
+ ref={aceEditorRef}
64
+ focus={false}
65
+ mode={mode}
66
+ theme="monokai"
67
+ width="100%"
68
+ onChange={handleEditorChange}
69
+ maxLines={200}
70
+ readOnly
71
+ wrapEnabled
72
+ showPrintMargin={false}
73
+ highlightActiveLine={false}
74
+ cursorStart={-1}
75
+ value={(value && value.code) || ''}
76
+ markers={
77
+ value && value.highlightedLines
78
+ ? createHighlightMarkers(value.highlightedLines)
79
+ : undefined
80
+ }
81
+ tabSize={2}
82
+ showGutter={false}
83
+ setOptions={ACE_SET_OPTIONS}
84
+ editorProps={ACE_EDITOR_PROPS}
85
+ />
86
+ </PreviewInner>
87
+ </PreviewContainer>
88
+ )
89
+ }
@@ -0,0 +1,35 @@
1
+ import {FormFieldPresence} from '@sanity/base/presence'
2
+ import {createSchema} from '@sanity/base/schema'
3
+ import {ValidationMarker, Path} from '@sanity/types'
4
+ import {Box} from '@sanity/ui'
5
+ import {useAction} from '@sanity/ui-workshop'
6
+ import React, {useState} from 'react'
7
+ import CodeInput, {CodeSchemaType} from '../CodeInput'
8
+ import typeDef from '../schema'
9
+
10
+ const schema = createSchema({name: 'dev', types: [typeDef]})
11
+ const type = schema.get('code') as CodeSchemaType
12
+
13
+ export default function DevStory() {
14
+ const [focusPath] = useState<Path>([])
15
+ const [validation] = useState<ValidationMarker[]>([])
16
+ const onBlur = useAction('onBlur')
17
+ const onChange = useAction('onChange')
18
+ const onFocus = useAction('onFocus')
19
+ const [presence] = useState<FormFieldPresence[]>([])
20
+
21
+ return (
22
+ <Box padding={4}>
23
+ <CodeInput
24
+ focusPath={focusPath}
25
+ level={0}
26
+ validation={validation}
27
+ onBlur={onBlur}
28
+ onChange={onChange}
29
+ onFocus={onFocus}
30
+ presence={presence}
31
+ type={type}
32
+ />
33
+ </Box>
34
+ )
35
+ }
@@ -0,0 +1,10 @@
1
+ import {defineScope} from '@sanity/ui-workshop'
2
+ import {lazy} from 'react'
3
+
4
+ export default defineScope('code-input', '@sanity/code-input', [
5
+ {
6
+ name: 'dev',
7
+ title: 'Dev',
8
+ component: lazy(() => import('./dev')),
9
+ },
10
+ ])
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']