@tinacms/schema-tools 2.9.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +115 -4
- package/dist/types/index.d.ts +81 -0
- package/package.json +4 -5
package/dist/index.js
CHANGED
|
@@ -2146,8 +2146,11 @@ const resolveField = (field, schema) => {
|
|
|
2146
2146
|
if (field.list) {
|
|
2147
2147
|
return {
|
|
2148
2148
|
component: "list",
|
|
2149
|
+
// The list plugin builds each item from `field.field` alone, so
|
|
2150
|
+
// anything the item's input needs has to be carried in here.
|
|
2149
2151
|
field: {
|
|
2150
|
-
component: "image"
|
|
2152
|
+
component: "image",
|
|
2153
|
+
...field.accept ? { accept: field.accept } : {}
|
|
2151
2154
|
},
|
|
2152
2155
|
...field,
|
|
2153
2156
|
...extraFields
|
|
@@ -2313,6 +2316,93 @@ const CONTENT_FORMATS = [
|
|
|
2313
2316
|
"yml",
|
|
2314
2317
|
"toml"
|
|
2315
2318
|
];
|
|
2319
|
+
const MEDIA_EXTENSIONS = [
|
|
2320
|
+
"jpg",
|
|
2321
|
+
"jpeg",
|
|
2322
|
+
"png",
|
|
2323
|
+
"gif",
|
|
2324
|
+
"webp",
|
|
2325
|
+
"svg",
|
|
2326
|
+
"avif",
|
|
2327
|
+
"ico",
|
|
2328
|
+
"mp4",
|
|
2329
|
+
"webm",
|
|
2330
|
+
"mov",
|
|
2331
|
+
"mp3",
|
|
2332
|
+
"wav",
|
|
2333
|
+
"ogg",
|
|
2334
|
+
"pdf",
|
|
2335
|
+
"json",
|
|
2336
|
+
"csv",
|
|
2337
|
+
"txt"
|
|
2338
|
+
];
|
|
2339
|
+
const MEDIA_CATEGORIES = {
|
|
2340
|
+
image: ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico"],
|
|
2341
|
+
video: ["mp4", "webm", "mov"],
|
|
2342
|
+
audio: ["mp3", "wav", "ogg"],
|
|
2343
|
+
document: ["pdf", "json", "csv", "txt"]
|
|
2344
|
+
};
|
|
2345
|
+
const MEDIA_CATEGORY_NAMES = [
|
|
2346
|
+
"image",
|
|
2347
|
+
"video",
|
|
2348
|
+
"audio",
|
|
2349
|
+
"document"
|
|
2350
|
+
];
|
|
2351
|
+
const MEDIA_ACCEPT_VALUES = [
|
|
2352
|
+
...MEDIA_CATEGORY_NAMES,
|
|
2353
|
+
...MEDIA_EXTENSIONS
|
|
2354
|
+
];
|
|
2355
|
+
const MEDIA_EXTENSION_ALIASES = {
|
|
2356
|
+
jpg: ["jpeg"],
|
|
2357
|
+
jpeg: ["jpg"]
|
|
2358
|
+
};
|
|
2359
|
+
const MEDIA_MIME_TYPES = {
|
|
2360
|
+
jpg: "image/jpeg",
|
|
2361
|
+
jpeg: "image/jpeg",
|
|
2362
|
+
png: "image/png",
|
|
2363
|
+
gif: "image/gif",
|
|
2364
|
+
webp: "image/webp",
|
|
2365
|
+
svg: "image/svg+xml",
|
|
2366
|
+
avif: "image/avif",
|
|
2367
|
+
ico: "image/x-icon",
|
|
2368
|
+
mp4: "video/mp4",
|
|
2369
|
+
webm: "video/webm",
|
|
2370
|
+
mov: "video/quicktime",
|
|
2371
|
+
mp3: "audio/mpeg",
|
|
2372
|
+
wav: "audio/wav",
|
|
2373
|
+
ogg: "audio/ogg",
|
|
2374
|
+
pdf: "application/pdf",
|
|
2375
|
+
json: "application/json",
|
|
2376
|
+
csv: "text/csv",
|
|
2377
|
+
txt: "text/plain"
|
|
2378
|
+
};
|
|
2379
|
+
const extensionOf = (value) => {
|
|
2380
|
+
const path = value.split(/[?#]/)[0];
|
|
2381
|
+
const dot = path.lastIndexOf(".");
|
|
2382
|
+
const slash = path.lastIndexOf("/");
|
|
2383
|
+
return dot > slash + 1 ? path.slice(dot + 1).toLowerCase() : "";
|
|
2384
|
+
};
|
|
2385
|
+
const resolveMediaAccept = (accept) => {
|
|
2386
|
+
if (!accept) return [];
|
|
2387
|
+
const requested = Array.isArray(accept) ? accept : [accept];
|
|
2388
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
2389
|
+
const add = (ext) => {
|
|
2390
|
+
resolved.add(ext);
|
|
2391
|
+
for (const alias of MEDIA_EXTENSION_ALIASES[ext] ?? []) {
|
|
2392
|
+
resolved.add(alias);
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
for (const entry of requested) {
|
|
2396
|
+
if (entry in MEDIA_CATEGORIES) {
|
|
2397
|
+
for (const ext of MEDIA_CATEGORIES[entry]) {
|
|
2398
|
+
add(ext);
|
|
2399
|
+
}
|
|
2400
|
+
} else if (MEDIA_EXTENSIONS.includes(entry)) {
|
|
2401
|
+
add(entry);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
return [...resolved];
|
|
2405
|
+
};
|
|
2316
2406
|
const ALL_HEADING_LEVELS = [
|
|
2317
2407
|
"h1",
|
|
2318
2408
|
"h2",
|
|
@@ -2410,6 +2500,19 @@ const TinaField = z.object({
|
|
|
2410
2500
|
required: z.boolean().optional()
|
|
2411
2501
|
});
|
|
2412
2502
|
const FieldWithList = TinaField.extend({ list: z.boolean().optional() });
|
|
2503
|
+
const validMediaAcceptList = MEDIA_ACCEPT_VALUES.map(
|
|
2504
|
+
(value) => `\`${value}\``
|
|
2505
|
+
).join(", ");
|
|
2506
|
+
const MediaAcceptValue = z.enum(MEDIA_ACCEPT_VALUES, {
|
|
2507
|
+
errorMap: (issue, ctx) => {
|
|
2508
|
+
if (issue.code === "invalid_enum_value") {
|
|
2509
|
+
return {
|
|
2510
|
+
message: `Invalid \`accept\` value \`${typeof ctx.data === "string" ? ctx.data : JSON.stringify(ctx.data)}\`. Must be one of: ${validMediaAcceptList}.`
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
return { message: ctx.defaultError };
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
2413
2516
|
const TinaScalerBase = FieldWithList.extend({
|
|
2414
2517
|
options: z.array(Option).optional(),
|
|
2415
2518
|
uid: z.boolean().optional()
|
|
@@ -2444,7 +2547,8 @@ const ImageField = TinaScalerBase.extend({
|
|
|
2444
2547
|
invalid_type_error: typeTypeError,
|
|
2445
2548
|
required_error: typeRequiredError
|
|
2446
2549
|
}),
|
|
2447
|
-
uploadDir: z.function().args(z.any()).returns(z.string()).optional()
|
|
2550
|
+
uploadDir: z.function().args(z.any()).returns(z.string()).optional(),
|
|
2551
|
+
accept: z.union([MediaAcceptValue, z.array(MediaAcceptValue)]).optional()
|
|
2448
2552
|
});
|
|
2449
2553
|
const DateTimeField = TinaScalerBase.extend({
|
|
2450
2554
|
type: z.literal("datetime", {
|
|
@@ -2485,7 +2589,7 @@ const TinaFieldZod = z.lazy(() => {
|
|
|
2485
2589
|
const TemplateTemp = z.object({
|
|
2486
2590
|
label: z.string().optional(),
|
|
2487
2591
|
name,
|
|
2488
|
-
fields: z.array(TinaFieldZod),
|
|
2592
|
+
fields: z.array(TinaFieldZod).min(1, "Property `fields` cannot be empty."),
|
|
2489
2593
|
match: z.object({
|
|
2490
2594
|
start: z.string(),
|
|
2491
2595
|
end: z.string(),
|
|
@@ -2685,7 +2789,7 @@ const Template = z.object({
|
|
|
2685
2789
|
required_error: "Missing `label` property. Property `label` is required."
|
|
2686
2790
|
}),
|
|
2687
2791
|
name,
|
|
2688
|
-
fields: z.array(TinaFieldZod)
|
|
2792
|
+
fields: z.array(TinaFieldZod).min(1, "Property `fields` cannot be empty.")
|
|
2689
2793
|
}).superRefine((val, ctx) => {
|
|
2690
2794
|
var _a;
|
|
2691
2795
|
const dups = findDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
|
|
@@ -2877,6 +2981,11 @@ export {
|
|
|
2877
2981
|
ERR_HAS_REFERENCES,
|
|
2878
2982
|
ERR_NOT_INDEXED,
|
|
2879
2983
|
ERR_PATH_TRAVERSAL,
|
|
2984
|
+
MEDIA_ACCEPT_VALUES,
|
|
2985
|
+
MEDIA_CATEGORIES,
|
|
2986
|
+
MEDIA_CATEGORY_NAMES,
|
|
2987
|
+
MEDIA_EXTENSIONS,
|
|
2988
|
+
MEDIA_MIME_TYPES,
|
|
2880
2989
|
NAMER,
|
|
2881
2990
|
RELATIVE_PATH_ALLOWED_CHARS_MESSAGE,
|
|
2882
2991
|
RELATIVE_PATH_REGEX,
|
|
@@ -2885,6 +2994,7 @@ export {
|
|
|
2885
2994
|
TinaSchemaValidationError,
|
|
2886
2995
|
addNamespaceToSchema,
|
|
2887
2996
|
canonicalPath,
|
|
2997
|
+
extensionOf,
|
|
2888
2998
|
isHeadingLevel,
|
|
2889
2999
|
isValidRelativePath,
|
|
2890
3000
|
normalizeHeadingLevels,
|
|
@@ -2892,6 +3002,7 @@ export {
|
|
|
2892
3002
|
parseURL,
|
|
2893
3003
|
resolveField,
|
|
2894
3004
|
resolveForm,
|
|
3005
|
+
resolveMediaAccept,
|
|
2895
3006
|
validateSchema,
|
|
2896
3007
|
validateTinaCloudSchemaConfig
|
|
2897
3008
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -206,6 +206,62 @@ type DateFormatProps = {
|
|
|
206
206
|
export type DateTimeField = (FieldGeneric<string, undefined, DateFormatProps> | FieldGeneric<string, true, DateFormatProps> | FieldGeneric<string, false, DateFormatProps>) & BaseField & {
|
|
207
207
|
type: 'datetime';
|
|
208
208
|
};
|
|
209
|
+
/**
|
|
210
|
+
* File extensions the media manager can filter on. Not a list of what may be
|
|
211
|
+
* uploaded — that is `media.accept` — but the vocabulary a field or filter
|
|
212
|
+
* uses to narrow what is shown.
|
|
213
|
+
*/
|
|
214
|
+
export declare const MEDIA_EXTENSIONS: readonly ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico", "mp4", "webm", "mov", "mp3", "wav", "ogg", "pdf", "json", "csv", "txt"];
|
|
215
|
+
export type MediaExtension = (typeof MEDIA_EXTENSIONS)[number];
|
|
216
|
+
export declare const MEDIA_CATEGORIES: {
|
|
217
|
+
readonly image: readonly ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico"];
|
|
218
|
+
readonly video: readonly ["mp4", "webm", "mov"];
|
|
219
|
+
readonly audio: readonly ["mp3", "wav", "ogg"];
|
|
220
|
+
readonly document: readonly ["pdf", "json", "csv", "txt"];
|
|
221
|
+
};
|
|
222
|
+
export type MediaCategory = keyof typeof MEDIA_CATEGORIES;
|
|
223
|
+
export type MediaAccept = MediaExtension | MediaCategory;
|
|
224
|
+
export declare const MEDIA_CATEGORY_NAMES: readonly ["image", "video", "audio", "document"];
|
|
225
|
+
export declare const MEDIA_ACCEPT_VALUES: readonly ["image", "video", "audio", "document", "jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "ico", "mp4", "webm", "mov", "mp3", "wav", "ogg", "pdf", "json", "csv", "txt"];
|
|
226
|
+
/**
|
|
227
|
+
* The MIME type each extension belongs to. Needed because react-dropzone
|
|
228
|
+
* keys its `accept` by MIME type and drops bare extensions from the native
|
|
229
|
+
* file picker.
|
|
230
|
+
*/
|
|
231
|
+
export declare const MEDIA_MIME_TYPES: {
|
|
232
|
+
readonly jpg: "image/jpeg";
|
|
233
|
+
readonly jpeg: "image/jpeg";
|
|
234
|
+
readonly png: "image/png";
|
|
235
|
+
readonly gif: "image/gif";
|
|
236
|
+
readonly webp: "image/webp";
|
|
237
|
+
readonly svg: "image/svg+xml";
|
|
238
|
+
readonly avif: "image/avif";
|
|
239
|
+
readonly ico: "image/x-icon";
|
|
240
|
+
readonly mp4: "video/mp4";
|
|
241
|
+
readonly webm: "video/webm";
|
|
242
|
+
readonly mov: "video/quicktime";
|
|
243
|
+
readonly mp3: "audio/mpeg";
|
|
244
|
+
readonly wav: "audio/wav";
|
|
245
|
+
readonly ogg: "audio/ogg";
|
|
246
|
+
readonly pdf: "application/pdf";
|
|
247
|
+
readonly json: "application/json";
|
|
248
|
+
readonly csv: "text/csv";
|
|
249
|
+
readonly txt: "text/plain";
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Lowercased extension without the dot, from a filename, path or URL.
|
|
253
|
+
* Query strings and hashes are dropped first, so a transformed asset URL
|
|
254
|
+
* like `hero.png?fit=crop` still reads as `png`. Dotfiles and extensionless
|
|
255
|
+
* names return ''.
|
|
256
|
+
*/
|
|
257
|
+
export declare const extensionOf: (value: string) => string;
|
|
258
|
+
/**
|
|
259
|
+
* Flattens a field's `accept` into concrete extensions, expanding any
|
|
260
|
+
* category shorthand and pulling in aliases. Unknown values are dropped
|
|
261
|
+
* rather than passed through, so a typo narrows to nothing visible instead
|
|
262
|
+
* of silently disabling the filter.
|
|
263
|
+
*/
|
|
264
|
+
export declare const resolveMediaAccept: (accept: MediaAccept | MediaAccept[] | undefined) => MediaExtension[];
|
|
209
265
|
export type ImageField = (FieldGeneric<string, undefined> | FieldGeneric<string, true> | FieldGeneric<string, false>) & BaseField & {
|
|
210
266
|
type: 'image';
|
|
211
267
|
/**
|
|
@@ -218,6 +274,19 @@ export type ImageField = (FieldGeneric<string, undefined> | FieldGeneric<string,
|
|
|
218
274
|
* ```
|
|
219
275
|
*/
|
|
220
276
|
uploadDir?: (formValues: Record<string, any>) => string;
|
|
277
|
+
/**
|
|
278
|
+
* Restricts this field to specific file types. Narrows what the media
|
|
279
|
+
* manager offers when browsing and what the field will accept on upload,
|
|
280
|
+
* overriding the global `media.accept`.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* accept: 'pdf'
|
|
285
|
+
* accept: ['png', 'svg']
|
|
286
|
+
* accept: 'image'
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
accept?: MediaAccept | MediaAccept[];
|
|
221
290
|
};
|
|
222
291
|
type ReferenceFieldOptions = {
|
|
223
292
|
optionComponent?: OptionComponent;
|
|
@@ -429,7 +498,19 @@ export interface AuthProvider {
|
|
|
429
498
|
**/
|
|
430
499
|
authenticate: (props?: Record<string, any>) => Promise<any | null>;
|
|
431
500
|
fetchWithToken: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
501
|
+
/**
|
|
502
|
+
* Turns a tokened 401 into the CMS sending the user back to login. The
|
|
503
|
+
* Tina client assigns this automatically; providers extending
|
|
504
|
+
* AbstractAuthProvider get the invocation for free, while a provider that
|
|
505
|
+
* implements its own fetchWithToken must invoke it itself.
|
|
506
|
+
*/
|
|
507
|
+
sessionExpiredListener?: () => void;
|
|
432
508
|
isAuthorized: (context?: any) => Promise<boolean>;
|
|
509
|
+
/**
|
|
510
|
+
* May reject when the identity backend is unreachable (a transport
|
|
511
|
+
* failure is not an auth answer); callers should catch and treat that as
|
|
512
|
+
* an error rather than as logged-out.
|
|
513
|
+
*/
|
|
433
514
|
isAuthenticated: () => Promise<boolean>;
|
|
434
515
|
getLoginStrategy: () => LoginStrategy;
|
|
435
516
|
getLoginScreen: () => FC<LoginScreenProps> | null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinacms/schema-tools",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.10.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"ts-jest": "^29.2.5",
|
|
25
25
|
"typescript": "^5.7.3",
|
|
26
26
|
"yup": "^1.6.1",
|
|
27
|
-
"@tinacms/scripts": "1.6.
|
|
27
|
+
"@tinacms/scripts": "1.6.4"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": ">=16.14.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"repository": {
|
|
37
37
|
"url": "https://github.com/tinacms/tinacms.git",
|
|
38
|
-
"directory": "packages/@tinacms/
|
|
38
|
+
"directory": "packages/@tinacms/schema-tools"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"picomatch-browser": "2.2.6",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"build": "tinacms-scripts build",
|
|
47
47
|
"test": "jest --config jest.config.js",
|
|
48
48
|
"types": "pnpm tsc",
|
|
49
|
-
"test-watch": "jest --passWithNoTests --watch"
|
|
50
|
-
"generate:schema": "pnpm node scripts/generateSchema.js"
|
|
49
|
+
"test-watch": "jest --passWithNoTests --watch"
|
|
51
50
|
}
|
|
52
51
|
}
|