@raketa-cloud/admin 1.10.5 → 1.10.7

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.
@@ -326,6 +326,9 @@
326
326
  .z-50 {
327
327
  z-index: 50;
328
328
  }
329
+ .m-auto {
330
+ margin: auto;
331
+ }
329
332
  .my-4 {
330
333
  margin-block: calc(var(--spacing) * 4);
331
334
  }
@@ -514,6 +517,9 @@
514
517
  .flex-none {
515
518
  flex: none;
516
519
  }
520
+ .shrink-0 {
521
+ flex-shrink: 0;
522
+ }
517
523
  .-translate-x-1\/2 {
518
524
  --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
519
525
  translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -569,6 +575,9 @@
569
575
  .justify-end {
570
576
  justify-content: flex-end;
571
577
  }
578
+ .gap-0\.5 {
579
+ gap: calc(var(--spacing) * 0.5);
580
+ }
572
581
  .gap-1 {
573
582
  gap: var(--spacing);
574
583
  }
@@ -587,6 +596,11 @@
587
596
  .gap-8 {
588
597
  gap: calc(var(--spacing) * 8);
589
598
  }
599
+ :where(.space-y-1 > :not(:last-child)) {
600
+ --tw-space-y-reverse: 0;
601
+ margin-block-start: calc(var(--spacing) * var(--tw-space-y-reverse));
602
+ margin-block-end: calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)));
603
+ }
590
604
  :where(.divide-y > :not(:last-child)) {
591
605
  --tw-divide-y-reverse: 0;
592
606
  border-bottom-style: var(--tw-border-style);
@@ -1146,92 +1160,78 @@
1146
1160
  padding-right: 0px;
1147
1161
  }
1148
1162
  }
1149
- @media (prefers-color-scheme: dark) {
1150
- :where(.dark\:divide-neutral-700 > :not(:last-child)) {
1151
- border-color: var(--color-neutral-700);
1152
- }
1153
- .dark\:border-neutral-500 {
1154
- border-color: var(--color-neutral-500);
1155
- }
1156
- .dark\:border-neutral-700 {
1157
- border-color: var(--color-neutral-700);
1158
- }
1159
- .dark\:bg-blue-900\/50 {
1160
- background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 50%, transparent);
1161
- }
1163
+ :where(.dark\:divide-neutral-700:where(.dark, .dark *) > :not(:last-child)) {
1164
+ border-color: var(--color-neutral-700);
1165
+ }
1166
+ .dark\:border-neutral-500:where(.dark, .dark *) {
1167
+ border-color: var(--color-neutral-500);
1168
+ }
1169
+ .dark\:border-neutral-700:where(.dark, .dark *) {
1170
+ border-color: var(--color-neutral-700);
1171
+ }
1172
+ .dark\:bg-blue-900\/50:where(.dark, .dark *) {
1173
+ background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 50%, transparent);
1162
1174
  @supports (color: color-mix(in lab, red, red)) {
1163
- .dark\:bg-blue-900\/50 {
1164
- background-color: color-mix(in oklab, var(--color-blue-900) 50%, transparent);
1165
- }
1166
- }
1167
- .dark\:bg-emerald-900\/50 {
1168
- background-color: color-mix(in srgb, oklch(37.8% 0.077 168.94) 50%, transparent);
1175
+ background-color: color-mix(in oklab, var(--color-blue-900) 50%, transparent);
1169
1176
  }
