diff --git a/.mapping.json b/.mapping.json index 3870712c6..814ebab8a 100644 --- a/.mapping.json +++ b/.mapping.json @@ -22987,6 +22987,7 @@ "visual-editor/src/lib/utils/capitalize.ts":"divkit/public/visual-editor/src/lib/utils/capitalize.ts", "visual-editor/src/lib/utils/checkDivjson.test.ts":"divkit/public/visual-editor/src/lib/utils/checkDivjson.test.ts", "visual-editor/src/lib/utils/checkDivjson.ts":"divkit/public/visual-editor/src/lib/utils/checkDivjson.ts", + "visual-editor/src/lib/utils/checkValue.ts":"divkit/public/visual-editor/src/lib/utils/checkValue.ts", "visual-editor/src/lib/utils/colors.test.ts":"divkit/public/visual-editor/src/lib/utils/colors.test.ts", "visual-editor/src/lib/utils/colors.ts":"divkit/public/visual-editor/src/lib/utils/colors.ts", "visual-editor/src/lib/utils/componentIcon.ts":"divkit/public/visual-editor/src/lib/utils/componentIcon.ts", diff --git a/visual-editor/src/lib.ts b/visual-editor/src/lib.ts index c46298abe..5c60f7f39 100644 --- a/visual-editor/src/lib.ts +++ b/visual-editor/src/lib.ts @@ -62,6 +62,12 @@ export interface CardLocale { text: Record; } +export type StringValueFilter = RegExp | ((value: string) => boolean); + +export interface ValueFilters { + actionUrl?: StringValueFilter; +} + export interface EditorOptions { node: HTMLElement; shadowRoot?: ShadowRoot; @@ -148,6 +154,7 @@ export interface DivProEditorOptions { /* @deprecated */ errorFileLimit?: number; fileLimits?: FileLimits; + valueFilters?: ValueFilters; rootConfigurable?: boolean; customFontFaces?: FontFaceDesc[]; directionSelector?: boolean; @@ -193,6 +200,7 @@ export const DivProEditor = { state.direction.set(opts.direction || 'ltr'); state.safeAreaEmulation = opts.safeAreaEmulation; state.safeAreaEmulationEnabled.set(Boolean(opts.safeAreaEmulation)); + state.valueFilters = opts.valueFilters; if (Array.isArray(json?.card?.variables)) { const localPalette = json.card.variables.find((it?: JsonVariable) => it?.type === 'dict' && it.name === 'local_palette'); diff --git a/visual-editor/src/lib/components/controls/Text.svelte b/visual-editor/src/lib/components/controls/Text.svelte index 66de06eb8..54bb0cb67 100644 --- a/visual-editor/src/lib/components/controls/Text.svelte +++ b/visual-editor/src/lib/components/controls/Text.svelte @@ -16,6 +16,8 @@ import { formatFileSize } from '../../utils/formatFileSize'; import { calcFileSizeMod, getFileSize } from '../../utils/fileSize'; import type { VideoSource } from '../../utils/video'; + import type { StringValueFilter } from '../../../lib'; + import { checkStringValue } from '../../utils/checkValue'; export let id: string = ''; export let value: string | number | undefined; @@ -34,6 +36,7 @@ export let size: 'medium' | 'small' = 'medium'; export let width: 'auto' | 'small' = 'auto'; export let pattern: string | null = null; + export let filter: StringValueFilter | undefined = undefined; export let error = ''; export let title = ''; export let generateFromVideo: VideoSource[] | undefined = undefined; @@ -54,11 +57,18 @@ let elem: HTMLElement; let subtypeError = false; let requiredError = false; + let filterError = false; let isFocused = false; let currentSize: number | undefined = undefined; let sizeLabelWidth = 0; let customButtonsWidth = 0; + function validateValue(value: string | number | undefined): void { + requiredError = !value && required; + + filterError = !checkStringValue(value, filter); + } + function updateInternalValue( subtype: string, value: string | number | undefined, @@ -80,7 +90,7 @@ loadFileSize(); - requiredError = !internalValue && required; + validateValue(internalValue); } $: updateInternalValue(subtype, value, defaultValue); @@ -135,7 +145,7 @@ loadFileSize(); - requiredError = !value && required; + validateValue(value); dispatch('change', { value @@ -194,7 +204,7 @@ class:text_inline-label={Boolean(inlineLabel)} class:text_button={hasButton} class:text_disabled={disabled} - class:text_error={Boolean(error || subtypeError || requiredError)} + class:text_error={Boolean(error || subtypeError || requiredError || filterError)} title={error} aria-label={title} data-custom-tooltip={title} diff --git a/visual-editor/src/lib/components/prop-dialog/Actions2Dialog.svelte b/visual-editor/src/lib/components/prop-dialog/Actions2Dialog.svelte index 90ec5d5d2..953ec5608 100644 --- a/visual-editor/src/lib/components/prop-dialog/Actions2Dialog.svelte +++ b/visual-editor/src/lib/components/prop-dialog/Actions2Dialog.svelte @@ -36,7 +36,7 @@ const { l10n, lang } = getContext(LANGUAGE_CTX); const { state } = getContext(APP_CTX); - const { customActions } = state; + const { customActions, valueFilters } = state; $: if (isShown && !readOnly && callback) { if (!value.log_url) { @@ -224,6 +224,7 @@ diff --git a/visual-editor/src/lib/components/simple-props/Actions2Item.svelte b/visual-editor/src/lib/components/simple-props/Actions2Item.svelte index 0f63c5870..d25efc6e1 100644 --- a/visual-editor/src/lib/components/simple-props/Actions2Item.svelte +++ b/visual-editor/src/lib/components/simple-props/Actions2Item.svelte @@ -22,27 +22,31 @@ import { LANGUAGE_CTX, type LanguageContext } from '../../ctx/languageContext'; import { parseAction } from '../../data/actions'; import { APP_CTX, type AppContext } from '../../ctx/appContext'; + import { checkStringValue } from '../../utils/checkValue'; export let value: Action; const { l10n, lang } = getContext(LANGUAGE_CTX); const { state, actions2Dialog } = getContext(APP_CTX); - const { customActions, readOnly } = state; + const { customActions, readOnly, valueFilters } = state; const dispatch = createEventDispatcher(); let elem: HTMLElement; let type = ''; let text = ''; + let hasError = false; function updateVal(value: Action): void { const parsed = parseAction(value, $customActions); type = ''; + hasError = false; if (parsed.type === 'url') { type = $l10n('actions-url'); text = parsed.url || ''; + hasError = !checkStringValue(parsed.url, valueFilters?.actionUrl); } else if (parsed.type.startsWith('typed:')) { if (parsed.type === 'typed:focus_element') { type = $l10n('actions.focus_element'); @@ -133,6 +137,7 @@