@prismicio/types-internal 3.15.0 → 3.16.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.
@@ -0,0 +1,236 @@
1
+ import * as t from "io-ts";
2
+ import type { ContentPath, TraverseWidgetContentFn } from "../../_internal/utils";
3
+ import { type Link, NestableFieldTypes, NestableWidget } from "../../customtypes";
4
+ import type { LegacyContentCtx, WithTypes } from "../LegacyContentCtx";
5
+ export declare const RepeatableContentType: "RepeatableContent";
6
+ export declare const RepeatableFieldType: "Repeatable";
7
+ export declare const RepeatableItemContent: t.ExactC<t.TypeC<{
8
+ __TYPE__: t.LiteralC<"LinkContent">;
9
+ value: t.UnionC<[t.IntersectionC<[t.ExactC<t.TypeC<{
10
+ __TYPE__: t.LiteralC<"ImageLink">;
11
+ }>>, t.UnionC<[t.IntersectionC<[t.IntersectionC<[t.ExactC<t.TypeC<{
12
+ kind: t.StringC;
13
+ id: t.StringC;
14
+ url: t.StringC;
15
+ height: t.StringC;
16
+ width: t.StringC;
17
+ size: t.StringC;
18
+ name: t.StringC;
19
+ }>>, t.ExactC<t.PartialC<{
20
+ date: t.UnionC<[t.Type<string, string, unknown>, t.NullC, t.UndefinedC]>;
21
+ }>>]>, t.ExactC<t.PartialC<{
22
+ text: t.StringC;
23
+ }>>]>, t.ExactC<t.TypeC<{
24
+ kind: t.LiteralC<"image">;
25
+ text: t.StringC;
26
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
27
+ __TYPE__: t.LiteralC<"FileLink">;
28
+ }>>, t.UnionC<[t.IntersectionC<[t.ExactC<t.IntersectionC<[t.TypeC<{
29
+ kind: t.StringC;
30
+ id: t.StringC;
31
+ url: t.StringC;
32
+ name: t.StringC;
33
+ size: t.StringC;
34
+ }>, t.PartialC<{
35
+ date: t.UnionC<[t.Type<string, string, unknown>, t.NullC, t.UndefinedC]>;
36
+ }>]>>, t.ExactC<t.PartialC<{
37
+ text: t.StringC;
38
+ }>>]>, t.ExactC<t.TypeC<{
39
+ kind: t.LiteralC<"file">;
40
+ text: t.StringC;
41
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
42
+ __TYPE__: t.LiteralC<"DocumentLink">;
43
+ }>>, t.UnionC<[t.IntersectionC<[t.ExactC<t.TypeC<{
44
+ id: t.Type<string, string, unknown>;
45
+ }>>, t.ExactC<t.PartialC<{
46
+ text: t.StringC;
47
+ }>>]>, t.ExactC<t.TypeC<{
48
+ kind: t.LiteralC<"document">;
49
+ text: t.StringC;
50
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
51
+ __TYPE__: t.LiteralC<"ExternalLink">;
52
+ }>>, t.UnionC<[t.IntersectionC<[t.ExactC<t.IntersectionC<[t.TypeC<{
53
+ url: t.StringC;
54
+ }>, t.PartialC<{
55
+ kind: t.LiteralC<"web">;
56
+ target: t.UnionC<[t.Type<string, string, unknown>, t.NullC, t.UndefinedC]>;
57
+ preview: t.UnionC<[t.Type<{
58
+ title?: string;
59
+ }, {
60
+ title?: string;
61
+ }, unknown>, t.NullC, t.UndefinedC]>;
62
+ }>]>>, t.ExactC<t.PartialC<{
63
+ text: t.StringC;
64
+ }>>]>, t.ExactC<t.TypeC<{
65
+ kind: t.LiteralC<"web">;
66
+ text: t.StringC;
67
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
68
+ __TYPE__: t.LiteralC<"MediaLink">;
69
+ }>>, t.ExactC<t.TypeC<{
70
+ kind: t.LiteralC<"media">;
71
+ text: t.StringC;
72
+ }>>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
73
+ __TYPE__: t.LiteralC<"AnyLink">;
74
+ }>>, t.ExactC<t.TypeC<{
75
+ text: t.StringC;
76
+ }>>]>]>;
77
+ }>>;
78
+ export declare type RepeatableItemContent = t.TypeOf<typeof RepeatableItemContent>;
79
+ export declare const RepeatableContent: t.ExactC<t.TypeC<{
80
+ __TYPE__: t.LiteralC<"RepeatableContent">;
81
+ type: t.UnionC<[t.LiteralC<"Color">, t.LiteralC<"Boolean">, t.LiteralC<"Embed">, t.LiteralC<"GeoPoint">, t.LiteralC<"Date">, t.LiteralC<"Number">, t.LiteralC<"Range">, t.LiteralC<"StructuredText">, t.LiteralC<"Select">, t.LiteralC<"Separator">, t.LiteralC<"Text">, t.LiteralC<"Timestamp">, t.LiteralC<"Link">, t.LiteralC<"Image">, t.LiteralC<"IntegrationFields">]>;
82
+ value: t.ArrayC<t.ExactC<t.TypeC<{
83
+ __TYPE__: t.LiteralC<"LinkContent">;
84
+ value: t.UnionC<[t.IntersectionC<[t.ExactC<t.TypeC<{
85
+ __TYPE__: t.LiteralC<"ImageLink">;
86
+ }>>, t.UnionC<[t.IntersectionC<[t.IntersectionC<[t.ExactC<t.TypeC<{
87
+ kind: t.StringC;
88
+ id: t.StringC;
89
+ url: t.StringC;
90
+ height: t.StringC;
91
+ width: t.StringC;
92
+ size: t.StringC;
93
+ name: t.StringC;
94
+ }>>, t.ExactC<t.PartialC<{
95
+ date: t.UnionC<[t.Type<string, string, unknown>, t.NullC, t.UndefinedC]>;
96
+ }>>]>, t.ExactC<t.PartialC<{
97
+ text: t.StringC;
98
+ }>>]>, t.ExactC<t.TypeC<{
99
+ kind: t.LiteralC<"image">;
100
+ text: t.StringC;
101
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
102
+ __TYPE__: t.LiteralC<"FileLink">;
103
+ }>>, t.UnionC<[t.IntersectionC<[t.ExactC<t.IntersectionC<[t.TypeC<{
104
+ kind: t.StringC;
105
+ id: t.StringC;
106
+ url: t.StringC;
107
+ name: t.StringC;
108
+ size: t.StringC;
109
+ }>, t.PartialC<{
110
+ date: t.UnionC<[t.Type<string, string, unknown>, t.NullC, t.UndefinedC]>;
111
+ }>]>>, t.ExactC<t.PartialC<{
112
+ text: t.StringC;
113
+ }>>]>, t.ExactC<t.TypeC<{
114
+ kind: t.LiteralC<"file">;
115
+ text: t.StringC;
116
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
117
+ __TYPE__: t.LiteralC<"DocumentLink">;
118
+ }>>, t.UnionC<[t.IntersectionC<[t.ExactC<t.TypeC<{
119
+ id: t.Type<string, string, unknown>;
120
+ }>>, t.ExactC<t.PartialC<{
121
+ text: t.StringC;
122
+ }>>]>, t.ExactC<t.TypeC<{
123
+ kind: t.LiteralC<"document">;
124
+ text: t.StringC;
125
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
126
+ __TYPE__: t.LiteralC<"ExternalLink">;
127
+ }>>, t.UnionC<[t.IntersectionC<[t.ExactC<t.IntersectionC<[t.TypeC<{
128
+ url: t.StringC;
129
+ }>, t.PartialC<{
130
+ kind: t.LiteralC<"web">;
131
+ target: t.UnionC<[t.Type<string, string, unknown>, t.NullC, t.UndefinedC]>;
132
+ preview: t.UnionC<[t.Type<{
133
+ title?: string;
134
+ }, {
135
+ title?: string;
136
+ }, unknown>, t.NullC, t.UndefinedC]>;
137
+ }>]>>, t.ExactC<t.PartialC<{
138
+ text: t.StringC;
139
+ }>>]>, t.ExactC<t.TypeC<{
140
+ kind: t.LiteralC<"web">;
141
+ text: t.StringC;
142
+ }>>]>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
143
+ __TYPE__: t.LiteralC<"MediaLink">;
144
+ }>>, t.ExactC<t.TypeC<{
145
+ kind: t.LiteralC<"media">;
146
+ text: t.StringC;
147
+ }>>]>, t.IntersectionC<[t.ExactC<t.TypeC<{
148
+ __TYPE__: t.LiteralC<"AnyLink">;
149
+ }>>, t.ExactC<t.TypeC<{
150
+ text: t.StringC;
151
+ }>>]>]>;
152
+ }>>>;
153
+ }>>;
154
+ export declare type RepeatableContent = t.TypeOf<typeof RepeatableContent>;
155
+ export declare const isRepeatableContent: t.Is<{
156
+ __TYPE__: "RepeatableContent";
157
+ type: "Boolean" | "Color" | "Date" | "Embed" | "GeoPoint" | "Image" | "IntegrationFields" | "Link" | "Number" | "Range" | "StructuredText" | "Select" | "Separator" | "Text" | "Timestamp";
158
+ value: {
159
+ __TYPE__: "LinkContent";
160
+ value: ({
161
+ __TYPE__: "ImageLink";
162
+ } & (({
163
+ kind: string;
164
+ id: string;
165
+ url: string;
166
+ height: string;
167
+ width: string;
168
+ size: string;
169
+ name: string;
170
+ } & {
171
+ date?: string | null | undefined;
172
+ } & {
173
+ text?: string;
174
+ }) | {
175
+ kind: "image";
176
+ text: string;
177
+ })) | ({
178
+ __TYPE__: "FileLink";
179
+ } & (({
180
+ kind: string;
181
+ id: string;
182
+ url: string;
183
+ name: string;
184
+ size: string;
185
+ } & {
186
+ date?: string | null | undefined;
187
+ } & {
188
+ text?: string;
189
+ }) | {
190
+ kind: "file";
191
+ text: string;
192
+ })) | ({
193
+ __TYPE__: "MediaLink";
194
+ } & {
195
+ kind: "media";
196
+ text: string;
197
+ }) | ({
198
+ __TYPE__: "DocumentLink";
199
+ } & (({
200
+ id: string;
201
+ } & {
202
+ text?: string;
203
+ }) | {
204
+ kind: "document";
205
+ text: string;
206
+ })) | ({
207
+ __TYPE__: "ExternalLink";
208
+ } & (({
209
+ url: string;
210
+ } & {
211
+ kind?: "web";
212
+ target?: string | null | undefined;
213
+ preview?: {
214
+ title?: string;
215
+ } | null | undefined;
216
+ } & {
217
+ text?: string;
218
+ }) | {
219
+ kind: "web";
220
+ text: string;
221
+ })) | ({
222
+ __TYPE__: "AnyLink";
223
+ } & {
224
+ text: string;
225
+ });
226
+ }[];
227
+ }>;
228
+ export declare const RepeatableLegacy: (ctx: LegacyContentCtx, fieldType: NestableFieldTypes) => t.Type<RepeatableContent, WithTypes<Array<unknown>>, unknown>;
229
+ export declare type RepeatableCustomType = Link;
230
+ export declare function traverseRepeatableContent({ path, key, apiId, model, content, }: {
231
+ path: ContentPath;
232
+ key: string;
233
+ apiId: string;
234
+ content: RepeatableContent;
235
+ model?: NestableWidget | undefined;
236
+ }): (transform: TraverseWidgetContentFn) => RepeatableContent | undefined;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traverseRepeatableContent = exports.RepeatableLegacy = exports.isRepeatableContent = exports.RepeatableContent = exports.RepeatableItemContent = exports.RepeatableFieldType = exports.RepeatableContentType = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fp_ts_1 = require("fp-ts");
6
+ const Either_1 = require("fp-ts/lib/Either");
7
+ const function_1 = require("fp-ts/lib/function");
8
+ const t = (0, tslib_1.__importStar)(require("io-ts"));
9
+ const customtypes_1 = require("../../customtypes");
10
+ const nestable_1 = require("./nestable");
11
+ exports.RepeatableContentType = "RepeatableContent";
12
+ exports.RepeatableFieldType = "Repeatable";
13
+ exports.RepeatableItemContent = nestable_1.LinkContent;
14
+ exports.RepeatableContent = t.strict({
15
+ __TYPE__: t.literal(exports.RepeatableContentType),
16
+ type: customtypes_1.NestableFieldTypes,
17
+ // TODO: How to ensure it's an array of only one type
18
+ value: t.array(exports.RepeatableItemContent),
19
+ });
20
+ exports.isRepeatableContent = exports.RepeatableContent.is;
21
+ const RepeatableLegacy = (ctx, fieldType) => {
22
+ const codecDecode = t.array(t.unknown);
23
+ const codecEncode = t.array(exports.RepeatableItemContent);
24
+ return new t.Type("RepeatableLegacy", exports.isRepeatableContent, (items) => {
25
+ const parsed = (0, function_1.pipe)(codecDecode.decode(items), fp_ts_1.either.map((items) => {
26
+ const parsedItems = items.reduce((acc, item) => {
27
+ let result;
28
+ switch (fieldType) {
29
+ case "Link":
30
+ result = (0, nestable_1.LinkContentLegacy)(ctx).decode(item);
31
+ break;
32
+ }
33
+ if (!result)
34
+ return acc;
35
+ if ((0, Either_1.isLeft)(result))
36
+ return acc;
37
+ return [...acc, result.right];
38
+ }, []);
39
+ return {
40
+ value: parsedItems,
41
+ type: fieldType,
42
+ __TYPE__: exports.RepeatableContentType,
43
+ };
44
+ }));
45
+ return parsed;
46
+ }, (r) => {
47
+ const res = codecEncode.encode(r.value);
48
+ const encodedItems = res.reduce((acc, item) => {
49
+ let encoded;
50
+ switch (item.__TYPE__) {
51
+ case nestable_1.LinkContentType:
52
+ encoded = (0, nestable_1.LinkContentLegacy)(ctx).encode(item);
53
+ break;
54
+ }
55
+ if (!encoded)
56
+ return acc;
57
+ return [...acc, encoded];
58
+ }, []);
59
+ return {
60
+ content: encodedItems.map((encodedItem) => encodedItem.content),
61
+ types: { [ctx.keyOfType]: `Repeatable.${fieldType}` },
62
+ };
63
+ });
64
+ };
65
+ exports.RepeatableLegacy = RepeatableLegacy;
66
+ function traverseRepeatableContent({ path, key, apiId, model, content, }) {
67
+ return (transform) => {
68
+ const items = content.value.reduce((acc, fieldContent, index) => {
69
+ const itemPath = path.concat([
70
+ { key: index.toString(), type: "Widget" },
71
+ ]);
72
+ const transformedField = transform({
73
+ path: itemPath,
74
+ key: key,
75
+ apiId: apiId,
76
+ model: model,
77
+ content: fieldContent,
78
+ });
79
+ // Can happen if the transform function returns undefined to filter out a field
80
+ if (!transformedField)
81
+ return acc;
82
+ return acc.concat(transformedField);
83
+ }, []);
84
+ return transform({
85
+ path,
86
+ key,
87
+ apiId,
88
+ model,
89
+ content: {
90
+ __TYPE__: content.__TYPE__,
91
+ type: content.type,
92
+ value: items,
93
+ },
94
+ });
95
+ };
96
+ }
97
+ exports.traverseRepeatableContent = traverseRepeatableContent;
@@ -225,3 +225,28 @@ export declare const ImageContent: t.IntersectionC<[t.IntersectionC<[t.ExactC<t.
225
225
  __TYPE__: t.LiteralC<"ImageContent">;
226
226
  }>>]>;
227
227
  export declare type ImageContent = t.TypeOf<typeof ImageContent>;
228
+ interface BuildCropUrlArgs {
229
+ origin: ImageContent["origin"];
230
+ /** Image crop parameters */
231
+ croppedImage: CroppedImage;
232
+ }
233
+ export declare function buildCropUrl(args: BuildCropUrlArgs): string;
234
+ export interface CroppedImage {
235
+ zoom: number;
236
+ /** X coordinate of the crop rect on the original image */
237
+ x: number;
238
+ /** Y coordinate of the crop rect on the original image */
239
+ y: number;
240
+ /** The width of the crop rect over the original image */
241
+ cropWidth: number;
242
+ /** The height of the crop rect over the original image */
243
+ cropHeight: number;
244
+ /** Final (user defined via W input) height of the image */
245
+ width: number;
246
+ /** Final (user defined via H input) height of the image */
247
+ height: number;
248
+ }
249
+ export declare function getCroppedImage({ contentView, }: {
250
+ contentView: ImageContentView;
251
+ }): CroppedImage;
252
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ImageContent = exports.ImageLegacy = exports.ImageContentView = exports.isImageContent = exports.ImageContentType = void 0;
3
+ exports.getCroppedImage = exports.buildCropUrl = exports.ImageContent = exports.ImageLegacy = exports.ImageContentView = exports.isImageContent = exports.ImageContentType = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fp_ts_1 = require("fp-ts");
6
6
  const function_1 = require("fp-ts/lib/function");
@@ -71,3 +71,52 @@ exports.ImageContent = t.intersection([
71
71
  __TYPE__: t.literal(exports.ImageContentType),
72
72
  }),
73
73
  ]);
74
+ function buildCropUrl(args) {
75
+ const { origin, croppedImage } = args;
76
+ const { x, y, width, height, cropHeight, cropWidth } = croppedImage;
77
+ const url = new URL(origin.url);
78
+ const hasResize = origin.height !== height || origin.width !== width;
79
+ const hasCrop = x !== 0 ||
80
+ y !== 0 ||
81
+ cropHeight !== origin.height ||
82
+ cropWidth !== origin.width;
83
+ if (hasCrop) {
84
+ const crop = [x, y, cropWidth, cropHeight].map(Math.round).join(",");
85
+ url.searchParams.set("rect", crop);
86
+ url.searchParams.set("w", width.toString());
87
+ url.searchParams.set("h", height.toString());
88
+ }
89
+ if (hasResize) {
90
+ url.searchParams.set("w", width.toString());
91
+ url.searchParams.set("h", height.toString());
92
+ }
93
+ return url.toString();
94
+ }
95
+ exports.buildCropUrl = buildCropUrl;
96
+ function getCroppedImage({ contentView, }) {
97
+ const { edit: { crop: { x, y }, zoom, }, width, height, origin: { width: originWidth, height: originHeight }, } = contentView;
98
+ let cropWidth;
99
+ let cropHeight;
100
+ const originAspect = originWidth / originHeight;
101
+ const cropAspect = width / height;
102
+ if (cropAspect >= originAspect) {
103
+ // crop and origin image would be the same width if zoom was equal to 1
104
+ cropWidth = originWidth / zoom;
105
+ cropHeight = cropWidth / cropAspect;
106
+ }
107
+ else {
108
+ // crop and origin image would be the same height if zoom was equal to 1
109
+ cropHeight = originHeight / zoom;
110
+ cropWidth = cropHeight * cropAspect;
111
+ }
112
+ return {
113
+ x,
114
+ y,
115
+ width,
116
+ height,
117
+ zoom,
118
+ cropWidth,
119
+ cropHeight,
120
+ };
121
+ }
122
+ exports.getCroppedImage = getCroppedImage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismicio/types-internal",
3
- "version": "3.15.0",
3
+ "version": "3.16.0",
4
4
  "description": "Prismic types for Custom Types and Prismic Data",
5
5
  "keywords": [
6
6
  "typescript",
@@ -23,10 +23,6 @@
23
23
  "dev": "tsc --watch",
24
24
  "format": "prettier --write .",
25
25
  "prepare": "npm run build",
26
- "release": "npm run test && standard-version && git push --follow-tags && npm run build && npm publish",
27
- "release:dry": "standard-version --dry-run",
28
- "release:alpha": "npm run test && standard-version --prerelease alpha --release-as minor && git push --follow-tags && npm run build && npm publish --tag alpha",
29
- "release:alpha:dry": "standard-version --prerelease alpha --release-as minor --dry-run",
30
26
  "test": "jest --no-cache --silent=false --verbose=false --coverage",
31
27
  "test:watch": "npm run test -- --watch",
32
28
  "test:clear": "jest --clearCache",
@@ -99,3 +99,98 @@ export const ImageContent = t.intersection([
99
99
  ])
100
100
 
101
101
  export type ImageContent = t.TypeOf<typeof ImageContent>
102
+
103
+ // Helpers to build image crop URLs (used in editor, migration-api, and prismic-mocks)
104
+ interface BuildCropUrlArgs {
105
+ origin: ImageContent["origin"]
106
+ /** Image crop parameters */
107
+ croppedImage: CroppedImage
108
+ }
109
+
110
+ export function buildCropUrl(args: BuildCropUrlArgs) {
111
+ const { origin, croppedImage } = args
112
+ const { x, y, width, height, cropHeight, cropWidth } = croppedImage
113
+
114
+ const url = new URL(origin.url)
115
+
116
+ const hasResize = origin.height !== height || origin.width !== width
117
+
118
+ const hasCrop =
119
+ x !== 0 ||
120
+ y !== 0 ||
121
+ cropHeight !== origin.height ||
122
+ cropWidth !== origin.width
123
+
124
+ if (hasCrop) {
125
+ const crop = [x, y, cropWidth, cropHeight].map(Math.round).join(",")
126
+
127
+ url.searchParams.set("rect", crop)
128
+ url.searchParams.set("w", width.toString())
129
+ url.searchParams.set("h", height.toString())
130
+ }
131
+
132
+ if (hasResize) {
133
+ url.searchParams.set("w", width.toString())
134
+ url.searchParams.set("h", height.toString())
135
+ }
136
+
137
+ return url.toString()
138
+ }
139
+
140
+ export interface CroppedImage {
141
+ zoom: number
142
+ /** X coordinate of the crop rect on the original image */
143
+ x: number
144
+ /** Y coordinate of the crop rect on the original image */
145
+ y: number
146
+
147
+ /** The width of the crop rect over the original image */
148
+ cropWidth: number
149
+ /** The height of the crop rect over the original image */
150
+ cropHeight: number
151
+
152
+ /** Final (user defined via W input) height of the image */
153
+ width: number
154
+ /** Final (user defined via H input) height of the image */
155
+ height: number
156
+ }
157
+
158
+ export function getCroppedImage({
159
+ contentView,
160
+ }: {
161
+ contentView: ImageContentView
162
+ }): CroppedImage {
163
+ const {
164
+ edit: {
165
+ crop: { x, y },
166
+ zoom,
167
+ },
168
+ width,
169
+ height,
170
+ origin: { width: originWidth, height: originHeight },
171
+ } = contentView
172
+
173
+ let cropWidth
174
+ let cropHeight
175
+ const originAspect = originWidth / originHeight
176
+ const cropAspect = width / height
177
+ if (cropAspect >= originAspect) {
178
+ // crop and origin image would be the same width if zoom was equal to 1
179
+ cropWidth = originWidth / zoom
180
+ cropHeight = cropWidth / cropAspect
181
+ } else {
182
+ // crop and origin image would be the same height if zoom was equal to 1
183
+ cropHeight = originHeight / zoom
184
+ cropWidth = cropHeight * cropAspect
185
+ }
186
+
187
+ return {
188
+ x,
189
+ y,
190
+ width,
191
+ height,
192
+ zoom,
193
+ cropWidth,
194
+ cropHeight,
195
+ }
196
+ }