1177
+ }
1178
+ .dark\:bg-emerald-900\/50:where(.dark, .dark *) {
1179
+ background-color: color-mix(in srgb, oklch(37.8% 0.077 168.94) 50%, transparent);
1170
1180
  @supports (color: color-mix(in lab, red, red)) {
1171
- .dark\:bg-emerald-900\/50 {
1172
- background-color: color-mix(in oklab, var(--color-emerald-900) 50%, transparent);
1173
- }
1174
- }
1175
- .dark\:bg-neutral-800 {
1176
- background-color: var(--color-neutral-800);
1177
- }
1178
- .dark\:bg-orange-900\/50 {
1179
- background-color: color-mix(in srgb, oklch(40.8% 0.123 38.172) 50%, transparent);
1181
+ background-color: color-mix(in oklab, var(--color-emerald-900) 50%, transparent);
1180
1182
  }
1183
+ }
1184
+ .dark\:bg-neutral-800:where(.dark, .dark *) {
1185
+ background-color: var(--color-neutral-800);
1186
+ }
1187
+ .dark\:bg-orange-900\/50:where(.dark, .dark *) {
1188
+ background-color: color-mix(in srgb, oklch(40.8% 0.123 38.172) 50%, transparent);
1181
1189
  @supports (color: color-mix(in lab, red, red)) {
1182
- .dark\:bg-orange-900\/50 {
1183
- background-color: color-mix(in oklab, var(--color-orange-900) 50%, transparent);
1184
- }
1185
- }
1186
- .dark\:bg-orange-950\/40 {
1187
- background-color: color-mix(in srgb, oklch(26.6% 0.079 36.259) 40%, transparent);
1190
+ background-color: color-mix(in oklab, var(--color-orange-900) 50%, transparent);
1188
1191
  }
1192
+ }
1193
+ .dark\:bg-orange-950\/40:where(.dark, .dark *) {
1194
+ background-color: color-mix(in srgb, oklch(26.6% 0.079 36.259) 40%, transparent);
1189
1195
  @supports (color: color-mix(in lab, red, red)) {
1190
- .dark\:bg-orange-950\/40 {
1191
- background-color: color-mix(in oklab, var(--color-orange-950) 40%, transparent);
1192
- }
1193
- }
1194
- .dark\:bg-raketa-primary\/15 {
1195
- background-color: color-mix(in srgb, rgb(var(--primary)) 15%, transparent);
1196
+ background-color: color-mix(in oklab, var(--color-orange-950) 40%, transparent);
1196
1197
  }
1198
+ }
1199
+ .dark\:bg-raketa-primary\/15:where(.dark, .dark *) {
1200
+ background-color: color-mix(in srgb, rgb(var(--primary)) 15%, transparent);
1197
1201
  @supports (color: color-mix(in lab, red, red)) {
1198
- .dark\:bg-raketa-primary\/15 {
1199
- background-color: color-mix(in oklab, var(--color-raketa-primary) 15%, transparent);
1200
- }
1201
- }
1202
- .dark\:bg-red-900\/50 {
1203
- background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 50%, transparent);
1202
+ background-color: color-mix(in oklab, var(--color-raketa-primary) 15%, transparent);
1204
1203
  }
1204
+ }
1205
+ .dark\:bg-red-900\/50:where(.dark, .dark *) {
1206
+ background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 50%, transparent);
1205
1207
  @supports (color: color-mix(in lab, red, red)) {
1206
- .dark\:bg-red-900\/50 {
1207
- background-color: color-mix(in oklab, var(--color-red-900) 50%, transparent);
1208
- }
1209
- }
1210
- .dark\:text-blue-300 {
1211
- color: var(--color-blue-300);
1212
- }
1213
- .dark\:text-emerald-300 {
1214
- color: var(--color-emerald-300);
1215
- }
1216
- .dark\:text-orange-300 {
1217
- color: var(--color-orange-300);
1208
+ background-color: color-mix(in oklab, var(--color-red-900) 50%, transparent);
1218
1209
  }
