mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-21 16:21:25 +02:00
Placeholder CM tags working
This commit is contained in:
@@ -8,6 +8,7 @@ import { Icon } from './core/Icon';
|
|||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
import { StatusTag } from './core/StatusTag';
|
import { StatusTag } from './core/StatusTag';
|
||||||
|
import { useCopyHttpResponse } from './useCopyHttpResponse';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
responses: HttpResponse[];
|
responses: HttpResponse[];
|
||||||
@@ -25,6 +26,7 @@ export const RecentResponsesDropdown = function ResponsePane({
|
|||||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||||
const latestResponseId = responses[0]?.id ?? 'n/a';
|
const latestResponseId = responses[0]?.id ?? 'n/a';
|
||||||
const saveResponse = useSaveResponse(activeResponse);
|
const saveResponse = useSaveResponse(activeResponse);
|
||||||
|
const copyResponse = useCopyHttpResponse(activeResponse);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -37,6 +39,14 @@ export const RecentResponsesDropdown = function ResponsePane({
|
|||||||
hidden: responses.length === 0,
|
hidden: responses.length === 0,
|
||||||
disabled: responses.length === 0,
|
disabled: responses.length === 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'copy',
|
||||||
|
label: 'Copy to Clipboard',
|
||||||
|
onSelect: copyResponse.mutate,
|
||||||
|
leftSlot: <Icon icon="copy" />,
|
||||||
|
hidden: responses.length === 0,
|
||||||
|
disabled: responses.length === 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'clear-single',
|
key: 'clear-single',
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { CountBadge } from './core/CountBadge';
|
|||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
|
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
|
import type { Pair } from './core/PairEditor';
|
||||||
import type { TabItem } from './core/Tabs/Tabs';
|
import type { TabItem } from './core/Tabs/Tabs';
|
||||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||||
import { EmptyStateText } from './EmptyStateText';
|
import { EmptyStateText } from './EmptyStateText';
|
||||||
@@ -89,6 +90,27 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
const { urlParameterPairs, urlParametersKey } = useMemo(() => {
|
||||||
|
const placeholderNames = Array.from(activeRequest.url.matchAll(/\/(:[^/]+)/g)).map(
|
||||||
|
(m) => m[1] ?? '',
|
||||||
|
);
|
||||||
|
const items: Pair[] = [...activeRequest.urlParameters];
|
||||||
|
for (const name of placeholderNames) {
|
||||||
|
const index = items.findIndex((p) => p.name === name);
|
||||||
|
if (index >= 0) {
|
||||||
|
items[index]!.readOnlyName = true;
|
||||||
|
} else {
|
||||||
|
items.push({
|
||||||
|
name,
|
||||||
|
value: '',
|
||||||
|
enabled: true,
|
||||||
|
readOnlyName: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { urlParameterPairs: items, urlParametersKey: placeholderNames.join(',') };
|
||||||
|
}, [activeRequest.url, activeRequest.urlParameters]);
|
||||||
|
|
||||||
const tabs: TabItem[] = useMemo(
|
const tabs: TabItem[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -162,7 +184,7 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
label: (
|
label: (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
Params
|
Params
|
||||||
<CountBadge count={activeRequest.urlParameters.filter((p) => p.name).length} />
|
<CountBadge count={urlParameterPairs.filter((p) => p.name).length} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -212,11 +234,11 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
activeRequest.bodyType,
|
activeRequest.bodyType,
|
||||||
activeRequest.headers,
|
activeRequest.headers,
|
||||||
activeRequest.method,
|
activeRequest.method,
|
||||||
activeRequest.urlParameters,
|
|
||||||
activeRequestId,
|
activeRequestId,
|
||||||
handleContentTypeChange,
|
handleContentTypeChange,
|
||||||
toast,
|
toast,
|
||||||
updateRequest,
|
updateRequest,
|
||||||
|
urlParameterPairs,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -342,9 +364,8 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value="params">
|
<TabContent value="params">
|
||||||
<UrlParametersEditor
|
<UrlParametersEditor
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
||||||
urlParameters={activeRequest.urlParameters}
|
pairs={urlParameterPairs}
|
||||||
url={activeRequest.url}
|
|
||||||
onChange={handleUrlParametersChange}
|
onChange={handleUrlParametersChange}
|
||||||
/>
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|||||||
@@ -1,37 +1,14 @@
|
|||||||
import type { HttpRequest } from '@yaakapp/api';
|
import type { HttpRequest } from '@yaakapp/api';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import type { Pair } from './core/PairEditor';
|
|
||||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
forceUpdateKey: string;
|
forceUpdateKey: string;
|
||||||
urlParameters: HttpRequest['headers'];
|
pairs: HttpRequest['headers'];
|
||||||
onChange: (headers: HttpRequest['urlParameters']) => void;
|
onChange: (headers: HttpRequest['urlParameters']) => void;
|
||||||
url: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange, url }: Props) {
|
export function UrlParametersEditor({ pairs, forceUpdateKey, onChange }: Props) {
|
||||||
const placeholderNames = Array.from(url.matchAll(/\/(:[^/]+)/g)).map((m) => m[1] ?? '');
|
|
||||||
|
|
||||||
const pairs = useMemo(() => {
|
|
||||||
const items: Pair[] = [...urlParameters];
|
|
||||||
for (const name of placeholderNames) {
|
|
||||||
const index = items.findIndex((p) => p.name === name);
|
|
||||||
if (index >= 0) {
|
|
||||||
items[index]!.readOnlyName = true;
|
|
||||||
} else {
|
|
||||||
items.push({
|
|
||||||
name,
|
|
||||||
value: '',
|
|
||||||
enabled: true,
|
|
||||||
readOnlyName: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}, [placeholderNames, urlParameters]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack className="h-full">
|
<VStack className="h-full">
|
||||||
<PairOrBulkEditor
|
<PairOrBulkEditor
|
||||||
@@ -42,7 +19,7 @@ export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange, u
|
|||||||
valuePlaceholder="Value"
|
valuePlaceholder="Value"
|
||||||
pairs={pairs}
|
pairs={pairs}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
forceUpdateKey={forceUpdateKey + placeholderNames.join(':')}
|
forceUpdateKey={forceUpdateKey}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -227,6 +227,10 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
[dialog],
|
[dialog],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onClickPathParameter = useCallback(async (name: string) => {
|
||||||
|
console.log('TODO: Focus', name, 'in params tab');
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Update the language extension when the language changes
|
// Update the language extension when the language changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cm.current === null) return;
|
if (cm.current === null) return;
|
||||||
@@ -240,6 +244,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
onClickFunction,
|
onClickFunction,
|
||||||
onClickVariable,
|
onClickVariable,
|
||||||
onClickMissingVariable,
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
});
|
});
|
||||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||||
}, [
|
}, [
|
||||||
@@ -251,6 +256,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
onClickFunction,
|
onClickFunction,
|
||||||
onClickVariable,
|
onClickVariable,
|
||||||
onClickMissingVariable,
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Initialize the editor when ref mounts
|
// Initialize the editor when ref mounts
|
||||||
@@ -274,6 +280,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
onClickVariable,
|
onClickVariable,
|
||||||
onClickFunction,
|
onClickFunction,
|
||||||
onClickMissingVariable,
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
|
|||||||
@@ -88,12 +88,14 @@ export function getLanguageExtension({
|
|||||||
onClickVariable,
|
onClickVariable,
|
||||||
onClickFunction,
|
onClickFunction,
|
||||||
onClickMissingVariable,
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
}: {
|
}: {
|
||||||
environmentVariables: EnvironmentVariable[];
|
environmentVariables: EnvironmentVariable[];
|
||||||
templateFunctions: TemplateFunction[];
|
templateFunctions: TemplateFunction[];
|
||||||
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
||||||
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
||||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
||||||
|
onClickPathParameter: (name: string) => void;
|
||||||
} & Pick<EditorProps, 'language' | 'useTemplating' | 'autocomplete'>) {
|
} & Pick<EditorProps, 'language' | 'useTemplating' | 'autocomplete'>) {
|
||||||
if (language === 'graphql') {
|
if (language === 'graphql') {
|
||||||
return graphql();
|
return graphql();
|
||||||
@@ -112,6 +114,7 @@ export function getLanguageExtension({
|
|||||||
onClickFunction,
|
onClickFunction,
|
||||||
onClickVariable,
|
onClickVariable,
|
||||||
onClickMissingVariable,
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function twig({
|
|||||||
onClickFunction,
|
onClickFunction,
|
||||||
onClickVariable,
|
onClickVariable,
|
||||||
onClickMissingVariable,
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
}: {
|
}: {
|
||||||
base: LanguageSupport;
|
base: LanguageSupport;
|
||||||
environmentVariables: EnvironmentVariable[];
|
environmentVariables: EnvironmentVariable[];
|
||||||
@@ -26,6 +27,7 @@ export function twig({
|
|||||||
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
||||||
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
||||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
||||||
|
onClickPathParameter: (name: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const language = mixLanguage(base);
|
const language = mixLanguage(base);
|
||||||
|
|
||||||
@@ -62,11 +64,11 @@ export function twig({
|
|||||||
return [
|
return [
|
||||||
language,
|
language,
|
||||||
base.support,
|
base.support,
|
||||||
templateTagsPlugin(options, onClickMissingVariable),
|
|
||||||
language.data.of({ autocomplete: completions }),
|
language.data.of({ autocomplete: completions }),
|
||||||
base.language.data.of({ autocomplete: completions }),
|
base.language.data.of({ autocomplete: completions }),
|
||||||
language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
||||||
base.language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
base.language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
||||||
|
templateTagsPlugin(options, onClickMissingVariable, onClickPathParameter),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,42 @@ import { syntaxTree } from '@codemirror/language';
|
|||||||
import type { Range } from '@codemirror/state';
|
import type { Range } from '@codemirror/state';
|
||||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||||
import { Decoration, ViewPlugin, WidgetType } from '@codemirror/view';
|
import { Decoration, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||||
|
import type { SyntaxNodeRef } from '@lezer/common';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import type { TwigCompletionOption } from './completion';
|
import type { TwigCompletionOption } from './completion';
|
||||||
|
|
||||||
|
class PathPlaceholderWidget extends WidgetType {
|
||||||
|
readonly #clickListenerCallback: () => void;
|
||||||
|
|
||||||
|
constructor(readonly rawText: string, readonly startPos: number, readonly onClick: () => void) {
|
||||||
|
super();
|
||||||
|
this.#clickListenerCallback = () => {
|
||||||
|
this.onClick?.();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eq(other: PathPlaceholderWidget) {
|
||||||
|
return this.startPos === other.startPos && this.rawText === other.rawText;
|
||||||
|
}
|
||||||
|
|
||||||
|
toDOM() {
|
||||||
|
const elt = document.createElement('span');
|
||||||
|
elt.className = `x-theme-templateTag x-theme-templateTag--secondary template-tag`;
|
||||||
|
elt.textContent = this.rawText;
|
||||||
|
elt.addEventListener('click', this.#clickListenerCallback);
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(dom: HTMLElement) {
|
||||||
|
dom.removeEventListener('click', this.#clickListenerCallback);
|
||||||
|
super.destroy(dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreEvent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TemplateTagWidget extends WidgetType {
|
class TemplateTagWidget extends WidgetType {
|
||||||
readonly #clickListenerCallback: () => void;
|
readonly #clickListenerCallback: () => void;
|
||||||
|
|
||||||
@@ -62,20 +95,41 @@ function templateTags(
|
|||||||
view: EditorView,
|
view: EditorView,
|
||||||
options: TwigCompletionOption[],
|
options: TwigCompletionOption[],
|
||||||
onClickMissingVariable: (name: string, rawTag: string, startPos: number) => void,
|
onClickMissingVariable: (name: string, rawTag: string, startPos: number) => void,
|
||||||
|
onClickPathParameter: (name: string) => void,
|
||||||
): DecorationSet {
|
): DecorationSet {
|
||||||
const widgets: Range<Decoration>[] = [];
|
const widgets: Range<Decoration>[] = [];
|
||||||
for (const { from, to } of view.visibleRanges) {
|
for (const { from, to } of view.visibleRanges) {
|
||||||
syntaxTree(view.state).iterate({
|
const tree = syntaxTree(view.state);
|
||||||
|
tree.iterate({
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
enter(node) {
|
enter(node) {
|
||||||
if (node.name == 'Tag') {
|
if (node.name === 'Text') {
|
||||||
// Don't decorate if the cursor is inside the match
|
// Find the `url` node and then jump into it to find the placeholders
|
||||||
for (const r of view.state.selection.ranges) {
|
for (let i = node.from; i < node.to; i++) {
|
||||||
if (r.from > node.from && r.to < node.to) {
|
const innerTree = syntaxTree(view.state).resolveInner(i);
|
||||||
return;
|
if (innerTree.node.name === 'url') {
|
||||||
|
innerTree.toTree().iterate({
|
||||||
|
enter(node) {
|
||||||
|
if (node.name !== 'Placeholder') return;
|
||||||
|
if (isSelectionInsideNode(view, node)) return;
|
||||||
|
|
||||||
|
const globalFrom = innerTree.node.from + node.from;
|
||||||
|
const globalTo = innerTree.node.from + node.to;
|
||||||
|
const rawText = view.state.doc.sliceString(globalFrom, globalTo);
|
||||||
|
const onClick = () => onClickPathParameter(rawText);
|
||||||
|
const widget = new PathPlaceholderWidget(rawText, globalFrom, onClick);
|
||||||
|
const deco = Decoration.replace({ widget, inclusive: false });
|
||||||
|
console.log('ADDED WIDGET', globalFrom, node, rawText);
|
||||||
|
widgets.push(deco.range(globalFrom, globalTo));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (node.name === 'Tag') {
|
||||||
|
// Don't decorate if the cursor is inside the match
|
||||||
|
if (isSelectionInsideNode(view, node)) return;
|
||||||
|
|
||||||
const rawTag = view.state.doc.sliceString(node.from, node.to);
|
const rawTag = view.state.doc.sliceString(node.from, node.to);
|
||||||
|
|
||||||
@@ -114,17 +168,28 @@ function templateTags(
|
|||||||
export function templateTagsPlugin(
|
export function templateTagsPlugin(
|
||||||
options: TwigCompletionOption[],
|
options: TwigCompletionOption[],
|
||||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void,
|
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void,
|
||||||
|
onClickPathParameter: (name: string) => void,
|
||||||
) {
|
) {
|
||||||
return ViewPlugin.fromClass(
|
return ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
decorations: DecorationSet;
|
decorations: DecorationSet;
|
||||||
|
|
||||||
constructor(view: EditorView) {
|
constructor(view: EditorView) {
|
||||||
this.decorations = templateTags(view, options, onClickMissingVariable);
|
this.decorations = templateTags(
|
||||||
|
view,
|
||||||
|
options,
|
||||||
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
this.decorations = templateTags(update.view, options, onClickMissingVariable);
|
this.decorations = templateTags(
|
||||||
|
update.view,
|
||||||
|
options,
|
||||||
|
onClickMissingVariable,
|
||||||
|
onClickPathParameter,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -146,3 +211,10 @@ export function templateTagsPlugin(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSelectionInsideNode(view: EditorView, node: SyntaxNodeRef) {
|
||||||
|
for (const r of view.state.selection.ranges) {
|
||||||
|
if (r.from > node.from && r.to < node.to) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { styleTags, tags as t } from '@lezer/highlight';
|
|||||||
export const highlight = styleTags({
|
export const highlight = styleTags({
|
||||||
Protocol: t.comment,
|
Protocol: t.comment,
|
||||||
Placeholder: t.emphasis,
|
Placeholder: t.emphasis,
|
||||||
// PathSegment: t.tagName,
|
PathSegment: t.tagName,
|
||||||
// Port: t.attributeName,
|
Port: t.attributeName,
|
||||||
// Host: t.variableName,
|
Host: t.variableName,
|
||||||
// Path: t.bool,
|
Path: t.bool,
|
||||||
// Query: t.string,
|
Query: t.string,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
@top Program { url }
|
@top url { Protocol? Host Port? Path? Query? }
|
||||||
|
|
||||||
url { Protocol? Host Port? Path? Query? }
|
|
||||||
|
|
||||||
Path { ("/" (Placeholder | PathSegment))+ }
|
Path { ("/" (Placeholder | PathSegment))+ }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
export const
|
export const
|
||||||
Program = 1,
|
url = 1,
|
||||||
Protocol = 2,
|
Protocol = 2,
|
||||||
Host = 3,
|
Host = 3,
|
||||||
Port = 4,
|
Port = 4,
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ import {LRParser} from "@lezer/lr"
|
|||||||
import {highlight} from "./highlight"
|
import {highlight} from "./highlight"
|
||||||
export const parser = LRParser.deserialize({
|
export const parser = LRParser.deserialize({
|
||||||
version: 14,
|
version: 14,
|
||||||
states: "$UOQOPOOOYOPO'#ChOhOPO'#ChQOOOOOOmOQO'#CeOuOPO'#CaO!QOSO'#CdOOOO,59S,59SO!VOPO,59SO!_OPO,59SO!jOPO,59SOOOO,59P,59POOOO-E6c-E6cO!xOPO,59OOOOO1G.n1G.nO#QOPO1G.nO#YOPO1G.nO#eOSO'#CfO#jOPO1G.jOOOO7+$Y7+$YO#rOPO7+$YOOOO,59Q,59QOOOO-E6d-E6dOOOO<<Gt<<Gt",
|
states: "#SOQOPOOQYOPOOOTOPOOOeOQO'#CeOmOPO'#CaOxOSO'#CdQOOOOOQ`OPOOQ]OPOOOOOO,59P,59POOOO-E6c-E6cO}OPO,59OO!VOSO'#CfO![OPO1G.jOOOO,59Q,59QOOOO-E6d-E6d",
|
||||||
stateData: "$Q~OQQORPO~OSXO]SO^UOZ[X~ORYO~OUZOVZO~O]SOZTX^TX~O_]O~O^UOZ[a~O]SO^UOZ[a~OS`O]SO^UOZ[a~O`aOZWa~O^UOZ[i~O]SO^UOZ[i~O_eO~O`aOZWi~O^UOZ[q~OQRUVU~",
|
stateData: "!j~OQQORPO~OSWO[RO]TO~OUXOVXO~O[ROZTX]TX~O^ZO~O_[OZWa~O^^O~O_[OZWi~OQRUVU~",
|
||||||
goto: "!Z]PPPPP^PPhw!QP!WQWPS_XYRd`QVPU^WXYSc_`RgdWTPXY`R[TQb]RfbRRO",
|
goto: "rZPPPPP[PP`elTVPWVUPVWSSPWRYSQ]ZR_]",
|
||||||
nodeNames: "⚠ Program Protocol Host Port Path Placeholder PathSegment Query",
|
nodeNames: "⚠ url Protocol Host Port Path Placeholder PathSegment Query",
|
||||||
maxTerm: 16,
|
maxTerm: 15,
|
||||||
propSources: [highlight],
|
propSources: [highlight],
|
||||||
skippedNodes: [0],
|
skippedNodes: [0],
|
||||||
repeatNodeCount: 2,
|
repeatNodeCount: 2,
|
||||||
tokenData: "+h~RdOs!atv!avw#Ow}!a}!O#i!O!P#i!P!Q$o!Q![$t![!]&|!]!a!a!a!b)Z!b!c!a!c!})`!}#R!a#R#S#i#S#T!a#T#o)`#o;'S!a;'S;=`!x<%lO!aQ!fUVQOs!at!P!a!Q!a!a!b;'S!a;'S;=`!x<%lO!aQ!{P;=`<%l!aR#VU`PVQOs!at!P!a!Q!a!a!b;'S!a;'S;=`!x<%lO!aR#p_RPVQOs!at}!a}!O#i!O!P#i!Q![#i![!a!a!b!c!a!c!}#i!}#R!a#R#S#i#S#T!a#T#o#i#o;'S!a;'S;=`!x<%lO!a~$tO]~V$}a_SRPVQOs!at}!a}!O#i!O!P#i!Q![$t![!_!a!_!`&S!`!a!a!b!c!a!c!}$t!}#R!a#R#S#i#S#T!a#T#o$t#o;'S!a;'S;=`!x<%lO!aU&ZZ_SVQOs!at!P!a!Q![&S![!a!a!b!c!a!c!}&S!}#T!a#T#o&S#o;'S!a;'S;=`!x<%lO!aR'RXVQOs'ntv'nvw!aw!P'n!Q![(e![!a'n!b;'S'n;'S;=`(_<%lO'nQ'uWUQVQOs'ntv'nvw!aw!P'n!Q!a'n!b;'S'n;'S;=`(_<%lO'nQ(bP;=`<%l'nR(nXSPUQVQOs'ntv'nvw!aw!P'n!Q![(e![!a'n!b;'S'n;'S;=`(_<%lO'n~)`O^~V)ib_SRPVQOs!at}!a}!O#i!O!P#i!Q![$t![!]*q!]!_!a!_!`&S!`!a!a!b!c!a!c!})`!}#R!a#R#S#i#S#T!a#T#o)`#o;'S!a;'S;=`!x<%lO!aR*vVVQOs!at!P!a!P!Q+]!Q!a!a!b;'S!a;'S;=`!x<%lO!aP+`P!P!Q+cP+hOQP",
|
tokenData: "+U~RdOs!atv!avw#Ow}!a}!O#i!O!P#i!P!Q$o!Q![$t![!]&|!]!a!a!a!b(w!b!c!a!c!}(|!}#R!a#R#S#i#S#T!a#T#o(|#o;'S!a;'S;=`!x<%lO!aQ!fUVQOs!at!P!a!Q!a!a!b;'S!a;'S;=`!x<%lO!aQ!{P;=`<%l!aR#VU_PVQOs!at!P!a!Q!a!a!b;'S!a;'S;=`!x<%lO!aR#p_RPVQOs!at}!a}!O#i!O!P#i!Q![#i![!a!a!b!c!a!c!}#i!}#R!a#R#S#i#S#T!a#T#o#i#o;'S!a;'S;=`!x<%lO!a~$tO[~V$}a^SRPVQOs!at}!a}!O#i!O!P#i!Q![$t![!_!a!_!`&S!`!a!a!b!c!a!c!}$t!}#R!a#R#S#i#S#T!a#T#o$t#o;'S!a;'S;=`!x<%lO!aU&ZZ^SVQOs!at!P!a!Q![&S![!a!a!b!c!a!c!}&S!}#T!a#T#o&S#o;'S!a;'S;=`!x<%lO!aR'RVVQOs'ht!P'h!Q![(X![!a'h!b;'S'h;'S;=`(R<%lO'hQ'oUUQVQOs'ht!P'h!Q!a'h!b;'S'h;'S;=`(R<%lO'hQ(UP;=`<%l'hR(bVSPUQVQOs'ht!P'h!Q![(X![!a'h!b;'S'h;'S;=`(R<%lO'h~(|O]~V)Vb^SRPVQOs!at}!a}!O#i!O!P#i!Q![$t![!]*_!]!_!a!_!`&S!`!a!a!b!c!a!c!}(|!}#R!a#R#S#i#S#T!a#T#o(|#o;'S!a;'S;=`!x<%lO!aR*dVVQOs!at!P!a!P!Q*y!Q!a!a!b;'S!a;'S;=`!x<%lO!aP*|P!P!Q+PP+UOQP",
|
||||||
tokenizers: [0, 1, 2],
|
tokenizers: [0, 1, 2],
|
||||||
topRules: {"Program":[0,1]},
|
topRules: {"url":[0,1]},
|
||||||
tokenPrec: 134,
|
tokenPrec: 66,
|
||||||
termNames: {"0":"⚠","1":"@top","2":"Protocol","3":"Host","4":"Port","5":"Path","6":"Placeholder","7":"PathSegment","8":"Query","9":"(\"/\" (Placeholder | PathSegment))+","10":"(\"&\" queryPair)+","11":"␄","12":"url","13":"\"/\"","14":"\"?\"","15":"queryPair","16":"\"&\""}
|
termNames: {"0":"⚠","1":"@top","2":"Protocol","3":"Host","4":"Port","5":"Path","6":"Placeholder","7":"PathSegment","8":"Query","9":"(\"/\" (Placeholder | PathSegment))+","10":"(\"&\" queryPair)+","11":"␄","12":"\"/\"","13":"\"?\"","14":"queryPair","15":"\"&\""}
|
||||||
})
|
})
|
||||||
|
|||||||
15
src-web/components/useCopyHttpResponse.ts
Normal file
15
src-web/components/useCopyHttpResponse.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import type { HttpResponse } from '@yaakapp/api';
|
||||||
|
import { useCopy } from '../hooks/useCopy';
|
||||||
|
import { getResponseBodyText } from '../lib/responseBody';
|
||||||
|
|
||||||
|
export function useCopyHttpResponse(response: HttpResponse) {
|
||||||
|
const copy = useCopy();
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ['copy_http_response'],
|
||||||
|
async mutationFn() {
|
||||||
|
const body = await getResponseBodyText(response);
|
||||||
|
copy(body);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
import { attachConsole } from '@tauri-apps/plugin-log';
|
|
||||||
import { type } from '@tauri-apps/plugin-os';
|
import { type } from '@tauri-apps/plugin-os';
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|||||||
Reference in New Issue
Block a user