mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 16:48:30 +02:00
URL highlighting with inline CM
This commit is contained in:
@@ -63,7 +63,7 @@ function App() {
|
|||||||
<Editor
|
<Editor
|
||||||
key={request.id}
|
key={request.id}
|
||||||
useTemplating
|
useTemplating
|
||||||
defaultValue={request.body}
|
defaultValue={request.body ?? undefined}
|
||||||
contentType="application/json"
|
contentType="application/json"
|
||||||
onChange={(body) => updateRequest.mutate({ body })}
|
onChange={(body) => updateRequest.mutate({ body })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,14 +4,21 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor {
|
.cm-wrapper .cm-editor {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
font-size: 0.9rem;
|
}
|
||||||
font-family: monospace;
|
|
||||||
|
.cm-editor {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-singleline .cm-scroller {
|
||||||
|
overflow: hidden !important;;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-tooltip {
|
.cm-editor .cm-tooltip {
|
||||||
@@ -35,14 +42,22 @@
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor.cm-focused .cm-scroller {
|
.cm-multiline.cm-editor.cm-focused .cm-scroller {
|
||||||
box-shadow: 0 0 0 1px hsl(var(--color-blue-400)/0.4);
|
box-shadow: 0 0 0 1px hsl(var(--color-blue-400)/0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-line {
|
.cm-editor .cm-line {
|
||||||
|
color: hsl(var(--color-gray-900));
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-multiline .cm-editor .cm-line {
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: 1.5em;
|
padding-right: 1.5em;
|
||||||
color: hsl(var(--color-gray-900));
|
}
|
||||||
|
|
||||||
|
.cm-singleline .cm-scroller {
|
||||||
|
display: flex;
|
||||||
|
align-items: center !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-gutters {
|
.cm-editor .cm-gutters {
|
||||||
|
|||||||
@@ -1,22 +1,81 @@
|
|||||||
import './Editor.css';
|
import './Editor.css';
|
||||||
import { useEffect, useMemo, useRef } from 'react';
|
import { HTMLAttributes, useEffect, useMemo, useRef } from 'react';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import { baseExtensions, syntaxExtension } from './extensions';
|
import { baseExtensions, multiLineExtensions, syntaxExtension } from './extensions';
|
||||||
import { EditorState } from '@codemirror/state';
|
import { EditorState, Transaction, EditorSelection } from '@codemirror/state';
|
||||||
|
import type { TransactionSpec } from '@codemirror/state';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
|
|
||||||
interface Props {
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
||||||
contentType: string;
|
contentType: string;
|
||||||
useTemplating?: boolean;
|
useTemplating?: boolean;
|
||||||
defaultValue?: string | null;
|
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
singleLine?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Editor({ contentType, useTemplating, defaultValue, onChange }: Props) {
|
export default function Editor({
|
||||||
|
contentType,
|
||||||
|
useTemplating,
|
||||||
|
defaultValue,
|
||||||
|
onChange,
|
||||||
|
onSubmit,
|
||||||
|
className,
|
||||||
|
singleLine,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const extensions = useMemo(() => {
|
const extensions = useMemo(() => {
|
||||||
const ext = syntaxExtension({ contentType, useTemplating });
|
const ext = syntaxExtension({ contentType, useTemplating });
|
||||||
return [
|
return [
|
||||||
|
autocompletion(),
|
||||||
|
...(singleLine
|
||||||
|
? [
|
||||||
|
EditorView.domEventHandlers({
|
||||||
|
keydown: (e) => {
|
||||||
|
// TODO: Figure out how to not have this mess up autocomplete
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
EditorState.transactionFilter.of(
|
||||||
|
(tr: Transaction): TransactionSpec | TransactionSpec[] => {
|
||||||
|
if (!tr.isUserEvent('input.paste')) {
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
console.log('GOT PASTE', tr);
|
||||||
|
|
||||||
|
// let addedNewline = false;
|
||||||
|
const trs: TransactionSpec[] = [];
|
||||||
|
tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
||||||
|
// console.log('CHANGE', { fromA, toA }, { fromB, toB }, inserted);
|
||||||
|
let insert = '';
|
||||||
|
for (const line of inserted) {
|
||||||
|
insert += line.replace('\n', '');
|
||||||
|
}
|
||||||
|
trs.push({
|
||||||
|
...tr,
|
||||||
|
selection: undefined,
|
||||||
|
changes: [{ from: fromB, to: toA, insert }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// selection: EditorSelection.create([EditorSelection.cursor(8)], 1),
|
||||||
|
// console.log('TRS', trs, tr);
|
||||||
|
trs.push({
|
||||||
|
selection: EditorSelection.create([EditorSelection.cursor(8)], 1),
|
||||||
|
});
|
||||||
|
return trs;
|
||||||
|
// return addedNewline ? [] : tr;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
...baseExtensions,
|
...baseExtensions,
|
||||||
|
...(!singleLine ? [multiLineExtensions] : []),
|
||||||
...(ext ? [ext] : []),
|
...(ext ? [ext] : []),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (typeof onChange === 'function') {
|
if (typeof onChange === 'function') {
|
||||||
@@ -33,7 +92,7 @@ export default function Editor({ contentType, useTemplating, defaultValue, onCha
|
|||||||
try {
|
try {
|
||||||
view = new EditorView({
|
view = new EditorView({
|
||||||
state: EditorState.create({
|
state: EditorState.create({
|
||||||
doc: defaultValue ?? '',
|
doc: `${defaultValue ?? ''}`,
|
||||||
extensions: extensions,
|
extensions: extensions,
|
||||||
}),
|
}),
|
||||||
parent: ref.current,
|
parent: ref.current,
|
||||||
@@ -44,5 +103,15 @@ export default function Editor({ contentType, useTemplating, defaultValue, onCha
|
|||||||
return () => view?.destroy();
|
return () => view?.destroy();
|
||||||
}, [ref.current]);
|
}, [ref.current]);
|
||||||
|
|
||||||
return <div ref={ref} className="cm-wrapper" />;
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={classnames(
|
||||||
|
className,
|
||||||
|
'cm-wrapper text-base',
|
||||||
|
singleLine ? 'cm-singleline' : 'cm-multiline',
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
completionKeymap,
|
completionKeymap,
|
||||||
} from '@codemirror/autocomplete';
|
} from '@codemirror/autocomplete';
|
||||||
import { placeholders } from './widgets';
|
import { placeholders } from './widgets';
|
||||||
|
import { url } from './url/extension';
|
||||||
|
|
||||||
export const myHighlightStyle = HighlightStyle.define([
|
export const myHighlightStyle = HighlightStyle.define([
|
||||||
{
|
{
|
||||||
@@ -84,6 +85,7 @@ const syntaxExtensions: Record<string, { base: LanguageSupport; ext: any[] }> =
|
|||||||
'text/html': { base: html(), ext: [] },
|
'text/html': { base: html(), ext: [] },
|
||||||
'application/xml': { base: xml(), ext: [] },
|
'application/xml': { base: xml(), ext: [] },
|
||||||
'text/xml': { base: xml(), ext: [] },
|
'text/xml': { base: xml(), ext: [] },
|
||||||
|
url: { base: url(), ext: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function syntaxExtension({
|
export function syntaxExtension({
|
||||||
@@ -125,10 +127,17 @@ export function syntaxExtension({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const baseExtensions = [
|
export const baseExtensions = [
|
||||||
lineNumbers(),
|
|
||||||
highlightActiveLineGutter(),
|
|
||||||
highlightSpecialChars(),
|
highlightSpecialChars(),
|
||||||
history(),
|
history(),
|
||||||
|
drawSelection(),
|
||||||
|
dropCursor(),
|
||||||
|
EditorState.allowMultipleSelections.of(true),
|
||||||
|
syntaxHighlighting(myHighlightStyle),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const multiLineExtensions = [
|
||||||
|
lineNumbers(),
|
||||||
|
highlightActiveLineGutter(),
|
||||||
foldGutter({
|
foldGutter({
|
||||||
markerDOM: (open) => {
|
markerDOM: (open) => {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
@@ -141,7 +150,6 @@ export const baseExtensions = [
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
drawSelection(),
|
drawSelection(),
|
||||||
dropCursor(),
|
|
||||||
EditorState.allowMultipleSelections.of(true),
|
EditorState.allowMultipleSelections.of(true),
|
||||||
indentOnInput(),
|
indentOnInput(),
|
||||||
bracketMatching(),
|
bracketMatching(),
|
||||||
@@ -160,5 +168,4 @@ export const baseExtensions = [
|
|||||||
...completionKeymap,
|
...completionKeymap,
|
||||||
...lintKeymap,
|
...lintKeymap,
|
||||||
]),
|
]),
|
||||||
syntaxHighlighting(myHighlightStyle),
|
|
||||||
];
|
];
|
||||||
|
|||||||
46
src-web/components/Editor/url/extension.ts
Normal file
46
src-web/components/Editor/url/extension.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { parser } from './url';
|
||||||
|
// import { foldNodeProp, foldInside, indentNodeProp } from '@codemirror/language';
|
||||||
|
import { styleTags, tags as t } from '@lezer/highlight';
|
||||||
|
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||||
|
import { completeFromList } from '@codemirror/autocomplete';
|
||||||
|
|
||||||
|
const parserWithMetadata = parser.configure({
|
||||||
|
props: [
|
||||||
|
styleTags({
|
||||||
|
ProtocolName: t.comment,
|
||||||
|
Slashy: t.comment,
|
||||||
|
Host: t.variableName,
|
||||||
|
Slash: t.comment,
|
||||||
|
PathSegment: t.bool,
|
||||||
|
QueryName: t.variableName,
|
||||||
|
QueryValue: t.string,
|
||||||
|
Question: t.comment,
|
||||||
|
Equal: t.comment,
|
||||||
|
Amp: t.comment,
|
||||||
|
}),
|
||||||
|
// indentNodeProp.add({
|
||||||
|
// Application: (context) => context.column(context.node.from) + context.unit,
|
||||||
|
// }),
|
||||||
|
// foldNodeProp.add({
|
||||||
|
// Application: foldInside,
|
||||||
|
// }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const urlLanguage = LRLanguage.define({
|
||||||
|
parser: parserWithMetadata,
|
||||||
|
languageData: {
|
||||||
|
// commentTokens: {line: ";"}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const exampleCompletion = urlLanguage.data.of({
|
||||||
|
autocomplete: completeFromList([
|
||||||
|
{ label: 'http://', type: 'keyword' },
|
||||||
|
{ label: 'https://', type: 'keyword' },
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function url() {
|
||||||
|
return new LanguageSupport(urlLanguage, [exampleCompletion]);
|
||||||
|
}
|
||||||
30
src-web/components/Editor/url/url.grammar
Normal file
30
src-web/components/Editor/url/url.grammar
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
@top url { Protocol Host Path Query }
|
||||||
|
|
||||||
|
Protocol {
|
||||||
|
ProtocolName Slashy
|
||||||
|
}
|
||||||
|
|
||||||
|
Path {
|
||||||
|
(Slash PathSegment)*
|
||||||
|
}
|
||||||
|
|
||||||
|
Query {
|
||||||
|
Question (QueryPair)*
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryPair {
|
||||||
|
Amp? QueryName Equal QueryValue
|
||||||
|
}
|
||||||
|
|
||||||
|
@tokens {
|
||||||
|
ProtocolName { "http" | "https" }
|
||||||
|
Host { $[a-zA-Z0-9-_.]+ }
|
||||||
|
QueryName { $[a-zA-Z0-9-_.]+ }
|
||||||
|
QueryValue { $[a-zA-Z0-9-_.]+ }
|
||||||
|
PathSegment { $[a-zA-Z0-9-_.]+ }
|
||||||
|
Slashy { "://" }
|
||||||
|
Slash { "/" }
|
||||||
|
Question { "?" }
|
||||||
|
Equal { "=" }
|
||||||
|
Amp { "&" }
|
||||||
|
}
|
||||||
17
src-web/components/Editor/url/url.terms.ts
Normal file
17
src-web/components/Editor/url/url.terms.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
export const
|
||||||
|
url = 1,
|
||||||
|
Protocol = 2,
|
||||||
|
ProtocolName = 3,
|
||||||
|
Slashy = 4,
|
||||||
|
Host = 5,
|
||||||
|
Path = 6,
|
||||||
|
Slash = 7,
|
||||||
|
PathSegment = 8,
|
||||||
|
Query = 9,
|
||||||
|
Question = 10,
|
||||||
|
QueryPair = 11,
|
||||||
|
Amp = 12,
|
||||||
|
QueryName = 13,
|
||||||
|
Equal = 14,
|
||||||
|
QueryValue = 15
|
||||||
16
src-web/components/Editor/url/url.ts
Normal file
16
src-web/components/Editor/url/url.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
import {LRParser} from "@lezer/lr"
|
||||||
|
export const parser = LRParser.deserialize({
|
||||||
|
version: 14,
|
||||||
|
states: "#xOQOPOOOVOPO'#C^O[OQOOOOOO,58x,58xOaOPOOOiOSO'#ClOnOPO'#CbOvOPOOOOOO,59W,59WOOOO-E6j-E6jO{OWO'#CeQOOOOOO!WOPO'#CgO!]OWO'#CgOOOO'#Cm'#CmO!bOWO,59PO!mO`O,59RO!rOPO,59ROOOO-E6k-E6kOOOO1G.m1G.mO!wO`O1G.mOOOO7+$X7+$X",
|
||||||
|
stateData: "!|~ORPO~OSRO~OTSO~OVTOYUP~OWWO~OVTOYUX~OYYO~O[]O][ObXX~O^`O~O]aO~O[]O][ObXa~O_cO~O^dO~O_eO~O",
|
||||||
|
goto: "|bPPcPPPfPPiPlPPPPpvRQORVSRZVT^Y_QUSRXUQ_YRb_",
|
||||||
|
nodeNames: "⚠ url Protocol ProtocolName Slashy Host Path Slash PathSegment Query Question QueryPair Amp QueryName Equal QueryValue",
|
||||||
|
maxTerm: 18,
|
||||||
|
skippedNodes: [0],
|
||||||
|
repeatNodeCount: 2,
|
||||||
|
tokenData: "'T~R]vwz}!O!P!O!P!P!P!Q!n!Q![!P![!]!s!_!`#U!a!b#Z!c!}!P#R#S!P#T#[!P#[#]#`#]#o!P~!PO[~n![UTQWS]W_`}!O!P!O!P!P!Q![!P!c!}!P#R#S!P#T#o!P~!sOV~~!vP!P!Q!y~!|P!P!Q#P~#UOS~~#ZO^~~#`OY~o#kWTQWS]W_`}!O!P!O!P!P!Q![!P!c!}!P#R#S!P#T#h!P#h#i$T#i#o!Po$`WTQWS]W_`}!O!P!O!P!P!Q![!P!c!}!P#R#S!P#T#h!P#h#i$x#i#o!Po%TWTQWS]W_`}!O!P!O!P!P!Q![!P!c!}!P#R#S!P#T#d!P#d#e%m#e#o!Po%zWTQWSRP]W_`}!O!P!O!P!P!Q![!P!c!}!P#R#S!P#T#g!P#g#h&d#h#o!Po&qUTQWSRP]W_`}!O!P!O!P!P!Q![!P!c!}!P#R#S!P#T#o!P",
|
||||||
|
tokenizers: [0, 1, 2, 3, 4],
|
||||||
|
topRules: {"url":[0,1]},
|
||||||
|
tokenPrec: 0
|
||||||
|
})
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
import { InputHTMLAttributes, ReactNode } from 'react';
|
import { InputHTMLAttributes, ReactNode } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { HStack, VStack } from './Stacks';
|
import { HStack, VStack } from './Stacks';
|
||||||
|
import Editor from './Editor/Editor';
|
||||||
|
|
||||||
interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'onChange'> {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
hideLabel?: boolean;
|
hideLabel?: boolean;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
useEditor?: boolean;
|
||||||
leftSlot?: ReactNode;
|
leftSlot?: ReactNode;
|
||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
size?: 'sm' | 'md';
|
size?: 'sm' | 'md';
|
||||||
@@ -19,10 +23,14 @@ export function Input({
|
|||||||
className,
|
className,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
|
onSubmit,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
|
useEditor,
|
||||||
|
onChange,
|
||||||
name,
|
name,
|
||||||
leftSlot,
|
leftSlot,
|
||||||
rightSlot,
|
rightSlot,
|
||||||
|
defaultValue,
|
||||||
...props
|
...props
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const id = `input-${name}`;
|
const id = `input-${name}`;
|
||||||
@@ -49,16 +57,34 @@ export function Input({
|
|||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<input
|
{useEditor ? (
|
||||||
id={id}
|
<Editor
|
||||||
className={classnames(
|
id={id}
|
||||||
className,
|
singleLine
|
||||||
'bg-transparent min-w-0 pl-3 pr-2 h-full w-full focus:outline-none',
|
contentType="url"
|
||||||
leftSlot && '!pl-1',
|
defaultValue={defaultValue}
|
||||||
rightSlot && '!pr-1',
|
onChange={onChange}
|
||||||
)}
|
onSubmit={onSubmit}
|
||||||
{...props}
|
className={classnames(
|
||||||
/>
|
className,
|
||||||
|
'bg-transparent min-w-0 pl-3 pr-2 h-full w-full focus:outline-none',
|
||||||
|
leftSlot && '!pl-1',
|
||||||
|
rightSlot && '!pr-1',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
id={id}
|
||||||
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
|
className={classnames(
|
||||||
|
className,
|
||||||
|
'bg-transparent min-w-0 pl-3 pr-2 h-full w-full focus:outline-none',
|
||||||
|
leftSlot && '!pl-1',
|
||||||
|
rightSlot && '!pr-1',
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
{rightSlot}
|
{rightSlot}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -83,12 +83,14 @@ export function ResponsePane({ requestId, error }: Props) {
|
|||||||
{response.elapsed}ms •
|
{response.elapsed}ms •
|
||||||
{Math.round(response.body.length / 1000)} KB
|
{Math.round(response.body.length / 1000)} KB
|
||||||
</div>
|
</div>
|
||||||
<IconButton
|
{contentType.includes('html') && (
|
||||||
icon={viewMode === 'pretty' ? 'eye' : 'code'}
|
<IconButton
|
||||||
size="sm"
|
icon={viewMode === 'pretty' ? 'eye' : 'code'}
|
||||||
className="ml-auto"
|
size="sm"
|
||||||
onClick={() => setViewMode((m) => (m === 'pretty' ? 'raw' : 'pretty'))}
|
className="ml-auto"
|
||||||
/>
|
onClick={() => setViewMode((m) => (m === 'pretty' ? 'raw' : 'pretty'))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
{viewMode === 'pretty' && contentType.includes('html') ? (
|
{viewMode === 'pretty' && contentType.includes('html') ? (
|
||||||
<iframe
|
<iframe
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ export function UrlBar({ sendRequest, loading, onMethodChange, method, onUrlChan
|
|||||||
<form onSubmit={handleSendRequest} className="w-full flex items-center">
|
<form onSubmit={handleSendRequest} className="w-full flex items-center">
|
||||||
<Input
|
<Input
|
||||||
hideLabel
|
hideLabel
|
||||||
|
useEditor
|
||||||
|
onSubmit={sendRequest}
|
||||||
name="url"
|
name="url"
|
||||||
label="Enter URL"
|
label="Enter URL"
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
onChange={(e) => onUrlChange(e.currentTarget.value)}
|
onChange={onUrlChange}
|
||||||
defaultValue={url}
|
defaultValue={url}
|
||||||
placeholder="Enter a URL..."
|
placeholder="Enter a URL..."
|
||||||
leftSlot={
|
leftSlot={
|
||||||
|
|||||||
Reference in New Issue
Block a user