1219
- .dark\:text-red-300 {
1220
- color: var(--color-red-300);
1221
- }
1222
- .dark\:invert {
1223
- --tw-invert: invert(100%);
1224
- filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
1225
- }
1226
- @media (hover: hover) {
1227
- .dark\:hover\:bg-neutral-700:hover {
1228
- background-color: var(--color-neutral-700);
1229
- }
1210
+ }
1211
+ .dark\:text-blue-300:where(.dark, .dark *) {
1212
+ color: var(--color-blue-300);
1213
+ }
1214
+ .dark\:text-emerald-300:where(.dark, .dark *) {
1215
+ color: var(--color-emerald-300);
1216
+ }
1217
+ .dark\:text-orange-300:where(.dark, .dark *) {
1218
+ color: var(--color-orange-300);
1219
+ }
1220
+ .dark\:text-red-300:where(.dark, .dark *) {
1221
+ color: var(--color-red-300);
1222
+ }
1223
+ .dark\:invert:where(.dark, .dark *) {
1224
+ --tw-invert: invert(100%);
1225
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
1226
+ }
1227
+ @media (hover: hover) {
1228
+ .dark\:hover\:bg-neutral-700:where(.dark, .dark *):hover {
1229
+ background-color: var(--color-neutral-700);
1230
1230
  }
1231
- @media (width >= 64rem) {
1232
- :where(.dark\:lg\:divide-neutral-700 > :not(:last-child)) {
1233
- border-color: var(--color-neutral-700);
1234
- }
1231
+ }
1232
+ @media (width >= 64rem) {
1233
+ :where(.dark\:lg\:divide-neutral-700:where(.dark, .dark *) > :not(:last-child)) {
1234
+ border-color: var(--color-neutral-700);
1235
1235
  }
1236
1236
  }
1237
1237
  }
