nextjs-cms 0.9.41 → 0.10.0
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 +4 -3
- package/dist/api/actions/pages.d.ts.map +1 -1
- package/dist/api/actions/pages.js +11 -4
- package/dist/api/trpc/root.d.ts +4 -3
- package/dist/api/trpc/root.d.ts.map +1 -1
- package/dist/api/trpc/routers/config.d.ts.map +1 -1
- package/dist/api/trpc/routers/hasItemsSection.d.ts +1 -0
- package/dist/api/trpc/routers/hasItemsSection.d.ts.map +1 -1
- package/dist/api/trpc/routers/navigation.d.ts +3 -3
- package/dist/api/trpc/server.d.ts +12 -9
- package/dist/api/trpc/server.d.ts.map +1 -1
- package/dist/cli/lib/update-sections.d.ts.map +1 -1
- package/dist/cli/lib/update-sections.js +19 -0
- package/dist/core/config/config-loader.d.ts +25 -6
- package/dist/core/config/config-loader.d.ts.map +1 -1
- package/dist/core/config/config-loader.js +9 -13
- package/dist/core/fields/photo.d.ts +80 -8
- package/dist/core/fields/photo.d.ts.map +1 -1
- package/dist/core/fields/photo.js +120 -31
- 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 +135 -35
- package/dist/core/fields/select.d.ts +1 -1
- package/dist/core/fields/select.d.ts.map +1 -1
- package/dist/core/fields/select.js +18 -2
- package/dist/core/fields/selectMultiple.d.ts.map +1 -1
- package/dist/core/fields/selectMultiple.js +9 -0
- 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/watermark.d.ts +96 -0
- package/dist/core/helpers/watermark.d.ts.map +1 -0
- package/dist/core/helpers/watermark.js +61 -0
- package/dist/core/sections/category.d.ts +78 -42
- package/dist/core/sections/category.d.ts.map +1 -1
- package/dist/core/sections/hasItems.d.ts +132 -48
- package/dist/core/sections/hasItems.d.ts.map +1 -1
- package/dist/core/sections/section.d.ts +62 -25
- package/dist/core/sections/section.d.ts.map +1 -1
- package/dist/core/sections/section.js +2 -2
- package/dist/core/sections/simple.d.ts +44 -8
- package/dist/core/sections/simple.d.ts.map +1 -1
- package/dist/core/submit/gallery-order.d.ts +34 -0
- package/dist/core/submit/gallery-order.d.ts.map +1 -0
- package/dist/core/submit/gallery-order.js +50 -0
- package/dist/core/submit/submit.d.ts +8 -0
- package/dist/core/submit/submit.d.ts.map +1 -1
- package/dist/core/submit/submit.js +63 -3
- package/dist/core/types/index.d.ts +1 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/translations/client.d.ts +4 -4
- package/dist/translations/server.d.ts +4 -4
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/core/config/config-loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/core/config/config-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { setConfigDicts } from '../../translations/dict-store.js';
|
|
4
|
+
import { watermarkConfigSchema } from '../helpers/index.js';
|
|
5
|
+
import { getConfigImportVersion, loadConfigModule } from './loader.js';
|
|
1
6
|
if (typeof window !== 'undefined') {
|
|
2
7
|
throw new Error('This module can only run on the server');
|
|
3
8
|
}
|
|
4
|
-
// import 'server-only'
|
|
5
|
-
// import chalk from 'chalk'
|
|
6
|
-
import * as z from 'zod';
|
|
7
|
-
import { loadConfigModule, getConfigImportVersion } from './loader.js';
|
|
8
|
-
import { setConfigDicts } from '../../translations/dict-store.js';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
9
|
/** Locale codes for which we ship dictionaries in core. */
|
|
11
10
|
export const AVAILABLE_I18N_LANGUAGES = ['en'];
|
|
12
11
|
// 1. Define config schema
|
|
@@ -135,9 +134,10 @@ const cmsConfigSchema = z.object({
|
|
|
135
134
|
})
|
|
136
135
|
.optional(),
|
|
137
136
|
/**
|
|
138
|
-
* Global images watermark configuration
|
|
137
|
+
* Global images watermark configuration.
|
|
138
|
+
* `false` = off; object = watermark asset + defaults. See core/helpers/watermark.
|
|
139
139
|
*/
|
|
140
|
-
watermark:
|
|
140
|
+
watermark: watermarkConfigSchema.optional(),
|
|
141
141
|
})
|
|
142
142
|
.optional(),
|
|
143
143
|
})
|
|
@@ -315,11 +315,7 @@ function isPluginConfigWithOverrides(input) {
|
|
|
315
315
|
return typeof input === 'object' && input !== null && 'plugin' in input;
|
|
316
316
|
}
|
|
317
317
|
function isPluginDescriptor(input) {
|
|
318
|
-
return
|
|
319
|
-
input !== null &&
|
|
320
|
-
'package' in input &&
|
|
321
|
-
'title' in input &&
|
|
322
|
-
!('plugin' in input));
|
|
318
|
+
return typeof input === 'object' && input !== null && 'package' in input && 'title' in input && !('plugin' in input);
|
|
323
319
|
}
|
|
324
320
|
function normalizePluginConfigInputs(plugins) {
|
|
325
321
|
return plugins?.map((entry) => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import type { ResolvedWatermark, WatermarkConfig, WatermarkPosition } from '../helpers/index.js';
|
|
1
4
|
import type { BaseFieldConfig } from './field.js';
|
|
2
5
|
import { entityKind } from '../helpers/index.js';
|
|
3
|
-
import * as z from 'zod';
|
|
4
6
|
import { FileField } from './fileField.js';
|
|
5
7
|
declare const sizeSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
6
8
|
width: z.ZodNumber;
|
|
@@ -29,9 +31,43 @@ declare const sizeSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
29
31
|
}, z.core.$strict>>;
|
|
30
32
|
}, z.core.$strict>], "fit">]>;
|
|
31
33
|
type SizeConfig = z.infer<typeof sizeSchema>;
|
|
34
|
+
/**
|
|
35
|
+
* Watermark position → sharp gravity. Kept here (server-only) with the rest of the sharp logic;
|
|
36
|
+
* the pure config schema lives in core/helpers/watermark.
|
|
37
|
+
*/
|
|
38
|
+
export declare const positionToGravity: Record<WatermarkPosition, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Load and validate a watermark image once, caching by path. Fail-fast: throws a clear
|
|
41
|
+
* error if the file cannot be read or decoded.
|
|
42
|
+
*/
|
|
43
|
+
export declare function loadWatermarkSource(imagePath: string): Promise<Buffer>;
|
|
44
|
+
/**
|
|
45
|
+
* Build a sharp composite overlay for a resolved watermark against a base image of `outputWidth`.
|
|
46
|
+
* Resizes the mark to `size * outputWidth`, applies opacity, and (for non-tiled marks with a
|
|
47
|
+
* margin) pads a transparent border so `gravity` insets the mark from the edge/corner.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildOverlay(watermark: ResolvedWatermark, outputWidth: number): Promise<sharp.OverlayOptions>;
|
|
32
50
|
declare const configSchema: z.ZodObject<{
|
|
33
|
-
/**
|
|
34
|
-
watermark: z.ZodOptional<z.
|
|
51
|
+
/** Watermark config: false = off, or a watermark object. See core/helpers/watermark. */
|
|
52
|
+
watermark: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
53
|
+
imagePath: z.ZodString;
|
|
54
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
55
|
+
"top-left": "top-left";
|
|
56
|
+
top: "top";
|
|
57
|
+
"top-right": "top-right";
|
|
58
|
+
left: "left";
|
|
59
|
+
center: "center";
|
|
60
|
+
right: "right";
|
|
61
|
+
"bottom-left": "bottom-left";
|
|
62
|
+
bottom: "bottom";
|
|
63
|
+
"bottom-right": "bottom-right";
|
|
64
|
+
}>>;
|
|
65
|
+
size: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
margin: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
tile: z.ZodOptional<z.ZodBoolean>;
|
|
69
|
+
applyToThumbnail: z.ZodOptional<z.ZodBoolean>;
|
|
70
|
+
}, z.core.$strict>]>>;
|
|
35
71
|
blurPlaceholder: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
36
72
|
size: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
37
73
|
width: z.ZodNumber;
|
|
@@ -118,7 +154,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
118
154
|
type Config = z.infer<typeof configSchema>;
|
|
119
155
|
export declare class PhotoField extends FileField<'photo', Config> {
|
|
120
156
|
static readonly [entityKind]: string;
|
|
121
|
-
readonly watermark:
|
|
157
|
+
readonly watermark: WatermarkConfig | undefined;
|
|
122
158
|
readonly blurPlaceholder: boolean | null | undefined;
|
|
123
159
|
readonly size: SizeConfig | undefined;
|
|
124
160
|
readonly maxFileSize: {
|
|
@@ -225,8 +261,26 @@ export declare class PhotoField extends FileField<'photo', Config> {
|
|
|
225
261
|
}
|
|
226
262
|
export type PhotoFieldClientConfig = ReturnType<PhotoField['exportForClient']>;
|
|
227
263
|
declare const optionsSchema: z.ZodObject<{
|
|
228
|
-
/**
|
|
229
|
-
watermark: z.ZodOptional<z.
|
|
264
|
+
/** Watermark config: false = off, or a watermark object. See core/helpers/watermark. */
|
|
265
|
+
watermark: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
266
|
+
imagePath: z.ZodString;
|
|
267
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
268
|
+
"top-left": "top-left";
|
|
269
|
+
top: "top";
|
|
270
|
+
"top-right": "top-right";
|
|
271
|
+
left: "left";
|
|
272
|
+
center: "center";
|
|
273
|
+
right: "right";
|
|
274
|
+
"bottom-left": "bottom-left";
|
|
275
|
+
bottom: "bottom";
|
|
276
|
+
"bottom-right": "bottom-right";
|
|
277
|
+
}>>;
|
|
278
|
+
size: z.ZodOptional<z.ZodNumber>;
|
|
279
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
280
|
+
margin: z.ZodOptional<z.ZodNumber>;
|
|
281
|
+
tile: z.ZodOptional<z.ZodBoolean>;
|
|
282
|
+
applyToThumbnail: z.ZodOptional<z.ZodBoolean>;
|
|
283
|
+
}, z.core.$strict>]>>;
|
|
230
284
|
blurPlaceholder: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
231
285
|
size: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
232
286
|
width: z.ZodNumber;
|
|
@@ -321,8 +375,26 @@ declare const optionsSchema: z.ZodObject<{
|
|
|
321
375
|
declare const photoFieldConfigSchema: z.ZodObject<{
|
|
322
376
|
type: z.ZodLiteral<"photo">;
|
|
323
377
|
build: z.ZodFunction<z.core.$ZodFunctionArgs, z.ZodCustom<PhotoField, PhotoField>>;
|
|
324
|
-
/**
|
|
325
|
-
watermark: z.ZodOptional<z.
|
|
378
|
+
/** Watermark config: false = off, or a watermark object. See core/helpers/watermark. */
|
|
379
|
+
watermark: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
380
|
+
imagePath: z.ZodString;
|
|
381
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
382
|
+
"top-left": "top-left";
|
|
383
|
+
top: "top";
|
|
384
|
+
"top-right": "top-right";
|
|
385
|
+
left: "left";
|
|
386
|
+
center: "center";
|
|
387
|
+
right: "right";
|
|
388
|
+
"bottom-left": "bottom-left";
|
|
389
|
+
bottom: "bottom";
|
|
390
|
+
"bottom-right": "bottom-right";
|
|
391
|
+
}>>;
|
|
392
|
+
size: z.ZodOptional<z.ZodNumber>;
|
|
393
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
394
|
+
margin: z.ZodOptional<z.ZodNumber>;
|
|
395
|
+
tile: z.ZodOptional<z.ZodBoolean>;
|
|
396
|
+
applyToThumbnail: z.ZodOptional<z.ZodBoolean>;
|
|
397
|
+
}, z.core.$strict>]>>;
|
|
326
398
|
blurPlaceholder: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
327
399
|
size: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
328
400
|
width: z.ZodNumber;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"photo.d.ts","sourceRoot":"","sources":["../../../src/core/fields/photo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"photo.d.ts","sourceRoot":"","sources":["../../../src/core/fields/photo.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import { baseFieldConfigSchema } from './field.js';
|
|
2
|
-
import { entityKind } from '../helpers/index.js';
|
|
3
|
-
import * as z from 'zod';
|
|
4
|
-
import sharp from 'sharp';
|
|
5
|
-
import path from 'path';
|
|
6
1
|
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
7
3
|
import { customAlphabet } from 'nanoid';
|
|
8
|
-
import
|
|
4
|
+
import sharp from 'sharp';
|
|
5
|
+
import * as z from 'zod';
|
|
6
|
+
import getString from '../../translations/index.js';
|
|
9
7
|
import { humanReadableFileSize } from '../../utils/index.js';
|
|
10
8
|
import { getCMSConfig } from '../config/index.js';
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
r: z.number().min(0).max(255).describe('Red channel (0-255)'),
|
|
15
|
-
g: z.number().min(0).max(255).describe('Green channel (0-255)'),
|
|
16
|
-
b: z.number().min(0).max(255).describe('Blue channel (0-255)'),
|
|
17
|
-
alpha: z.number().min(0).max(1).describe('Alpha channel (0-1)'),
|
|
18
|
-
})
|
|
19
|
-
.describe('Background color for image padding (used with fit: contain)');
|
|
9
|
+
import { backgroundSchema, entityKind, resolveWatermark, watermarkConfigSchema } from '../helpers/index.js';
|
|
10
|
+
import { baseFieldConfigSchema } from './field.js';
|
|
11
|
+
import { FileField } from './fileField.js';
|
|
20
12
|
/**
|
|
21
13
|
* Size schema — discriminated union of three variants:
|
|
22
14
|
* - strict: true — user must upload exact dimensions, no server resize
|
|
@@ -75,9 +67,85 @@ const maxFileSizeSchema = z.strictObject({
|
|
|
75
67
|
size: z.number().describe('Maximum file size'),
|
|
76
68
|
unit: z.enum(['kb', 'mb']).describe('Size unit'),
|
|
77
69
|
});
|
|
70
|
+
/**
|
|
71
|
+
* Watermark position → sharp gravity. Kept here (server-only) with the rest of the sharp logic;
|
|
72
|
+
* the pure config schema lives in core/helpers/watermark.
|
|
73
|
+
*/
|
|
74
|
+
export const positionToGravity = {
|
|
75
|
+
'top-left': 'northwest',
|
|
76
|
+
top: 'north',
|
|
77
|
+
'top-right': 'northeast',
|
|
78
|
+
left: 'west',
|
|
79
|
+
center: 'centre',
|
|
80
|
+
right: 'east',
|
|
81
|
+
'bottom-left': 'southwest',
|
|
82
|
+
bottom: 'south',
|
|
83
|
+
'bottom-right': 'southeast',
|
|
84
|
+
};
|
|
85
|
+
/** Cache of validated watermark source buffers, keyed by resolved absolute path. */
|
|
86
|
+
const watermarkSourceCache = new Map();
|
|
87
|
+
/**
|
|
88
|
+
* Load and validate a watermark image once, caching by path. Fail-fast: throws a clear
|
|
89
|
+
* error if the file cannot be read or decoded.
|
|
90
|
+
*/
|
|
91
|
+
export function loadWatermarkSource(imagePath) {
|
|
92
|
+
const resolved = path.isAbsolute(imagePath) ? imagePath : path.join(process.cwd(), imagePath);
|
|
93
|
+
let cached = watermarkSourceCache.get(resolved);
|
|
94
|
+
if (!cached) {
|
|
95
|
+
cached = (async () => {
|
|
96
|
+
try {
|
|
97
|
+
const buffer = await fs.promises.readFile(resolved);
|
|
98
|
+
await sharp(buffer).metadata();
|
|
99
|
+
return buffer;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
watermarkSourceCache.delete(resolved);
|
|
103
|
+
throw new Error(`Watermark image could not be loaded: ${resolved} (${error.message})`);
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
watermarkSourceCache.set(resolved, cached);
|
|
107
|
+
}
|
|
108
|
+
return cached;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build a sharp composite overlay for a resolved watermark against a base image of `outputWidth`.
|
|
112
|
+
* Resizes the mark to `size * outputWidth`, applies opacity, and (for non-tiled marks with a
|
|
113
|
+
* margin) pads a transparent border so `gravity` insets the mark from the edge/corner.
|
|
114
|
+
*/
|
|
115
|
+
export async function buildOverlay(watermark, outputWidth) {
|
|
116
|
+
const source = await loadWatermarkSource(watermark.imagePath);
|
|
117
|
+
const targetWidth = Math.max(1, Math.round(watermark.size * outputWidth));
|
|
118
|
+
let overlay = sharp(source).resize({ width: targetWidth });
|
|
119
|
+
// Apply opacity via the dest-in alpha trick: multiply the mark's alpha by `opacity`.
|
|
120
|
+
if (watermark.opacity < 1) {
|
|
121
|
+
overlay = overlay.ensureAlpha().composite([
|
|
122
|
+
{
|
|
123
|
+
input: Buffer.from([255, 255, 255, Math.round(watermark.opacity * 255)]),
|
|
124
|
+
raw: { width: 1, height: 1, channels: 4 },
|
|
125
|
+
tile: true,
|
|
126
|
+
blend: 'dest-in',
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
// Inset from edge/corner: pad the mark with a transparent margin, then let gravity place it flush.
|
|
131
|
+
if (!watermark.tile && watermark.margin > 0) {
|
|
132
|
+
overlay = overlay.extend({
|
|
133
|
+
top: watermark.margin,
|
|
134
|
+
bottom: watermark.margin,
|
|
135
|
+
left: watermark.margin,
|
|
136
|
+
right: watermark.margin,
|
|
137
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
input: await overlay.png().toBuffer(),
|
|
142
|
+
gravity: positionToGravity[watermark.position],
|
|
143
|
+
tile: watermark.tile,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
78
146
|
const configSchema = z.strictObject({
|
|
79
|
-
/**
|
|
80
|
-
watermark:
|
|
147
|
+
/** Watermark config: false = off, or a watermark object. See core/helpers/watermark. */
|
|
148
|
+
watermark: watermarkConfigSchema.optional(),
|
|
81
149
|
blurPlaceholder: z.boolean().nullable().optional(),
|
|
82
150
|
size: sizeSchema.optional(),
|
|
83
151
|
thumbnail: thumbnailSchema.optional(),
|
|
@@ -237,45 +305,66 @@ export class PhotoField extends FileField {
|
|
|
237
305
|
* - fit: 'contain' -> pad with background to fill dimensions
|
|
238
306
|
*/
|
|
239
307
|
const cmsImageQuality = cmsConfig.media.images.image.quality ?? 80;
|
|
308
|
+
/**
|
|
309
|
+
* Resolve the watermark once (field overrides gallery-resolved value overrides global).
|
|
310
|
+
* The overlay is sized against the final output width so it scales with the image.
|
|
311
|
+
*/
|
|
312
|
+
const resolvedWatermark = resolveWatermark(this.watermark, cmsConfig.media.images.watermark);
|
|
313
|
+
const mainOutputWidth = this.size
|
|
314
|
+
? this.size.width
|
|
315
|
+
: ((await this._sharpImage.clone().metadata()).width ?? 0);
|
|
316
|
+
const mainOverlay = resolvedWatermark && mainOutputWidth > 0 ? await buildOverlay(resolvedWatermark, mainOutputWidth) : null;
|
|
240
317
|
if (this.size) {
|
|
241
318
|
const quality = this.size.quality ?? cmsImageQuality;
|
|
242
319
|
const outputPath = path.join(uploadsFolder, '.photos', this._folder, this.value);
|
|
243
320
|
if ('strict' in this.size) {
|
|
244
321
|
// Strict mode: exact dimensions already validated, just convert to webp
|
|
245
|
-
|
|
322
|
+
let pipeline = this._sharpImage.clone();
|
|
323
|
+
if (mainOverlay)
|
|
324
|
+
pipeline = pipeline.composite([mainOverlay]);
|
|
325
|
+
await pipeline.webp({ quality }).toFile(outputPath);
|
|
246
326
|
}
|
|
247
327
|
else if (this.size.fit === 'cover') {
|
|
248
|
-
|
|
328
|
+
let pipeline = this._sharpImage
|
|
249
329
|
.clone()
|
|
250
|
-
.resize({ width: this.size.width, height: this.size.height, fit: 'cover' })
|
|
251
|
-
|
|
252
|
-
.
|
|
330
|
+
.resize({ width: this.size.width, height: this.size.height, fit: 'cover' });
|
|
331
|
+
if (mainOverlay)
|
|
332
|
+
pipeline = pipeline.composite([mainOverlay]);
|
|
333
|
+
await pipeline.webp({ quality }).toFile(outputPath);
|
|
253
334
|
}
|
|
254
335
|
else {
|
|
255
336
|
// contain: pad with background
|
|
256
337
|
const background = this.size.background ?? cmsBackground;
|
|
257
|
-
|
|
338
|
+
let pipeline = this._sharpImage
|
|
258
339
|
.clone()
|
|
259
|
-
.resize({ width: this.size.width, height: this.size.height, fit: 'contain', background })
|
|
260
|
-
|
|
261
|
-
.
|
|
340
|
+
.resize({ width: this.size.width, height: this.size.height, fit: 'contain', background });
|
|
341
|
+
if (mainOverlay)
|
|
342
|
+
pipeline = pipeline.composite([mainOverlay]);
|
|
343
|
+
await pipeline.webp({ quality }).toFile(outputPath);
|
|
262
344
|
}
|
|
263
345
|
}
|
|
264
346
|
else {
|
|
265
|
-
|
|
347
|
+
let pipeline = this._sharpImage.clone();
|
|
348
|
+
if (mainOverlay)
|
|
349
|
+
pipeline = pipeline.composite([mainOverlay]);
|
|
350
|
+
await pipeline.toFile(path.join(uploadsFolder, '.photos', this._folder, this.value));
|
|
266
351
|
}
|
|
267
352
|
/**
|
|
268
353
|
* Write a thumbnail
|
|
269
354
|
*/
|
|
270
355
|
const thumbBackground = ('background' in thumbnail ? thumbnail.background : undefined) ?? cmsBackground;
|
|
271
|
-
|
|
272
|
-
.
|
|
273
|
-
|
|
356
|
+
const thumbOverlay = resolvedWatermark && resolvedWatermark.applyToThumbnail
|
|
357
|
+
? await buildOverlay(resolvedWatermark, thumbnail.width)
|
|
358
|
+
: null;
|
|
359
|
+
let thumbPipeline = this._sharpImage.clone().resize({
|
|
274
360
|
width: thumbnail.width,
|
|
275
361
|
height: thumbnail.height,
|
|
276
362
|
fit: thumbnail.fit,
|
|
277
363
|
...(thumbnail.fit === 'contain' ? { background: thumbBackground } : {}),
|
|
278
|
-
})
|
|
364
|
+
});
|
|
365
|
+
if (thumbOverlay)
|
|
366
|
+
thumbPipeline = thumbPipeline.composite([thumbOverlay]);
|
|
367
|
+
await thumbPipeline
|
|
279
368
|
.webp({
|
|
280
369
|
quality: thumbnail.quality ?? cmsImageQuality,
|
|
281
370
|
})
|