React 18 and Vite (#4510)

This commit is contained in:
Matthew Penner 2022-11-25 13:25:03 -07:00 committed by GitHub
parent 1bb1b13f6d
commit 21613fa602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
244 changed files with 4547 additions and 8933 deletions

View file

@ -0,0 +1,206 @@
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
import {
defaultHighlightStyle,
syntaxHighlighting,
indentOnInput,
bracketMatching,
foldGutter,
foldKeymap,
LanguageDescription,
indentUnit,
} from '@codemirror/language';
import { languages } from '@codemirror/language-data';
import { lintKeymap } from '@codemirror/lint';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import type { Extension } from '@codemirror/state';
import { EditorState, Compartment } from '@codemirror/state';
import {
keymap,
highlightSpecialChars,
drawSelection,
highlightActiveLine,
dropCursor,
rectangularSelection,
crosshairCursor,
lineNumbers,
highlightActiveLineGutter,
EditorView,
} from '@codemirror/view';
import type { CSSProperties } from 'react';
import { useEffect, useRef, useState } from 'react';
import { ayuMirageHighlightStyle, ayuMirageTheme } from './theme';
function findLanguageByFilename(filename: string): LanguageDescription | undefined {
const language = LanguageDescription.matchFilename(languages, filename);
if (language !== null) {
return language;
}
return undefined;
}
const defaultExtensions: Extension = [
// Ayu Mirage
ayuMirageTheme,
syntaxHighlighting(ayuMirageHighlightStyle),
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...lintKeymap,
indentWithTab,
]),
EditorState.tabSize.of(4),
indentUnit.of('\t'),
];
export interface EditorProps {
// DOM
className?: string;
style?: CSSProperties;
// CodeMirror Config
extensions?: Extension[];
language?: LanguageDescription;
// Options
filename?: string;
initialContent?: string;
// ?
fetchContent?: (callback: () => Promise<string>) => void;
// Events
onContentSaved?: () => void;
onLanguageChanged?: (language: LanguageDescription | undefined) => void;
}
export function Editor(props: EditorProps) {
const ref = useRef<HTMLDivElement>(null);
const [view, setView] = useState<EditorView>();
// eslint-disable-next-line react/hook-use-state
const [languageConfig] = useState(new Compartment());
// eslint-disable-next-line react/hook-use-state
const [keybindings] = useState(new Compartment());
const createEditorState = () =>
EditorState.create({
doc: props.initialContent,
extensions: [
defaultExtensions,
props.extensions === undefined ? [] : props.extensions,
languageConfig.of([]),
keybindings.of([]),
],
});
useEffect(() => {
if (ref.current === null) {
return;
}
setView(
new EditorView({
state: createEditorState(),
parent: ref.current,
}),
);
return () => {
if (view === undefined) {
return;
}
view.destroy();
setView(undefined);
};
}, [ref]);
useEffect(() => {
if (view === undefined) {
return;
}
view.setState(createEditorState());
}, [props.initialContent]);
useEffect(() => {
if (view === undefined) {
return;
}
const language = props.language ?? findLanguageByFilename(props.filename ?? '');
if (language === undefined) {
return;
}
void language.load().then(language => {
view.dispatch({
effects: languageConfig.reconfigure(language),
});
});
if (props.onLanguageChanged !== undefined) {
props.onLanguageChanged(language);
}
}, [view, props.filename, props.language]);
useEffect(() => {
if (props.fetchContent === undefined) {
return;
}
if (!view) {
props.fetchContent(async () => {
throw new Error('no editor session has been configured');
});
return;
}
const { onContentSaved } = props;
if (onContentSaved !== undefined) {
view.dispatch({
effects: keybindings.reconfigure(
keymap.of([
{
key: 'Mod-s',
run() {
onContentSaved();
return true;
},
},
]),
),
});
}
props.fetchContent(async () => view.state.doc.toJSON().join('\n'));
}, [view, props.fetchContent, props.onContentSaved]);
return <div ref={ref} className={`relative ${props.className ?? ''}`.trimEnd()} style={props.style} />;
}

View file

@ -0,0 +1 @@
export { Editor } from './Editor';

View file

@ -0,0 +1,148 @@
import { HighlightStyle } from '@codemirror/language';
import type { Extension } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { tags as t } from '@lezer/highlight';
const highlightBackground = 'transparent';
const background = '#1F2430';
const selection = '#34455A';
const cursor = '#FFCC66';
export const ayuMirageTheme: Extension = EditorView.theme(
{
'&': {
color: '#CBCCC6',
backgroundColor: background,
},
'.cm-content': {
caretColor: cursor,
},
'&.cm-focused .cm-cursor': { borderLeftColor: cursor },
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': {
backgroundColor: selection,
},
'.cm-panels': { backgroundColor: '#232834', color: '#CBCCC6' },
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
'.cm-searchMatch': {
backgroundColor: '#72a1ff59',
outline: '1px solid #457dff',
},
'.cm-searchMatch.cm-searchMatch-selected': {
backgroundColor: '#6199ff2f',
},
'.cm-activeLine': { backgroundColor: highlightBackground },
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
'.cm-matchingBracket, .cm-nonmatchingBracket': {
backgroundColor: '#bad0f847',
outline: '1px solid #515a6b',
},
'.cm-gutters': {
backgroundColor: 'transparent',
color: '#FF3333',
border: 'none',
},
'.cm-gutterElement': {
color: 'rgba(61, 66, 77, 99)',
},
'.cm-activeLineGutter': {
backgroundColor: highlightBackground,
},
'.cm-foldPlaceholder': {
backgroundColor: 'transparent',
border: 'none',
color: '#ddd',
},
'.cm-tooltip': {
border: '1px solid #181a1f',
backgroundColor: '#232834',
},
'.cm-tooltip-autocomplete': {
'& > ul > li[aria-selected]': {
backgroundColor: highlightBackground,
color: '#CBCCC6',
},
},
},
{ dark: true },
);
export const ayuMirageHighlightStyle = HighlightStyle.define([
{
tag: t.keyword,
color: '#FFA759',
},
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: '#5CCFE6',
},
{
tag: [t.function(t.variableName), t.labelName],
color: '#CBCCC6',
},
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: '#F29E74',
},
{
tag: [t.definition(t.name), t.separator],
color: '#CBCCC6B3',
},
{
tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: '#FFCC66',
},
{
tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
color: '#5CCFE6',
},
{
tag: [t.meta, t.comment],
color: '#5C6773',
},
{
tag: t.strong,
fontWeight: 'bold',
},
{
tag: t.emphasis,
fontStyle: 'italic',
},
{
tag: t.strikethrough,
textDecoration: 'line-through',
},
{
tag: t.link,
color: '#FF3333',
textDecoration: 'underline',
},
{
tag: t.heading,
fontWeight: 'bold',
color: '#BAE67E',
},
{
tag: [t.atom, t.bool, t.special(t.variableName)],
color: '#5CCFE6',
},
{
tag: [t.processingInstruction, t.string, t.inserted],
color: '#BAE67E',
},
{
tag: t.invalid,
color: '#FF3333',
},
]);