@uxf/resizer 11.125.0 → 11.129.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/.resizer-config.json +4 -4
- package/README.md +35 -25
- package/bin/uxf-resizer.js +20 -20
- package/bin/uxf-resizer.ts +19 -17
- package/package.json +4 -9
- package/src/handler.d.ts +10 -0
- package/src/handler.js +90 -0
- package/src/handler.test.js +65 -0
- package/src/handler.test.ts +79 -0
- package/src/handler.ts +121 -0
- package/src/utils/get-source-filename.d.ts +1 -1
- package/src/utils/get-source-filename.js +14 -15
- package/src/utils/get-source-filename.test.js +21 -0
- package/src/utils/get-source-filename.test.ts +33 -0
- package/src/utils/get-source-filename.ts +16 -16
- package/src/utils/match-route.d.ts +13 -0
- package/src/utils/match-route.js +38 -0
- package/src/utils/match-route.test.d.ts +1 -0
- package/src/utils/match-route.test.js +79 -0
- package/src/utils/match-route.test.ts +94 -0
- package/src/utils/match-route.ts +41 -0
- package/src/utils/tools.d.ts +10 -10
- package/src/utils/tools.js +2 -2
- package/src/utils/tools.test.d.ts +1 -0
- package/src/utils/tools.test.js +46 -0
- package/src/utils/tools.test.ts +48 -0
- package/src/utils/tools.ts +12 -11
- package/src/middleware.d.ts +0 -5
- package/src/middleware.js +0 -92
- package/src/middleware.ts +0 -73
- package/src/utils/parse-http-source.d.ts +0 -7
- package/src/utils/parse-http-source.js +0 -7
- package/src/utils/parse-http-source.test.js +0 -26
- package/src/utils/parse-http-source.test.ts +0 -25
- package/src/utils/parse-http-source.ts +0 -9
- package/src/utils/repair-params.d.ts +0 -8
- package/src/utils/repair-params.js +0 -19
- package/src/utils/repair-params.ts +0 -15
- /package/src/{utils/parse-http-source.test.d.ts → handler.test.d.ts} +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getBackground, getFit, getHeight, getPosition, getQuality, getTrim, getWidth, Params } from "./tools";
|
|
2
|
+
|
|
3
|
+
const minimal: Params = {
|
|
4
|
+
width: "146",
|
|
5
|
+
height: "100",
|
|
6
|
+
fit: undefined,
|
|
7
|
+
position: undefined,
|
|
8
|
+
background: undefined,
|
|
9
|
+
trim: undefined,
|
|
10
|
+
quality: undefined,
|
|
11
|
+
filename: "logo",
|
|
12
|
+
extension: "png",
|
|
13
|
+
toFormat: "avif",
|
|
14
|
+
p1: undefined,
|
|
15
|
+
p2: undefined,
|
|
16
|
+
namespace: undefined,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
test("a route without the optional parameters falls back to defaults", () => {
|
|
20
|
+
expect(getWidth(minimal)).toBe(146);
|
|
21
|
+
expect(getHeight(minimal)).toBe(100);
|
|
22
|
+
expect(getFit(minimal)).toBe("cover");
|
|
23
|
+
expect(getPosition(minimal)).toBeUndefined();
|
|
24
|
+
expect(getBackground(minimal)).toBe("transparent");
|
|
25
|
+
expect(getTrim(minimal)).toBeUndefined();
|
|
26
|
+
expect(getQuality(minimal)).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("explicit parameters are mapped", () => {
|
|
30
|
+
const params: Params = {
|
|
31
|
+
...minimal,
|
|
32
|
+
width: "x",
|
|
33
|
+
fit: "cn",
|
|
34
|
+
position: "lt",
|
|
35
|
+
background: "t",
|
|
36
|
+
trim: "10",
|
|
37
|
+
quality: "75",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
expect(getWidth(params)).toBeUndefined();
|
|
41
|
+
expect(getFit(params)).toBe("contain");
|
|
42
|
+
expect(getPosition(params)).toBe("left top");
|
|
43
|
+
expect(getBackground(params)).toBe("transparent");
|
|
44
|
+
expect(getTrim(params)).toBe(10);
|
|
45
|
+
expect(getQuality(params)).toBe(75);
|
|
46
|
+
expect(getBackground({ ...minimal, background: "ff0000" })).toBe("#ff0000");
|
|
47
|
+
expect(getTrim({ ...minimal, trim: "nt" })).toBeUndefined();
|
|
48
|
+
});
|
package/src/utils/tools.ts
CHANGED
|
@@ -29,14 +29,14 @@ export const CONTENT_TYPES: Record<string, string | undefined> = {
|
|
|
29
29
|
jpeg: "image/jpeg",
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
export
|
|
33
|
-
width: string;
|
|
34
|
-
height: string;
|
|
35
|
-
fit: string;
|
|
36
|
-
position: string;
|
|
37
|
-
background: string;
|
|
38
|
-
trim: string;
|
|
39
|
-
quality: string;
|
|
32
|
+
export type Params = {
|
|
33
|
+
width: string | undefined;
|
|
34
|
+
height: string | undefined;
|
|
35
|
+
fit: string | undefined;
|
|
36
|
+
position: string | undefined;
|
|
37
|
+
background: string | undefined;
|
|
38
|
+
trim: string | undefined;
|
|
39
|
+
quality: string | undefined;
|
|
40
40
|
filename: string;
|
|
41
41
|
extension: string;
|
|
42
42
|
toFormat: string;
|
|
@@ -44,13 +44,14 @@ export interface Params {
|
|
|
44
44
|
p1: string | undefined;
|
|
45
45
|
p2: string | undefined;
|
|
46
46
|
namespace: string | undefined;
|
|
47
|
-
}
|
|
47
|
+
};
|
|
48
48
|
|
|
49
49
|
export const getQuality = ({ quality }: Params) => (quality && quality !== "x" ? Number(quality) : undefined);
|
|
50
50
|
export const getWidth = ({ width }: Params) => (width && width !== "x" ? Number(width) : undefined);
|
|
51
51
|
export const getHeight = ({ height }: Params) => (height && height !== "x" ? Number(height) : undefined);
|
|
52
|
-
export const getBackground = ({ background }: Params) =>
|
|
52
|
+
export const getBackground = ({ background: bg }: Params) =>
|
|
53
|
+
bg === undefined || bg === "t" ? "transparent" : `#${bg}`;
|
|
53
54
|
export const getFit = ({ fit }: Params): keyof FitEnum => (fit ? (FIT_OPTIONS[fit] ?? "cover") : "cover");
|
|
54
55
|
export const getPosition = ({ position }: Params) => (position ? (POSITION_OPTIONS[position] ?? "centre") : undefined);
|
|
55
56
|
export const getWithoutEnlargement = ({ extension }: Params) => extension !== "svg";
|
|
56
|
-
export const getTrim = ({ trim }: Params) => (trim === "nt" ? undefined : Number(trim));
|
|
57
|
+
export const getTrim = ({ trim }: Params) => (trim === undefined || trim === "nt" ? undefined : Number(trim));
|
package/src/middleware.d.ts
DELETED
package/src/middleware.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.resizerMiddleware = void 0;
|
|
37
|
-
const express_1 = require("express");
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const fs_1 = require("fs");
|
|
40
|
-
const path_1 = require("path");
|
|
41
|
-
const get_source_filename_1 = require("./utils/get-source-filename");
|
|
42
|
-
const image_converter_1 = require("./utils/image-converter");
|
|
43
|
-
const log_1 = require("./utils/log");
|
|
44
|
-
const tools_1 = require("./utils/tools");
|
|
45
|
-
const resizerMiddleware = (config) => {
|
|
46
|
-
const middleware = (0, express_1.Router)();
|
|
47
|
-
config.forEach((c, i) => {
|
|
48
|
-
(0, log_1.log)(`Config #${i + 1}:`);
|
|
49
|
-
(0, log_1.log)(` Route: ${c.route}`);
|
|
50
|
-
(0, log_1.log)(` Source: ${c.source}`);
|
|
51
|
-
middleware.get(c.route, async (req, res) => {
|
|
52
|
-
var _a, _b;
|
|
53
|
-
try {
|
|
54
|
-
(0, log_1.log)("----------------------------------------");
|
|
55
|
-
const params = req.params;
|
|
56
|
-
const generatedFilename = ((_a = process.env.UXF_RESIZER_GENERATE_PATH) !== null && _a !== void 0 ? _a : process.cwd()) + req.path;
|
|
57
|
-
(0, log_1.log)(`Generated filename: ${generatedFilename}`);
|
|
58
|
-
if ((0, fs_1.existsSync)(generatedFilename)) {
|
|
59
|
-
return res.sendFile(generatedFilename);
|
|
60
|
-
}
|
|
61
|
-
const generatedDirectory = (0, path_1.dirname)(generatedFilename);
|
|
62
|
-
if (!(0, fs_1.existsSync)(generatedDirectory)) {
|
|
63
|
-
(0, fs_1.mkdirSync)(generatedDirectory, { recursive: true });
|
|
64
|
-
}
|
|
65
|
-
const sourceFilename = (0, get_source_filename_1.getSourceFilename)(c.source, params);
|
|
66
|
-
(0, log_1.log)(`Source filename: ${sourceFilename}`);
|
|
67
|
-
const sourceFile = /^(http|https)/.test(sourceFilename)
|
|
68
|
-
? await fetch(sourceFilename).then((response) => response.arrayBuffer())
|
|
69
|
-
: sourceFilename;
|
|
70
|
-
if (params.toFormat === "svg") {
|
|
71
|
-
fs.writeFileSync(generatedFilename, sourceFile instanceof ArrayBuffer ? Buffer.from(sourceFile) : sourceFile);
|
|
72
|
-
return res.sendFile(generatedFilename, {
|
|
73
|
-
headers: {
|
|
74
|
-
"Content-Type": "image/svg+xml",
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const headers = {
|
|
79
|
-
"Content-Type": (_b = tools_1.CONTENT_TYPES[params.toFormat]) !== null && _b !== void 0 ? _b : "image/jpeg",
|
|
80
|
-
};
|
|
81
|
-
await (0, image_converter_1.convertImage)(sourceFile, generatedFilename, params);
|
|
82
|
-
return res.sendFile(generatedFilename, { headers });
|
|
83
|
-
}
|
|
84
|
-
catch (e) {
|
|
85
|
-
(0, log_1.log)(e, "error");
|
|
86
|
-
return res.sendStatus(404);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
return middleware;
|
|
91
|
-
};
|
|
92
|
-
exports.resizerMiddleware = resizerMiddleware;
|
package/src/middleware.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import { existsSync, mkdirSync } from "fs";
|
|
4
|
-
import { dirname } from "path";
|
|
5
|
-
import { getSourceFilename } from "./utils/get-source-filename";
|
|
6
|
-
import { convertImage } from "./utils/image-converter";
|
|
7
|
-
import { log } from "./utils/log";
|
|
8
|
-
import { CONTENT_TYPES, Params } from "./utils/tools";
|
|
9
|
-
|
|
10
|
-
export type Config = Array<{
|
|
11
|
-
route: string;
|
|
12
|
-
source: string;
|
|
13
|
-
}>;
|
|
14
|
-
|
|
15
|
-
export const resizerMiddleware = (config: Config) => {
|
|
16
|
-
const middleware = Router();
|
|
17
|
-
|
|
18
|
-
config.forEach((c, i) => {
|
|
19
|
-
log(`Config #${i + 1}:`);
|
|
20
|
-
log(` Route: ${c.route}`);
|
|
21
|
-
log(` Source: ${c.source}`);
|
|
22
|
-
|
|
23
|
-
middleware.get(c.route, async (req, res) => {
|
|
24
|
-
try {
|
|
25
|
-
log("----------------------------------------");
|
|
26
|
-
const params = req.params as unknown as Params;
|
|
27
|
-
|
|
28
|
-
const generatedFilename = (process.env.UXF_RESIZER_GENERATE_PATH ?? process.cwd()) + req.path;
|
|
29
|
-
log(`Generated filename: ${generatedFilename}`);
|
|
30
|
-
|
|
31
|
-
if (existsSync(generatedFilename)) {
|
|
32
|
-
return res.sendFile(generatedFilename);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const generatedDirectory = dirname(generatedFilename);
|
|
36
|
-
if (!existsSync(generatedDirectory)) {
|
|
37
|
-
mkdirSync(generatedDirectory, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const sourceFilename = getSourceFilename(c.source, params);
|
|
41
|
-
log(`Source filename: ${sourceFilename}`);
|
|
42
|
-
|
|
43
|
-
const sourceFile = /^(http|https)/.test(sourceFilename)
|
|
44
|
-
? await fetch(sourceFilename).then((response) => response.arrayBuffer())
|
|
45
|
-
: sourceFilename;
|
|
46
|
-
|
|
47
|
-
if (params.toFormat === "svg") {
|
|
48
|
-
fs.writeFileSync(
|
|
49
|
-
generatedFilename,
|
|
50
|
-
sourceFile instanceof ArrayBuffer ? Buffer.from(sourceFile) : sourceFile,
|
|
51
|
-
);
|
|
52
|
-
return res.sendFile(generatedFilename, {
|
|
53
|
-
headers: {
|
|
54
|
-
"Content-Type": "image/svg+xml",
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const headers = {
|
|
60
|
-
"Content-Type": CONTENT_TYPES[params.toFormat] ?? "image/jpeg",
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
await convertImage(sourceFile, generatedFilename, params);
|
|
64
|
-
return res.sendFile(generatedFilename, { headers });
|
|
65
|
-
} catch (e) {
|
|
66
|
-
log(e, "error");
|
|
67
|
-
return res.sendStatus(404);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return middleware;
|
|
73
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseHttpSource = parseHttpSource;
|
|
4
|
-
function parseHttpSource(source) {
|
|
5
|
-
var _a;
|
|
6
|
-
return (_a = /^(?<protocol>http|https):\/\/(?<domain>[^/]*)\/(?<path>.*)$/.exec(source)) === null || _a === void 0 ? void 0 : _a.groups;
|
|
7
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const parse_http_source_1 = require("./parse-http-source");
|
|
4
|
-
test("parse source", () => {
|
|
5
|
-
expect((0, parse_http_source_1.parseHttpSource)("https://domain.uxf.dev/:filename+.:extension")).toEqual({
|
|
6
|
-
protocol: "https",
|
|
7
|
-
domain: "domain.uxf.dev",
|
|
8
|
-
path: ":filename+.:extension",
|
|
9
|
-
});
|
|
10
|
-
expect((0, parse_http_source_1.parseHttpSource)("http://localhost:3000/:filename+.:extension")).toEqual({
|
|
11
|
-
protocol: "http",
|
|
12
|
-
domain: "localhost:3000",
|
|
13
|
-
path: ":filename+.:extension",
|
|
14
|
-
});
|
|
15
|
-
expect((0, parse_http_source_1.parseHttpSource)("http://localhost:3000/test/:filename+.:extension")).toEqual({
|
|
16
|
-
protocol: "http",
|
|
17
|
-
domain: "localhost:3000",
|
|
18
|
-
path: "test/:filename+.:extension",
|
|
19
|
-
});
|
|
20
|
-
expect((0, parse_http_source_1.parseHttpSource)("https://:url/:filename+.:extension")).toEqual({
|
|
21
|
-
protocol: "https",
|
|
22
|
-
domain: ":url",
|
|
23
|
-
path: ":filename+.:extension",
|
|
24
|
-
});
|
|
25
|
-
expect((0, parse_http_source_1.parseHttpSource)("/var/www/:filename+.:extension")).toBeUndefined();
|
|
26
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { parseHttpSource } from "./parse-http-source";
|
|
2
|
-
|
|
3
|
-
test("parse source", () => {
|
|
4
|
-
expect(parseHttpSource("https://domain.uxf.dev/:filename+.:extension")).toEqual({
|
|
5
|
-
protocol: "https",
|
|
6
|
-
domain: "domain.uxf.dev",
|
|
7
|
-
path: ":filename+.:extension",
|
|
8
|
-
});
|
|
9
|
-
expect(parseHttpSource("http://localhost:3000/:filename+.:extension")).toEqual({
|
|
10
|
-
protocol: "http",
|
|
11
|
-
domain: "localhost:3000",
|
|
12
|
-
path: ":filename+.:extension",
|
|
13
|
-
});
|
|
14
|
-
expect(parseHttpSource("http://localhost:3000/test/:filename+.:extension")).toEqual({
|
|
15
|
-
protocol: "http",
|
|
16
|
-
domain: "localhost:3000",
|
|
17
|
-
path: "test/:filename+.:extension",
|
|
18
|
-
});
|
|
19
|
-
expect(parseHttpSource("https://:url/:filename+.:extension")).toEqual({
|
|
20
|
-
protocol: "https",
|
|
21
|
-
domain: ":url",
|
|
22
|
-
path: ":filename+.:extension",
|
|
23
|
-
});
|
|
24
|
-
expect(parseHttpSource("/var/www/:filename+.:extension")).toBeUndefined();
|
|
25
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
interface Result {
|
|
2
|
-
protocol: string;
|
|
3
|
-
path: string;
|
|
4
|
-
domain: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function parseHttpSource(source: string): Result | undefined {
|
|
8
|
-
return /^(?<protocol>http|https):\/\/(?<domain>[^/]*)\/(?<path>.*)$/.exec(source)?.groups as Result | undefined;
|
|
9
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TODO @vejvis HACK
|
|
3
|
-
*
|
|
4
|
-
* pro odchycení celé cesty i s lomítkama je potřeba udělat hack: /generated/:restPath(*) => restPath bude string
|
|
5
|
-
* pro zpětné složení pomocí pathToRegexp::compile() se používá: /generated/:restPath+ => restPath musí být string[]
|
|
6
|
-
* Tato funkce projde celý objekt a pokud v hodnotě atributu najde "/" tak tento atribut převede na pole
|
|
7
|
-
*/
|
|
8
|
-
export declare const repairParams: (params: any) => any;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.repairParams = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* TODO @vejvis HACK
|
|
6
|
-
*
|
|
7
|
-
* pro odchycení celé cesty i s lomítkama je potřeba udělat hack: /generated/:restPath(*) => restPath bude string
|
|
8
|
-
* pro zpětné složení pomocí pathToRegexp::compile() se používá: /generated/:restPath+ => restPath musí být string[]
|
|
9
|
-
* Tato funkce projde celý objekt a pokud v hodnotě atributu najde "/" tak tento atribut převede na pole
|
|
10
|
-
*/
|
|
11
|
-
const repairParams = (params) => {
|
|
12
|
-
const obj = {};
|
|
13
|
-
Object.keys(params).forEach((key) => {
|
|
14
|
-
const value = params[key];
|
|
15
|
-
obj[key] = value.includes("/") ? value.split("/") : value;
|
|
16
|
-
});
|
|
17
|
-
return obj;
|
|
18
|
-
};
|
|
19
|
-
exports.repairParams = repairParams;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TODO @vejvis HACK
|
|
3
|
-
*
|
|
4
|
-
* pro odchycení celé cesty i s lomítkama je potřeba udělat hack: /generated/:restPath(*) => restPath bude string
|
|
5
|
-
* pro zpětné složení pomocí pathToRegexp::compile() se používá: /generated/:restPath+ => restPath musí být string[]
|
|
6
|
-
* Tato funkce projde celý objekt a pokud v hodnotě atributu najde "/" tak tento atribut převede na pole
|
|
7
|
-
*/
|
|
8
|
-
export const repairParams = (params: any) => {
|
|
9
|
-
const obj: any = {};
|
|
10
|
-
Object.keys(params).forEach((key) => {
|
|
11
|
-
const value: string = params[key];
|
|
12
|
-
obj[key] = value.includes("/") ? value.split("/") : value;
|
|
13
|
-
});
|
|
14
|
-
return obj;
|
|
15
|
-
};
|
|
File without changes
|