mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 18:31:16 +01:00
Add previewArgs support for template functions and enhance validation logic for form inputs
This commit is contained in:
@@ -101,12 +101,33 @@
|
||||
|
||||
.template-tag {
|
||||
/* Colors */
|
||||
@apply bg-surface text-text-subtle border-border-subtle whitespace-nowrap;
|
||||
@apply bg-surface text-text border-border-subtle whitespace-nowrap;
|
||||
@apply hover:border-border-subtle hover:text-text hover:bg-surface-highlight;
|
||||
|
||||
@apply inline border px-1 mx-[0.5px] rounded cursor-default dark:shadow;
|
||||
@apply inline border px-1 mx-[0.5px] rounded dark:shadow;
|
||||
|
||||
-webkit-text-security: none;
|
||||
|
||||
* {
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
.fn {
|
||||
@apply inline-block;
|
||||
.fn-inner {
|
||||
@apply text-text-subtle max-w-[40em] italic inline-flex items-end whitespace-pre text-[0.9em];
|
||||
}
|
||||
.fn-arg-name {
|
||||
/* Nothing yet */
|
||||
@apply opacity-60;
|
||||
}
|
||||
.fn-arg-value {
|
||||
@apply inline-block truncate;
|
||||
}
|
||||
.fn-bracket {
|
||||
@apply text-text-subtle opacity-30;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hyperlink-widget {
|
||||
|
||||
@@ -23,7 +23,7 @@ export type TwigCompletionOption = (
|
||||
| TwigCompletionOptionNamespace
|
||||
) & {
|
||||
name: string;
|
||||
label: string;
|
||||
label: string | HTMLElement;
|
||||
description?: string;
|
||||
onClick: (rawTag: string, startPos: number) => void;
|
||||
value: string | null;
|
||||
@@ -34,7 +34,7 @@ export interface TwigCompletionConfig {
|
||||
options: TwigCompletionOption[];
|
||||
}
|
||||
|
||||
const MIN_MATCH_NAME = 2;
|
||||
const MIN_MATCH_NAME = 1;
|
||||
|
||||
export function twigCompletion({ options }: TwigCompletionConfig) {
|
||||
return function completions(context: CompletionContext) {
|
||||
@@ -44,7 +44,7 @@ export function twigCompletion({ options }: TwigCompletionConfig) {
|
||||
if (toMatch === null) return null;
|
||||
|
||||
const matchLen = toMatch.to - toMatch.from;
|
||||
if (toMatch.from > 0 && matchLen < MIN_MATCH_NAME) {
|
||||
if (!context.explicit && toMatch.from > 0 && matchLen < MIN_MATCH_NAME) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { Range } from '@codemirror/state';
|
||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||
import type { SyntaxNodeRef } from '@lezer/common';
|
||||
import { applyFormInputDefaults, validateTemplateFunctionArgs } from '@yaakapp-internal/lib';
|
||||
import type { FormInput, JsonPrimitive, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import { parseTemplate } from '@yaakapp-internal/templates';
|
||||
import type { TwigCompletionOption } from './completion';
|
||||
import { collectArgumentValues } from './util';
|
||||
@@ -42,7 +44,8 @@ class TemplateTagWidget extends WidgetType {
|
||||
}`;
|
||||
elt.title = this.option.invalid ? 'Not Found' : (this.option.value ?? '');
|
||||
elt.setAttribute('data-tag-type', this.option.type);
|
||||
elt.textContent = this.option.label;
|
||||
if (typeof this.option.label === 'string') elt.textContent = this.option.label;
|
||||
else elt.appendChild(this.option.label);
|
||||
elt.addEventListener('click', this.#clickListenerCallback);
|
||||
return elt;
|
||||
}
|
||||
@@ -109,15 +112,11 @@ function templateTags(
|
||||
|
||||
if (option.type === 'function') {
|
||||
const tokens = parseTemplate(rawTag);
|
||||
const values = collectArgumentValues(tokens, option);
|
||||
for (const arg of option.args) {
|
||||
if (!('optional' in arg)) continue;
|
||||
if (!arg.optional && values[arg.name] == null) {
|
||||
// Clone so we don't mutate the original
|
||||
option = { ...option, invalid: true };
|
||||
break;
|
||||
}
|
||||
}
|
||||
const rawValues = collectArgumentValues(tokens, option);
|
||||
const values = applyFormInputDefaults(option.args, rawValues);
|
||||
const label = makeFunctionLabel(option, values);
|
||||
const validationErr = validateTemplateFunctionArgs(option.name, option.args, values);
|
||||
option = { ...option, label, invalid: !!validationErr }; // Clone so we don't mutate the original
|
||||
}
|
||||
|
||||
const widget = new TemplateTagWidget(option, rawTag, node.from);
|
||||
@@ -169,3 +168,57 @@ function isSelectionInsideNode(view: EditorView, node: SyntaxNodeRef) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function makeFunctionLabel(
|
||||
fn: TemplateFunction,
|
||||
values: { [p: string]: JsonPrimitive | undefined },
|
||||
): HTMLElement | string {
|
||||
if (fn.args.length === 0) return fn.name;
|
||||
|
||||
const $outer = document.createElement('span');
|
||||
$outer.className = 'fn';
|
||||
const $bOpen = document.createElement('span');
|
||||
$bOpen.className = 'fn-bracket';
|
||||
$bOpen.textContent = '(';
|
||||
$outer.appendChild(document.createTextNode(fn.name));
|
||||
$outer.appendChild($bOpen);
|
||||
|
||||
const $inner = document.createElement('span');
|
||||
$inner.className = 'fn-inner';
|
||||
$inner.title = '';
|
||||
fn.previewArgs?.forEach((name: string, i: number, all: string[]) => {
|
||||
const v = String(values[name] || '');
|
||||
if (!v) return;
|
||||
if (all.length > 1) {
|
||||
const $c = document.createElement('span');
|
||||
$c.className = 'fn-arg-name';
|
||||
$c.textContent = i > 0 ? `, ${name}=` : `${name}=`;
|
||||
$inner.appendChild($c);
|
||||
}
|
||||
|
||||
const $v = document.createElement('span');
|
||||
$v.className = 'fn-arg-value';
|
||||
$v.textContent = v.includes(' ') ? `'${v}'` : v;
|
||||
$inner.appendChild($v);
|
||||
});
|
||||
fn.args.forEach((a: FormInput, i: number) => {
|
||||
if (!('name' in a)) return;
|
||||
const v = values[a.name];
|
||||
if (v == null) return;
|
||||
if (i > 0) $inner.title += '\n';
|
||||
$inner.title += `${a.name} = ${JSON.stringify(v)}`;
|
||||
});
|
||||
|
||||
if ($inner.childNodes.length === 0) {
|
||||
$inner.appendChild(document.createTextNode('…'));
|
||||
}
|
||||
|
||||
$outer.appendChild($inner);
|
||||
|
||||
const $bClose = document.createElement('span');
|
||||
$bClose.className = 'fn-bracket';
|
||||
$bClose.textContent = ')';
|
||||
$outer.appendChild($bClose);
|
||||
|
||||
return $outer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user