nextjs-cms 0.9.42 → 0.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/api/actions/pages.d.ts +3 -3
- package/dist/api/trpc/root.d.ts +3 -3
- package/dist/api/trpc/routers/navigation.d.ts +3 -3
- package/dist/api/trpc/server.d.ts +9 -9
- package/dist/core/config/config-loader.d.ts +2 -2
- package/dist/core/fields/date-range.d.ts +4 -4
- package/dist/core/fields/inline-image.d.ts +45 -0
- package/dist/core/fields/inline-image.d.ts.map +1 -0
- package/dist/core/fields/inline-image.js +30 -0
- package/dist/core/fields/photo.d.ts.map +1 -1
- package/dist/core/fields/photo.js +1 -9
- package/dist/core/fields/richText.d.ts +162 -42
- package/dist/core/fields/richText.d.ts.map +1 -1
- package/dist/core/fields/richText.js +104 -35
- package/dist/core/fields/select.d.ts +1 -1
- package/dist/core/helpers/background.d.ts +18 -0
- package/dist/core/helpers/background.d.ts.map +1 -0
- package/dist/core/helpers/background.js +18 -0
- package/dist/core/helpers/index.d.ts +5 -1
- package/dist/core/helpers/index.d.ts.map +1 -1
- package/dist/core/helpers/index.js +3 -1
- package/dist/core/helpers/inline-image.d.ts +46 -0
- package/dist/core/helpers/inline-image.d.ts.map +1 -0
- package/dist/core/helpers/inline-image.js +30 -0
- package/dist/core/sections/category.d.ts +4 -4
- package/dist/core/sections/hasItems.d.ts +4 -4
- package/dist/core/sections/section.d.ts +3 -3
- package/dist/core/sections/simple.d.ts +4 -4
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/utils.d.ts +13 -0
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +15 -0
- package/dist/validators/richText.d.ts.map +1 -1
- package/dist/validators/richText.js +35 -1
- package/package.json +3 -3
|
@@ -1,39 +1,93 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { and, eq, sql } from 'drizzle-orm';
|
|
4
4
|
import { customAlphabet } from 'nanoid';
|
|
5
5
|
import sharp from 'sharp';
|
|
6
|
-
import
|
|
6
|
+
import * as z from 'zod';
|
|
7
7
|
import { db } from '../../db/client.js';
|
|
8
8
|
import { EditorPhotosTable } from '../../db/schema.js';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
9
|
+
import getString from '../../translations/index.js';
|
|
10
|
+
import { base64ByteLength, humanReadableFileSize, maxFileSizeToBytes } from '../../utils/index.js';
|
|
11
11
|
import { getCMSConfig } from '../config/index.js';
|
|
12
|
+
import { backgroundSchema, entityKind } from '../helpers/index.js';
|
|
13
|
+
import { DEFAULT_INLINE_IMAGE_SIZE, DEFAULT_MAX_FILE_SIZE, inlineImageResizeOptions } from '../helpers/inline-image.js';
|
|
12
14
|
import { sanitizeRichText } from '../security/dom.js';
|
|
15
|
+
import { baseFieldConfigSchema, Field } from './field.js';
|
|
13
16
|
const positiveInt = z.number().int().positive();
|
|
14
17
|
const nonNegativeInt = z.number().int().nonnegative();
|
|
18
|
+
/**
|
|
19
|
+
* Bounds each inline image is fitted into, discriminated on `fit` so `background` only exists
|
|
20
|
+
* where it means something. Mirrors the `fit` vocabulary of the photo field, using sharp's own
|
|
21
|
+
* names so the behaviour is not renamed in translation.
|
|
22
|
+
*/
|
|
23
|
+
const inlineImageSizeSchema = z.discriminatedUnion('fit', [
|
|
24
|
+
z.strictObject({
|
|
25
|
+
width: positiveInt,
|
|
26
|
+
height: positiveInt,
|
|
27
|
+
fit: z.literal('inside').describe('Scale down to fit within the box, keeping aspect ratio (sharp fit: inside)'),
|
|
28
|
+
}),
|
|
29
|
+
z.strictObject({
|
|
30
|
+
width: positiveInt,
|
|
31
|
+
height: positiveInt,
|
|
32
|
+
fit: z.literal('cover').describe('Crop to fill dimensions (sharp fit: cover)'),
|
|
33
|
+
}),
|
|
34
|
+
z.strictObject({
|
|
35
|
+
width: positiveInt,
|
|
36
|
+
height: positiveInt,
|
|
37
|
+
fit: z.literal('contain').describe('Pad with background to fill dimensions (sharp fit: contain)'),
|
|
38
|
+
/**
|
|
39
|
+
* Falls back to `media.images.background` from the CMS config, which is transparent by
|
|
40
|
+
* default. Transparency needs an alpha-capable `format` (webp or png) — jpeg flattens it
|
|
41
|
+
* to black, so set an opaque colour when writing jpeg.
|
|
42
|
+
*/
|
|
43
|
+
background: backgroundSchema.optional().describe('Background color for padding'),
|
|
44
|
+
}),
|
|
45
|
+
]);
|
|
46
|
+
/**
|
|
47
|
+
* Matches the `{name}` placeholder (whitespace tolerated: `{ name }`).
|
|
48
|
+
*
|
|
49
|
+
* Built on demand rather than shared as a single global regex: a `/g` regex is stateful via
|
|
50
|
+
* `lastIndex`, so reusing one instance across `test()` calls silently alternates its result.
|
|
51
|
+
*/
|
|
52
|
+
const NAME_PLACEHOLDER = String.raw `\{\s*name\s*\}`;
|
|
53
|
+
const hasNamePlaceholder = (value) => new RegExp(NAME_PLACEHOLDER).test(value);
|
|
54
|
+
const resolvePublicURL = (template, name) => template.replace(new RegExp(NAME_PLACEHOLDER, 'g'), name);
|
|
15
55
|
const allowImageUploadsSchema = z.strictObject({
|
|
16
56
|
/**
|
|
17
|
-
* The public URL
|
|
57
|
+
* The public URL template for inline photos.
|
|
18
58
|
* This url should be handled by the website app, not the NextJS-CMS app
|
|
19
59
|
* This is the URL that will be used to access the photos publicly on the website
|
|
20
|
-
*
|
|
60
|
+
*
|
|
61
|
+
* `{name}` marks where the generated filename goes, so it can sit anywhere in the URL —
|
|
62
|
+
* a path segment (`https://site.com/photos/{name}`) or a query parameter
|
|
63
|
+
* (`https://site.com/api/photo?folder=posts&name={name}`).
|
|
21
64
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
height: positiveInt,
|
|
27
|
-
crop: z.boolean().default(false),
|
|
65
|
+
publicURL: z
|
|
66
|
+
.url()
|
|
67
|
+
.refine(hasNamePlaceholder, {
|
|
68
|
+
message: 'publicURL must contain the {name} placeholder, e.g. https://site.com/photos/{name}',
|
|
28
69
|
})
|
|
29
|
-
.
|
|
70
|
+
.describe('The public URL template for inline photos; {name} is replaced with the generated filename'),
|
|
71
|
+
/**
|
|
72
|
+
* Bounds applied to each inline image before it is written to disk.
|
|
73
|
+
* Defaults to {@link DEFAULT_INLINE_IMAGE_SIZE}.
|
|
74
|
+
*
|
|
75
|
+
* `fit: 'inside'` scales the image down to fit within the box, keeping its aspect ratio and
|
|
76
|
+
* never enlarging a smaller image. `fit: 'cover'` fills the box exactly, cropping the overflow.
|
|
77
|
+
* `fit: 'contain'` pads the image out to the full box with `background`.
|
|
78
|
+
*/
|
|
79
|
+
size: inlineImageSizeSchema.optional(),
|
|
80
|
+
/**
|
|
81
|
+
* Largest accepted inline image, measured on the decoded image rather than the base64 text.
|
|
82
|
+
* Rejected during submission, before the item is saved. Defaults to {@link DEFAULT_MAX_FILE_SIZE}.
|
|
83
|
+
*/
|
|
30
84
|
maxFileSize: z
|
|
31
85
|
.strictObject({
|
|
32
86
|
size: positiveInt,
|
|
33
87
|
unit: z.enum(['kb', 'mb']),
|
|
34
88
|
})
|
|
35
89
|
.optional(),
|
|
36
|
-
handleMethod: z.enum(['base64', 'tempSave']).optional(),
|
|
90
|
+
// handleMethod: z.enum(['base64', 'tempSave']).optional(),
|
|
37
91
|
/**
|
|
38
92
|
* Omit the extension of the image when saving it to disk
|
|
39
93
|
*/
|
|
@@ -96,13 +150,13 @@ export class RichTextField extends Field {
|
|
|
96
150
|
this.placeholder = config.placeholder;
|
|
97
151
|
this.sanitize = config.sanitize ?? true;
|
|
98
152
|
this.rtl = config.rtl;
|
|
99
|
-
if (config.allowMedia && config.allowImageUploads?.
|
|
153
|
+
if (config.allowMedia && config.allowImageUploads?.publicURL) {
|
|
100
154
|
this.allowMedia = true;
|
|
101
155
|
this.allowImageUploads = {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
156
|
+
// handleMethod: config.allowImageUploads.handleMethod ?? 'base64',
|
|
157
|
+
publicURL: config.allowImageUploads.publicURL,
|
|
158
|
+
size: config.allowImageUploads.size ?? DEFAULT_INLINE_IMAGE_SIZE,
|
|
159
|
+
maxFileSize: config.allowImageUploads.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
|
|
106
160
|
omitExtension: config.allowImageUploads.omitExtension ?? true,
|
|
107
161
|
format: config.allowImageUploads.format ?? 'webp',
|
|
108
162
|
};
|
|
@@ -164,9 +218,24 @@ export class RichTextField extends Field {
|
|
|
164
218
|
return;
|
|
165
219
|
const regex = /<img.*?src="(data:image\/.*?;base64,.*?)".*?>/g;
|
|
166
220
|
const matches = this.value.matchAll(regex);
|
|
221
|
+
const maxFileSize = this.allowImageUploads.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
222
|
+
const maxBytes = maxFileSizeToBytes(maxFileSize);
|
|
167
223
|
for (const match of matches) {
|
|
168
224
|
const base64String = match[1];
|
|
169
225
|
const extension = base64String.split(';')[0].split('/')[1];
|
|
226
|
+
/**
|
|
227
|
+
* Reject oversized images here rather than in `writeInlineImages`: extraction runs
|
|
228
|
+
* during submission, so throwing prevents the item from being saved at all, whereas
|
|
229
|
+
* the write step runs afterwards and the row would already exist.
|
|
230
|
+
*/
|
|
231
|
+
const byteLength = base64ByteLength(base64String.split(',')[1] ?? '');
|
|
232
|
+
if (byteLength > maxBytes) {
|
|
233
|
+
throw new Error(getString('fileSizeExceedsMax', this.language, {
|
|
234
|
+
field: this.getLocalizedLabel(),
|
|
235
|
+
actual: humanReadableFileSize(byteLength),
|
|
236
|
+
max: `${maxFileSize.size} ${maxFileSize.unit}`,
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
170
239
|
const randomString = customAlphabet('1234567890abcdef', 21)();
|
|
171
240
|
const name = `${randomString}${this.allowImageUploads.omitExtension ? '' : `.${extension}`}`;
|
|
172
241
|
this._inlinePhotos.push({
|
|
@@ -175,9 +244,9 @@ export class RichTextField extends Field {
|
|
|
175
244
|
name: name,
|
|
176
245
|
});
|
|
177
246
|
/**
|
|
178
|
-
* Replace the inline image src with the
|
|
247
|
+
* Replace the inline image src with the public URL of the image
|
|
179
248
|
*/
|
|
180
|
-
this.value = this.value.replace(base64String,
|
|
249
|
+
this.value = this.value.replace(base64String, resolvePublicURL(this.allowImageUploads.publicURL, name));
|
|
181
250
|
}
|
|
182
251
|
}
|
|
183
252
|
async writeInlineImages(sectionName, itemId, locale) {
|
|
@@ -203,7 +272,9 @@ export class RichTextField extends Field {
|
|
|
203
272
|
sharp.cache({ files: 0 });
|
|
204
273
|
sharp.cache(false);
|
|
205
274
|
const image = sharp(buffer);
|
|
275
|
+
const size = this.allowImageUploads.size ?? DEFAULT_INLINE_IMAGE_SIZE;
|
|
206
276
|
await image
|
|
277
|
+
.resize(inlineImageResizeOptions(size, cmsConfig.media.images.background))
|
|
207
278
|
.toFormat(this.allowImageUploads.format ?? 'webp')
|
|
208
279
|
.toFile(path.join(uploadsFolder, '.photos', sectionName, photo.name));
|
|
209
280
|
/**
|
|
@@ -237,7 +308,7 @@ export class RichTextField extends Field {
|
|
|
237
308
|
/**
|
|
238
309
|
* Sanitize the value
|
|
239
310
|
*/
|
|
240
|
-
this.sanitizeValue();
|
|
311
|
+
await this.sanitizeValue();
|
|
241
312
|
if (!this.value)
|
|
242
313
|
return;
|
|
243
314
|
/**
|
|
@@ -291,19 +362,17 @@ export class RichTextField extends Field {
|
|
|
291
362
|
if (tablePhotos.length === 0)
|
|
292
363
|
return;
|
|
293
364
|
/**
|
|
294
|
-
*
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Check if the photos in the database are still present in the value
|
|
365
|
+
* Check if the photos in the database are still present in the value.
|
|
366
|
+
*
|
|
367
|
+
* Matched on the generated filename rather than the full public URL: the name is a unique
|
|
368
|
+
* random token, while the URL is presentation. It can be re-encoded by the sanitizer
|
|
369
|
+
* (`&` -> `&`), or change wholesale when `publicURL` or the site's domain changes —
|
|
370
|
+
* none of which mean the admin removed the image. A stale match here deletes a file that
|
|
371
|
+
* is still in use, so the check errs towards keeping (an orphan file is recoverable, a
|
|
372
|
+
* deleted one is not).
|
|
304
373
|
*/
|
|
305
374
|
for (const tablePhoto of tablePhotos) {
|
|
306
|
-
if (!
|
|
375
|
+
if (!this.value.includes(tablePhoto.name)) {
|
|
307
376
|
/**
|
|
308
377
|
* Delete the photo from the database
|
|
309
378
|
*/
|
|
@@ -379,8 +379,8 @@ declare const selectFieldConfigSchema: z.ZodIntersection<z.ZodUnion<readonly [z.
|
|
|
379
379
|
}, z.core.$strict>]>, z.ZodObject<{
|
|
380
380
|
type: z.ZodLiteral<"select">;
|
|
381
381
|
optionsType: z.ZodEnum<{
|
|
382
|
-
db: "db";
|
|
383
382
|
section: "section";
|
|
383
|
+
db: "db";
|
|
384
384
|
static: "static";
|
|
385
385
|
}>;
|
|
386
386
|
build: z.ZodFunction<z.core.$ZodFunctionArgs, z.ZodCustom<SelectField, SelectField>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Padding colour schema — shared by every field that resizes images, so the shape stays
|
|
4
|
+
* identical across them. Only meaningful alongside `fit: 'contain'`, where the image is padded
|
|
5
|
+
* out to the full box; fields fall back to `media.images.background` from the CMS config when
|
|
6
|
+
* it is omitted.
|
|
7
|
+
*
|
|
8
|
+
* `alpha: 0` only survives in formats with an alpha channel (webp, png). Encoding transparent
|
|
9
|
+
* padding to jpeg flattens it to black, so pick an opaque colour for jpeg output.
|
|
10
|
+
*/
|
|
11
|
+
export declare const backgroundSchema: z.ZodObject<{
|
|
12
|
+
r: z.ZodNumber;
|
|
13
|
+
g: z.ZodNumber;
|
|
14
|
+
b: z.ZodNumber;
|
|
15
|
+
alpha: z.ZodNumber;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
export type Background = z.infer<typeof backgroundSchema>;
|
|
18
|
+
//# sourceMappingURL=background.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../../src/core/helpers/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;;;;;kBAO+C,CAAA;AAE5E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Padding colour schema — shared by every field that resizes images, so the shape stays
|
|
4
|
+
* identical across them. Only meaningful alongside `fit: 'contain'`, where the image is padded
|
|
5
|
+
* out to the full box; fields fall back to `media.images.background` from the CMS config when
|
|
6
|
+
* it is omitted.
|
|
7
|
+
*
|
|
8
|
+
* `alpha: 0` only survives in formats with an alpha channel (webp, png). Encoding transparent
|
|
9
|
+
* padding to jpeg flattens it to black, so pick an opaque colour for jpeg output.
|
|
10
|
+
*/
|
|
11
|
+
export const backgroundSchema = z
|
|
12
|
+
.strictObject({
|
|
13
|
+
r: z.number().min(0).max(255).describe('Red channel (0-255)'),
|
|
14
|
+
g: z.number().min(0).max(255).describe('Green channel (0-255)'),
|
|
15
|
+
b: z.number().min(0).max(255).describe('Blue channel (0-255)'),
|
|
16
|
+
alpha: z.number().min(0).max(1).describe('Alpha channel (0-1)'),
|
|
17
|
+
})
|
|
18
|
+
.describe('Background color for image padding (used with fit: contain)');
|
|
@@ -2,7 +2,11 @@ export { is } from './entity.js';
|
|
|
2
2
|
export { entityKind } from './entity.js';
|
|
3
3
|
export type { LZEntity } from './entity.js';
|
|
4
4
|
export { hasOwnEntityKind } from './entity.js';
|
|
5
|
-
export { evaluateConditionalRule, evaluateConditionalRuleGroup, normalizeConditionalRules } from './conditional-rules.js';
|
|
5
|
+
export { evaluateConditionalRule, evaluateConditionalRuleGroup, normalizeConditionalRules, } from './conditional-rules.js';
|
|
6
|
+
export { backgroundSchema } from './background.js';
|
|
7
|
+
export { inlineImageResizeOptions, DEFAULT_INLINE_IMAGE_SIZE, DEFAULT_MAX_FILE_SIZE, INLINE_IMAGE_REGEX, } from './inline-image.js';
|
|
8
|
+
export type { InlineImageSize, InlineImageMaxFileSize } from './inline-image.js';
|
|
9
|
+
export type { Background } from './background.js';
|
|
6
10
|
export { watermarkPositionEnum, watermarkObjectSchema, watermarkConfigSchema, WATERMARK_DEFAULTS, resolveWatermark, } from './watermark.js';
|
|
7
11
|
export type { WatermarkPosition, WatermarkObject, WatermarkConfig, ResolvedWatermark } from './watermark.js';
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAI9C,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAI9C,OAAO,EACH,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,GAC5B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EACH,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAChF,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EACH,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,GACnB,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -4,5 +4,7 @@ export { hasOwnEntityKind } from './entity.js';
|
|
|
4
4
|
// Note: the `and` / `or` authoring wrappers are re-exported from `core/fields` (alongside the field
|
|
5
5
|
// factories that section configs use), so they are intentionally not re-exported here to avoid a
|
|
6
6
|
// duplicate export when `core/index` merges both barrels.
|
|
7
|
-
export { evaluateConditionalRule, evaluateConditionalRuleGroup, normalizeConditionalRules } from './conditional-rules.js';
|
|
7
|
+
export { evaluateConditionalRule, evaluateConditionalRuleGroup, normalizeConditionalRules, } from './conditional-rules.js';
|
|
8
|
+
export { backgroundSchema } from './background.js';
|
|
9
|
+
export { inlineImageResizeOptions, DEFAULT_INLINE_IMAGE_SIZE, DEFAULT_MAX_FILE_SIZE, INLINE_IMAGE_REGEX, } from './inline-image.js';
|
|
8
10
|
export { watermarkPositionEnum, watermarkObjectSchema, watermarkConfigSchema, WATERMARK_DEFAULTS, resolveWatermark, } from './watermark.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type sharp from 'sharp';
|
|
2
|
+
import type { Background } from './background.js';
|
|
3
|
+
/**
|
|
4
|
+
* Inline-image sizing shared by the rich-text field, the editor upload route, and the client-side
|
|
5
|
+
* validator.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately a leaf module with type-only imports. The validators are environment-neutral —
|
|
8
|
+
* the admin form calls them in the browser, and a consumer may call them server-side — so reaching
|
|
9
|
+
* these through a barrel that pulls the database client, sharp, or fs would break the client build.
|
|
10
|
+
* Import this file directly rather than via `core/helpers/index.js` for the same reason.
|
|
11
|
+
*/
|
|
12
|
+
export type InlineImageSize = {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
fit: 'inside';
|
|
16
|
+
} | {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
fit: 'cover';
|
|
20
|
+
} | {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
fit: 'contain';
|
|
24
|
+
background?: Background;
|
|
25
|
+
};
|
|
26
|
+
export type InlineImageMaxFileSize = {
|
|
27
|
+
size: number;
|
|
28
|
+
unit: 'kb' | 'mb';
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Bounds every inline image is fitted into unless the field overrides `size`.
|
|
32
|
+
* 1200x675 is 16:9 at a width that covers typical article body content, scaled down to fit so a
|
|
33
|
+
* body image is never cropped or padded unless the field asks for it.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_INLINE_IMAGE_SIZE: InlineImageSize;
|
|
36
|
+
/** Largest accepted inline image unless the field overrides `maxFileSize`. */
|
|
37
|
+
export declare const DEFAULT_MAX_FILE_SIZE: InlineImageMaxFileSize;
|
|
38
|
+
/** Same shape `extractInlineImages` looks for, so every side agrees on what an inline image is. */
|
|
39
|
+
export declare const INLINE_IMAGE_REGEX: RegExp;
|
|
40
|
+
/**
|
|
41
|
+
* Sharp resize options for an inline image, so every place that writes one — the field on save and
|
|
42
|
+
* the editor upload route — sizes it identically. `background` is the CMS-config fallback, used
|
|
43
|
+
* only when the field asks for `contain` without naming its own.
|
|
44
|
+
*/
|
|
45
|
+
export declare function inlineImageResizeOptions(size: InlineImageSize, background: Background): sharp.ResizeOptions;
|
|
46
|
+
//# sourceMappingURL=inline-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-image.d.ts","sourceRoot":"","sources":["../../../src/core/helpers/inline-image.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD;;;;;;;;GAQG;AAEH,MAAM,MAAM,eAAe,GACrB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,QAAQ,CAAA;CAAE,GAChD;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,GAC/C;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,UAAU,CAAA;CAAE,CAAA;AAEhF,MAAM,MAAM,sBAAsB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAAE,CAAA;AAExE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,eAIvC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,EAAE,sBAAgD,CAAA;AAEpF,mGAAmG;AACnG,eAAO,MAAM,kBAAkB,QAAmD,CAAA;AAElF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,GAAG,KAAK,CAAC,aAAa,CAU3G"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds every inline image is fitted into unless the field overrides `size`.
|
|
3
|
+
* 1200x675 is 16:9 at a width that covers typical article body content, scaled down to fit so a
|
|
4
|
+
* body image is never cropped or padded unless the field asks for it.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_INLINE_IMAGE_SIZE = {
|
|
7
|
+
width: 1200,
|
|
8
|
+
height: 675,
|
|
9
|
+
fit: 'inside',
|
|
10
|
+
};
|
|
11
|
+
/** Largest accepted inline image unless the field overrides `maxFileSize`. */
|
|
12
|
+
export const DEFAULT_MAX_FILE_SIZE = { size: 1, unit: 'mb' };
|
|
13
|
+
/** Same shape `extractInlineImages` looks for, so every side agrees on what an inline image is. */
|
|
14
|
+
export const INLINE_IMAGE_REGEX = /<img.*?src="(data:image\/.*?;base64,.*?)".*?>/g;
|
|
15
|
+
/**
|
|
16
|
+
* Sharp resize options for an inline image, so every place that writes one — the field on save and
|
|
17
|
+
* the editor upload route — sizes it identically. `background` is the CMS-config fallback, used
|
|
18
|
+
* only when the field asks for `contain` without naming its own.
|
|
19
|
+
*/
|
|
20
|
+
export function inlineImageResizeOptions(size, background) {
|
|
21
|
+
return {
|
|
22
|
+
width: size.width,
|
|
23
|
+
height: size.height,
|
|
24
|
+
fit: size.fit,
|
|
25
|
+
// `inside` only ever scales down — enlarging a smaller image to fill the box would cost
|
|
26
|
+
// quality for nothing. `cover` and `contain` produce the exact box, so they may enlarge.
|
|
27
|
+
withoutEnlargement: size.fit === 'inside',
|
|
28
|
+
...(size.fit === 'contain' ? { background: size.background ?? background } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|