fix: clean up dependencies and config schema (#704)

This commit is contained in:
Daniel Lautzenheiser 2023-04-17 10:35:35 -04:00 committed by GitHub
parent 778e6bac75
commit ecb1218646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 304 additions and 179 deletions

View File

@ -156,8 +156,6 @@
"symbol-observable": "4.0.0",
"unified": "10.1.2",
"unist-util-visit": "4.1.2",
"uploadcare-widget": "3.21.0",
"uploadcare-widget-tab-effects": "1.6.0",
"url": "0.11.0",
"url-join": "5.0.0",
"uuid": "9.0.0",

View File

@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { Navigate, useParams } from 'react-router-dom';
import { Navigate, useParams, useSearchParams } from 'react-router-dom';
import {
selectCollection,
@ -16,6 +16,8 @@ interface CollectionRouteProps {
const CollectionRoute = ({ isSearchResults, isSingleSearchResult }: CollectionRouteProps) => {
const { name, searchTerm } = useParams();
const [searchParams] = useSearchParams();
const noRedirect = searchParams.has('noredirect');
const collectionSelector = useMemo(() => selectCollection(name), [name]);
const collection = useAppSelector(collectionSelector);
@ -27,7 +29,12 @@ const CollectionRoute = ({ isSearchResults, isSingleSearchResult }: CollectionRo
return <Navigate to={defaultPath} />;
}
if (collection && 'files' in collection && collection.files?.length === 1) {
if (collection && 'files' in collection && collection.files?.length === 1 && !noRedirect) {
const href = window.location.href;
if (!href.includes('noredirect')) {
window.history.replaceState(null, document.title, `${href}?noredirect`);
console.log('REPLACE STATE', document.title, `${href}?noredirect`);
}
return <Navigate to={`/collections/${collection.name}/entries/${collection.files[0].name}`} />;
}

View File

@ -1,23 +1,31 @@
import React from 'react';
import type { FC } from 'react';
import classNames from '@staticcms/core/lib/util/classNames.util';
import type { FieldError } from '@staticcms/core/interface';
import type { FC } from 'react';
export interface ErrorMessageProps {
errors: FieldError[];
className?: string;
}
const ErrorMessage: FC<ErrorMessageProps> = ({ errors }) => {
const ErrorMessage: FC<ErrorMessageProps> = ({ errors, className }) => {
return errors.length ? (
<div
key="error"
data-testid="error"
className="flex
className={classNames(
`
flex
w-full
text-xs
text-red-500
px-3
pt-1"
pt-1
`,
className,
)}
>
{errors[0].message}
</div>

View File

@ -58,7 +58,6 @@ const Pill: FC<PillProps> = ({
`
text-xs
font-medium
mr-2
px-3
py-1
rounded-lg

View File

@ -80,7 +80,7 @@ const EditorControl = ({
[field.name, fieldName, parentPath],
);
const [dirty, setDirty] = useState(!isEmpty(value));
const [dirty, setDirty] = useState(!isEmpty(widget.getValidValue(value, field as UnknownField)));
const fieldErrorsSelector = useMemo(
() => selectFieldErrors(path, i18n, isMeta),
@ -114,10 +114,12 @@ const EditorControl = ({
const handleChangeDraftField = useCallback(
(value: ValueOrNestedValue) => {
setDirty(true);
setDirty(
oldDirty => oldDirty || !isEmpty(widget.getValidValue(value, field as UnknownField)),
);
changeDraftField({ path, field, value, i18n, isMeta });
},
[changeDraftField, field, i18n, isMeta, path],
[changeDraftField, field, i18n, isMeta, path, widget],
);
const config = useMemo(() => configState.config, [configState.config]);

View File

@ -113,6 +113,9 @@ const viewFilters = {
{
type: 'string',
},
{
type: 'number',
},
],
},
},
@ -149,52 +152,42 @@ function getConfigSchema() {
type: 'object',
properties: {
name: { type: 'string', examples: ['test-repo'] },
repo: { type: 'string' },
branch: { type: 'string' },
api_root: { type: 'string' },
site_domain: { type: 'string' },
base_url: { type: 'string' },
auth_endpoint: { type: 'string' },
app_id: { type: 'string' },
auth_type: {
type: 'string',
examples: ['implicit', 'pkce'],
enum: ['implicit', 'public_pkcerepo'],
},
proxy_url: { type: 'string' },
large_media_url: { type: 'string' },
login: { type: 'boolean' },
identity_url: { type: 'string' },
gateway_url: { type: 'string' },
auth_scope: {
type: 'string',
examples: ['repo', 'public_repo'],
enum: ['repo', 'public_repo'],
},
},
required: ['name'],
},
local_backend: {
oneOf: [
{ type: 'boolean' },
{
commit_messages: {
type: 'object',
properties: {
url: { type: 'string', examples: ['http://localhost:8081/api/v1'] },
allowed_hosts: {
type: 'array',
items: { type: 'string' },
},
create: { type: 'string' },
update: { type: 'string' },
delete: { type: 'string' },
uploadMedia: { type: 'string' },
deleteMedia: { type: 'string' },
},
additionalProperties: false,
},
],
},
locale: { type: 'string', examples: ['en', 'fr', 'de'] },
i18n: i18nRoot,
site_url: { type: 'string', examples: ['https://example.com'] },
display_url: { type: 'string', examples: ['https://example.com'] },
logo_url: { type: 'string', examples: ['https://example.com/images/logo.svg'] },
media_folder: { type: 'string', examples: ['assets/uploads'] },
public_folder: { type: 'string', examples: ['/uploads'] },
media_folder_relative: { type: 'boolean' },
media_library: {
type: 'object',
properties: {
name: { type: 'string', examples: ['uploadcare'] },
config: { type: 'object' },
},
required: [],
},
slug: {
type: 'object',
properties: {
encoding: { type: 'string', enum: ['unicode', 'ascii'] },
clean_accents: { type: 'boolean' },
},
required: ['name'],
additionalProperties: false,
},
collections: {
type: 'array',
@ -204,34 +197,8 @@ function getConfigSchema() {
type: 'object',
properties: {
name: { type: 'string' },
label: { type: 'string' },
label_singular: { type: 'string' },
description: { type: 'string' },
folder: { type: 'string' },
files: {
type: 'array',
items: {
// ------- Each file: -------
type: 'object',
properties: {
name: { type: 'string' },
label: { type: 'string' },
label_singular: { type: 'string' },
description: { type: 'string' },
file: { type: 'string' },
editor: {
type: 'object',
properties: {
preview: { type: 'boolean' },
},
},
fields: fieldsConfig(),
},
required: ['name', 'label', 'file', 'fields'],
},
uniqueItemProperties: ['name'],
},
identifier_field: { type: 'string' },
icon: { type: 'string' },
summary: { type: 'string' },
summary_fields: {
type: 'array',
@ -239,28 +206,17 @@ function getConfigSchema() {
type: 'string',
},
},
slug: { type: 'string' },
path: { type: 'string' },
create: { type: 'boolean' },
publish: { type: 'boolean' },
hide: { type: 'boolean' },
editor: {
filter: {
type: 'object',
properties: {
preview: { type: 'boolean' },
value: { type: 'string' },
field: { type: 'string' },
},
required: ['value', 'field'],
additionalProperties: false,
},
format: { type: 'string', enum: Object.keys(formatExtensions) },
extension: { type: 'string' },
frontmatter_delimiter: {
type: ['string', 'array'],
minItems: 2,
maxItems: 2,
items: {
type: 'string',
},
},
fields: fieldsConfig(),
label_singular: { type: 'string' },
label: { type: 'string' },
sortable_fields: {
type: 'object',
properties: {
@ -275,6 +231,7 @@ function getConfigSchema() {
},
},
required: ['field'],
additionalProperties: false,
},
fields: {
type: 'array',
@ -284,9 +241,47 @@ function getConfigSchema() {
},
},
required: ['fields'],
additionalProperties: false,
},
view_filters: viewFilters,
view_groups: viewGroups,
i18n: i18nCollection,
hide: { type: 'boolean' },
editor: {
type: 'object',
properties: {
preview: { type: 'boolean' },
frame: { type: 'boolean' },
},
additionalProperties: false,
},
identifier_field: { type: 'string' },
path: { type: 'string' },
extension: { type: 'string' },
format: { type: 'string', enum: Object.keys(formatExtensions) },
frontmatter_delimiter: {
type: ['string', 'array'],
minItems: 2,
maxItems: 2,
items: {
type: 'string',
},
},
slug: { type: 'string' },
media_folder: { type: 'string' },
public_folder: { type: 'string' },
media_library: {
type: 'object',
properties: {
max_file_size: { type: 'number' },
folder_support: { type: 'boolean' },
},
additionalProperties: false,
},
folder: { type: 'string' },
fields: fieldsConfig(),
create: { type: 'boolean' },
delete: { type: 'boolean' },
nested: {
type: 'object',
properties: {
@ -299,11 +294,49 @@ function getConfigSchema() {
index_file: { type: 'string' },
},
required: ['index_file'],
additionalProperties: false,
},
},
required: ['depth'],
additionalProperties: false,
},
files: {
type: 'array',
items: {
// ------- Each file: -------
type: 'object',
properties: {
name: { type: 'string' },
label: { type: 'string' },
file: { type: 'string' },
fields: fieldsConfig(),
label_singular: { type: 'string' },
description: { type: 'string' },
media_folder: { type: 'string' },
public_folder: { type: 'string' },
media_library: {
type: 'object',
properties: {
max_file_size: { type: 'number' },
folder_support: { type: 'boolean' },
},
additionalProperties: false,
},
i18n: i18nCollection,
editor: {
type: 'object',
properties: {
preview: { type: 'boolean' },
frame: { type: 'boolean' },
},
additionalProperties: false,
},
},
required: ['name', 'label', 'file', 'fields'],
additionalProperties: false,
},
uniqueItemProperties: ['name'],
},
},
required: ['name', 'label'],
oneOf: [{ required: ['files'] }, { required: ['folder', 'fields'] }],
@ -328,15 +361,62 @@ function getConfigSchema() {
},
uniqueItemProperties: ['name'],
},
locale: { type: 'string', examples: ['en', 'fr', 'de'] },
site_id: { type: 'string' },
site_url: { type: 'string', examples: ['https://example.com'] },
display_url: { type: 'string', examples: ['https://example.com'] },
base_url: { type: 'string' },
logo_url: { type: 'string', examples: ['https://example.com/images/logo.svg'] },
media_folder: { type: 'string', examples: ['assets/uploads'] },
public_folder: { type: 'string', examples: ['/uploads'] },
media_folder_relative: { type: 'boolean' },
media_library: {
type: 'object',
properties: {
max_file_size: { type: 'number' },
folder_support: { type: 'boolean' },
},
additionalProperties: false,
},
load_config_file: { type: 'boolean' },
slug: {
type: 'object',
properties: {
encoding: { type: 'string', enum: ['unicode', 'ascii'] },
clean_accents: { type: 'boolean' },
sanitize_replacement: { type: 'string' },
},
additionalProperties: false,
},
i18n: i18nRoot,
local_backend: {
oneOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
url: { type: 'string', examples: ['http://localhost:8081/api/v1'] },
allowed_hosts: {
type: 'array',
items: { type: 'string' },
},
},
additionalProperties: false,
},
],
},
editor: {
type: 'object',
properties: {
preview: { type: 'boolean' },
frame: { type: 'boolean' },
},
additionalProperties: false,
},
search: { type: 'string' },
},
required: ['backend', 'collections'],
anyOf: [{ required: ['media_folder'] }, { required: ['media_library'] }],
required: ['backend', 'collections', 'media_folder'],
additionalProperties: false,
};
}

View File

@ -121,8 +121,9 @@ export interface FieldsErrors {
[field: string]: FieldError[];
}
export type FieldGetValidValueMethod<T = unknown> = (
export type FieldGetValidValueMethod<T = unknown, F extends BaseField = UnknownField> = (
value: T | undefined | null,
field: F,
) => T | undefined | null;
export type FieldGetDefaultMethod<T = unknown, F extends BaseField = UnknownField> = (
@ -363,7 +364,7 @@ export interface Widget<T = unknown, F extends BaseField = UnknownField> {
control: ComponentType<WidgetControlProps<T, F>>;
preview?: WidgetPreviewComponent<T, F>;
validator: FieldValidationMethod<T, F>;
getValidValue: FieldGetValidValueMethod<T>;
getValidValue: FieldGetValidValueMethod<T, F>;
getDefaultValue?: FieldGetDefaultMethod<T, F>;
schema?: PropertiesSchema<unknown>;
}

View File

@ -34,7 +34,7 @@ export default function useBreadcrumbs(
const crumbs: Breadcrumb[] = [
{
name: collection.label,
to: `/collections/${collection.name}`,
to: `/collections/${collection.name}?noredirect`,
},
];

View File

@ -83,7 +83,7 @@ export async function validate(
widget: Widget<any, any>,
t: t,
): Promise<FieldError[]> {
const validValue = widget.getValidValue(value);
const validValue = widget.getValidValue(value, field);
const errors: FieldError[] = [];
const validations: FieldValidationMethod<ValueOrNestedValue>[] = [
validatePresence,

View File

@ -8,6 +8,7 @@ import {
ENTRIES_SUCCESS,
ENTRY_DELETE_SUCCESS,
ENTRY_FAILURE,
ENTRY_PERSIST_SUCCESS,
ENTRY_REQUEST,
ENTRY_SUCCESS,
FILTER_ENTRIES_FAILURE,
@ -551,6 +552,19 @@ function entries(
};
}
case ENTRY_PERSIST_SUCCESS: {
const payload = action.payload;
const { collectionName } = payload;
const pages = { ...state.pages };
delete pages[collectionName];
return {
...state,
pages,
};
}
default:
return state;
}

View File

@ -1,33 +0,0 @@
declare module 'uploadcare-widget-tab-effects';
declare module 'uploadcare-widget' {
interface UploadcareFileGroupInfo {
cdnUrl: string;
name: string;
isImage: boolean;
}
const Uploadcare: {
loadFileGroup: (groupId: string | undefined) => {
done: (callback: (group: UploadcareFileGroupInfo) => void) => void;
};
fileFrom: (uploadedOrUrl: 'uploaded' | 'url', url: string) => Promise<UploadcareFileGroupInfo>;
registerTab: (tab: string, tabContent: unknown) => void;
openDialog: (
files:
| UploadcareFileGroupInfo
| UploadcareFileGroupInfo[]
| Promise<UploadcareFileGroupInfo | UploadcareFileGroupInfo[]>,
settings: Record<string, unknown>,
) => {
done: (
callback: (values: {
promise: () => Promise<UploadcareFileGroupInfo>;
files: () => Promise<UploadcareFileGroupInfo>[];
}) => void,
) => void;
};
};
export default Uploadcare;
}

View File

@ -279,7 +279,7 @@ const CodeControl: FC<WidgetControlProps<string | { [key: string]: string }, Cod
{field.hint}
</Hint>
) : null}
<ErrorMessage errors={errors} />
<ErrorMessage errors={errors} className="pt-2 pb-3" />
</div>
</div>
);

View File

@ -11,6 +11,15 @@ const CodeWidget = (): WidgetParam<string | { [key: string]: string }, CodeField
previewComponent,
options: {
schema,
getValidValue: (value, field) => {
if (!value || typeof value === 'string') {
return value;
}
const codeKey = field.keys ? field.keys.code : 'code';
return value[codeKey];
},
getDefaultValue: (
defaultValue: string | { [key: string]: string } | null | undefined,
field: CodeField,

View File

@ -1,14 +1,15 @@
export default {
properties: {
default: {
oneOf: [{ type: 'string' }, { type: 'object' }],
},
default_language: { type: 'string' },
allow_language_selection: { type: 'boolean' },
output_code_only: { type: 'boolean' },
keys: {
type: 'object',
properties: { code: { type: 'string' }, lang: { type: 'string' } },
},
default: {
oneOf: [{ type: 'string' }, { type: 'object' }],
},
output_code_only: { type: 'boolean' },
code_mirror_config: { type: 'object' },
},
};

View File

@ -1,5 +1,7 @@
export default {
properties: {
default: { type: 'string' },
allow_input: { type: 'boolean' },
enable_alpha: { type: 'boolean' },
},
};

View File

@ -1,9 +1,9 @@
export default {
properties: {
default: { type: 'string' },
format: { type: 'string' },
date_format: { oneOf: [{ type: 'string' }, { type: 'boolean' }] },
time_format: { oneOf: [{ type: 'string' }, { type: 'boolean' }] },
picker_utc: { type: 'boolean' },
default: { type: 'string' },
},
};

View File

@ -12,5 +12,17 @@ export default {
},
],
},
media_folder: { type: 'string' },
public_folder: { type: 'string' },
choose_url: { type: 'boolean' },
multiple: { type: 'boolean' },
media_library: {
type: 'object',
properties: {
max_file_size: { type: 'number' },
folder_support: { type: 'boolean' },
},
additionalProperties: false,
},
},
};

View File

@ -12,5 +12,17 @@ export default {
},
],
},
media_folder: { type: 'string' },
public_folder: { type: 'string' },
choose_url: { type: 'boolean' },
multiple: { type: 'boolean' },
media_library: {
type: 'object',
properties: {
max_file_size: { type: 'number' },
folder_support: { type: 'boolean' },
},
additionalProperties: false,
},
},
};

View File

@ -142,7 +142,7 @@ const ListFieldWrapper: FC<ListFieldWrapperProps> = ({
{hint}
</Hint>
) : null}
<ErrorMessage errors={errors} />
<ErrorMessage errors={errors} className="pb-3" />
</div>
</div>
);

View File

@ -1,11 +1,27 @@
export default {
properties: {
default: {
oneOf: [
{ type: 'boolean' },
{ type: 'string' },
{ type: 'number' },
{
type: 'array',
minItems: 1,
items: { oneOf: [{ type: 'boolean' }, { type: 'string' }, { type: 'number' }] },
},
],
},
allow_add: { type: 'boolean' },
collapsed: { type: 'boolean' },
summary: { type: 'string' },
label_singular: { type: 'string' },
i18n: { type: 'boolean' },
min: { type: 'number' },
fields: { type: 'object' },
max: { type: 'number' },
min: { type: 'number' },
i18n: { type: 'boolean' },
add_to_top: { type: 'boolean' },
types: { type: 'object' },
type_key: { type: 'string' },
},
};

View File

@ -1,5 +1,17 @@
export default {
properties: {
default: { type: 'string' },
media_folder: { type: 'string' },
public_folder: { type: 'string' },
choose_url: { type: 'boolean' },
multiple: { type: 'boolean' },
media_library: {
type: 'object',
properties: {
max_file_size: { type: 'number' },
folder_support: { type: 'boolean' },
},
additionalProperties: false,
},
},
};

View File

@ -134,7 +134,7 @@ const ObjectFieldWrapper: FC<ObjectFieldWrapperProps> = ({
{hint}
</Hint>
) : null}
<ErrorMessage errors={errors} />
<ErrorMessage errors={errors} className="pl-4 pt-2 pb-3" />
</div>
);
};

View File

@ -1,6 +1,9 @@
export default {
properties: {
default: { type: 'object' },
collapsed: { type: 'boolean' },
summary: { type: 'string' },
i18n: { type: 'boolean' },
fields: { type: 'object' },
},
};

View File

@ -322,7 +322,7 @@ const RelationControl: FC<WidgetControlProps<string | string[], RelationField>>
label={
<>
{Array.isArray(selectedValue) && selectedValue.length > 0 ? (
<div className="flex flex-wrap gap-0.5 w-full pr-4 p-2">
<div className="flex flex-wrap gap-2 w-full p-2 pr-0 max-w-fit">
{selectedValue.map(selectValue => {
const option = uniqueOptionsByValue[selectValue];
return (

View File

@ -1,14 +1,5 @@
export default {
properties: {
collection: { type: 'string' },
value_field: { type: 'string' },
search_fields: { type: 'array', minItems: 1, items: { type: 'string' } },
file: { type: 'string' },
multiple: { type: 'boolean' },
min: { type: 'integer' },
max: { type: 'integer' },
display_fields: { type: 'array', minItems: 1, items: { type: 'string' } },
options_length: { type: 'integer' },
default: {
oneOf: [
{ type: 'string' },
@ -20,6 +11,15 @@ export default {
},
],
},
collection: { type: 'string' },
value_field: { type: 'string' },
search_fields: { type: 'array', minItems: 1, items: { type: 'string' } },
file: { type: 'string' },
display_fields: { type: 'array', minItems: 1, items: { type: 'string' } },
multiple: { type: 'boolean' },
min: { type: 'integer' },
max: { type: 'integer' },
options_length: { type: 'integer' },
},
oneOf: [
{

View File

@ -126,7 +126,7 @@ const SelectControl: FC<WidgetControlProps<string | number | (string | number)[]
<Select
label={
Array.isArray(stringValue) ? (
<div className="flex wrap gap-0.5">
<div className="flex wrap gap-2 max-w-fit">
{stringValue.map(selectValue => {
const label = optionsByValue[selectValue]?.label ?? selectValue;
return (

View File

@ -1,8 +1,5 @@
export default {
properties: {
multiple: { type: 'boolean' },
min: { type: 'integer' },
max: { type: 'integer' },
default: {
oneOf: [
{ type: 'string' },
@ -32,6 +29,9 @@ export default {
],
},
},
multiple: { type: 'boolean' },
min: { type: 'integer' },
max: { type: 'integer' },
},
required: ['options'],
};

View File

@ -8470,7 +8470,7 @@ escape-goat@^3.0.0:
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-3.0.0.tgz#e8b5fb658553fe8a3c4959c316c6ebb8c842b19c"
integrity sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==
escape-html@1.0.3, escape-html@^1.0.3, escape-html@~1.0.3:
escape-html@1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
@ -11905,11 +11905,6 @@ jotai@^1.7.2:
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.13.1.tgz#20cc46454cbb39096b12fddfa635b873b3668236"
integrity sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw==
jquery@^3.6.0:
version "3.6.4"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.4.tgz#ba065c188142100be4833699852bf7c24dc0252f"
integrity sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ==
js-base64@3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
@ -18605,19 +18600,6 @@ update-browserslist-db@^1.0.10:
escalade "^3.1.1"
picocolors "^1.0.0"
uploadcare-widget-tab-effects@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/uploadcare-widget-tab-effects/-/uploadcare-widget-tab-effects-1.6.0.tgz#6de4664b0b2fa47100b5a3e0afc1d41b35b8f429"
integrity sha512-RoWxeZnk41qm8Mo19R8Zvshsuqetc4y+gFtxA89x+noLCSFItuviGZJBf7rcX3ocwn+aFNKbXp44dlaVDoYRIA==
uploadcare-widget@3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/uploadcare-widget/-/uploadcare-widget-3.21.0.tgz#db67e7f97d257c3ab8da65328121502f8ab6b1f8"
integrity sha512-9C/WLwK3Anx+76rDzg/pe1vBv41j4HapgxfbyVLS+3oolB3bd9NvURAkvfyEif8QUnt7MwK2Ubw3f7UP/a3v9w==
dependencies:
escape-html "^1.0.3"
jquery "^3.6.0"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"