mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 02:11:10 +01:00
GraphQL autocomplete and duplicate request
This commit is contained in:
@@ -16,7 +16,6 @@ import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||
import { DEFAULT_FONT_SIZE } from '../lib/constants';
|
||||
import { extractKeyValue } from '../lib/keyValueStore';
|
||||
import type { HttpRequest, HttpResponse, KeyValue, Workspace } from '../lib/models';
|
||||
import { convertDates } from '../lib/models';
|
||||
import { AppRouter } from './AppRouter';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
@@ -52,13 +51,13 @@ await listen('updated_request', ({ payload: request }: { payload: HttpRequest })
|
||||
for (const r of requests) {
|
||||
if (r.id === request.id) {
|
||||
found = true;
|
||||
newRequests.push(convertDates(request));
|
||||
newRequests.push(request);
|
||||
} else {
|
||||
newRequests.push(r);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
newRequests.push(convertDates(request));
|
||||
newRequests.push(request);
|
||||
}
|
||||
return newRequests;
|
||||
},
|
||||
@@ -74,13 +73,13 @@ await listen('updated_response', ({ payload: response }: { payload: HttpResponse
|
||||
for (const r of responses) {
|
||||
if (r.id === response.id) {
|
||||
found = true;
|
||||
newResponses.push(convertDates(response));
|
||||
newResponses.push(response);
|
||||
} else {
|
||||
newResponses.push(r);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
newResponses.push(convertDates(response));
|
||||
newResponses.push(response);
|
||||
}
|
||||
return newResponses;
|
||||
},
|
||||
@@ -94,13 +93,13 @@ await listen('updated_workspace', ({ payload: workspace }: { payload: Workspace
|
||||
for (const w of workspaces) {
|
||||
if (w.id === workspace.id) {
|
||||
found = true;
|
||||
newWorkspaces.push(convertDates(workspace));
|
||||
newWorkspaces.push(workspace);
|
||||
} else {
|
||||
newWorkspaces.push(w);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
newWorkspaces.push(convertDates(workspace));
|
||||
newWorkspaces.push(workspace);
|
||||
}
|
||||
return newWorkspaces;
|
||||
});
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { graphql } from 'cm6-graphql';
|
||||
import { formatSdl } from 'format-graphql';
|
||||
import { useMemo } from 'react';
|
||||
import { buildClientSchema, getIntrospectionQuery } from 'graphql/utilities';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useUniqueKey } from '../hooks/useUniqueKey';
|
||||
import { Separator } from './core/Separator';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';
|
||||
import type { EditorProps } from './core/Editor';
|
||||
import { Editor } from './core/Editor';
|
||||
import { Separator } from './core/Separator';
|
||||
|
||||
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'defaultValue' | 'className'>;
|
||||
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'defaultValue' | 'className'> & {
|
||||
baseRequest: HttpRequest;
|
||||
};
|
||||
|
||||
interface GraphQLBody {
|
||||
query: string;
|
||||
@@ -13,7 +20,7 @@ interface GraphQLBody {
|
||||
operationName?: string;
|
||||
}
|
||||
|
||||
export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: Props) {
|
||||
export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEditorProps }: Props) {
|
||||
const queryKey = useUniqueKey();
|
||||
const { query, variables } = useMemo<GraphQLBody>(() => {
|
||||
if (!defaultValue) {
|
||||
@@ -46,12 +53,29 @@ export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: P
|
||||
}
|
||||
};
|
||||
|
||||
const [graphqlExtension, setGraphqlExtension] = useState<Extension>();
|
||||
|
||||
useEffect(() => {
|
||||
const body = JSON.stringify({
|
||||
query: getIntrospectionQuery(),
|
||||
operationName: 'IntrospectionQuery',
|
||||
});
|
||||
const req: HttpRequest = { ...baseRequest, body, id: '' };
|
||||
sendEphemeralRequest(req).then((response) => {
|
||||
console.log('RESPONSE', response.body);
|
||||
const { data } = JSON.parse(response.body);
|
||||
const schema = buildClientSchema(data);
|
||||
setGraphqlExtension(graphql(schema, {}));
|
||||
});
|
||||
}, [baseRequest.url]);
|
||||
|
||||
return (
|
||||
<div className="pb-2 h-full grid grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||
<Editor
|
||||
key={queryKey.key}
|
||||
heightMode="auto"
|
||||
defaultValue={query ?? ''}
|
||||
languageExtension={graphqlExtension}
|
||||
onChange={handleChangeQuery}
|
||||
contentType="application/graphql"
|
||||
placeholder="..."
|
||||
@@ -59,7 +83,6 @@ export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: P
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
<Separator variant="primary" />
|
||||
{/*<Separator variant="secondary" />*/}
|
||||
<p className="pt-1 text-gray-500 text-sm">Variables</p>
|
||||
<Editor
|
||||
useTemplating
|
||||
|
||||
@@ -39,17 +39,21 @@ const headerOptionsMap: Record<string, string[]> = {
|
||||
const valueAutocomplete = (headerName: string): GenericCompletionConfig | undefined => {
|
||||
const name = headerName.toLowerCase().trim();
|
||||
const options: GenericCompletionConfig['options'] =
|
||||
headerOptionsMap[name]?.map((o, i) => ({
|
||||
headerOptionsMap[name]?.map((o) => ({
|
||||
label: o,
|
||||
type: 'constant',
|
||||
boost: 99 - i, // Max boost is 99
|
||||
boost: 1, // Put above other completions
|
||||
})) ?? [];
|
||||
return { minMatch: MIN_MATCH, options };
|
||||
};
|
||||
|
||||
const nameAutocomplete: PairEditorProps['nameAutocomplete'] = {
|
||||
minMatch: MIN_MATCH,
|
||||
options: headerNames.map((t, i) => ({ label: t, type: 'constant', boost: 99 - i })),
|
||||
options: headerNames.map((t) => ({
|
||||
label: t,
|
||||
type: 'constant',
|
||||
boost: 1, // Put above other completions
|
||||
})),
|
||||
};
|
||||
|
||||
const validateHttpHeader = (v: string) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classnames from 'classnames';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
@@ -23,6 +23,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeRequestId = activeRequest?.id ?? null;
|
||||
const updateRequest = useUpdateRequest(activeRequestId);
|
||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||
const activeTab = useKeyValue<string>({
|
||||
key: ['active_request_body_tab'],
|
||||
defaultValue: 'body',
|
||||
@@ -34,12 +35,24 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
value: 'body',
|
||||
label: activeRequest?.bodyType ?? 'No Body',
|
||||
options: {
|
||||
onChange: (bodyType: HttpRequest['bodyType']) => {
|
||||
onChange: async (bodyType: HttpRequest['bodyType']) => {
|
||||
const patch: Partial<HttpRequest> = { bodyType };
|
||||
if (bodyType == HttpRequestBodyType.GraphQL) {
|
||||
patch.method = 'POST';
|
||||
patch.headers = [
|
||||
...(activeRequest?.headers.filter((h) => h.name.toLowerCase() !== 'content-type') ??
|
||||
[]),
|
||||
{
|
||||
name: 'Content-Type',
|
||||
value: 'application/json',
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
setTimeout(() => {
|
||||
setForceUpdateHeaderEditorKey((u) => u + 1);
|
||||
}, 100);
|
||||
}
|
||||
updateRequest.mutate(patch);
|
||||
await updateRequest.mutate(patch);
|
||||
},
|
||||
value: activeRequest?.bodyType ?? null,
|
||||
items: [
|
||||
@@ -54,7 +67,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
{ value: 'headers', label: 'Headers' },
|
||||
{ value: 'auth', label: 'Auth' },
|
||||
],
|
||||
[activeRequest?.bodyType ?? 'n/a'],
|
||||
[activeRequest?.bodyType, activeRequest?.headers],
|
||||
);
|
||||
|
||||
const handleBodyChange = useCallback((body: string) => updateRequest.mutate({ body }), []);
|
||||
@@ -88,7 +101,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
</TabContent>
|
||||
<TabContent value="headers">
|
||||
<HeaderEditor
|
||||
key={activeRequestId}
|
||||
key={`${activeRequest.id}::${forceUpdateHeaderEditorKey}`}
|
||||
headers={activeRequest.headers}
|
||||
onChange={handleHeadersChange}
|
||||
/>
|
||||
@@ -123,6 +136,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
) : activeRequest.bodyType === HttpRequestBodyType.GraphQL ? (
|
||||
<GraphQLEditor
|
||||
key={activeRequest.id}
|
||||
baseRequest={activeRequest}
|
||||
className="!bg-gray-50"
|
||||
defaultValue={activeRequest?.body ?? ''}
|
||||
onChange={handleBodyChange}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import type { HTMLAttributes, ReactElement } from 'react';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
requestId: string;
|
||||
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
||||
}
|
||||
|
||||
export function RequestSettingsDropdown({ className }: Props) {
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const deleteRequest = useDeleteRequest(activeRequestId ?? null);
|
||||
export function RequestSettingsDropdown({ requestId, children }: Props) {
|
||||
const deleteRequest = useDeleteRequest(requestId ?? null);
|
||||
const duplicateRequest = useDuplicateRequest({ id: requestId, navigateAfter: true });
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: 'Something Else',
|
||||
onSelect: () => null,
|
||||
leftSlot: <Icon icon="camera" />,
|
||||
label: 'Duplicate',
|
||||
onSelect: duplicateRequest.mutate,
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
},
|
||||
'-----',
|
||||
{
|
||||
label: 'Delete Request',
|
||||
label: 'Delete',
|
||||
onSelect: deleteRequest.mutate,
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<IconButton className={className} size="sm" title="Request Options" icon="gear" />
|
||||
{children}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,19 +5,17 @@ import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { WindowDragRegion } from './core/WindowDragRegion';
|
||||
import { DropMarker } from './DropMarker';
|
||||
import { RequestSettingsDropdown } from './RequestSettingsDropdown';
|
||||
import { ToggleThemeButton } from './ToggleThemeButton';
|
||||
|
||||
interface Props {
|
||||
@@ -204,7 +202,6 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
{ className, requestName, requestId, workspaceId, active, sidebarWidth }: SidebarItemProps,
|
||||
ref: ForwardedRef<HTMLLIElement>,
|
||||
) {
|
||||
const deleteRequest = useDeleteRequest(requestId);
|
||||
const updateRequest = useUpdateRequest(requestId);
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
|
||||
@@ -244,17 +241,6 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
[active],
|
||||
);
|
||||
|
||||
const actionItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: 'Delete Request',
|
||||
onSelect: deleteRequest.mutate,
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
ref={ref}
|
||||
@@ -295,19 +281,18 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Dropdown items={actionItems}>
|
||||
<RequestSettingsDropdown requestId={requestId}>
|
||||
<IconButton
|
||||
color="custom"
|
||||
size="sm"
|
||||
title="Request Options"
|
||||
icon="dotsH"
|
||||
className={classnames(
|
||||
'absolute right-0 top-0 transition-opacity opacity-0',
|
||||
'group-hover/item:opacity-100 focus-visible:opacity-100',
|
||||
)}
|
||||
color="custom"
|
||||
size="sm"
|
||||
iconSize="sm"
|
||||
title="Delete request"
|
||||
icon="dotsH"
|
||||
/>
|
||||
</Dropdown>
|
||||
</RequestSettingsDropdown>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
className="px-0"
|
||||
name="url"
|
||||
label="Enter URL"
|
||||
containerClassName="shadow shadow-gray-100 dark:shadow-gray-0"
|
||||
containerClassName="shadow shadow-gray-100 dark:shadow-gray-50"
|
||||
onChange={handleUrlChange}
|
||||
defaultValue={url}
|
||||
placeholder="https://example.com"
|
||||
|
||||
@@ -50,7 +50,14 @@ export default function Workspace() {
|
||||
</div>
|
||||
<div className="flex-1 flex justify-end -mr-2 pointer-events-none">
|
||||
<IconButton size="sm" title="" icon="magnifyingGlass" />
|
||||
<RequestSettingsDropdown className="pointer-events-auto" />
|
||||
<RequestSettingsDropdown>
|
||||
<IconButton
|
||||
size="sm"
|
||||
title="Request Options"
|
||||
icon="gear"
|
||||
className="pointer-events-auto"
|
||||
/>
|
||||
</RequestSettingsDropdown>
|
||||
</div>
|
||||
</HStack>
|
||||
<div
|
||||
|
||||
@@ -162,6 +162,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
>
|
||||
{containerStyles && (
|
||||
<VStack
|
||||
space={0.5}
|
||||
ref={initMenu}
|
||||
style={menuStyles}
|
||||
tabIndex={-1}
|
||||
|
||||
@@ -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: '𝑥';
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
() => (
|
||||
|
||||
@@ -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),
|
||||
];
|
||||
|
||||
@@ -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' };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user