diff --git a/apps/yaak-client/components/EmptyStateText.tsx b/apps/yaak-client/components/EmptyStateText.tsx index f68ee6c8..30f75bb4 100644 --- a/apps/yaak-client/components/EmptyStateText.tsx +++ b/apps/yaak-client/components/EmptyStateText.tsx @@ -4,11 +4,12 @@ import type { ReactNode } from "react"; interface Props { children: ReactNode; className?: string; + wrapperClassName?: string; } -export function EmptyStateText({ children, className }: Props) { +export function EmptyStateText({ children, className, wrapperClassName }: Props) { return ( -
+
(null); const treeRef = useRef(null); const filterRef = useRef(null); @@ -227,7 +230,7 @@ function Sidebar({ className }: { className?: string }) { ); const clearFilterText = useCallback(() => { - jotaiStore.set(sidebarFilterAtom, { text: "", key: `${Math.random()}` }); + setSidebarFilterText(""); requestAnimationFrame(() => { filterRef.current?.focus(); }); @@ -252,6 +255,13 @@ function Sidebar({ className }: { className?: string }) { [], ); + const applyFilterExample = useCallback((text: string) => { + setSidebarFilterText(text); + requestAnimationFrame(() => { + filterRef.current?.focus(); + }); + }, []); + const treeHasFocus = useCallback(() => treeRef.current?.hasFocus() ?? false, []); const getSelectedTreeModels = useCallback( @@ -654,8 +664,43 @@ function Sidebar({ className }: { className?: string }) { )}
{allHidden ? ( -
- No results for {filterText.text} +
+ {(emptyFilterSuggestions?.length ?? 0) > 0 ? ( + +
+ No results, but found matches for{" "} + {emptyFilterSuggestions?.map((suggestion, i) => ( + + {i > 0 && " or "} + + + ))} +
+
+ ) : ( + +
+ No results for{" "} + + {filterText.text} + +
+
+ )}
) : ( ({ key: "", }); -const sidebarTreeAtom = atom<[TreeNode, FieldDef[]] | null>((get) => { +type SidebarFilterSuggestion = { + field: string; + filterText: string; +}; + +function setSidebarFilterText(text: string) { + jotaiStore.set(sidebarFilterAtom, { text, key: `${Math.random()}` }); +} + +function getSidebarSuggestionValue(ast: Ast | null) { + if (ast == null) return null; + + if (ast.type === "Term" || ast.type === "Phrase") { + const value = ast.value.trim(); + return value.length > 0 ? value : null; + } + + if (ast.type === "Field") { + const value = ast.value.trim(); + return value.length > 0 ? value : null; + } + + return null; +} + +function sidebarFieldMatchesValue(fieldValue: string, filterValue: string) { + return fieldValue.toLowerCase().includes(filterValue.toLowerCase()); +} + +const sidebarSuggestionFieldOrder = [ + "url", + "folder", + "method", + "type", + "grpc_service", + "grpc_method", + "name", +]; + +const sidebarTreeAtom = atom< + [TreeNode, FieldDef[], SidebarFilterSuggestion[]] | null +>((get) => { const allModels = get(memoAllPotentialChildrenAtom); const activeWorkspace = get(activeWorkspaceAtom); const filter = get(sidebarFilterAtom); @@ -807,9 +893,11 @@ const sidebarTreeAtom = atom<[TreeNode, FieldDef[]] | null>((get) } const queryAst = parseQuery(filter.text); + const suggestionValue = getSidebarSuggestionValue(queryAst); // returns true if this node OR any child matches the filter const allFields: Record> = {}; + const suggestionFields = new Set(); const build = (node: TreeNode, depth: number): boolean => { const childItems = childrenMap[node.item.id] ?? []; let matchesSelf = true; @@ -821,6 +909,13 @@ const sidebarTreeAtom = atom<[TreeNode, FieldDef[]] | null>((get) if (!value) continue; allFields[field] = allFields[field] ?? new Set(); allFields[field].add(value); + if ( + isLeafNode && + suggestionValue != null && + sidebarFieldMatchesValue(value, suggestionValue) + ) { + suggestionFields.add(field); + } } if (queryAst != null) { @@ -874,7 +969,18 @@ const sidebarTreeAtom = atom<[TreeNode, FieldDef[]] | null>((get) values: Array.from(values).filter((v) => v.length < 20), }); } - return [root, fields] as const; + const suggestions = Array.from(suggestionFields) + .sort((a, b) => { + const aIndex = sidebarSuggestionFieldOrder.indexOf(a); + const bIndex = sidebarSuggestionFieldOrder.indexOf(b); + if (aIndex === -1 && bIndex === -1) return a.localeCompare(b); + return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex); + }) + .map((field) => ({ + field, + filterText: formatFieldFilter(field, suggestionValue ?? ""), + })); + return [root, fields, suggestions] as const; }); const sidebarGitStatusByModelIdAtom = atom>((get) => { diff --git a/apps/yaak-client/components/core/Editor/filter/extension.ts b/apps/yaak-client/components/core/Editor/filter/extension.ts index 88f6c4bb..722c442a 100644 --- a/apps/yaak-client/components/core/Editor/filter/extension.ts +++ b/apps/yaak-client/components/core/Editor/filter/extension.ts @@ -15,8 +15,9 @@ export interface FilterOptions { fields: FieldDef[] | null; // e.g., ['method','status','path'] or [{name:'tag', values:()=>cachedTags}] } -const IDENT = /[A-Za-z0-9_/]+$/; -const IDENT_ONLY = /^[A-Za-z0-9_/]+$/; +const FIELD_IDENT = /[A-Za-z0-9_/]+$/; +const VALUE_IDENT = /[A-Za-z0-9_\-./]+$/; +const VALUE_IDENT_ONLY = /^[A-Za-z0-9_\-./]+$/; function normalizeFields(fields: FieldDef[]): { fieldNames: string[]; @@ -31,14 +32,37 @@ function normalizeFields(fields: FieldDef[]): { return { fieldNames, fieldMap }; } -function wordBefore(doc: string, pos: number): { from: number; to: number; text: string } | null { +function wordBefore( + doc: string, + pos: number, + pattern: RegExp, +): { from: number; to: number; text: string } | null { const upto = doc.slice(0, pos); - const m = upto.match(IDENT); + const m = upto.match(pattern); if (!m) return null; const from = pos - m[0].length; return { from, to: pos, text: m[0] }; } +function fieldCompletionFrom(doc: string, pos: number): { from: number; includeAt: boolean } | null { + const w = wordBefore(doc, pos, FIELD_IDENT); + const from = w?.from ?? pos; + const beforeToken = doc[from - 1]; + + if (from === 0 || (beforeToken != null && /\s/.test(beforeToken))) { + return { from, includeAt: true }; + } + + if (beforeToken === "@") { + const beforeAt = doc[from - 2]; + if (from === 1 || (beforeAt != null && /\s/.test(beforeAt))) { + return { from, includeAt: false }; + } + } + + return null; +} + function inPhrase(ctx: CompletionContext): boolean { // Lezer node names from your grammar: Phrase is the quoted token let n: SyntaxNode | null = syntaxTree(ctx.state).resolveInner(ctx.pos, -1); @@ -81,7 +105,7 @@ function contextInfo(stateDoc: string, pos: number) { if (inValue) { // word before the colon = field name const beforeColon = stateDoc.slice(0, lastColon); - const m = beforeColon.match(IDENT); + const m = beforeColon.match(FIELD_IDENT); fieldName = m ? m[0] : null; // nothing (or only spaces) typed after the colon? @@ -93,15 +117,16 @@ function contextInfo(stateDoc: string, pos: number) { } /** Build a completion list for field names */ -function fieldNameCompletions(fieldNames: string[]): Completion[] { +function fieldNameCompletions(fieldNames: string[], includeAt: boolean): Completion[] { return fieldNames.map((name) => ({ label: name, type: "property", apply: (view, _completion, from, to) => { - // Insert "name:" (leave cursor right after colon) + // Leave cursor right after the field filter colon. + const insert = `${includeAt ? "@" : ""}${name}:`; view.dispatch({ - changes: { from, to, insert: `${name}:` }, - selection: { anchor: from + name.length + 1 }, + changes: { from, to, insert }, + selection: { anchor: from + insert.length }, }); startCompletion(view); }, @@ -115,7 +140,7 @@ function fieldValueCompletions( if (!def || !def.values) return null; const vals = Array.isArray(def.values) ? def.values : def.values(); return vals.map((v) => ({ - label: v.match(IDENT_ONLY) ? v : `"${v}"`, + label: v.match(VALUE_IDENT_ONLY) ? v : `"${v}"`, displayLabel: v, type: "constant", })); @@ -132,14 +157,13 @@ function makeCompletionSource(opts: FilterOptions) { return null; } - const w = wordBefore(doc, pos); - const from = w?.from ?? pos; - const to = pos; - const { inValue, fieldName, emptyAfterColon } = contextInfo(doc, pos); // In field value position if (inValue && fieldName) { + const w = wordBefore(doc, pos, VALUE_IDENT); + const from = w?.from ?? pos; + const to = pos; const valDefs = fieldMap[fieldName]; const vals = fieldValueCompletions(valDefs); @@ -162,7 +186,11 @@ function makeCompletionSource(opts: FilterOptions) { } // Not in a value: suggest field names (and maybe boolean ops) - const options: Completion[] = fieldNameCompletions(fieldNames); + const completion = fieldCompletionFrom(doc, pos); + if (completion == null) return null; + const { from, includeAt } = completion; + const to = pos; + const options: Completion[] = fieldNameCompletions(fieldNames, includeAt); return { from, to, options, filter: true }; }; diff --git a/apps/yaak-client/components/core/Editor/filter/filter.grammar b/apps/yaak-client/components/core/Editor/filter/filter.grammar index 0d857e72..077ab945 100644 --- a/apps/yaak-client/components/core/Editor/filter/filter.grammar +++ b/apps/yaak-client/components/core/Editor/filter/filter.grammar @@ -6,6 +6,7 @@ LParen { "(" } RParen { ")" } + At { "@" } Colon { ":" } Not { "-" | "NOT" } @@ -60,7 +61,7 @@ Field { } FieldName { - Word + At? Word } FieldValue { diff --git a/apps/yaak-client/components/core/Editor/filter/filter.ts b/apps/yaak-client/components/core/Editor/filter/filter.ts index f7a1dae3..65767606 100644 --- a/apps/yaak-client/components/core/Editor/filter/filter.ts +++ b/apps/yaak-client/components/core/Editor/filter/filter.ts @@ -1,27 +1,22 @@ -/* oxlint-disable */ // This file was generated by lezer-generator. You probably shouldn't edit it. -import { LRParser } from "@lezer/lr"; -import { highlight } from "./highlight"; +import {LRParser} from "@lezer/lr" +import {highlight} from "./highlight" export const parser = LRParser.deserialize({ version: 14, - states: - "%QOVQPOOPeOPOOOVQPO'#CfOjQPO'#ChO!XQPO'#CgOOQO'#Cc'#CcOVQPO'#CaOOQO'#Ca'#CaO!oQPO'#C`O!|QPO'#C_OOQO'#C^'#C^QOQPOOPOOO'#Cp'#CpP#XOPO)C>jO#`QPO,59QO#eQPO,59ROOQO,58{,58{OVQPO'#CqOOQO'#Cq'#CqO#mQPO,58zOVQPO'#CrO#zQPO,58yPOOO-E6n-E6nOOQO1G.l1G.lOOQO'#Cm'#CmOOQO'#Ck'#CkOOQO1G.m1G.mOOQO,59],59]OOQO-E6o-E6oOOQO,59^,59^OOQO-E6p-E6p", - stateData: - "$]~OiPQ~OUUOXQO]RO`TO~Oi[O~OUaXXaX]aX^[X`aXbaXcaXgaXWaX~O^_O~OUUOXQO]RO`TObaO~OcSXgSXWSX~P!^OcdOgRXWRX~Oi[O~Qh]WgO~O]hO`iO~OcSagSaWSa~P!^OcdOgRaWRa~OUbc]c~", - goto: "#hgPPhnryP!YPP!c!c!lPP!uP!xPP#U#[#bQZOR^QTYOQSXOQRmdUWOQdQ`USbWcRka_VOQUWacd_TOQUWacd_SOQUWacdRj_^TOQUWacdRi_Q]PRf]QcWRlcQeXRne", - nodeNames: - "⚠ Query Expr OrExpr AndExpr Unary Not Primary RParen LParen Group Field FieldName Word Colon FieldValue Phrase Term And Or", - maxTerm: 25, + states: "%^OVQPOOPhOPOOOVQPO'#CfOmQPO'#ChO!_QPO'#ChO!dQPO'#CgOOQO'#Cc'#CcOVQPO'#CaOOQO'#Ca'#CaO!iQPO'#C`O!yQPO'#C_OOQO'#C^'#C^QOQPOOPOOO'#Cq'#CqP#UOPO)C>kO#]QPO,59QOOQO,59S,59SO#bQPO,59ROOQO,58{,58{OVQPO'#CrOOQO'#Cr'#CrO#jQPO,58zOVQPO'#CsO#zQPO,58yPOOO-E6o-E6oOOQO1G.l1G.lOOQO'#Cn'#CnOOQO'#Cl'#ClOOQO1G.m1G.mOOQO,59^,59^OOQO-E6p-E6pOOQO,59_,59_OOQO-E6q-E6q", + stateData: "$]~OjPQ~OUVOXQO]SO^ROaUO~Oj]O~OUbXXbX]bX^bX_[XabXcbXdbXhbXWbX~O^`O~O_aO~OccOdSXhSXWSX~PVOdfOhRXWRX~Oj]O~Qi]WiO~O^jOakO~OccOdSahSaWSa~PVOdfOhRaWRa~OUcd^d~", + goto: "#ihPPioszP!ZPP!d!d!mPPP!vP!yPP#V#]#cQ[OR_QTZOQSYOQRofUXOQfQbVSdXeRmc_WOQVXcef_UOQVXcef_TOQVXcefRla^UOQVXcefRkaQ^PRh^QeXRneQgYRpg", + nodeNames: "⚠ Query Expr OrExpr AndExpr Unary Not Primary RParen LParen Group Field FieldName At Word Colon FieldValue Phrase Term And Or", + maxTerm: 26, nodeProps: [ - ["openedBy", 8, "LParen"], - ["closedBy", 9, "RParen"], + ["openedBy", 8,"LParen"], + ["closedBy", 9,"RParen"] ], propSources: [highlight], - skippedNodes: [0, 20], + skippedNodes: [0,21], repeatNodeCount: 3, - tokenData: - ")f~RgX^!jpq!jrs#_xy${yz%Q}!O%V!Q![%[![!]%m!c!d%r!d!p%[!p!q'V!q!r(j!r!}%[#R#S%[#T#o%[#y#z!j$f$g!j#BY#BZ!j$IS$I_!j$I|$JO!j$JT$JU!j$KV$KW!j&FU&FV!j~!oYi~X^!jpq!j#y#z!j$f$g!j#BY#BZ!j$IS$I_!j$I|$JO!j$JT$JU!j$KV$KW!j&FU&FV!j~#bVOr#_rs#ws#O#_#O#P#|#P;'S#_;'S;=`$u<%lO#_~#|O`~~$PRO;'S#_;'S;=`$Y;=`O#_~$]WOr#_rs#ws#O#_#O#P#|#P;'S#_;'S;=`$u;=`<%l#_<%lO#_~$xP;=`<%l#_~%QOX~~%VOW~~%[OU~~%aS]~!Q![%[!c!}%[#R#S%[#T#o%[~%rO^~~%wU]~!Q![%[!c!p%[!p!q&Z!q!}%[#R#S%[#T#o%[~&`U]~!Q![%[!c!f%[!f!g&r!g!}%[#R#S%[#T#o%[~&ySb~]~!Q![%[!c!}%[#R#S%[#T#o%[~'[U]~!Q![%[!c!q%[!q!r'n!r!}%[#R#S%[#T#o%[~'sU]~!Q![%[!c!v%[!v!w(V!w!}%[#R#S%[#T#o%[~(^SU~]~!Q![%[!c!}%[#R#S%[#T#o%[~(oU]~!Q![%[!c!t%[!t!u)R!u!}%[#R#S%[#T#o%[~)YSc~]~!Q![%[!c!}%[#R#S%[#T#o%[", + tokenData: ")n~RhX^!mpq!mrs#bxy%Oyz%T}!O%Y!Q![%_![!]%p!b!c%u!c!d%z!d!p%_!p!q'_!q!r(r!r!}%_#R#S%_#T#o%_#y#z!m$f$g!m#BY#BZ!m$IS$I_!m$I|$JO!m$JT$JU!m$KV$KW!m&FU&FV!m~!rYj~X^!mpq!m#y#z!m$f$g!m#BY#BZ!m$IS$I_!m$I|$JO!m$JT$JU!m$KV$KW!m&FU&FV!m~#eVOr#brs#zs#O#b#O#P$P#P;'S#b;'S;=`$x<%lO#b~$POa~~$SRO;'S#b;'S;=`$];=`O#b~$`WOr#brs#zs#O#b#O#P$P#P;'S#b;'S;=`$x;=`<%l#b<%lO#b~${P;=`<%l#b~%TOX~~%YOW~~%_OU~~%dS^~!Q![%_!c!}%_#R#S%_#T#o%_~%uO_~~%zO]~~&PU^~!Q![%_!c!p%_!p!q&c!q!}%_#R#S%_#T#o%_~&hU^~!Q![%_!c!f%_!f!g&z!g!}%_#R#S%_#T#o%_~'RSc~^~!Q![%_!c!}%_#R#S%_#T#o%_~'dU^~!Q![%_!c!q%_!q!r'v!r!}%_#R#S%_#T#o%_~'{U^~!Q![%_!c!v%_!v!w(_!w!}%_#R#S%_#T#o%_~(fSU~^~!Q![%_!c!}%_#R#S%_#T#o%_~(wU^~!Q![%_!c!t%_!t!u)Z!u!}%_#R#S%_#T#o%_~)bSd~^~!Q![%_!c!}%_#R#S%_#T#o%_", tokenizers: [0], - topRules: { Query: [0, 1] }, - tokenPrec: 145, -}); + topRules: {"Query":[0,1]}, + tokenPrec: 145 +}) diff --git a/apps/yaak-client/components/core/Editor/filter/format.test.ts b/apps/yaak-client/components/core/Editor/filter/format.test.ts new file mode 100644 index 00000000..c7f90a5c --- /dev/null +++ b/apps/yaak-client/components/core/Editor/filter/format.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "vite-plus/test"; +import { formatFieldFilter } from "./format"; +import { evaluate, parseQuery } from "./query"; + +function matchesFormattedUrl(value: string) { + return evaluate(parseQuery(formatFieldFilter("url", value)), { + fields: { url: value }, + }); +} + +describe("formatFieldFilter", () => { + test("keeps URL-like values bare", () => { + expect(formatFieldFilter("url", "yaak.app/foo-bar")).toBe("@url:yaak.app/foo-bar"); + expect(matchesFormattedUrl("yaak.app/foo-bar")).toBe(true); + }); + + test("quotes values that start with an operator token", () => { + expect(formatFieldFilter("url", "-foo")).toBe('@url:"-foo"'); + expect(matchesFormattedUrl("-foo")).toBe(true); + }); + + test("quotes boolean operator words", () => { + expect(formatFieldFilter("url", "AND")).toBe('@url:"AND"'); + expect(formatFieldFilter("url", "or")).toBe('@url:"or"'); + expect(formatFieldFilter("url", "Not")).toBe('@url:"Not"'); + expect(matchesFormattedUrl("AND")).toBe(true); + }); + + test("escapes quoted values", () => { + expect(formatFieldFilter("url", 'say "hi"')).toBe('@url:"say \\"hi\\""'); + expect(matchesFormattedUrl('say "hi"')).toBe(true); + }); +}); diff --git a/apps/yaak-client/components/core/Editor/filter/format.ts b/apps/yaak-client/components/core/Editor/filter/format.ts new file mode 100644 index 00000000..96173353 --- /dev/null +++ b/apps/yaak-client/components/core/Editor/filter/format.ts @@ -0,0 +1,9 @@ +const bareFieldValue = /^[A-Za-z0-9_./][A-Za-z0-9_\-./]*$/; +const operatorWord = /^(?:AND|OR|NOT)$/i; + +export function formatFieldFilter(field: string, value: string) { + const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + const filterValue = + bareFieldValue.test(value) && !operatorWord.test(value) ? value : `"${escapedValue}"`; + return `@${field}:${filterValue}`; +} diff --git a/apps/yaak-client/components/core/Editor/filter/highlight.ts b/apps/yaak-client/components/core/Editor/filter/highlight.ts index 0491e5bb..c6092819 100644 --- a/apps/yaak-client/components/core/Editor/filter/highlight.ts +++ b/apps/yaak-client/components/core/Editor/filter/highlight.ts @@ -16,6 +16,7 @@ export const highlight = styleTags({ Phrase: t.string, // "quoted string" // Fields + "FieldName/At": t.attributeName, "FieldName/Word": t.attributeName, "FieldValue/Term/Word": t.attributeValue, }); diff --git a/package.json b/package.json index 8f2265b4..439d8b10 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "start": "npm run client:dev", "client:build": "node scripts/run-build.mjs client", "client:dev": "node scripts/run-dev.mjs client", + "client:bundle": "node scripts/run-build.mjs client --config crates-tauri/yaak-app-client/tauri.release.conf.json --no-sign", "proxy:build": "node scripts/run-build.mjs proxy", "proxy:dev": "node scripts/run-dev.mjs proxy", "migration": "node scripts/create-migration.cjs", diff --git a/packages/plugin-runtime-types/src/bindings/gen_events.ts b/packages/plugin-runtime-types/src/bindings/gen_events.ts index df3a1378..abd4fac3 100644 --- a/packages/plugin-runtime-types/src/bindings/gen_events.ts +++ b/packages/plugin-runtime-types/src/bindings/gen_events.ts @@ -18,12 +18,12 @@ export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: export type CallHttpAuthenticationRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, method: string, url: string, headers: Array, }; -export type CallHttpAuthenticationResponse = { +export type CallHttpAuthenticationResponse = { /** * HTTP headers to add to the request. Existing headers will be replaced, while * new headers will be added. */ -setHeaders?: Array, +setHeaders?: Array, /** * Query parameters to add to the request. Existing params will be replaced, while * new params will be added. @@ -78,7 +78,7 @@ export type ExportHttpRequestRequest = { httpRequest: HttpRequest, }; export type ExportHttpRequestResponse = { content: string, }; -export type FileFilter = { name: string, +export type FileFilter = { name: string, /** * File extensions to require */ @@ -100,149 +100,149 @@ export type FormInputAccordion = { label: string, inputs?: Array, hid export type FormInputBanner = { inputs?: Array, hidden?: boolean, color?: Color, }; -export type FormInputBase = { +export type FormInputBase = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputCheckbox = { +export type FormInputCheckbox = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputEditor = { +export type FormInputEditor = { /** * Placeholder for the text input */ -placeholder?: string | null, +placeholder?: string | null, /** * Don't show the editor gutter (line numbers, folds, etc.) */ -hideGutter?: boolean, +hideGutter?: boolean, /** * Language for syntax highlighting */ -language?: EditorLanguage, readOnly?: boolean, +language?: EditorLanguage, readOnly?: boolean, /** * Fixed number of visible rows */ -rows?: number, completionOptions?: Array, +rows?: number, completionOptions?: Array, /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputFile = { +export type FormInputFile = { /** * The title of the file selection window */ -title: string, +title: string, /** * Allow selecting multiple files */ -multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array, +multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array, /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ @@ -250,63 +250,63 @@ description?: string, }; export type FormInputHStack = { inputs?: Array, hidden?: boolean, }; -export type FormInputHttpRequest = { +export type FormInputHttpRequest = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputKeyValue = { +export type FormInputKeyValue = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ @@ -314,36 +314,36 @@ description?: string, }; export type FormInputMarkdown = { content: string, hidden?: boolean, }; -export type FormInputSelect = { +export type FormInputSelect = { /** * The options that will be available in the select input */ -options: Array, +options: Array, /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ @@ -351,44 +351,44 @@ description?: string, }; export type FormInputSelectOption = { label: string, value: string, }; -export type FormInputText = { +export type FormInputText = { /** * Placeholder for the text input */ -placeholder?: string | null, +placeholder?: string | null, /** * Placeholder for the text input */ -password?: boolean, +password?: boolean, /** * Whether to allow newlines in the input, like a