From 2111776f91cda2a472b5d1bfaba36e2fa5f2a68e Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Tue, 3 Jan 2023 16:59:26 +0100 Subject: [PATCH] fix: refactored default language --- README.md | 4 +- src/CodeInput.tsx | 84 ++++-------------------- src/PreviewCode.tsx | 32 ++------- src/codemirror/CodeMirrorProxy.tsx | 21 ++++-- src/codemirror/highlightLineExtension.ts | 57 ++++++++-------- src/codemirror/useLanguageMode.tsx | 52 +++++++++++++++ src/index.ts | 4 +- src/schema.tsx | 10 +-- src/types.ts | 22 ++++++- 9 files changed, 143 insertions(+), 143 deletions(-) create mode 100644 src/codemirror/useLanguageMode.tsx diff --git a/README.md b/README.md index c671431c..bde7d35d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Code input for [Sanity](https://sanity.io/). -Currently only a subset of languages and features are exposed, over time we will implement a richer set of options. +Currently only a subset of languages and features are exposed, but more can be added via plugin options. ![Code input](assets/basic-input.png) @@ -58,7 +58,7 @@ Now you can use the `code` type in your schema types: ## Options -- `language` - Default language for this code field. If none is provided, code-input will use the most recently selected language (stored in localstorage) +- `language` - Default language for this code field. - `languageAlternatives` - Array of languages that should be available (se its format in the example below) - `withFilename` - Boolean option to display input field for filename diff --git a/src/CodeInput.tsx b/src/CodeInput.tsx index e2fdee5f..b649fd58 100644 --- a/src/CodeInput.tsx +++ b/src/CodeInput.tsx @@ -6,7 +6,6 @@ import { MemberField, ObjectInputProps, ObjectMember, - ObjectSchemaType, RenderInputCallback, set, setIfMissing, @@ -15,9 +14,10 @@ import { } from 'sanity' import {Card, Select, Stack, ThemeColorSchemeKey} from '@sanity/ui' import styled from 'styled-components' -import {CodeInputLanguage, CodeInputValue} from './types' -import {LANGUAGE_ALIASES, PATH_CODE, SUPPORTED_LANGUAGES} from './config' +import {CodeInputLanguage, CodeInputValue, CodeSchemaType} from './types' +import {PATH_CODE} from './config' import {useCodeMirror} from './codemirror/useCodeMirror' +import {useLanguageMode} from './codemirror/useLanguageMode' export type {CodeInputLanguage, CodeInputValue} from './types' @@ -31,25 +31,6 @@ const EditorContainer = styled(Card)` height: 250px; overflow-y: auto; ` -const defaultMode = 'text' - -/** - * @public - */ -export interface CodeOptions { - theme?: string - darkTheme?: string - languageAlternatives?: CodeInputLanguage[] - language?: string - withFilename?: boolean -} - -/** - * @public - */ -export type CodeSchemaType = Omit & { - options?: CodeOptions -} /** * @public @@ -59,10 +40,6 @@ export type CodeInputProps = ObjectInputProps & colorScheme?: ThemeColorSchemeKey } -function resolveAliasedLanguage(lang?: string) { - return (lang && LANGUAGE_ALIASES[lang]) ?? lang -} - export function CodeInput(props: CodeInputProps) { const { members, @@ -103,14 +80,7 @@ export function CodeInput(props: CodeInputProps) { }, [onChange, type] ) - const languages = useLanguageAlternatives(props.schemaType) - const fixedLanguage = type.options?.language - const language = value?.language || fixedLanguage - - // the language config from the schema - const configured = languages.find((entry) => entry.value === language) - - const languageMode = configured?.mode ?? resolveAliasedLanguage(language) ?? defaultMode + const {languages, language, languageMode} = useLanguageMode(props.schemaType, props.value) const CodeMirror = useCodeMirror() @@ -150,7 +120,12 @@ export function CodeInput(props: CodeInputProps) { return ( {languageFieldMember && ( - + )} {type.options?.withFilename && filenameMember && ( @@ -177,15 +152,15 @@ export function CodeInput(props: CodeInputProps) { } function LanguageField( - props: CodeInputProps & {member: FieldMember; languages: CodeInputLanguage[]} + props: CodeInputProps & {member: FieldMember; languages: CodeInputLanguage[]; language: string} ) { - const {member, languages, renderItem, renderField, renderPreview} = props + const {member, languages, language, renderItem, renderField, renderPreview} = props const renderLanguageInput = useCallback( (inputProps: Omit) => { return ( ) }, - [languages] + [languages, language] ) return ( @@ -222,34 +197,3 @@ function useFieldMember(members: ObjectMember[], fieldName: string) { [members, fieldName] ) } - -function useLanguageAlternatives(type: CodeSchemaType) { - return useMemo((): CodeInputLanguage[] => { - const languageAlternatives = type.options?.languageAlternatives - if (!languageAlternatives) { - return SUPPORTED_LANGUAGES - } - - if (!Array.isArray(languageAlternatives)) { - throw new Error( - `'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}` - ) - } - - return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => { - const alias = LANGUAGE_ALIASES[val] - if (alias) { - // eslint-disable-next-line no-console - console.warn( - `'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`, - val, - alias, - alias - ) - - return acc.concat({title, value: alias, mode: mode}) - } - return acc.concat({title, value: val, mode}) - }, []) - }, [type]) -} diff --git a/src/PreviewCode.tsx b/src/PreviewCode.tsx index f5e75f40..2c40e848 100644 --- a/src/PreviewCode.tsx +++ b/src/PreviewCode.tsx @@ -1,9 +1,10 @@ -import React, {Suspense, useEffect, useRef} from 'react' +import React, {Suspense} from 'react' import styled from 'styled-components' import {Box, Card} from '@sanity/ui' -import {CodeInputValue} from './types' +import {CodeInputValue, CodeSchemaType} from './types' import {PreviewProps} from 'sanity' import {useCodeMirror} from './codemirror/useCodeMirror' +import {useLanguageMode} from './codemirror/useLanguageMode' const PreviewContainer = styled(Box)` position: relative; @@ -20,24 +21,8 @@ export interface PreviewCodeProps extends PreviewProps { * @public */ export default function PreviewCode(props: PreviewCodeProps) { - const aceEditorRef = useRef() - - useEffect(() => { - if (!aceEditorRef?.current) return - - const editor = aceEditorRef.current?.editor - - if (editor) { - // Avoid cursor and focus tracking by Ace - editor.renderer.$cursorLayer.element.style.opacity = 0 - editor.textInput.getElement().disabled = true - } - }, []) - const {selection, schemaType: type} = props - const fixedLanguage = type?.options?.language - - const language = selection?.language || fixedLanguage || 'text' + const {languageMode} = useLanguageMode(type as CodeSchemaType, props.selection) const CodeMirror = useCodeMirror() return ( @@ -46,7 +31,6 @@ export default function PreviewCode(props: PreviewCodeProps) { {CodeMirror && ( Loading code preview...}> )} diff --git a/src/codemirror/CodeMirrorProxy.tsx b/src/codemirror/CodeMirrorProxy.tsx index 3c3d00bb..5d3c3db2 100644 --- a/src/codemirror/CodeMirrorProxy.tsx +++ b/src/codemirror/CodeMirrorProxy.tsx @@ -19,7 +19,15 @@ export interface CodeMirrorProps extends ReactCodeMirrorProps { * It is also responsible for integrating any CodeMirror extensions. */ const CodeMirrorProxy = forwardRef((props, ref) => { - const {value, languageMode, onHighlightChange, highlightLines, ...codeMirrorProps} = props + const { + value, + readOnly, + basicSetup, + languageMode, + onHighlightChange, + highlightLines, + ...codeMirrorProps + } = props const theme = useCodeMirrorTheme() const [editorView, setEditorView] = useState(undefined) @@ -29,6 +37,7 @@ const CodeMirrorProxy = forwardRef((props, const baseExtensions = [ highlightLine({ onHighlightChange, + readOnly, }), EditorView.lineWrapping, ] @@ -36,7 +45,7 @@ const CodeMirrorProxy = forwardRef((props, return [...baseExtensions, languageExtension] } return baseExtensions - }, [onHighlightChange, languageExtension]) + }, [onHighlightChange, languageExtension, readOnly]) useEffect(() => { if (editorView) { @@ -71,9 +80,11 @@ const CodeMirrorProxy = forwardRef((props, setEditorView(view) }} initialState={initialState} - basicSetup={{ - highlightActiveLine: false, - }} + basicSetup={ + basicSetup ?? { + highlightActiveLine: false, + } + } /> ) }) diff --git a/src/codemirror/highlightLineExtension.ts b/src/codemirror/highlightLineExtension.ts index b6177d80..3df48248 100644 --- a/src/codemirror/highlightLineExtension.ts +++ b/src/codemirror/highlightLineExtension.ts @@ -64,6 +64,7 @@ export const highlightState: { export interface HighlightLineConfig { onHighlightChange?: (lines: number[]) => void + readOnly?: boolean } const highlightTheme = EditorView.baseTheme({ @@ -97,34 +98,36 @@ const highlightTheme = EditorView.baseTheme({ export const highlightLine = (config: HighlightLineConfig): Extension => { return [ lineHighlightField, - lineNumbers({ - domEventHandlers: { - mousedown: (editorView, lineInfo) => { - // Determine if the line for the clicked gutter line number has highlighted state or not - const line = editorView.state.doc.lineAt(lineInfo.from) - let isHighlighted = false - editorView.state - .field(lineHighlightField) - .between(line.from, line.to, (from, to, value) => { - if (value) { - isHighlighted = true - return false // stop iteration - } - return undefined - }) + config.readOnly + ? [] + : lineNumbers({ + domEventHandlers: { + mousedown: (editorView, lineInfo) => { + // Determine if the line for the clicked gutter line number has highlighted state or not + const line = editorView.state.doc.lineAt(lineInfo.from) + let isHighlighted = false + editorView.state + .field(lineHighlightField) + .between(line.from, line.to, (from, to, value) => { + if (value) { + isHighlighted = true + return false // stop iteration + } + return undefined + }) - if (isHighlighted) { - editorView.dispatch({effects: removeLineHighlight.of(line.from)}) - } else { - editorView.dispatch({effects: addLineHighlight.of(line.from)}) - } - if (config?.onHighlightChange) { - config.onHighlightChange(editorView.state.toJSON(highlightState).highlight) - } - return true - }, - }, - }), + if (isHighlighted) { + editorView.dispatch({effects: removeLineHighlight.of(line.from)}) + } else { + editorView.dispatch({effects: addLineHighlight.of(line.from)}) + } + if (config?.onHighlightChange) { + config.onHighlightChange(editorView.state.toJSON(highlightState).highlight) + } + return true + }, + }, + }), highlightTheme, ] } diff --git a/src/codemirror/useLanguageMode.tsx b/src/codemirror/useLanguageMode.tsx new file mode 100644 index 00000000..e4870e00 --- /dev/null +++ b/src/codemirror/useLanguageMode.tsx @@ -0,0 +1,52 @@ +import {LANGUAGE_ALIASES, SUPPORTED_LANGUAGES} from '../config' +import {CodeInputLanguage, CodeInputValue, CodeSchemaType} from '../types' +import {useMemo} from 'react' + +export const defaultLanguageMode = 'text' + +export function useLanguageMode(schemaType: CodeSchemaType, value?: CodeInputValue) { + const languages = useLanguageAlternatives(schemaType) + const fixedLanguage = schemaType.options?.language + const language = value?.language ?? fixedLanguage ?? defaultLanguageMode + + // the language config from the schema + const configured = languages.find((entry) => entry.value === language) + const languageMode = configured?.mode ?? resolveAliasedLanguage(language) ?? defaultLanguageMode + + return {language, languageMode, languages} +} + +function resolveAliasedLanguage(lang?: string) { + return (lang && LANGUAGE_ALIASES[lang]) ?? lang +} + +function useLanguageAlternatives(type: CodeSchemaType) { + return useMemo((): CodeInputLanguage[] => { + const languageAlternatives = type.options?.languageAlternatives + if (!languageAlternatives) { + return SUPPORTED_LANGUAGES + } + + if (!Array.isArray(languageAlternatives)) { + throw new Error( + `'options.languageAlternatives' should be an array, got ${typeof languageAlternatives}` + ) + } + + return languageAlternatives.reduce((acc: CodeInputLanguage[], {title, value: val, mode}) => { + const alias = LANGUAGE_ALIASES[val] + if (alias) { + // eslint-disable-next-line no-console + console.warn( + `'options.languageAlternatives' lists a language with value "%s", which is an alias of "%s" - please replace the value to read "%s"`, + val, + alias, + alias + ) + + return acc.concat({title, value: alias, mode: mode}) + } + return acc.concat({title, value: val, mode}) + }, []) + }, [type]) +} diff --git a/src/index.ts b/src/index.ts index 0a0dccaf..3e6c6e56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ import {codeSchema, codeTypeName, CodeDefinition} from './schema' import PreviewCode, {PreviewCodeProps} from './PreviewCode' -export type {CodeInputProps, CodeSchemaType, CodeOptions} from './CodeInput' +export type {CodeInputProps, CodeInput} from './CodeInput' -export type {CodeInputLanguage, CodeInputValue} from './types' +export type {CodeInputLanguage, CodeInputValue, CodeSchemaType, CodeOptions} from './types' export {PreviewCode, type PreviewCodeProps} export {codeSchema, codeTypeName} diff --git a/src/schema.tsx b/src/schema.tsx index a5af21ce..af34f49d 100644 --- a/src/schema.tsx +++ b/src/schema.tsx @@ -1,13 +1,9 @@ import {CodeBlockIcon} from '@sanity/icons' -import {CodeInput, CodeOptions} from './CodeInput' -import PreviewCode, {PreviewCodeProps} from './PreviewCode' +import {CodeInput} from './CodeInput' +import PreviewCode from './PreviewCode' import {getMedia} from './getMedia' import {defineType, ObjectDefinition} from 'sanity' - -export type {CodeInputProps, CodeSchemaType} from './CodeInput' - -export type {CodeInputLanguage, CodeInputValue} from './types' -export type {PreviewCode, PreviewCodeProps, CodeInput} +import {CodeOptions} from './types' /** * @public diff --git a/src/types.ts b/src/types.ts index b4c1c3fc..ed78487c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,5 @@ -/** - * @public - */ +import {ObjectSchemaType} from 'sanity' + export interface CodeInputLanguage { title: string value: string @@ -17,3 +16,20 @@ export interface CodeInputValue { language?: string highlightedLines?: number[] } +/** + * @public + */ +export interface CodeOptions { + theme?: string + darkTheme?: string + languageAlternatives?: CodeInputLanguage[] + language?: string + withFilename?: boolean +} + +/** + * @public + */ +export type CodeSchemaType = Omit & { + options?: CodeOptions +}