@raketa-cloud/admin 1.8.16 → 1.8.17
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/package.json +1 -1
- package/src/HonoAdapter.js +2 -0
- package/src/handlers/content_tasks/ContentTasksHandler.js +0 -25
- package/src/handlers/resources/TranslationsHandler.js +1 -5
- package/src/helpers/content_tasks_helper.js +25 -0
- package/src/tests/handlers/content_tasks/ContentTasksHandler.test/index.test.js +1 -82
- package/src/tests/helpers/content_tasks_helper.test.js +68 -0
- package/src/views/_shared/_content_tasks.ejs +110 -0
- package/src/views/content_tasks/index.ejs +1 -101
- package/src/views/resources/show.ejs +17 -0
- package/src/views/resources/translations/create_form.ejs +1 -0
- package/src/views/resources/translations/index.ejs +27 -5
package/package.json
CHANGED
package/src/HonoAdapter.js
CHANGED
|
@@ -13,6 +13,7 @@ import history from "./helpers/historyUtils";
|
|
|
13
13
|
import pagination from "./helpers/pagination_helper";
|
|
14
14
|
import sort_button from "./helpers/sort_button";
|
|
15
15
|
import utils from "./helpers/utils";
|
|
16
|
+
import * as content_tasks from "./helpers/content_tasks_helper";
|
|
16
17
|
|
|
17
18
|
function escapeHtml(string) {
|
|
18
19
|
const escape = {
|
|
@@ -168,6 +169,7 @@ export default class HonoAdapter {
|
|
|
168
169
|
sort_button,
|
|
169
170
|
pagination,
|
|
170
171
|
Inflector,
|
|
172
|
+
content_tasks,
|
|
171
173
|
};
|
|
172
174
|
}
|
|
173
175
|
|
|
@@ -1,36 +1,11 @@
|
|
|
1
|
-
function buildResourceUrl(resource, resourceRoutes) {
|
|
2
|
-
if (!resource) return "#";
|
|
3
|
-
|
|
4
|
-
const { locale, resourceType, id } = resource;
|
|
5
|
-
|
|
6
|
-
const route = resourceRoutes[resourceType];
|
|
7
|
-
|
|
8
|
-
return `/${locale}/${route}/${id}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const WORKFLOWS_MAP = {
|
|
12
|
-
lokalise_to_ai_rt_hum: "Lokalise: AI translation & Human review",
|
|
13
|
-
lokalise_tt_hum: "Lokalise: Human translation",
|
|
14
|
-
lokalise_tt_ai: "Lokalise: AI translation",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
async function index(ctx) {
|
|
18
2
|
const items = await ctx.client.contentTasks.list(ctx.query);
|
|
19
3
|
const editors = await ctx.client.contentTasks.editors();
|
|
20
|
-
const resourceRoutes = ctx.globalConfig.resourceRoutes;
|
|
21
|
-
|
|
22
|
-
if (!resourceRoutes) {
|
|
23
|
-
throw new Error("Missing resourceRoutes in the admin config!");
|
|
24
|
-
}
|
|
25
4
|
|
|
26
5
|
return {
|
|
27
6
|
assigns: {
|
|
28
7
|
items,
|
|
29
8
|
editors: editors.list || [],
|
|
30
|
-
buildResourceUrl: (resource) =>
|
|
31
|
-
buildResourceUrl(resource, resourceRoutes),
|
|
32
|
-
workflowsMap: (workflowType) =>
|
|
33
|
-
WORKFLOWS_MAP[workflowType] || "Unknown workflow",
|
|
34
9
|
},
|
|
35
10
|
};
|
|
36
11
|
}
|
|
@@ -252,14 +252,10 @@ const create = async (ctx) => {
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
redirect: `/${originalLocale}/${resourceTypePlural}/${parent_id}/translations?translations=${translationIds.join(";")}`,
|
|
258
|
-
};
|
|
259
255
|
}
|
|
260
256
|
|
|
261
257
|
return {
|
|
262
|
-
redirect: `/${originalLocale}/${resourceTypePlural}/${parent_id}`,
|
|
258
|
+
redirect: `/${originalLocale}/${resourceTypePlural}/${parent_id}/translations?translations=${translationIds.join(";")}`,
|
|
263
259
|
};
|
|
264
260
|
};
|
|
265
261
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const WORKFLOWS_MAP = {
|
|
2
|
+
lokalise_to_ai_rt_hum: "Lokalise: AI translation & Human review",
|
|
3
|
+
lokalise_tt_hum: "Lokalise: Human translation",
|
|
4
|
+
lokalise_tt_ai: "Lokalise: AI translation",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const workflowsMap = (workflowType) => {
|
|
8
|
+
return WORKFLOWS_MAP[workflowType] || "Unknown workflow";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const buildResourceUrl = (resource, globalConfig) => {
|
|
12
|
+
if (!resource) return "#";
|
|
13
|
+
|
|
14
|
+
const resourceRoutes = globalConfig.resourceRoutes;
|
|
15
|
+
|
|
16
|
+
if (!resourceRoutes) {
|
|
17
|
+
throw new Error("Missing resourceRoutes in the admin config!");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { locale, resourceType, id } = resource;
|
|
21
|
+
|
|
22
|
+
const route = resourceRoutes[resourceType];
|
|
23
|
+
|
|
24
|
+
return `/${locale}/${route}/${id}`;
|
|
25
|
+
};
|
|
@@ -21,7 +21,7 @@ describe("ContentTasksHandler", () => {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
test("index - returns content tasks with editors
|
|
24
|
+
test("index - returns content tasks with editors", async () => {
|
|
25
25
|
const contentTasks = [
|
|
26
26
|
Factory.build("contentTask", {
|
|
27
27
|
id: "1",
|
|
@@ -76,86 +76,5 @@ describe("ContentTasksHandler", () => {
|
|
|
76
76
|
pagination: { currentPage: 1, pagesCount: 1 },
|
|
77
77
|
});
|
|
78
78
|
expect(result.assigns.editors).toEqual(editors);
|
|
79
|
-
expect(typeof result.assigns.buildResourceUrl).toBe("function");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("index - throws error when resourceRoutes is missing", async () => {
|
|
83
|
-
const ctx = buildCtx();
|
|
84
|
-
ctx.globalConfig = {}; // No resourceRoutes
|
|
85
|
-
|
|
86
|
-
const mockedList = jest
|
|
87
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
88
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
89
|
-
|
|
90
|
-
const mockedEditors = jest
|
|
91
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
92
|
-
.mockImplementation(() => ({ list: [] }));
|
|
93
|
-
|
|
94
|
-
await expect(ContentTasksHandler.index(ctx)).rejects.toThrow(
|
|
95
|
-
"Missing resourceRoutes in the admin config!",
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("buildResourceUrl helper - builds correct URL for standard resource", async () => {
|
|
100
|
-
const ctx = buildCtx();
|
|
101
|
-
|
|
102
|
-
const mockedList = jest
|
|
103
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
104
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
105
|
-
|
|
106
|
-
const mockedEditors = jest
|
|
107
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
108
|
-
.mockImplementation(() => ({ list: [] }));
|
|
109
|
-
|
|
110
|
-
const result = await ContentTasksHandler.index(ctx);
|
|
111
|
-
const { buildResourceUrl } = result.assigns;
|
|
112
|
-
|
|
113
|
-
const resource = {
|
|
114
|
-
id: "123",
|
|
115
|
-
locale: "en",
|
|
116
|
-
resourceType: "page",
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
expect(buildResourceUrl(resource)).toBe("/en/pages/123");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("buildResourceUrl helper - builds correct URL for custom route", async () => {
|
|
123
|
-
const ctx = buildCtx();
|
|
124
|
-
|
|
125
|
-
const mockedList = jest
|
|
126
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
127
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
128
|
-
|
|
129
|
-
const mockedEditors = jest
|
|
130
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
131
|
-
.mockImplementation(() => ({ list: [] }));
|
|
132
|
-
|
|
133
|
-
const result = await ContentTasksHandler.index(ctx);
|
|
134
|
-
const { buildResourceUrl } = result.assigns;
|
|
135
|
-
|
|
136
|
-
const resource = {
|
|
137
|
-
id: "456",
|
|
138
|
-
locale: "de",
|
|
139
|
-
resourceType: "movie-type",
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
expect(buildResourceUrl(resource)).toBe("/de/movie-types/456");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("buildResourceUrl helper - returns # for null resource", async () => {
|
|
146
|
-
const ctx = buildCtx();
|
|
147
|
-
|
|
148
|
-
const mockedList = jest
|
|
149
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
150
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
151
|
-
|
|
152
|
-
const mockedEditors = jest
|
|
153
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
154
|
-
.mockImplementation(() => ({ list: [] }));
|
|
155
|
-
|
|
156
|
-
const result = await ContentTasksHandler.index(ctx);
|
|
157
|
-
const { buildResourceUrl } = result.assigns;
|
|
158
|
-
|
|
159
|
-
expect(buildResourceUrl(null)).toBe("#");
|
|
160
79
|
});
|
|
161
80
|
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
workflowsMap,
|
|
3
|
+
buildResourceUrl,
|
|
4
|
+
} from "../../helpers/content_tasks_helper";
|
|
5
|
+
|
|
6
|
+
describe("content_tasks_helper", () => {
|
|
7
|
+
describe("workflowsMap", () => {
|
|
8
|
+
test("returns correct workflow name for known type", () => {
|
|
9
|
+
expect(workflowsMap("lokalise_to_ai_rt_hum")).toBe(
|
|
10
|
+
"Lokalise: AI translation & Human review",
|
|
11
|
+
);
|
|
12
|
+
expect(workflowsMap("lokalise_tt_hum")).toBe(
|
|
13
|
+
"Lokalise: Human translation",
|
|
14
|
+
);
|
|
15
|
+
expect(workflowsMap("lokalise_tt_ai")).toBe("Lokalise: AI translation");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns 'Unknown workflow' for unknown type", () => {
|
|
19
|
+
expect(workflowsMap("unknown")).toBe("Unknown workflow");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("buildResourceUrl", () => {
|
|
24
|
+
const globalConfig = {
|
|
25
|
+
resourceRoutes: {
|
|
26
|
+
page: "pages",
|
|
27
|
+
author: "authors",
|
|
28
|
+
article: "articles",
|
|
29
|
+
"movie-type": "movie-types",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
test("builds correct URL for standard resource", () => {
|
|
34
|
+
const resource = {
|
|
35
|
+
id: "123",
|
|
36
|
+
locale: "en",
|
|
37
|
+
resourceType: "page",
|
|
38
|
+
};
|
|
39
|
+
expect(buildResourceUrl(resource, globalConfig)).toBe("/en/pages/123");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("builds correct URL for custom route", () => {
|
|
43
|
+
const resource = {
|
|
44
|
+
id: "456",
|
|
45
|
+
locale: "de",
|
|
46
|
+
resourceType: "movie-type",
|
|
47
|
+
};
|
|
48
|
+
expect(buildResourceUrl(resource, globalConfig)).toBe(
|
|
49
|
+
"/de/movie-types/456",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("returns # for null resource", () => {
|
|
54
|
+
expect(buildResourceUrl(null, globalConfig)).toBe("#");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("throws error when resourceRoutes is missing", () => {
|
|
58
|
+
const resource = {
|
|
59
|
+
id: "123",
|
|
60
|
+
locale: "en",
|
|
61
|
+
resourceType: "page",
|
|
62
|
+
};
|
|
63
|
+
expect(() => buildResourceUrl(resource, {})).toThrow(
|
|
64
|
+
"Missing resourceRoutes in the admin config!",
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<% if (tasks.length > 0) { %>
|
|
2
|
+
<table class="data-grid alt">
|
|
3
|
+
<thead>
|
|
4
|
+
<tr>
|
|
5
|
+
<% if (showAllDetails) { %>
|
|
6
|
+
<th>Resource</th>
|
|
7
|
+
<th>Locale</th>
|
|
8
|
+
<% } %>
|
|
9
|
+
<th>Workflow</th>
|
|
10
|
+
<th>Started at</th>
|
|
11
|
+
<% if (showAllDetails) { %>
|
|
12
|
+
<th>Finished at</th>
|
|
13
|
+
<% } %>
|
|
14
|
+
<th>Status</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
|
|
18
|
+
<tbody>
|
|
19
|
+
<% tasks.forEach(task => { %>
|
|
20
|
+
<tr>
|
|
21
|
+
<% if (showAllDetails) { %>
|
|
22
|
+
<td class="w-1/3">
|
|
23
|
+
<% if (task.resource) { %>
|
|
24
|
+
<a href="<%= content_tasks.buildResourceUrl(task.resource, _context.globalConfig) %>" target="_blank" class="btn-text">
|
|
25
|
+
<%= task.resource.title %>
|
|
26
|
+
</a>
|
|
27
|
+
|
|
28
|
+
<div class="body-3 text-raketa-foreground/50 mt-1">
|
|
29
|
+
<strong>Type:</strong> <%= Inflector.humanize(Inflector.capitalize(task.resource.resourceType)) %>
|
|
30
|
+
</div>
|
|
31
|
+
<% } else { %>
|
|
32
|
+
<span class="text-raketa-foreground/50">—</span>
|
|
33
|
+
<% } %>
|
|
34
|
+
</td>
|
|
35
|
+
|
|
36
|
+
<td>
|
|
37
|
+
<% if (task.resource && _context.locales[task.resource.locale]) { %>
|
|
38
|
+
<% const { name, flag } = _context.locales[task.resource.locale] %>
|
|
39
|
+
<img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="size-6 rounded-full border dark:border-neutral-700" />
|
|
40
|
+
<% } else { %>
|
|
41
|
+
<span class="text-raketa-foreground/50">—</span>
|
|
42
|
+
<% } %>
|
|
43
|
+
</td>
|
|
44
|
+
<% } %>
|
|
45
|
+
|
|
46
|
+
<td>
|
|
47
|
+
<span class="body-2">
|
|
48
|
+
<%= content_tasks.workflowsMap(task.workflow) %>
|
|
49
|
+
</span>
|
|
50
|
+
</td>
|
|
51
|
+
|
|
52
|
+
<td>
|
|
53
|
+
<div class="body-3 text-raketa-foreground/50">
|
|
54
|
+
<div class="flex items-center gap-1 mb-0.5">
|
|
55
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
56
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
57
|
+
</svg>
|
|
58
|
+
<time data-pretty-datetime datetime="<%= task.insertedAt %>"><%= utils.formattedDateTime(task.insertedAt) %></time>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="flex items-center gap-1" title="<%= task.operatorMeta?.name %>">
|
|
62
|
+
<% if (task.operatorMeta?.avatar) { %>
|
|
63
|
+
<img src="<%= task.operatorMeta?.avatar %>" class="size-4 rounded-full" alt="<%= task.operatorMeta?.name %>">
|
|
64
|
+
<% } else { %>
|
|
65
|
+
<div class="size-4 rounded-full"></div>
|
|
66
|
+
<% } %>
|
|
67
|
+
<%= task.operatorMeta?.name %>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</td>
|
|
71
|
+
|
|
72
|
+
<% if (showAllDetails) { %>
|
|
73
|
+
<td>
|
|
74
|
+
<% if (task.completedAt || task.failedAt) { %>
|
|
75
|
+
<div class="body-3 text-raketa-foreground/50">
|
|
76
|
+
<div class="flex items-center gap-1">
|
|
77
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
79
|
+
</svg>
|
|
80
|
+
<time data-pretty-datetime datetime="<%= task.completedAt || task.failedAt %>">
|
|
81
|
+
<%= utils.formattedDateTime(task.completedAt || task.failedAt) %>
|
|
82
|
+
</time>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<% } else { %>
|
|
86
|
+
<span class="text-raketa-foreground/50">—</span>
|
|
87
|
+
<% } %>
|
|
88
|
+
</td>
|
|
89
|
+
<% } %>
|
|
90
|
+
|
|
91
|
+
<td>
|
|
92
|
+
<% if (task.completedAt) { %>
|
|
93
|
+
<span class="bg-emerald-100 chip text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300">
|
|
94
|
+
Completed
|
|
95
|
+
</span>
|
|
96
|
+
<% } else if (task.failedAt) { %>
|
|
97
|
+
<span class="bg-red-100 chip text-red-700 dark:bg-red-900/50 dark:text-red-300">
|
|
98
|
+
Failed
|
|
99
|
+
</span>
|
|
100
|
+
<% } else { %>
|
|
101
|
+
<span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
|
102
|
+
In Progress
|
|
103
|
+
</span>
|
|
104
|
+
<% } %>
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
<% }) %>
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
<% } %>
|
|
@@ -11,107 +11,7 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
<thead>
|
|
16
|
-
<tr>
|
|
17
|
-
<th>Resource</th>
|
|
18
|
-
<th>Locale</th>
|
|
19
|
-
<th>Workflow</th>
|
|
20
|
-
<th>Started at</th>
|
|
21
|
-
<th>Finished at</th>
|
|
22
|
-
<th>Status</th>
|
|
23
|
-
</tr>
|
|
24
|
-
</thead>
|
|
25
|
-
|
|
26
|
-
<tbody>
|
|
27
|
-
<% if (items && items.list && items.list.length > 0) { %>
|
|
28
|
-
<% items.list.forEach(task => { %>
|
|
29
|
-
<tr>
|
|
30
|
-
<td class="w-1/3">
|
|
31
|
-
<% if (task.resource) { %>
|
|
32
|
-
<a href="<%= buildResourceUrl(task.resource) %>" target="_blank" class="btn-text">
|
|
33
|
-
<%= task.resource.title %>
|
|
34
|
-
</a>
|
|
35
|
-
<div class="body-3 text-raketa-foreground/50 mt-1">
|
|
36
|
-
<strong>Type:</strong> <%= Inflector.humanize(Inflector.capitalize(task.resource.resourceType)) %>
|
|
37
|
-
</div>
|
|
38
|
-
<% } else { %>
|
|
39
|
-
<span class="text-raketa-foreground/50">—</span>
|
|
40
|
-
<% } %>
|
|
41
|
-
</td>
|
|
42
|
-
|
|
43
|
-
<td>
|
|
44
|
-
<% if (task.resource && _context.locales[task.resource.locale]) { %>
|
|
45
|
-
<% const { name, flag } = _context.locales[task.resource.locale] %>
|
|
46
|
-
<img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="size-6 rounded-full border dark:border-neutral-700" />
|
|
47
|
-
<% } else { %>
|
|
48
|
-
<span class="text-raketa-foreground/50">—</span>
|
|
49
|
-
<% } %>
|
|
50
|
-
</td>
|
|
51
|
-
|
|
52
|
-
<td>
|
|
53
|
-
<span class="body-2">
|
|
54
|
-
<%= workflowsMap(task.workflow) %>
|
|
55
|
-
</span>
|
|
56
|
-
</td>
|
|
57
|
-
|
|
58
|
-
<td>
|
|
59
|
-
<div class="body-3 text-raketa-foreground/50">
|
|
60
|
-
<div class="flex items-center gap-1 mb-0.5">
|
|
61
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
62
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
63
|
-
</svg>
|
|
64
|
-
<time data-pretty-datetime datetime="<%= task.insertedAt %>"><%= utils.formattedDateTime(task.insertedAt) %></time>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div class="flex items-center gap-1" title="<%= task.operatorMeta?.name %>">
|
|
68
|
-
<% if (task.operatorMeta?.avatar) { %>
|
|
69
|
-
<img src="<%= task.operatorMeta?.avatar %>" class="size-4 rounded-full" alt="<%= task.operatorMeta?.name %>">
|
|
70
|
-
<% } else { %>
|
|
71
|
-
<div class="size-4 rounded-full"></div>
|
|
72
|
-
<% } %>
|
|
73
|
-
<%= task.operatorMeta?.name %>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</td>
|
|
77
|
-
|
|
78
|
-
<td>
|
|
79
|
-
<% if (task.completedAt || task.failedAt) { %>
|
|
80
|
-
<div class="body-3 text-raketa-foreground/50">
|
|
81
|
-
<div class="flex items-center gap-1">
|
|
82
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
83
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
84
|
-
</svg>
|
|
85
|
-
<time data-pretty-datetime datetime="<%= task.completedAt || task.failedAt %>">
|
|
86
|
-
<%= utils.formattedDateTime(task.completedAt || task.failedAt) %>
|
|
87
|
-
</time>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
<% } else { %>
|
|
91
|
-
<span class="text-raketa-foreground/50">—</span>
|
|
92
|
-
<% } %>
|
|
93
|
-
</td>
|
|
94
|
-
|
|
95
|
-
<td>
|
|
96
|
-
<% if (task.completedAt) { %>
|
|
97
|
-
<span class="bg-emerald-100 chip text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300">
|
|
98
|
-
Completed
|
|
99
|
-
</span>
|
|
100
|
-
<% } else if (task.failedAt) { %>
|
|
101
|
-
<span class="bg-red-100 chip text-red-700 dark:bg-red-900/50 dark:text-red-300">
|
|
102
|
-
Failed
|
|
103
|
-
</span>
|
|
104
|
-
<% } else { %>
|
|
105
|
-
<span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
|
106
|
-
In Progress
|
|
107
|
-
</span>
|
|
108
|
-
<% } %>
|
|
109
|
-
</td>
|
|
110
|
-
</tr>
|
|
111
|
-
<% }) %>
|
|
112
|
-
<% } %>
|
|
113
|
-
</tbody>
|
|
114
|
-
</table>
|
|
14
|
+
<%- render_partial('../_shared/_content_tasks.ejs', { tasks: items.list || [], showAllDetails: true }) %>
|
|
115
15
|
|
|
116
16
|
<% if (!items || !items.list || items.list.length === 0) { %>
|
|
117
17
|
<div class="empty-state">
|
|
@@ -166,6 +166,23 @@
|
|
|
166
166
|
</div>
|
|
167
167
|
<% } %>
|
|
168
168
|
|
|
169
|
+
<% if (utils.feature("contentTasks", _context) && resource.activeContentTask) { %>
|
|
170
|
+
<div class="mb-4">
|
|
171
|
+
<h4 class="flex items-center gap-2 pb-4 heading-3">
|
|
172
|
+
<svg class="size-4 text-raketa-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
173
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
174
|
+
</svg>
|
|
175
|
+
|
|
176
|
+
Active tasks
|
|
177
|
+
</h4>
|
|
178
|
+
|
|
179
|
+
<%- render_partial('../_shared/_content_tasks.ejs', {
|
|
180
|
+
tasks: [{...resource.activeContentTask, resource: resource}],
|
|
181
|
+
showAllDetails: false }
|
|
182
|
+
) %>
|
|
183
|
+
</div>
|
|
184
|
+
<% } %>
|
|
185
|
+
|
|
169
186
|
<%- render_partial('./_shared/_addons.ejs') %>
|
|
170
187
|
|
|
171
188
|
<div class="mb-4">
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
<ul>
|
|
103
103
|
<li>Content is required to start a Lokalise task.</li>
|
|
104
104
|
<li>The private copy and new revision will be automatically created here in the CMS only after the external task is completed and returned.</li>
|
|
105
|
+
<li>You can track translation progress in the <a href="<%= `/${_context.locale}/content_tasks` %>" target="_blank">Content tasks</a> module.</li>
|
|
105
106
|
</ul>
|
|
106
107
|
</div>
|
|
107
108
|
</div>
|
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
<%
|
|
9
|
+
<% const activeContentTasks = translations?.some(t => Boolean(t.activeContentTask)) %>
|
|
10
|
+
|
|
11
|
+
<% if (translations.length > 0) { %>
|
|
10
12
|
<table class="data-grid alt">
|
|
11
13
|
<thead>
|
|
12
14
|
<tr>
|
|
13
15
|
<th>Locale</th>
|
|
14
|
-
|
|
16
|
+
<% if (activeContentTasks) { %>
|
|
17
|
+
<th>Status</th>
|
|
18
|
+
<% } else { %>
|
|
19
|
+
<th> </th>
|
|
20
|
+
<% } %>
|
|
15
21
|
</tr>
|
|
16
22
|
</thead>
|
|
17
23
|
<tbody>
|
|
@@ -25,9 +31,25 @@
|
|
|
25
31
|
</div>
|
|
26
32
|
</td>
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
<% if (activeContentTasks) { %>
|
|
35
|
+
<td class="whitespace-nowrap">
|
|
36
|
+
<a href="<%= `/${_context.locale}/content_tasks`%>" target="blank">
|
|
37
|
+
<%= content_tasks.workflowsMap(translation.activeContentTask.workflow) %>
|
|
38
|
+
|
|
39
|
+
<% if (translation.activeContentTask.completedAt) { %>
|
|
40
|
+
completed
|
|
41
|
+
<% } else if (translation.activeContentTask.failedAt) { %>
|
|
42
|
+
failed
|
|
43
|
+
<% } else { %>
|
|
44
|
+
in Progress
|
|
45
|
+
<% } %>
|
|
46
|
+
</a>
|
|
47
|
+
</td>
|
|
48
|
+
<% } else { %>
|
|
49
|
+
<td class="whitespace-nowrap">
|
|
50
|
+
<%- render_partial('./_translation_toolbar.ejs', { translation }) %>
|
|
51
|
+
</td>
|
|
52
|
+
<% } %>
|
|
31
53
|
</tr>
|
|
32
54
|
<% }) %>
|
|
33
55
|
</tbody>
|