GraphQL autocomplete and duplicate request

This commit is contained in:
Gregory Schier
2023-03-21 23:54:45 -07:00
parent 9b8961c23d
commit 168dfb9f6b
31 changed files with 299 additions and 157 deletions

View File

@@ -162,6 +162,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
>
{containerStyles && (
<VStack
space={0.5}
ref={initMenu}
style={menuStyles}
tabIndex={-1}

View File

@@ -151,7 +151,15 @@
/* NOTE: Extra selector required to override default styles */
.cm-tooltip.cm-tooltip {
@apply shadow-lg bg-gray-50 rounded overflow-hidden text-gray-900 border border-gray-200 z-50 pointer-events-auto;
@apply shadow-lg bg-gray-50 rounded text-gray-700 border border-gray-200 z-50 pointer-events-auto text-sm;
&.cm-completionInfo-right {
@apply ml-1;
}
&.cm-completionInfo-right-narrow {
@apply ml-1;
}
* {
@apply transition-none;
@@ -159,7 +167,7 @@
&.cm-tooltip-autocomplete {
& > ul {
@apply p-1 max-h-[20rem];
@apply p-1 max-h-[40vh];
}
& > ul > li {
@@ -177,5 +185,18 @@
.cm-completionIcon {
@apply text-sm flex items-center pb-0.5;
}
.cm-completionLabel {
}
.cm-completionDetail {
@apply ml-auto;
}
}
}
/* Add default icon. Needs low priority so it can be overwritten */
.cm-completionIcon::after {
content: '𝑥';
}

View File

@@ -1,11 +1,11 @@
import { defaultKeymap } from '@codemirror/commands';
import type { Extension } from '@codemirror/state';
import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import classnames from 'classnames';
import { EditorView } from 'codemirror';
import type { MutableRefObject } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { useUnmount } from 'react-use';
import { IconButton } from '../IconButton';
import './Editor.css';
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
@@ -18,6 +18,7 @@ export interface _EditorProps {
className?: string;
heightMode?: 'auto' | 'full';
contentType?: string;
languageExtension?: Extension;
autoFocus?: boolean;
defaultValue?: string;
placeholder?: string;
@@ -38,6 +39,7 @@ export function _Editor({
placeholder,
useTemplating,
defaultValue,
languageExtension,
onChange,
onFocus,
className,
@@ -48,12 +50,6 @@ export function _Editor({
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
const wrapperRef = useRef<HTMLDivElement | null>(null);
// Unmount the editor
useUnmount(() => {
cm.current?.view.destroy();
cm.current = null;
});
// Use ref so we can update the onChange handler without re-initializing the editor
const handleChange = useRef<_EditorProps['onChange']>(onChange);
useEffect(() => {
@@ -87,9 +83,11 @@ export function _Editor({
// Initialize the editor when ref mounts
useEffect(() => {
if (wrapperRef.current === null || cm.current !== null) return;
let view: EditorView;
try {
const languageCompartment = new Compartment();
const langExt = getLanguageExtension({ contentType, useTemplating, autocomplete });
const langExt =
languageExtension ?? getLanguageExtension({ contentType, useTemplating, autocomplete });
const state = EditorState.create({
doc: `${defaultValue ?? ''}`,
extensions: [
@@ -106,18 +104,18 @@ export function _Editor({
}),
],
});
const view = new EditorView({ state, parent: wrapperRef.current });
view = new EditorView({ state, parent: wrapperRef.current });
cm.current = { view, languageCompartment };
syncGutterBg({ parent: wrapperRef.current, className });
if (autoFocus) view.focus();
} catch (e) {
console.log('Failed to initialize Codemirror', e);
}
}, [wrapperRef.current]);
useEffect(() => {
if (wrapperRef.current === null) return;
syncGutterBg({ parent: wrapperRef.current, className });
}, [className]);
return () => {
view.destroy();
cm.current = null;
};
}, [wrapperRef.current, languageExtension]);
const cmContainer = useMemo(
() => (

View File

@@ -32,7 +32,8 @@ import {
rectangularSelection,
} from '@codemirror/view';
import { tags as t } from '@lezer/highlight';
import { graphqlLanguageSupport } from 'cm6-graphql';
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
import { render } from 'react-dom';
import type { EditorProps } from './index';
import { text } from './text/extension';
import { twig } from './twig/extension';
@@ -97,6 +98,9 @@ export function getLanguageExtension({
useTemplating = false,
autocomplete,
}: Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>) {
if (contentType === 'application/graphql') {
return graphql();
}
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
const base = syntaxExtensions[justContentType] ?? text();
if (!useTemplating) {
@@ -115,7 +119,14 @@ export const baseExtensions = [
// TODO: Figure out how to debounce showing of autocomplete in a good way
// debouncedAutocompletionDisplay({ millis: 1000 }),
// autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }),
autocompletion({ closeOnBlur: true, interactionDelay: 200 }),
autocompletion({
// closeOnBlur: false,
interactionDelay: 200,
compareCompletions: (a, b) => {
// Don't sort completions at all, only on boost
return (a.boost ?? 0) - (b.boost ?? 0);
},
}),
syntaxHighlighting(myHighlightStyle),
EditorState.allowMultipleSelections.of(true),
];

View File

@@ -24,6 +24,6 @@ export function genericCompletion({ options, minMatch = 1 }: GenericCompletionCo
if (!matchedMinimumLength && !context.explicit) return null;
const optionsWithoutExactMatches = options.filter((o) => o.label !== toMatch.text);
return { from: toMatch.from, options: optionsWithoutExactMatches };
return { from: toMatch.from, options: optionsWithoutExactMatches, info: 'hello' };
};
}

View File

@@ -7,6 +7,7 @@ import {
ClockIcon,
CodeIcon,
ColorWheelIcon,
CopyIcon,
Cross2Icon,
DividerHorizontalIcon,
DotsHorizontalIcon,
@@ -40,8 +41,11 @@ const icons = {
check: CheckIcon,
checkbox: CheckboxIcon,
clock: ClockIcon,
chevronDown: ChevronDownIcon,
code: CodeIcon,
colorWheel: ColorWheelIcon,
copy: CopyIcon,
dividerH: DividerHorizontalIcon,
dotsH: DotsHorizontalIcon,
dotsV: DotsVerticalIcon,
drag: DragHandleDots2Icon,
@@ -50,12 +54,10 @@ const icons = {
home: HomeIcon,
listBullet: ListBulletIcon,
magicWand: MagicWandIcon,
chevronDown: ChevronDownIcon,
magnifyingGlass: MagnifyingGlassIcon,
moon: MoonIcon,
paperPlane: PaperPlaneIcon,
plus: PlusIcon,
dividerH: DividerHorizontalIcon,
plusCircle: PlusCircledIcon,
question: QuestionMarkIcon,
rows: RowsIcon,

View File

@@ -127,7 +127,7 @@ export const PairEditor = memo(function PairEditor({
'@container',
'pb-2 grid',
// NOTE: Add padding to top so overflow doesn't hide drop marker
'pt-1',
'pt-1 -my-1',
)}
>
{pairs.map((p, i) => {

View File

@@ -4,6 +4,7 @@ import { forwardRef } from 'react';
const gapClasses = {
0: 'gap-0',
0.5: 'gap-0.5',
1: 'gap-1',
2: 'gap-2',
3: 'gap-3',