@sanity/code-input 3.0.1 → 4.1.0
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 +140 -64
- package/lib/_chunks/CodeMirrorProxy-3836f097.js +619 -0
- package/lib/_chunks/CodeMirrorProxy-3836f097.js.map +1 -0
- package/lib/_chunks/CodeMirrorProxy-e83d4d37.js +611 -0
- package/lib/_chunks/CodeMirrorProxy-e83d4d37.js.map +1 -0
- package/lib/_chunks/index-17e68aff.js +563 -0
- package/lib/_chunks/index-17e68aff.js.map +1 -0
- package/lib/_chunks/index-9a4cb814.js +549 -0
- package/lib/_chunks/index-9a4cb814.js.map +1 -0
- package/lib/{src/index.d.ts → index.d.ts} +18 -10
- package/lib/index.esm.js +1 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +10 -1
- package/lib/index.js.map +1 -1
- package/package.json +53 -27
- package/src/CodeInput.tsx +72 -272
- package/src/LanguageField.tsx +33 -0
- package/src/LanguageInput.tsx +32 -0
- package/src/PreviewCode.tsx +40 -68
- package/src/__workshop__/index.ts +22 -0
- package/src/__workshop__/lazy.tsx +54 -0
- package/src/__workshop__/preview.tsx +24 -0
- package/src/__workshop__/props.tsx +24 -0
- package/src/codemirror/CodeMirrorProxy.tsx +157 -0
- package/src/codemirror/CodeModeContext.tsx +4 -0
- package/src/codemirror/defaultCodeModes.ts +109 -0
- package/src/codemirror/extensions/highlightLineExtension.ts +164 -0
- package/src/codemirror/extensions/theme.ts +61 -0
- package/src/codemirror/extensions/useCodeMirrorTheme.ts +63 -0
- package/src/codemirror/extensions/useFontSize.ts +24 -0
- package/src/codemirror/useCodeMirror-client.test.tsx +49 -0
- package/src/{ace-editor/AceEditor-server.test.tsx → codemirror/useCodeMirror-server.test.tsx} +3 -4
- package/src/codemirror/useCodeMirror.tsx +12 -0
- package/src/codemirror/useLanguageMode.tsx +52 -0
- package/src/config.ts +1 -13
- package/src/getMedia.tsx +0 -2
- package/src/index.ts +4 -11
- package/src/plugin.tsx +39 -0
- package/src/schema.tsx +3 -7
- package/src/types.ts +19 -3
- package/src/ui/focusRingStyle.ts +27 -0
- package/src/useFieldMember.ts +16 -0
- package/lib/_chunks/editorSupport-895caf32.esm.js +0 -2
- package/lib/_chunks/editorSupport-895caf32.esm.js.map +0 -1
- package/lib/_chunks/editorSupport-bda3d360.js +0 -2
- package/lib/_chunks/editorSupport-bda3d360.js.map +0 -1
- package/src/ace-editor/AceEditor-client.test.tsx +0 -37
- package/src/ace-editor/AceEditorLazy.tsx +0 -19
- package/src/ace-editor/editorSupport.ts +0 -34
- package/src/ace-editor/groq.ts +0 -630
- package/src/createHighlightMarkers.ts +0 -24
package/src/CodeInput.tsx
CHANGED
|
@@ -1,102 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
setIfMissing,
|
|
13
|
-
unset,
|
|
14
|
-
RenderInputCallback,
|
|
15
|
-
} from 'sanity'
|
|
16
|
-
import {Card, Select, Stack, ThemeColorSchemeKey} from '@sanity/ui'
|
|
17
|
-
import styled from 'styled-components'
|
|
18
|
-
import createHighlightMarkers, {highlightMarkersCSS} from './createHighlightMarkers'
|
|
19
|
-
import {CodeInputLanguage, CodeInputValue} from './types'
|
|
20
|
-
import {
|
|
21
|
-
ACE_EDITOR_PROPS,
|
|
22
|
-
ACE_SET_OPTIONS,
|
|
23
|
-
DEFAULT_DARK_THEME,
|
|
24
|
-
DEFAULT_THEME,
|
|
25
|
-
LANGUAGE_ALIASES,
|
|
26
|
-
PATH_CODE,
|
|
27
|
-
SUPPORTED_LANGUAGES,
|
|
28
|
-
SUPPORTED_THEMES,
|
|
29
|
-
} from './config'
|
|
30
|
-
import {useAceEditor} from './ace-editor/AceEditorLazy'
|
|
1
|
+
import {Suspense, useCallback} from 'react'
|
|
2
|
+
import {MemberField, ObjectInputProps, RenderInputCallback, set, setIfMissing, unset} from 'sanity'
|
|
3
|
+
import {Box, Card, Stack, Text} from '@sanity/ui'
|
|
4
|
+
import styled, {css} from 'styled-components'
|
|
5
|
+
import {LanguageField} from './LanguageField'
|
|
6
|
+
import {useCodeMirror} from './codemirror/useCodeMirror'
|
|
7
|
+
import {useLanguageMode} from './codemirror/useLanguageMode'
|
|
8
|
+
import {PATH_CODE} from './config'
|
|
9
|
+
import {CodeInputValue, CodeSchemaType} from './types'
|
|
10
|
+
import {useFieldMember} from './useFieldMember'
|
|
11
|
+
import {focusRingBorderStyle, focusRingStyle} from './ui/focusRingStyle'
|
|
31
12
|
|
|
32
13
|
export type {CodeInputLanguage, CodeInputValue} from './types'
|
|
33
14
|
|
|
34
|
-
const EditorContainer = styled(Card)`
|
|
35
|
-
position: relative;
|
|
36
|
-
box-sizing: border-box;
|
|
37
|
-
overflow: hidden;
|
|
38
|
-
z-index: 0;
|
|
39
|
-
|
|
40
|
-
.ace_editor {
|
|
41
|
-
font-family: ${({theme}) => theme.sanity.fonts.code.family};
|
|
42
|
-
font-size: ${({theme}) => theme.sanity.fonts.code.sizes[1]};
|
|
43
|
-
line-height: inherit;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
${highlightMarkersCSS}
|
|
47
|
-
|
|
48
|
-
&:not([disabled]):not([readonly]) {
|
|
49
|
-
&:focus,
|
|
50
|
-
&:focus-within {
|
|
51
|
-
box-shadow: 0 0 0 2px ${({theme}) => theme.sanity.color.base.focusRing};
|
|
52
|
-
background-color: ${({theme}) => theme.sanity.color.base.bg};
|
|
53
|
-
border-color: ${({theme}) => theme.sanity.color.base.focusRing};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @public
|
|
60
|
-
*/
|
|
61
|
-
export interface CodeOptions {
|
|
62
|
-
theme?: string
|
|
63
|
-
darkTheme?: string
|
|
64
|
-
languageAlternatives?: CodeInputLanguage[]
|
|
65
|
-
language?: string
|
|
66
|
-
withFilename?: boolean
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @public
|
|
71
|
-
*/
|
|
72
|
-
export type CodeSchemaType = Omit<ObjectSchemaType, 'options'> & {
|
|
73
|
-
options?: CodeOptions
|
|
74
|
-
}
|
|
75
|
-
|
|
76
15
|
/**
|
|
77
16
|
* @public
|
|
78
17
|
*/
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
18
|
+
export interface CodeInputProps extends ObjectInputProps<CodeInputValue, CodeSchemaType> {}
|
|
19
|
+
|
|
20
|
+
const EditorContainer = styled(Card)(({theme}) => {
|
|
21
|
+
const {focusRing, input} = theme.sanity
|
|
22
|
+
const base = theme.sanity.color.base
|
|
23
|
+
const color = theme.sanity.color.input
|
|
24
|
+
const border = {
|
|
25
|
+
color: color.default.enabled.border,
|
|
26
|
+
width: input.border.width,
|
|
27
|
+
}
|
|
83
28
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const alias = LANGUAGE_ALIASES[mode]
|
|
29
|
+
return css`
|
|
30
|
+
--input-box-shadow: ${focusRingBorderStyle(border)};
|
|
87
31
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
32
|
+
box-shadow: var(--input-box-shadow);
|
|
33
|
+
height: 250px;
|
|
34
|
+
min-height: 80px;
|
|
35
|
+
overflow-y: auto;
|
|
36
|
+
position: relative;
|
|
37
|
+
resize: vertical;
|
|
38
|
+
z-index: 0;
|
|
91
39
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
40
|
+
& > .cm-theme {
|
|
41
|
+
height: 100%;
|
|
42
|
+
}
|
|
96
43
|
|
|
97
|
-
|
|
98
|
-
|
|
44
|
+
&:focus-within {
|
|
45
|
+
--input-box-shadow: ${focusRingStyle({
|
|
46
|
+
base,
|
|
47
|
+
border,
|
|
48
|
+
focusRing,
|
|
49
|
+
})};
|
|
50
|
+
}
|
|
51
|
+
`
|
|
52
|
+
})
|
|
99
53
|
|
|
54
|
+
/** @public */
|
|
100
55
|
export function CodeInput(props: CodeInputProps) {
|
|
101
56
|
const {
|
|
102
57
|
members,
|
|
@@ -112,135 +67,19 @@ export function CodeInput(props: CodeInputProps) {
|
|
|
112
67
|
onPathFocus,
|
|
113
68
|
} = props
|
|
114
69
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
() => members.filter((member) => member.kind === 'field') as FieldMember[],
|
|
119
|
-
[members]
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
const languageFieldMember = fieldMembers.find((member) => member.name === 'language')
|
|
123
|
-
const filenameMember = fieldMembers.find((member) => member.name === 'filename')
|
|
124
|
-
const codeFieldMember = fieldMembers.find((member) => member.name === 'code')
|
|
125
|
-
|
|
126
|
-
useImperativeHandle(elementProps.ref, () => ({
|
|
127
|
-
focus: () => {
|
|
128
|
-
aceEditorRef?.current?.editor?.focus()
|
|
129
|
-
},
|
|
130
|
-
}))
|
|
70
|
+
const languageFieldMember = useFieldMember(members, 'language')
|
|
71
|
+
const filenameMember = useFieldMember(members, 'filename')
|
|
72
|
+
const codeFieldMember = useFieldMember(members, 'code')
|
|
131
73
|
|
|
132
74
|
const handleCodeFocus = useCallback(() => {
|
|
133
75
|
onPathFocus(PATH_CODE)
|
|
134
76
|
}, [onPathFocus])
|
|
135
77
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const isLight = scheme === 'light'
|
|
140
|
-
const preferredTheme = isLight ? type.options?.theme : type.options?.darkTheme
|
|
141
|
-
const defaultTheme = isLight ? DEFAULT_THEME : DEFAULT_DARK_THEME
|
|
142
|
-
return preferredTheme && SUPPORTED_THEMES.find((t) => t === preferredTheme)
|
|
143
|
-
? preferredTheme
|
|
144
|
-
: defaultTheme
|
|
145
|
-
}, [type, scheme])
|
|
146
|
-
|
|
147
|
-
const handleToggleSelectLine = useCallback(
|
|
148
|
-
(lineNumber: number) => {
|
|
149
|
-
const editorSession = aceEditorRef.current?.editor?.getSession()
|
|
150
|
-
const backgroundMarkers = editorSession?.getMarkers(true)
|
|
151
|
-
const currentHighlightedLines = Object.keys(backgroundMarkers)
|
|
152
|
-
.filter((key) => backgroundMarkers[key].type === 'screenLine')
|
|
153
|
-
.map((key) => backgroundMarkers[key].range.start.row)
|
|
154
|
-
const currentIndex = currentHighlightedLines.indexOf(lineNumber)
|
|
155
|
-
if (currentIndex > -1) {
|
|
156
|
-
// toggle remove
|
|
157
|
-
currentHighlightedLines.splice(currentIndex, 1)
|
|
158
|
-
} else {
|
|
159
|
-
// toggle add
|
|
160
|
-
currentHighlightedLines.push(lineNumber)
|
|
161
|
-
currentHighlightedLines.sort()
|
|
162
|
-
}
|
|
163
|
-
onChange(
|
|
164
|
-
set(
|
|
165
|
-
currentHighlightedLines.map(
|
|
166
|
-
(line) =>
|
|
167
|
-
// ace starts at line (row) 0, but we store it starting at line 1
|
|
168
|
-
line + 1
|
|
169
|
-
),
|
|
170
|
-
['highlightedLines']
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
},
|
|
174
|
-
[aceEditorRef, onChange]
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
const handleGutterMouseDown = useCallback(
|
|
178
|
-
(event: any) => {
|
|
179
|
-
const target = event.domEvent.target
|
|
180
|
-
if (target.classList.contains('ace_gutter-cell')) {
|
|
181
|
-
const row = event.getDocumentPosition().row
|
|
182
|
-
handleToggleSelectLine(row)
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
[handleToggleSelectLine]
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
const editor = aceEditorRef?.current?.editor
|
|
190
|
-
return () => {
|
|
191
|
-
editor?.session?.removeListener('guttermousedown', handleGutterMouseDown)
|
|
192
|
-
}
|
|
193
|
-
}, [aceEditorRef, handleGutterMouseDown])
|
|
194
|
-
|
|
195
|
-
const handleEditorLoad = useCallback(
|
|
196
|
-
(editor: any) => {
|
|
197
|
-
editor?.on('guttermousedown', handleGutterMouseDown)
|
|
198
|
-
},
|
|
199
|
-
[handleGutterMouseDown]
|
|
78
|
+
const onHighlightChange = useCallback(
|
|
79
|
+
(lines: number[]) => onChange(set(lines, ['highlightedLines'])),
|
|
80
|
+
[onChange]
|
|
200
81
|
)
|
|
201
82
|
|
|
202
|
-
const getLanguageAlternatives = useCallback((): {
|
|
203
|
-
title: string
|
|
204
|
-
value: string
|
|
205
|
-
mode?: string
|
|
206
|
-
}[] => {
|
|
207
|
-
const languageAlternatives = type.options?.languageAlternatives
|
|
208
|
-
if (!languageAlternatives) {
|
|
209
|
-
return SUPPORTED_LANGUAGES
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (!Array.isArray(languageAlternatives)) {
|
|
213
|
-
throw new Error(
|
|
214
|
-
`'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}`
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => {
|
|
219
|
-
const alias = LANGUAGE_ALIASES[val]
|
|
220
|
-
if (alias) {
|
|
221
|
-
// eslint-disable-next-line no-console
|
|
222
|
-
console.warn(
|
|
223
|
-
`'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`,
|
|
224
|
-
val,
|
|
225
|
-
alias,
|
|
226
|
-
alias
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
return acc.concat({title, value: alias, mode: mode})
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (!mode && !SUPPORTED_LANGUAGES.find((lang) => lang.value === val)) {
|
|
233
|
-
// eslint-disable-next-line no-console
|
|
234
|
-
console.warn(
|
|
235
|
-
`'options.languageAlternatives' lists a language which is not supported: "%s", syntax highlighting will be disabled.`,
|
|
236
|
-
val
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return acc.concat({title, value: val, mode})
|
|
241
|
-
}, [])
|
|
242
|
-
}, [type])
|
|
243
|
-
|
|
244
83
|
const handleCodeChange = useCallback(
|
|
245
84
|
(code: string) => {
|
|
246
85
|
const path = PATH_CODE
|
|
@@ -253,69 +92,29 @@ export function CodeInput(props: CodeInputProps) {
|
|
|
253
92
|
},
|
|
254
93
|
[onChange, type]
|
|
255
94
|
)
|
|
95
|
+
const {languages, language, languageMode} = useLanguageMode(props.schemaType, props.value)
|
|
256
96
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
const fixedLanguage = type.options?.language
|
|
260
|
-
|
|
261
|
-
const language = value?.language || fixedLanguage
|
|
262
|
-
|
|
263
|
-
// the language config from the schema
|
|
264
|
-
const configured = languages.find((entry) => entry.value === language)
|
|
265
|
-
|
|
266
|
-
// is the language officially supported (e.g. we import the mode by default)
|
|
267
|
-
const supported = language && isSupportedLanguage(language)
|
|
268
|
-
|
|
269
|
-
const mode = configured?.mode || (supported ? language : 'text')
|
|
270
|
-
|
|
271
|
-
const renderLanguageInput = useCallback(
|
|
272
|
-
(inputProps: Omit<InputProps, 'renderDefault'>) => {
|
|
273
|
-
return (
|
|
274
|
-
<Select
|
|
275
|
-
{...(inputProps as StringInputProps)}
|
|
276
|
-
onChange={(e) => {
|
|
277
|
-
const newValue = e.currentTarget.value
|
|
278
|
-
inputProps.onChange(newValue ? set(newValue) : unset())
|
|
279
|
-
}}
|
|
280
|
-
>
|
|
281
|
-
{languages.map((lang: {title: string; value: string}) => (
|
|
282
|
-
<option key={lang.value} value={lang.value}>
|
|
283
|
-
{lang.title}
|
|
284
|
-
</option>
|
|
285
|
-
))}
|
|
286
|
-
</Select>
|
|
287
|
-
)
|
|
288
|
-
},
|
|
289
|
-
[languages]
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
const AceEditor = useAceEditor()
|
|
97
|
+
const CodeMirror = useCodeMirror()
|
|
293
98
|
|
|
294
99
|
const renderCodeInput: RenderInputCallback = useCallback(
|
|
295
100
|
(inputProps) => {
|
|
296
101
|
return (
|
|
297
|
-
<EditorContainer radius={1}
|
|
298
|
-
{
|
|
299
|
-
<Suspense
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
102
|
+
<EditorContainer border overflow="hidden" radius={1} sizing="border" readOnly={readOnly}>
|
|
103
|
+
{CodeMirror && (
|
|
104
|
+
<Suspense
|
|
105
|
+
fallback={
|
|
106
|
+
<Box padding={3}>
|
|
107
|
+
<Text>Loading code editor...</Text>
|
|
108
|
+
</Box>
|
|
109
|
+
}
|
|
110
|
+
>
|
|
111
|
+
<CodeMirror
|
|
112
|
+
languageMode={languageMode}
|
|
305
113
|
onChange={handleCodeChange}
|
|
306
|
-
name={inputProps.id}
|
|
307
114
|
value={inputProps.value as string}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
? createHighlightMarkers(value.highlightedLines)
|
|
311
|
-
: undefined
|
|
312
|
-
}
|
|
313
|
-
onLoad={handleEditorLoad}
|
|
115
|
+
highlightLines={value?.highlightedLines}
|
|
116
|
+
onHighlightChange={onHighlightChange}
|
|
314
117
|
readOnly={readOnly}
|
|
315
|
-
tabSize={2}
|
|
316
|
-
wrapEnabled
|
|
317
|
-
setOptions={ACE_SET_OPTIONS}
|
|
318
|
-
editorProps={ACE_EDITOR_PROPS}
|
|
319
118
|
onFocus={handleCodeFocus}
|
|
320
119
|
onBlur={elementProps.onBlur}
|
|
321
120
|
/>
|
|
@@ -325,12 +124,11 @@ export function CodeInput(props: CodeInputProps) {
|
|
|
325
124
|
)
|
|
326
125
|
},
|
|
327
126
|
[
|
|
328
|
-
|
|
329
|
-
theme,
|
|
127
|
+
CodeMirror,
|
|
330
128
|
handleCodeChange,
|
|
331
129
|
handleCodeFocus,
|
|
332
|
-
|
|
333
|
-
|
|
130
|
+
onHighlightChange,
|
|
131
|
+
languageMode,
|
|
334
132
|
elementProps.onBlur,
|
|
335
133
|
readOnly,
|
|
336
134
|
value,
|
|
@@ -340,11 +138,13 @@ export function CodeInput(props: CodeInputProps) {
|
|
|
340
138
|
return (
|
|
341
139
|
<Stack space={4}>
|
|
342
140
|
{languageFieldMember && (
|
|
343
|
-
<
|
|
141
|
+
<LanguageField
|
|
344
142
|
member={languageFieldMember}
|
|
345
|
-
|
|
143
|
+
language={language}
|
|
144
|
+
languages={languages}
|
|
346
145
|
renderField={renderField}
|
|
347
|
-
|
|
146
|
+
renderItem={renderItem}
|
|
147
|
+
renderInput={renderInput}
|
|
348
148
|
renderPreview={renderPreview}
|
|
349
149
|
/>
|
|
350
150
|
)}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {useCallback} from 'react'
|
|
2
|
+
import {FieldMember, InputProps, MemberField, MemberFieldProps, StringInputProps} from 'sanity'
|
|
3
|
+
import {CodeInputLanguage} from './types'
|
|
4
|
+
import {LanguageInput} from './LanguageInput'
|
|
5
|
+
|
|
6
|
+
export function LanguageField(
|
|
7
|
+
props: MemberFieldProps & {member: FieldMember; language: string; languages: CodeInputLanguage[]}
|
|
8
|
+
) {
|
|
9
|
+
const {member, languages, language, renderItem, renderField, renderPreview} = props
|
|
10
|
+
|
|
11
|
+
const renderInput = useCallback(
|
|
12
|
+
(inputProps: Omit<InputProps, 'renderDefault'>) => {
|
|
13
|
+
return (
|
|
14
|
+
<LanguageInput
|
|
15
|
+
{...(inputProps as StringInputProps)}
|
|
16
|
+
language={language}
|
|
17
|
+
languages={languages}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
},
|
|
21
|
+
[languages, language]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<MemberField
|
|
26
|
+
member={member}
|
|
27
|
+
renderItem={renderItem}
|
|
28
|
+
renderField={renderField}
|
|
29
|
+
renderInput={renderInput}
|
|
30
|
+
renderPreview={renderPreview}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {ChangeEvent, useCallback} from 'react'
|
|
2
|
+
import {StringInputProps, set, unset} from 'sanity'
|
|
3
|
+
import {Select} from '@sanity/ui'
|
|
4
|
+
import {CodeInputLanguage} from './types'
|
|
5
|
+
|
|
6
|
+
export interface LanguageInputProps extends StringInputProps {
|
|
7
|
+
language: string
|
|
8
|
+
languages: CodeInputLanguage[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @internal */
|
|
12
|
+
export function LanguageInput(props: LanguageInputProps) {
|
|
13
|
+
const {language, languages, onChange} = props
|
|
14
|
+
|
|
15
|
+
const handleChange = useCallback(
|
|
16
|
+
(e: ChangeEvent<HTMLSelectElement>) => {
|
|
17
|
+
const newValue = e.currentTarget.value
|
|
18
|
+
onChange(newValue ? set(newValue) : unset())
|
|
19
|
+
},
|
|
20
|
+
[onChange]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Select {...props} value={language} onChange={handleChange}>
|
|
25
|
+
{languages.map((lang: {title: string; value: string}) => (
|
|
26
|
+
<option key={lang.value} value={lang.value}>
|
|
27
|
+
{lang.title}
|
|
28
|
+
</option>
|
|
29
|
+
))}
|
|
30
|
+
</Select>
|
|
31
|
+
)
|
|
32
|
+
}
|
package/src/PreviewCode.tsx
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {Suspense} from 'react'
|
|
2
2
|
import styled from 'styled-components'
|
|
3
|
-
import {Box} from '@sanity/ui'
|
|
4
|
-
import {
|
|
5
|
-
import createHighlightMarkers from './createHighlightMarkers'
|
|
6
|
-
import {CodeInputValue} from './types'
|
|
7
|
-
import {useAceEditor} from './ace-editor/AceEditorLazy'
|
|
3
|
+
import {Label, Box, Card, Flex, Text} from '@sanity/ui'
|
|
4
|
+
import {CodeInputValue, CodeSchemaType} from './types'
|
|
8
5
|
import {PreviewProps} from 'sanity'
|
|
6
|
+
import {useCodeMirror} from './codemirror/useCodeMirror'
|
|
7
|
+
import {useLanguageMode} from './codemirror/useLanguageMode'
|
|
9
8
|
|
|
10
9
|
const PreviewContainer = styled(Box)`
|
|
11
10
|
position: relative;
|
|
12
11
|
`
|
|
13
12
|
|
|
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
13
|
/**
|
|
30
14
|
* @public
|
|
31
15
|
*/
|
|
@@ -37,62 +21,50 @@ export interface PreviewCodeProps extends PreviewProps {
|
|
|
37
21
|
* @public
|
|
38
22
|
*/
|
|
39
23
|
export default function PreviewCode(props: PreviewCodeProps) {
|
|
40
|
-
const aceEditorRef = useRef<any>()
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (!aceEditorRef?.current) return
|
|
44
|
-
|
|
45
|
-
const editor = aceEditorRef.current?.editor
|
|
46
|
-
|
|
47
|
-
if (editor) {
|
|
48
|
-
// Avoid cursor and focus tracking by Ace
|
|
49
|
-
editor.renderer.$cursorLayer.element.style.opacity = 0
|
|
50
|
-
editor.textInput.getElement().disabled = true
|
|
51
|
-
}
|
|
52
|
-
}, [])
|
|
53
|
-
|
|
54
|
-
const handleEditorChange = useCallback(() => {
|
|
55
|
-
// do nothing when the editor changes
|
|
56
|
-
}, [])
|
|
57
|
-
|
|
58
24
|
const {selection, schemaType: type} = props
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const mode = selection?.language || fixedLanguage || 'text'
|
|
25
|
+
const {languageMode} = useLanguageMode(type as CodeSchemaType, props.selection)
|
|
62
26
|
|
|
63
|
-
const
|
|
27
|
+
const CodeMirror = useCodeMirror()
|
|
64
28
|
return (
|
|
65
29
|
<PreviewContainer>
|
|
66
|
-
<
|
|
67
|
-
{
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
30
|
+
<Card padding={4}>
|
|
31
|
+
{selection?.filename || selection?.language ? (
|
|
32
|
+
<Card
|
|
33
|
+
paddingBottom={4}
|
|
34
|
+
marginBottom={selection.code ? 4 : 0}
|
|
35
|
+
borderBottom={!!selection.code}
|
|
36
|
+
>
|
|
37
|
+
<Flex align="center" justify="flex-end">
|
|
38
|
+
{selection?.filename ? (
|
|
39
|
+
<Box flex={1}>
|
|
40
|
+
<Text>
|
|
41
|
+
<code>{selection.filename}</code>
|
|
42
|
+
</Text>
|
|
43
|
+
</Box>
|
|
44
|
+
) : null}
|
|
45
|
+
{selection?.language ? <Label muted>{selection.language}</Label> : null}
|
|
46
|
+
</Flex>
|
|
47
|
+
</Card>
|
|
48
|
+
) : null}
|
|
49
|
+
{CodeMirror && (
|
|
50
|
+
<Suspense fallback={<Card padding={2}>Loading code preview...</Card>}>
|
|
51
|
+
<CodeMirror
|
|
77
52
|
readOnly
|
|
78
|
-
|
|
79
|
-
showPrintMargin={false}
|
|
80
|
-
highlightActiveLine={false}
|
|
81
|
-
cursorStart={-1}
|
|
53
|
+
editable={false}
|
|
82
54
|
value={selection?.code || ''}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
55
|
+
highlightLines={selection?.highlightedLines || []}
|
|
56
|
+
basicSetup={{
|
|
57
|
+
lineNumbers: false,
|
|
58
|
+
foldGutter: false,
|
|
59
|
+
highlightSelectionMatches: false,
|
|
60
|
+
highlightActiveLineGutter: false,
|
|
61
|
+
highlightActiveLine: false,
|
|
62
|
+
}}
|
|
63
|
+
languageMode={languageMode}
|
|
92
64
|
/>
|
|
93
65
|
</Suspense>
|
|
94
66
|
)}
|
|
95
|
-
</
|
|
67
|
+
</Card>
|
|
96
68
|
</PreviewContainer>
|
|
97
69
|
)
|
|
98
70
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {defineScope} from '@sanity/ui-workshop'
|
|
2
|
+
import {lazy} from 'react'
|
|
3
|
+
|
|
4
|
+
export default defineScope({
|
|
5
|
+
stories: [
|
|
6
|
+
{
|
|
7
|
+
name: 'props',
|
|
8
|
+
title: 'Props',
|
|
9
|
+
component: lazy(() => import('./props')),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'preview',
|
|
13
|
+
title: 'Preview',
|
|
14
|
+
component: lazy(() => import('./preview')),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'lazy',
|
|
18
|
+
title: 'Lazy',
|
|
19
|
+
component: lazy(() => import('./lazy')),
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {Box, Card, Container, Text} from '@sanity/ui'
|
|
2
|
+
import {Suspense, useState} from 'react'
|
|
3
|
+
import {useCodeMirror} from '../codemirror/useCodeMirror'
|
|
4
|
+
import {SUPPORTED_LANGUAGES} from '../config'
|
|
5
|
+
import {useSelect} from '@sanity/ui-workshop'
|
|
6
|
+
import styled from 'styled-components'
|
|
7
|
+
|
|
8
|
+
const langs = {
|
|
9
|
+
...Object.fromEntries(SUPPORTED_LANGUAGES.map((l) => [l.title, l.mode ?? l.value])),
|
|
10
|
+
'Not supported': 'not-supported',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const defaultSnippet = `
|
|
14
|
+
<Card border padding={2}>
|
|
15
|
+
{variable}
|
|
16
|
+
</Card>
|
|
17
|
+
`.trim()
|
|
18
|
+
|
|
19
|
+
const Root = styled(Card)`
|
|
20
|
+
& > .cm-theme {
|
|
21
|
+
height: 100%;
|
|
22
|
+
}
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
export default function CodeMirrorStory() {
|
|
26
|
+
const language = useSelect('Language mode', langs, 'tsx')
|
|
27
|
+
const [code, setCode] = useState(defaultSnippet)
|
|
28
|
+
const [highlights, setHighlights] = useState<number[]>([])
|
|
29
|
+
const CodeMirror = useCodeMirror()
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Container padding={4} sizing="border" width={1}>
|
|
33
|
+
<Root border overflow="hidden" radius={3} style={{height: 300}}>
|
|
34
|
+
{CodeMirror && (
|
|
35
|
+
<Suspense
|
|
36
|
+
fallback={
|
|
37
|
+
<Box padding={3}>
|
|
38
|
+
<Text>Loading code editor...</Text>
|
|
39
|
+
</Box>
|
|
40
|
+
}
|
|
41
|
+
>
|
|
42
|
+
<CodeMirror
|
|
43
|
+
value={code}
|
|
44
|
+
onChange={setCode}
|
|
45
|
+
highlightLines={highlights}
|
|
46
|
+
onHighlightChange={setHighlights}
|
|
47
|
+
languageMode={language}
|
|
48
|
+
/>
|
|
49
|
+
</Suspense>
|
|
50
|
+
)}
|
|
51
|
+
</Root>
|
|
52
|
+
</Container>
|
|
53
|
+
)
|
|
54
|
+
}
|