@raketa-cloud/admin 1.10.6 → 1.10.8
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/application.css +73 -87
- package/package.json +1 -1
- package/src/handlers/resources/PartialTranslationsHandler.js +85 -16
- package/src/styles/application.css +4 -0
- package/src/tests/handlers/resources/PartialTranslationsHandler.test.js +25 -0
- package/src/tests/views/partial_translations.test.js +11 -0
- package/src/views/resource_revisions/_nav.ejs +1 -1
- package/src/views/resources/partial-translations/create_form.ejs +4 -3
- package/src/views/settings/_nav.ejs +1 -1
package/dist/application.css
CHANGED
|
@@ -1160,92 +1160,78 @@
|
|
|
1160
1160
|
padding-right: 0px;
|
|
1161
1161
|
}
|
|
1162
1162
|
}
|
|
1163
|
-
|
|
1164
|
-
:
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 50%, transparent);
|
|
1175
|
-
}
|
|
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);
|
|
1176
1174
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1177
|
-
|
|
1178
|
-
background-color: color-mix(in oklab, var(--color-blue-900) 50%, transparent);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
.dark\:bg-emerald-900\/50 {
|
|
1182
|
-
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);
|
|
1183
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);
|
|
1184
1180
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1185
|
-
|
|
1186
|
-
background-color: color-mix(in oklab, var(--color-emerald-900) 50%, transparent);
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
.dark\:bg-neutral-800 {
|
|
1190
|
-
background-color: var(--color-neutral-800);
|
|
1191
|
-
}
|
|
1192
|
-
.dark\:bg-orange-900\/50 {
|
|
1193
|
-
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);
|
|
1194
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);
|
|
1195
1189
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1196
|
-
|
|
1197
|
-
background-color: color-mix(in oklab, var(--color-orange-900) 50%, transparent);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
.dark\:bg-orange-950\/40 {
|
|
1201
|
-
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);
|
|
1202
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);
|
|
1203
1195
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1204
|
-
|
|
1205
|
-
background-color: color-mix(in oklab, var(--color-orange-950) 40%, transparent);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
.dark\:bg-raketa-primary\/15 {
|
|
1209
|
-
background-color: color-mix(in srgb, rgb(var(--primary)) 15%, transparent);
|
|
1196
|
+
background-color: color-mix(in oklab, var(--color-orange-950) 40%, transparent);
|
|
1210
1197
|
}
|
|
1198
|
+
}
|
|
1199
|
+
.dark\:bg-raketa-primary\/15:where(.dark, .dark *) {
|
|
1200
|
+
background-color: color-mix(in srgb, rgb(var(--primary)) 15%, transparent);
|
|
1211
1201
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1212
|
-
|
|
1213
|
-
background-color: color-mix(in oklab, var(--color-raketa-primary) 15%, transparent);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
.dark\:bg-red-900\/50 {
|
|
1217
|
-
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);
|
|
1218
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);
|
|
1219
1207
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1220
|
-
|
|
1221
|
-
background-color: color-mix(in oklab, var(--color-red-900) 50%, transparent);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
.dark\:text-blue-300 {
|
|
1225
|
-
color: var(--color-blue-300);
|
|
1226
|
-
}
|
|
1227
|
-
.dark\:text-emerald-300 {
|
|
1228
|
-
color: var(--color-emerald-300);
|
|
1229
|
-
}
|
|
1230
|
-
.dark\:text-orange-300 {
|
|
1231
|
-
color: var(--color-orange-300);
|
|
1208
|
+
background-color: color-mix(in oklab, var(--color-red-900) 50%, transparent);
|
|
1232
1209
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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);
|
|
1244
1230
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
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);
|
|
1249
1235
|
}
|
|
1250
1236
|
}
|
|
1251
1237
|
}
|
|
@@ -1706,7 +1692,7 @@ body {
|
|
|
1706
1692
|
line-height: var(--tw-leading, var(--text-sm--line-height));
|
|
1707
1693
|
--tw-leading: 1.5;
|
|
1708
1694
|
line-height: 1.5;
|
|
1709
|
-
|
|
1695
|
+
&:where(.dark, .dark *) {
|
|
1710
1696
|
background-color: color-mix(in srgb, rgb(var(--primary)) 20%, transparent);
|
|
1711
1697
|
@supports (color: color-mix(in lab, red, red)) {
|
|
1712
1698
|
background-color: color-mix(in oklab, var(--color-raketa-primary) 20%, transparent);
|
|
@@ -1839,7 +1825,7 @@ body {
|
|
|
1839
1825
|
color: var(--color-raketa-foreground);
|
|
1840
1826
|
}
|
|
1841
1827
|
}
|
|
1842
|
-
|
|
1828
|
+
&:where(.dark, .dark *) {
|
|
1843
1829
|
color: var(--color-red-700);
|
|
1844
1830
|
}
|
|
1845
1831
|
}
|
|
@@ -1917,18 +1903,18 @@ body {
|
|
|
1917
1903
|
padding-block: calc(var(--spacing) * 2);
|
|
1918
1904
|
--tw-shadow: inset 0 2px 4px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
|
1919
1905
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
1920
|
-
|
|
1906
|
+
&:where(.dark, .dark *) {
|
|
1921
1907
|
border-color: var(--color-neutral-700);
|
|
1922
1908
|
}
|
|
1923
|
-
|
|
1909
|
+
&:where(.dark, .dark *) {
|
|
1924
1910
|
background-color: var(--color-neutral-800);
|
|
1925
1911
|
}
|
|
1926
|
-
|
|
1912
|
+
&:where(.dark, .dark *) {
|
|
1927
1913
|
&:focus {
|
|
1928
1914
|
border-color: var(--color-raketa-primary);
|
|
1929
1915
|
}
|
|
1930
1916
|
}
|
|
1931
|
-
|
|
1917
|
+
&:where(.dark, .dark *) {
|
|
1932
1918
|
&:focus {
|
|
1933
1919
|
--tw-ring-color: var(--color-raketa-primary);
|
|
1934
1920
|
}
|
|
@@ -1958,7 +1944,7 @@ body {
|
|
|
1958
1944
|
padding-block: var(--spacing);
|
|
1959
1945
|
--tw-shadow: inset 0 2px 4px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
|
1960
1946
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
1961
|
-
|
|
1947
|
+
&:where(.dark, .dark *) {
|
|
1962
1948
|
border-color: var(--color-neutral-700);
|
|
1963
1949
|
}
|
|
1964
1950
|
}
|
|
@@ -2071,7 +2057,7 @@ body {
|
|
|
2071
2057
|
transition-duration: 100ms;
|
|
2072
2058
|
--tw-ease: var(--ease-in-out);
|
|
2073
2059
|
transition-timing-function: var(--ease-in-out);
|
|
2074
|
-
|
|
2060
|
+
&:where(.dark, .dark *) {
|
|
2075
2061
|
border-color: var(--color-neutral-700);
|
|
2076
2062
|
}
|
|
2077
2063
|
}
|
|
@@ -2083,7 +2069,7 @@ body {
|
|
|
2083
2069
|
border-bottom-color: color-mix(in oklab, var(--color-black) 25%, transparent);
|
|
2084
2070
|
}
|
|
2085
2071
|
color: var(--color-raketa-foreground);
|
|
2086
|
-
|
|
2072
|
+
&:where(.dark, .dark *) {
|
|
2087
2073
|
border-bottom-color: color-mix(in srgb, #fff 40%, transparent);
|
|
2088
2074
|
@supports (color: color-mix(in lab, red, red)) {
|
|
2089
2075
|
border-bottom-color: color-mix(in oklab, var(--color-white) 40%, transparent);
|
|
@@ -2096,10 +2082,10 @@ body {
|
|
|
2096
2082
|
border-color: var(--color-neutral-300);
|
|
2097
2083
|
background-color: var(--color-neutral-50);
|
|
2098
2084
|
color: var(--color-raketa-foreground);
|
|
2099
|
-
|
|
2085
|
+
&:where(.dark, .dark *) {
|
|
2100
2086
|
border-color: var(--color-neutral-600);
|
|
2101
2087
|
}
|
|
2102
|
-
|
|
2088
|
+
&:where(.dark, .dark *) {
|
|
2103
2089
|
background-color: var(--color-neutral-800);
|
|
2104
2090
|
}
|
|
2105
2091
|
}
|
|
@@ -2203,10 +2189,10 @@ body {
|
|
|
2203
2189
|
border-left-width: 1px;
|
|
2204
2190
|
background-color: var(--color-slate-50);
|
|
2205
2191
|
padding: calc(var(--spacing) * 4);
|
|
2206
|
-
|
|
2192
|
+
&:where(.dark, .dark *) {
|
|
2207
2193
|
border-color: var(--color-neutral-700);
|
|
2208
2194
|
}
|
|
2209
|
-
|
|
2195
|
+
&:where(.dark, .dark *) {
|
|
2210
2196
|
background-color: color-mix(in srgb, oklch(26.9% 0 none) 70%, transparent);
|
|
2211
2197
|
@supports (color: color-mix(in lab, red, red)) {
|
|
2212
2198
|
background-color: color-mix(in oklab, var(--color-neutral-800) 70%, transparent);
|
|
@@ -2229,7 +2215,7 @@ body {
|
|
|
2229
2215
|
height: calc(var(--spacing) * 6);
|
|
2230
2216
|
width: 1px;
|
|
2231
2217
|
background-color: var(--color-mono-50);
|
|
2232
|
-
|
|
2218
|
+
&:where(.dark, .dark *) {
|
|
2233
2219
|
background-color: var(--color-neutral-700);
|
|
2234
2220
|
}
|
|
2235
2221
|
}
|
package/package.json
CHANGED
|
@@ -22,6 +22,39 @@ const targetTranslationLocales = (resource, sourceLocale) =>
|
|
|
22
22
|
)
|
|
23
23
|
.map(([locale]) => locale);
|
|
24
24
|
|
|
25
|
+
const localesAtOpenRevisionLimit = (resource, sourceLocale, maxOpenRevisions) =>
|
|
26
|
+
Object.entries(resource.translations || {})
|
|
27
|
+
.filter(
|
|
28
|
+
([locale, translation]) =>
|
|
29
|
+
locale !== sourceLocale &&
|
|
30
|
+
translation?.id &&
|
|
31
|
+
translation.revisionsCount >= maxOpenRevisions,
|
|
32
|
+
)
|
|
33
|
+
.map(([locale]) => locale);
|
|
34
|
+
|
|
35
|
+
const withTranslationRevisionCounts = async (ctx, resource, resourceType) => {
|
|
36
|
+
const revisionCounts = await Promise.all(
|
|
37
|
+
Object.entries(resource.translations || {}).map(
|
|
38
|
+
async ([locale, translation]) => {
|
|
39
|
+
if (locale === ctx.locale || !translation?.id) {
|
|
40
|
+
return [locale, translation];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const target = await ctx.client.resources.find(
|
|
44
|
+
translation.id,
|
|
45
|
+
locale,
|
|
46
|
+
resourceType,
|
|
47
|
+
);
|
|
48
|
+
return [
|
|
49
|
+
locale,
|
|
50
|
+
{ ...translation, revisionsCount: target.revisionsCount },
|
|
51
|
+
];
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return { ...resource, translations: Object.fromEntries(revisionCounts) };
|
|
57
|
+
};
|
|
25
58
|
const selectedWidgetIds = (resource, query = {}) => {
|
|
26
59
|
const requested = Array.isArray(query.widget_ids)
|
|
27
60
|
? query.widget_ids
|
|
@@ -73,6 +106,7 @@ const validatePartialTranslation = ({
|
|
|
73
106
|
workflow,
|
|
74
107
|
unknownWidgetIds,
|
|
75
108
|
unknownLocales,
|
|
109
|
+
localesAtRevisionLimit,
|
|
76
110
|
}) => {
|
|
77
111
|
const errors = [];
|
|
78
112
|
|
|
@@ -91,6 +125,12 @@ const validatePartialTranslation = ({
|
|
|
91
125
|
message: `not found in the resource translations: ${unknownLocales.join(", ")}`,
|
|
92
126
|
});
|
|
93
127
|
}
|
|
128
|
+
if (localesAtRevisionLimit.length > 0) {
|
|
129
|
+
errors.push({
|
|
130
|
+
key: "locales",
|
|
131
|
+
message: `open revision limit reached for: ${localesAtRevisionLimit.join(", ")}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
94
134
|
if (!workflow.type) {
|
|
95
135
|
errors.push({ key: "workflow", message: "can't be blank" });
|
|
96
136
|
}
|
|
@@ -131,14 +171,19 @@ const create_form = async (ctx) => {
|
|
|
131
171
|
throw new Error(`Cannot find ${resourceType} with id ${parent_id}`);
|
|
132
172
|
}
|
|
133
173
|
|
|
134
|
-
const
|
|
174
|
+
const resourceWithTranslationRevisionCounts =
|
|
175
|
+
await withTranslationRevisionCounts(ctx, resource, resourceType);
|
|
176
|
+
const widgetIds = selectedWidgetIds(
|
|
177
|
+
resourceWithTranslationRevisionCounts,
|
|
178
|
+
ctx.query,
|
|
179
|
+
);
|
|
135
180
|
const { validWidgetIds, unknownWidgetIds } = partitionWidgetIds(
|
|
136
|
-
|
|
181
|
+
resourceWithTranslationRevisionCounts,
|
|
137
182
|
widgetIds,
|
|
138
183
|
);
|
|
139
184
|
|
|
140
185
|
const previewUrl = ctx.client.resources.previewUrl(
|
|
141
|
-
{ ...
|
|
186
|
+
{ ...resourceWithTranslationRevisionCounts, resourceType },
|
|
142
187
|
ctx.locale,
|
|
143
188
|
);
|
|
144
189
|
const validationErrors = [];
|
|
@@ -170,7 +215,7 @@ const create_form = async (ctx) => {
|
|
|
170
215
|
if (validationErrors.length > 0) {
|
|
171
216
|
return {
|
|
172
217
|
assigns: formAssigns(
|
|
173
|
-
|
|
218
|
+
resourceWithTranslationRevisionCounts,
|
|
174
219
|
ctx.query,
|
|
175
220
|
previewUrl,
|
|
176
221
|
{ widgetIds: validWidgetIds },
|
|
@@ -182,7 +227,13 @@ const create_form = async (ctx) => {
|
|
|
182
227
|
};
|
|
183
228
|
}
|
|
184
229
|
|
|
185
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
assigns: formAssigns(
|
|
232
|
+
resourceWithTranslationRevisionCounts,
|
|
233
|
+
ctx.query,
|
|
234
|
+
previewUrl,
|
|
235
|
+
),
|
|
236
|
+
};
|
|
186
237
|
};
|
|
187
238
|
|
|
188
239
|
const create = async (ctx) => {
|
|
@@ -198,8 +249,10 @@ const create = async (ctx) => {
|
|
|
198
249
|
throw new Error(`Cannot find ${resourceType} with id ${parent_id}`);
|
|
199
250
|
}
|
|
200
251
|
|
|
252
|
+
const resourceWithTranslationRevisionCounts =
|
|
253
|
+
await withTranslationRevisionCounts(ctx, resource, resourceType);
|
|
201
254
|
const previewUrl = ctx.client.resources.previewUrl(
|
|
202
|
-
{ ...
|
|
255
|
+
{ ...resourceWithTranslationRevisionCounts, resourceType },
|
|
203
256
|
ctx.locale,
|
|
204
257
|
);
|
|
205
258
|
const values = ctx.resourceParams?.partial_translation || {};
|
|
@@ -210,30 +263,42 @@ const create = async (ctx) => {
|
|
|
210
263
|
: toArray(values.widget_ids);
|
|
211
264
|
const workflow = values.workflow || {};
|
|
212
265
|
const { validWidgetIds, unknownWidgetIds } = partitionWidgetIds(
|
|
213
|
-
|
|
266
|
+
resourceWithTranslationRevisionCounts,
|
|
214
267
|
widgetIds,
|
|
215
268
|
);
|
|
216
|
-
const validLocales = targetTranslationLocales(
|
|
269
|
+
const validLocales = targetTranslationLocales(
|
|
270
|
+
resourceWithTranslationRevisionCounts,
|
|
271
|
+
ctx.locale,
|
|
272
|
+
);
|
|
217
273
|
const unknownLocales = locales.filter(
|
|
218
274
|
(locale) => !validLocales.includes(locale),
|
|
219
275
|
);
|
|
220
|
-
const
|
|
221
|
-
|
|
276
|
+
const localesAtRevisionLimit = localesAtOpenRevisionLimit(
|
|
277
|
+
resourceWithTranslationRevisionCounts,
|
|
278
|
+
ctx.locale,
|
|
279
|
+
ctx.maxOpenRevisions,
|
|
280
|
+
);
|
|
281
|
+
const selectedValidLocales = locales.filter(
|
|
282
|
+
(locale) =>
|
|
283
|
+
validLocales.includes(locale) && !localesAtRevisionLimit.includes(locale),
|
|
222
284
|
);
|
|
223
285
|
|
|
224
286
|
const validationErrors = validatePartialTranslation({
|
|
225
|
-
resource,
|
|
287
|
+
resource: resourceWithTranslationRevisionCounts,
|
|
226
288
|
locales,
|
|
227
289
|
widgetIds,
|
|
228
290
|
workflow,
|
|
229
291
|
unknownWidgetIds,
|
|
230
292
|
unknownLocales,
|
|
293
|
+
localesAtRevisionLimit: locales.filter((locale) =>
|
|
294
|
+
localesAtRevisionLimit.includes(locale),
|
|
295
|
+
),
|
|
231
296
|
});
|
|
232
297
|
|
|
233
298
|
if (validationErrors.length > 0) {
|
|
234
299
|
return {
|
|
235
300
|
assigns: formAssigns(
|
|
236
|
-
|
|
301
|
+
resourceWithTranslationRevisionCounts,
|
|
237
302
|
ctx.query,
|
|
238
303
|
previewUrl,
|
|
239
304
|
{
|
|
@@ -250,21 +315,25 @@ const create = async (ctx) => {
|
|
|
250
315
|
};
|
|
251
316
|
}
|
|
252
317
|
|
|
253
|
-
const widgets = (
|
|
254
|
-
validWidgetIds.includes(widget.widgetId),
|
|
318
|
+
const widgets = (resourceWithTranslationRevisionCounts.widgets || []).filter(
|
|
319
|
+
(widget) => validWidgetIds.includes(widget.widgetId),
|
|
255
320
|
);
|
|
256
321
|
const { errors } = await ctx.client.resources.multiPartialTranslate(
|
|
257
322
|
parent_id,
|
|
258
323
|
locales,
|
|
259
324
|
widgets,
|
|
260
325
|
workflow,
|
|
261
|
-
{
|
|
326
|
+
{
|
|
327
|
+
...resourceWithTranslationRevisionCounts,
|
|
328
|
+
locale: ctx.locale,
|
|
329
|
+
resourceType,
|
|
330
|
+
},
|
|
262
331
|
);
|
|
263
332
|
|
|
264
333
|
if (errors) {
|
|
265
334
|
return {
|
|
266
335
|
assigns: formAssigns(
|
|
267
|
-
|
|
336
|
+
resourceWithTranslationRevisionCounts,
|
|
268
337
|
ctx.query,
|
|
269
338
|
previewUrl,
|
|
270
339
|
{ ...values, locales, widgetIds: validWidgetIds, workflow },
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Keep dark utilities opt-in through a `.dark` ancestor, rather than the OS theme. */
|
|
4
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
5
|
+
|
|
2
6
|
@plugin "@tailwindcss/forms";
|
|
3
7
|
@plugin "@tailwindcss/typography";
|
|
4
8
|
@source "../**/*.{html,js,ejs}";
|
|
@@ -24,6 +24,7 @@ const buildCtx = (params = {}) => ({
|
|
|
24
24
|
params: { parent_id: "42" },
|
|
25
25
|
query: {},
|
|
26
26
|
locale: "en",
|
|
27
|
+
maxOpenRevisions: 1,
|
|
27
28
|
resourceConfig: { resourceType: "page", resourceTypePlural: "pages" },
|
|
28
29
|
resourceParams: params,
|
|
29
30
|
});
|
|
@@ -194,6 +195,30 @@ describe("PartialTranslationsHandler", () => {
|
|
|
194
195
|
});
|
|
195
196
|
});
|
|
196
197
|
|
|
198
|
+
test("does not submit to locales at the open revision limit", async () => {
|
|
199
|
+
const ctx = buildCtx({
|
|
200
|
+
partial_translation: {
|
|
201
|
+
locales: ["fr"],
|
|
202
|
+
widget_ids: ["hero-1"],
|
|
203
|
+
workflow: { type: "LOKALISE_TT_AI" },
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
ctx.client.resources.find
|
|
207
|
+
.mockResolvedValueOnce({
|
|
208
|
+
...resource,
|
|
209
|
+
translations: { fr: { id: "43" } },
|
|
210
|
+
})
|
|
211
|
+
.mockResolvedValueOnce({ id: "43", revisionsCount: 1 });
|
|
212
|
+
|
|
213
|
+
const result = await PartialTranslationsHandler.create(ctx);
|
|
214
|
+
|
|
215
|
+
expect(ctx.client.resources.multiPartialTranslate).not.toHaveBeenCalled();
|
|
216
|
+
expect(result.assigns.partialTranslation.locales).toEqual([]);
|
|
217
|
+
expect(result.assigns.errors.partialTranslation).toEqual({
|
|
218
|
+
locales: "open revision limit reached for: fr",
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
197
222
|
test("does not submit when a translation has an active content task", async () => {
|
|
198
223
|
const ctx = buildCtx({
|
|
199
224
|
partial_translation: {
|
|
@@ -10,6 +10,7 @@ function renderPartialTranslations(translations) {
|
|
|
10
10
|
return ejs.render(template, {
|
|
11
11
|
_context: {
|
|
12
12
|
locale: "en",
|
|
13
|
+
maxOpenRevisions: 1,
|
|
13
14
|
locales: { en: { name: "English" }, fr: { name: "French" } },
|
|
14
15
|
resourceConfig: { resourceTypePlural: "pages" },
|
|
15
16
|
},
|
|
@@ -47,4 +48,14 @@ describe("partial translations form", () => {
|
|
|
47
48
|
expect(rendered).not.toContain('value="de"');
|
|
48
49
|
expect(rendered).not.toContain("This resource has no translations.");
|
|
49
50
|
});
|
|
51
|
+
|
|
52
|
+
test("disables translations at the open revision limit", () => {
|
|
53
|
+
const rendered = renderPartialTranslations({
|
|
54
|
+
fr: { id: "43", revisionsCount: 1 },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(rendered).toContain('value="fr"');
|
|
58
|
+
expect(rendered).toContain('value="fr" disabled');
|
|
59
|
+
expect(rendered).toContain("open revision limit reached");
|
|
60
|
+
});
|
|
50
61
|
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
<div class="flex gap-4 w-full pb-4">
|
|
11
11
|
<div class="flex items-center justify-between mb-4">
|
|
12
|
-
<div class="flex items-center border-b-2">
|
|
12
|
+
<div class="flex items-center border-b-2 border-neutral-200 dark:border-neutral-700">
|
|
13
13
|
<div class=" <%= active === 'general' ? activeClass : inactiveClass %>">
|
|
14
14
|
<a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceType}_revisions/${resource.id}/general/edit` %>" class="text-raketa-primary font-semibold">
|
|
15
15
|
General
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
<div class="mb-4">
|
|
12
12
|
<h6>Current translations</h6>
|
|
13
13
|
<div class="checkbox-group !max-h-fit">
|
|
14
|
-
<% targetTranslations.forEach(([locale]) => { %>
|
|
15
|
-
<label>
|
|
16
|
-
<input type="checkbox" name="partial_translation[locales][]" value="<%= locale %>" <%= partialTranslation.locales.includes(locale) ? 'checked' : '' %>>
|
|
14
|
+
<% targetTranslations.forEach(([locale, translation]) => { const atRevisionLimit = translation.revisionsCount >= _context.maxOpenRevisions; %>
|
|
15
|
+
<label class="<%= atRevisionLimit ? 'opacity-50' : '' %>">
|
|
16
|
+
<input type="checkbox" name="partial_translation[locales][]" value="<%= locale %>" <%= partialTranslation.locales.includes(locale) ? 'checked' : '' %> <%= atRevisionLimit ? 'disabled' : '' %>>
|
|
17
17
|
<%= _context.locales[locale]?.name || locale %>
|
|
18
|
+
<% if (atRevisionLimit) { %><span class="text-sm text-raketa-foreground/50">(open revision limit reached)</span><% } %>
|
|
18
19
|
</label>
|
|
19
20
|
<% }) %>
|
|
20
21
|
</div>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<div class="flex w-full pb-4">
|
|
7
7
|
<div class="flex items-center justify-between mb-4">
|
|
8
|
-
<div class="flex items-center border-b-2">
|
|
8
|
+
<div class="flex items-center border-b-2 border-neutral-200 dark:border-neutral-700">
|
|
9
9
|
<div class="<%= active === 'meta' ? activeClass : inactiveClass %>">
|
|
10
10
|
<a href="<%= `/${_context.locale}/settings/meta/edit` %>" class="text-raketa-primary font-semibold">
|
|
11
11
|
Meta and OpenGraph
|