@@ -1692,7 +1692,7 @@ body {
1692
1692
  line-height: var(--tw-leading, var(--text-sm--line-height));
1693
1693
  --tw-leading: 1.5;
1694
1694
  line-height: 1.5;
1695
- @media (prefers-color-scheme: dark) {
1695
+ &:where(.dark, .dark *) {
1696
1696
  background-color: color-mix(in srgb, rgb(var(--primary)) 20%, transparent);
1697
1697
  @supports (color: color-mix(in lab, red, red)) {
1698
1698
  background-color: color-mix(in oklab, var(--color-raketa-primary) 20%, transparent);
@@ -1825,7 +1825,7 @@ body {
1825
1825
  color: var(--color-raketa-foreground);
1826
1826
  }
1827
1827
  }
1828
- @media (prefers-color-scheme: dark) {
1828
+ &:where(.dark, .dark *) {
1829
1829
  color: var(--color-red-700);
1830
1830
  }
1831
1831
  }
@@ -1903,18 +1903,18 @@ body {
1903
1903
  padding-block: calc(var(--spacing) * 2);
1904
1904
  --tw-shadow: inset 0 2px 4px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
1905
1905
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1906
- @media (prefers-color-scheme: dark) {
1906
+ &:where(.dark, .dark *) {
1907
1907
  border-color: var(--color-neutral-700);
1908
1908
  }
1909
- @media (prefers-color-scheme: dark) {
1909
+ &:where(.dark, .dark *) {
1910
1910
  background-color: var(--color-neutral-800);
1911
1911
  }
1912
- @media (prefers-color-scheme: dark) {
1912
+ &:where(.dark, .dark *) {
1913
1913
  &:focus {
1914
1914
  border-color: var(--color-raketa-primary);
1915
1915
  }
1916
1916
  }
1917
- @media (prefers-color-scheme: dark) {
1917
+ &:where(.dark, .dark *) {
1918
1918
  &:focus {
1919
1919
  --tw-ring-color: var(--color-raketa-primary);
1920
1920
  }
@@ -1944,7 +1944,7 @@ body {
1944
1944
  padding-block: var(--spacing);
1945
1945
  --tw-shadow: inset 0 2px 4px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
1946
1946
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1947
- @media (prefers-color-scheme: dark) {
1947
+ &:where(.dark, .dark *) {
1948
1948
  border-color: var(--color-neutral-700);
1949
1949
  }
1950
1950
  }
@@ -2057,7 +2057,7 @@ body {
2057
2057
  transition-duration: 100ms;
2058
2058
  --tw-ease: var(--ease-in-out);
2059
2059
  transition-timing-function: var(--ease-in-out);
2060
- @media (prefers-color-scheme: dark) {
2060
+ &:where(.dark, .dark *) {
2061
2061
  border-color: var(--color-neutral-700);
2062
2062
  }
2063
2063
  }
@@ -2069,7 +2069,7 @@ body {
2069
2069
  border-bottom-color: color-mix(in oklab, var(--color-black) 25%, transparent);
2070
2070
  }
2071
2071
  color: var(--color-raketa-foreground);
2072
- @media (prefers-color-scheme: dark) {
2072
+ &:where(.dark, .dark *) {
2073
2073
  border-bottom-color: color-mix(in srgb, #fff 40%, transparent);
2074
2074
  @supports (color: color-mix(in lab, red, red)) {
2075
2075
  border-bottom-color: color-mix(in oklab, var(--color-white) 40%, transparent);
@@ -2082,10 +2082,10 @@ body {
2082
2082
  border-color: var(--color-neutral-300);
2083
2083
  background-color: var(--color-neutral-50);
2084
2084
  color: var(--color-raketa-foreground);
2085
- @media (prefers-color-scheme: dark) {
2085
+ &:where(.dark, .dark *) {
2086
2086
  border-color: var(--color-neutral-600);
2087
2087
  }
2088
- @media (prefers-color-scheme: dark) {
2088
+ &:where(.dark, .dark *) {
2089
2089
  background-color: var(--color-neutral-800);
2090
2090
  }
2091
2091
  }
@@ -2189,10 +2189,10 @@ body {
2189
2189
  border-left-width: 1px;
2190
2190
  background-color: var(--color-slate-50);
2191
2191
  padding: calc(var(--spacing) * 4);
2192
- @media (prefers-color-scheme: dark) {
2192
+ &:where(.dark, .dark *) {
2193
2193
  border-color: var(--color-neutral-700);
2194
2194
  }
2195
- @media (prefers-color-scheme: dark) {
2195
+ &:where(.dark, .dark *) {
2196
2196
  background-color: color-mix(in srgb, oklch(26.9% 0 none) 70%, transparent);
2197
2197
  @supports (color: color-mix(in lab, red, red)) {
2198
2198
  background-color: color-mix(in oklab, var(--color-neutral-800) 70%, transparent);
@@ -2215,7 +2215,7 @@ body {
2215
2215
  height: calc(var(--spacing) * 6);
2216
2216
  width: 1px;
2217
2217
  background-color: var(--color-mono-50);
2218
- @media (prefers-color-scheme: dark) {
2218
+ &:where(.dark, .dark *) {
2219
2219
  background-color: var(--color-neutral-700);
2220
2220
  }
2221
2221
  }
@@ -2492,6 +2492,11 @@ body {
2492
2492
  syntax: "*";
2493
2493
  inherits: false;
2494
2494
  }
2495
+ @property --tw-space-y-reverse {
2496
+ syntax: "*";
2497
+ inherits: false;
2498
+ initial-value: 0;
2499
+ }
2495
2500
  @property --tw-divide-y-reverse {
2496
2501
  syntax: "*";
2497
2502
  inherits: false;
@@ -2724,6 +2729,7 @@ body {
2724
2729
  --tw-rotate-z: initial;
2725
2730
  --tw-skew-x: initial;
2726
2731
  --tw-skew-y: initial;
2732
+ --tw-space-y-reverse: 0;
2727
2733
  --tw-divide-y-reverse: 0;
2728
2734
  --tw-border-style: solid;
2729
2735
  --tw-gradient-position: initial;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "1.10.5",
3
+ "version": "1.10.7",
4
4
  "main": "src/index.js",
5
5
  "files": [
6
6
  "src/",
@@ -7,6 +7,7 @@ import {
7
7
  CreateResourceDocument,
8
8
  DeleteResourceDocument,
9
9
  MiniResourcesDocument,
10
+ MultiPartialTranslateResourceDocument,
10
11
  MultiTranslateResourceDocument,
11
12
  ResourceByIdDocument,
12
13
  ResourceChangePreviewDocument,
@@ -38,6 +39,17 @@ export default class Resources extends RaketaClient {
38
39
  };
39
40
  }
40
41
 
42
+ previewUrl(resource, locale) {
43
+ return buildMagicPreviewAddress(
44
+ {
45
+ slug: resource.slug,
46
+ locale,
47
+ resourceType: resource.resourceType,
48
+ },
49
+ this.getConfig(),
50
+ );
51
+ }
52
+
41
53
  visibilityOptions() {
42
54
  return [
43
55
  ["private", "Private"],
@@ -213,6 +225,33 @@ export default class Resources extends RaketaClient {
213
225
  return { resources, errors, localeErrors };
214
226
  }
215
227
 
228
+ async multiPartialTranslate(
229
+ sourceResourceId,
230
+ locales,
231
+ widgets,
232
+ workflow,
233
+ resource,
234
+ ) {
235
+ const input = {
236
+ sourceResourceId,
237
+ translations: locales.map((locale) => ({
238
+ locale,
239
+ widgets: JSON.stringify(widgets),
240
+ })),
241
+ };
242
+ this.appendWorkflow(input, workflow, {
243
+ slug: resource.slug,
244
+ locale: resource.locale,
245
+ resourceType: resource.resourceType,
246
+ });
247
+
248
+ const {
249
+ multiPartialTranslateResource: { contentTask, errors },
250
+ } = await this.query(MultiPartialTranslateResourceDocument, { input });
251
+
252
+ return { contentTask, errors };
253
+ }
254
+
216
255
  async translateBatch(
217
256
  sourceResourceId,
218
257
  locales,
@@ -28,6 +28,7 @@ export const ContentTasksDocument = gql`
28
28
  lokaliseWorkflow {
29
29
  id
30
30
  status
31
+ partial
31
32
  settings
32
33
  insertedAt
33
34
  updatedAt
@@ -199,6 +199,17 @@ const MultiTranslateResourceDocument = gql`
199
199
  }
200
200
  `;
201
201
 
202
+ const MultiPartialTranslateResourceDocument = gql`
203
+ mutation MultiPartialTranslateResource(
204
+ $input: MultiPartialTranslateResourceInput!
205
+ ) {
206
+ multiPartialTranslateResource(input: $input) {
207
+ contentTask ${contentTaskFragment()}
208
+ errors ${errorsFragment()}
209
+ }
210
+ }
211
+ `;
212
+
202
213
  const ResourceRevisionsDocument = gql`
203
214
  query ResourceRevisions($input: IdTypeInput!) {
204
215
  resourceRevisions(input: $input) {
@@ -351,6 +362,7 @@ export {
351
362
  ResourceAndSiteDocument,
352
363
  TranslateResourceDocument,
353
364
  MultiTranslateResourceDocument,
365
+ MultiPartialTranslateResourceDocument,
354
366
  ResourceRevisionDocument,
355
367
  ResourceRevisionsDocument,
356
368
  StartResourceRevisionDocument,
@@ -0,0 +1,279 @@
1
+ const toArray = (value) =>
2
+ Array.isArray(value) ? value : [value].filter(Boolean);
3
+
4
+ const isActiveContentTask = (task) =>
5
+ task && !task.completedAt && !task.failedAt;
6
+
7
+ const hasActiveContentTask = (resource) =>
8
+ isActiveContentTask(resource.activeContentTask) ||
9
+ Object.values(resource.translations || {}).some((translation) =>
10
+ isActiveContentTask(translation.active_content_task),
11
+ );
12
+
13
+ const hasTranslations = (resource) =>
14
+ Object.values(resource.translations || {}).some(
15
+ (translation) => translation?.id,
16
+ );
17
+
18
+ const targetTranslationLocales = (resource, sourceLocale) =>
19
+ Object.entries(resource.translations || {})
20
+ .filter(
21
+ ([locale, translation]) => locale !== sourceLocale && translation?.id,
22
+ )
23
+ .map(([locale]) => locale);
24
+
25
+ const selectedWidgetIds = (resource, query = {}) => {
26
+ const requested = Array.isArray(query.widget_ids)
27
+ ? query.widget_ids
28
+ : String(query.widget_ids || "")
29
+ .split(",")
30
+ .filter(Boolean);
31
+
32
+ return [...new Set(requested)];
33
+ };
34
+
35
+ const partitionWidgetIds = (resource, widgetIds) => {
36
+ const resourceWidgetIds = new Set(
37
+ (resource.widgets || []).map((widget) => widget.widgetId).filter(Boolean),
38
+ );
39
+
40
+ return {
41
+ validWidgetIds: widgetIds.filter((widgetId) =>
42
+ resourceWidgetIds.has(widgetId),
43
+ ),
44
+ unknownWidgetIds: widgetIds.filter(
45
+ (widgetId) => !resourceWidgetIds.has(widgetId),
46
+ ),
47
+ };
48
+ };
49
+
50
+ const formAssigns = (resource, query, previewUrl, values = {}, errors = {}) => {
51
+ const submittedWidgetIds = values.widgetIds ?? values.widget_ids;
52
+
53
+ return {
54
+ resource,
55
+ previewUrl,
56
+ partialTranslation: {
57
+ locales: [],
58
+ workflow: {},
59
+ ...values,
60
+ widgetIds:
61
+ submittedWidgetIds === undefined
62
+ ? selectedWidgetIds(resource, query)
63
+ : toArray(submittedWidgetIds),
64
+ },
65
+ errors,
66
+ };
67
+ };
68
+
69
+ const validatePartialTranslation = ({
70
+ resource,
71
+ locales,
72
+ widgetIds,
73
+ workflow,
74
+ unknownWidgetIds,
75
+ unknownLocales,
76
+ }) => {
77
+ const errors = [];
78
+
79
+ if (locales.length === 0) {
80
+ errors.push({
81
+ key: "locales",
82
+ message: "select at least one existing translation",
83
+ });
84
+ }
85
+ if (widgetIds.length === 0) {
86
+ errors.push({ key: "widgetIds", message: "can't be blank" });
87
+ }
88
+ if (unknownLocales.length > 0) {
89
+ errors.push({
90
+ key: "locales",
91
+ message: `not found in the resource translations: ${unknownLocales.join(", ")}`,
92
+ });
93
+ }
94
+ if (!workflow.type) {
95
+ errors.push({ key: "workflow", message: "can't be blank" });
96
+ }
97
+ if (unknownWidgetIds.length > 0) {
98
+ errors.push({
99
+ key: "widgetIds",
100
+ message: `not found in the source resource: ${unknownWidgetIds.join(", ")}`,
101
+ });
102
+ }
103
+ if (!hasTranslations(resource)) {
104
+ errors.push({
105
+ key: "generalError",
106
+ message:
107
+ "cannot start a partial translation without existing translations",
108
+ });
109
+ }
110
+ if (hasActiveContentTask(resource)) {
111
+ errors.push({
112
+ key: "generalError",
113
+ message:
114
+ "cannot start a partial translation while a translation task is in progress",
115
+ });
116
+ }
117
+
118
+ return errors;
119
+ };
120
+
121
+ const create_form = async (ctx) => {
122
+ const { parent_id } = ctx.params;
123
+ const { resourceType } = ctx.resourceConfig;
124
+ const resource = await ctx.client.resources.find(
125
+ parent_id,
126
+ ctx.locale,
127
+ resourceType,
128
+ );
129
+
130
+ if (!resource) {
131
+ throw new Error(`Cannot find ${resourceType} with id ${parent_id}`);
132
+ }
133
+
134
+ const widgetIds = selectedWidgetIds(resource, ctx.query);
135
+ const { validWidgetIds, unknownWidgetIds } = partitionWidgetIds(
136
+ resource,
137
+ widgetIds,
138
+ );
139
+
140
+ const previewUrl = ctx.client.resources.previewUrl(
141
+ { ...resource, resourceType },
142
+ ctx.locale,
143
+ );
144
+ const validationErrors = [];
145
+
146
+ if (widgetIds.length === 0) {
147
+ validationErrors.push({ key: "widgetIds", message: "can't be blank" });
148
+ }
149
+ if (unknownWidgetIds.length > 0) {
150
+ validationErrors.push({
151
+ key: "widgetIds",
152
+ message: `not found in the source resource: ${unknownWidgetIds.join(", ")}`,
153
+ });
154
+ }
155
+ if (!hasTranslations(resource)) {
156
+ validationErrors.push({
157
+ key: "generalError",
158
+ message:
159
+ "cannot start a partial translation without existing translations",
160
+ });
161
+ }
162
+ if (hasActiveContentTask(resource)) {
163
+ validationErrors.push({
164
+ key: "generalError",
165
+ message:
166
+ "cannot start a partial translation while a translation task is in progress",
167
+ });
168
+ }
169
+
170
+ if (validationErrors.length > 0) {
171
+ return {
172
+ assigns: formAssigns(
173
+ resource,
174
+ ctx.query,
175
+ previewUrl,
176
+ { widgetIds: validWidgetIds },
177
+ {
178
+ partialTranslation:
179
+ ctx.client.resources.formatErrors(validationErrors),
180
+ },
181
+ ),
182
+ };
183
+ }
184
+
185
+ return { assigns: formAssigns(resource, ctx.query, previewUrl) };
186
+ };
187
+
188
+ const create = async (ctx) => {
189
+ const { parent_id } = ctx.params;
190
+ const { resourceType, resourceTypePlural } = ctx.resourceConfig;
191
+ const resource = await ctx.client.resources.find(
192
+ parent_id,
193
+ ctx.locale,
194
+ resourceType,
195
+ );
196
+
197
+ if (!resource) {
198
+ throw new Error(`Cannot find ${resourceType} with id ${parent_id}`);
199
+ }
200
+
201
+ const previewUrl = ctx.client.resources.previewUrl(
202
+ { ...resource, resourceType },
203
+ ctx.locale,
204
+ );
205
+ const values = ctx.resourceParams?.partial_translation || {};
206
+ const locales = toArray(values.locales);
207
+ const widgetIds =
208
+ values.widget_ids === undefined
209
+ ? selectedWidgetIds({ widgets: [] }, ctx.query)
210
+ : toArray(values.widget_ids);
211
+ const workflow = values.workflow || {};
212
+ const { validWidgetIds, unknownWidgetIds } = partitionWidgetIds(
213
+ resource,
214
+ widgetIds,
215
+ );
216
+ const validLocales = targetTranslationLocales(resource, ctx.locale);
217
+ const unknownLocales = locales.filter(
218
+ (locale) => !validLocales.includes(locale),
219
+ );
220
+ const selectedValidLocales = locales.filter((locale) =>
221
+ validLocales.includes(locale),
222
+ );
223
+
224
+ const validationErrors = validatePartialTranslation({
225
+ resource,
226
+ locales,
227
+ widgetIds,
228
+ workflow,
229
+ unknownWidgetIds,
230
+ unknownLocales,
231
+ });
232
+
233
+ if (validationErrors.length > 0) {
234
+ return {
235
+ assigns: formAssigns(
236
+ resource,
237
+ ctx.query,
238
+ previewUrl,
239
+ {
240
+ ...values,
241
+ locales: selectedValidLocales,
242
+ widgetIds: validWidgetIds,
243
+ workflow,
244
+ },
245
+ {
246
+ partialTranslation:
247
+ ctx.client.resources.formatErrors(validationErrors),
248
+ },
249
+ ),
250
+ };
251
+ }
252
+
253
+ const widgets = (resource.widgets || []).filter((widget) =>
254
+ validWidgetIds.includes(widget.widgetId),
255
+ );
256
+ const { errors } = await ctx.client.resources.multiPartialTranslate(
257
+ parent_id,
258
+ locales,
259
+ widgets,
260
+ workflow,
261
+ { ...resource, locale: ctx.locale, resourceType },
262
+ );
263
+
264
+ if (errors) {
265
+ return {
266
+ assigns: formAssigns(
267
+ resource,
268
+ ctx.query,
269
+ previewUrl,
270
+ { ...values, locales, widgetIds: validWidgetIds, workflow },
271
+ { partialTranslation: ctx.client.resources.formatErrors(errors) },
272
+ ),
273
+ };
274
+ }
275
+
276
+ return { redirect: `/${ctx.locale}/${resourceTypePlural}/${parent_id}` };
277
+ };
278
+
279
+ export default { create_form, create };