@raketa-cloud/admin 1.8.9 → 1.8.11

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.
Files changed (106) hide show
  1. package/dist/application.css +113 -24
  2. package/package.json +5 -3
  3. package/src/HonoAdapter.js +23 -23
  4. package/src/Logger.js +7 -8
  5. package/src/RaketaContext.js +1 -0
  6. package/src/cipher.js +2 -2
  7. package/src/cloud/ContentTasks.js +31 -0
  8. package/src/cloud/RaketaClient.js +1 -1
  9. package/src/cloud/Redirects.js +2 -2
  10. package/src/cloud/ResourceChanges.js +1 -1
  11. package/src/cloud/ResourceRevisions.js +2 -2
  12. package/src/cloud/Resources.js +28 -12
  13. package/src/cloud/Tags.js +4 -4
  14. package/src/cloud/documents/common.js +16 -0
  15. package/src/cloud/documents/contentTasksDocuments.js +59 -0
  16. package/src/cloud/documents/resourcesDocuments.js +12 -0
  17. package/src/filters.js +3 -3
  18. package/src/handlers/TagsHandler.js +2 -2
  19. package/src/handlers/_shared/getResourceUpdateDestination.js +1 -1
  20. package/src/handlers/categories/index.js +3 -3
  21. package/src/handlers/content_tasks/ContentTasksHandler.js +32 -0
  22. package/src/handlers/resource_revisions/LocksHandler.js +4 -1
  23. package/src/handlers/resources/ClonesHandler.js +1 -3
  24. package/src/handlers/resources/ExportsHandler.js +1 -5
  25. package/src/handlers/resources/ResourcesHandler.js +1 -1
  26. package/src/handlers/resources/TranslationsHandler.js +28 -19
  27. package/src/handlers/resources/URLsHandler.js +3 -1
  28. package/src/helpers/AuthHelper.js +1 -1
  29. package/src/helpers/FormBuilder.js +14 -14
  30. package/src/helpers/Pagination.js +5 -5
  31. package/src/helpers/historyUtils.js +3 -3
  32. package/src/helpers/utils.js +6 -1
  33. package/src/index.js +2 -1
  34. package/src/routeMember.js +9 -4
  35. package/src/routeNestedCollection.js +14 -29
  36. package/src/routeNestedMember.js +17 -6
  37. package/src/{routeResource.js → routeResourceModule.js} +25 -20
  38. package/src/routeStandard.js +8 -4
  39. package/src/setupAuthentication.js +1 -1
  40. package/src/setupCloud.js +5 -0
  41. package/src/tests/Adapter.test.js +2 -2
  42. package/src/tests/HonoAdapter.coverage.test.js +47 -25
  43. package/src/tests/HonoAdapter.filter.test.js +28 -18
  44. package/src/tests/HonoAdapter.test.js +3 -3
  45. package/src/tests/RaketaClient.test.js +427 -413
  46. package/src/tests/RaketaContext.test.js +1 -1
  47. package/src/tests/buildResourcePath.test.js +1 -1
  48. package/src/tests/cipher.test.js +16 -16
  49. package/src/tests/filters.test.js +24 -24
  50. package/src/tests/handlers/RedirectsHandler.test/destroy.test.js +1 -1
  51. package/src/tests/handlers/TagsHandler.test/create.test.js +2 -2
  52. package/src/tests/handlers/TagsHandler.test/destroy.test.js +1 -1
  53. package/src/tests/handlers/TagsHandler.test/update.test.js +2 -2
  54. package/src/tests/handlers/categories/create.test.js +2 -2
  55. package/src/tests/handlers/categories/destroy.test.js +1 -1
  56. package/src/tests/handlers/content_tasks/ContentTasksHandler.test/index.test.js +161 -0
  57. package/src/tests/handlers/resource_revisions/CancelSchedulesHandler.test/create.test.js +2 -2
  58. package/src/tests/handlers/resource_revisions/CodeHandler.test/update.test.js +1 -1
  59. package/src/tests/handlers/resource_revisions/GeneralHandler.test/update.test.js +1 -1
  60. package/src/tests/handlers/resource_revisions/LocksHandler.test/create.test.js +5 -5
  61. package/src/tests/handlers/resource_revisions/LocksHandler.test/create_form.test.js +6 -2
  62. package/src/tests/handlers/resource_revisions/MetaHandler.test/update.test.js +1 -1
  63. package/src/tests/handlers/resource_revisions/PublishesHandler.test/create.test.js +5 -5
  64. package/src/tests/handlers/resource_revisions/ResourceRevisionsHandler.test/create.test.js +2 -2
  65. package/src/tests/handlers/resource_revisions/ResourceRevisionsHandler.test/destroy.test.js +1 -1
  66. package/src/tests/handlers/resource_revisions/RevertsHandler.test/create.test.js +18 -3
  67. package/src/tests/handlers/resource_revisions/TagHandler.test/update.test.js +1 -1
  68. package/src/tests/handlers/resource_revisions/UnlocksHandler.test/create.test.js +2 -2
  69. package/src/tests/handlers/resource_revisions/VisualHandler.test/update.test.js +1 -1
  70. package/src/tests/handlers/resource_revisions/WidgetHandler.test/update.test.js +2 -2
  71. package/src/tests/handlers/resources/ChangeHandler.test/show.test.js +7 -2
  72. package/src/tests/handlers/resources/ClonesHandler.test/create.test.js +1 -3
  73. package/src/tests/handlers/resources/ExportsHandler.test/index.test.js +8 -8
  74. package/src/tests/handlers/resources/HistoryHandler.test/index.test.js +1 -1
  75. package/src/tests/handlers/resources/ResourcesAPIHandler.test/index.test.js +1 -5
  76. package/src/tests/handlers/resources/ResourcesHandler.test/create.test.js +2 -2
  77. package/src/tests/handlers/resources/ResourcesHandler.test/show.test.js +1 -1
  78. package/src/tests/handlers/resources/TranslationsHandler.test/create.test.js +24 -14
  79. package/src/tests/handlers/resources/TranslationsHandler.test/create_form.test.js +1 -1
  80. package/src/tests/handlers/resources/TranslationsHandler.test/index.test.js +3 -3
  81. package/src/tests/handlers/resources/URLsHandler.test/create.test.js +4 -6
  82. package/src/tests/helpers/AuthHelper.test.js +11 -12
  83. package/src/tests/helpers/CSVExportHelper.test.js +3 -3
  84. package/src/tests/helpers/FormBuilder.test.js +21 -21
  85. package/src/tests/helpers/HTMLBuilder.test.js +4 -4
  86. package/src/tests/helpers/Inflector.test.js +1 -1
  87. package/src/tests/helpers/Pagination.test.js +1 -1
  88. package/src/tests/helpers/utils.test.js +46 -0
  89. package/src/tests/routeCategoriesModule.test.js +10 -5
  90. package/src/tests/routeCollection.test.js +1 -1
  91. package/src/tests/routeMember.test.js +2 -2
  92. package/src/tests/routeNestedCollection.test.js +3 -3
  93. package/src/tests/routeNestedMember.test.js +3 -3
  94. package/src/tests/{routeResource.test.js → routeResourceModule.test.js} +28 -25
  95. package/src/tests/routeSettings.test.js +3 -3
  96. package/src/tests/routeStandard.test.js +31 -13
  97. package/src/tests/setupAuthentication.test.js +9 -6
  98. package/src/tests/support/factory.js +33 -0
  99. package/src/tests/utils.test.js +19 -19
  100. package/src/views/content_tasks/index.ejs +191 -0
  101. package/src/views/layouts/application.ejs +2 -1
  102. package/src/views/resources/_shared/_toolbar.ejs +33 -2
  103. package/src/views/resources/_shared/_translations_strip.ejs +71 -0
  104. package/src/views/resources/index.ejs +5 -0
  105. package/src/views/resources/show.ejs +1 -29
  106. package/src/views/resources/translations/create_form.ejs +13 -0
@@ -0,0 +1,191 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Content Tasks</h1>
4
+
5
+ <div class="actions-toolbar">
6
+ <button type="button" data-toggle-sidebar="1" class="p-2.5 rounded hover:bg-neutral-200 transition-all duration-300 ease-in-out dark:hover:bg-neutral-700">
7
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
8
+ <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
9
+ </svg>
10
+ </button>
11
+ </div>
12
+ </div>
13
+
14
+ <table class="data-grid">
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
+ <%= Inflector.capitalize(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 || task.rejectedAt) { %>
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 || task.rejectedAt %>">
86
+ <%= utils.formattedDateTime(task.completedAt || task.failedAt || task.rejectedAt) %>
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 if (task.rejectedAt) { %>
105
+ <span class="bg-orange-100 chip text-orange-700 dark:bg-orange-900/50 dark:text-orange-300">
106
+ Rejected
107
+ </span>
108
+ <% } else { %>
109
+ <span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
110
+ In Progress
111
+ </span>
112
+ <% } %>
113
+ </td>
114
+ </tr>
115
+ <% }) %>
116
+ <% } %>
117
+ </tbody>
118
+ </table>
119
+
120
+ <% if (!items || !items.list || items.list.length === 0) { %>
121
+ <div class="empty-state">
122
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-12">
123
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" />
124
+ </svg>
125
+ <h3 class="heading-3 mt-4">No content tasks</h3>
126
+ <p class="body-2 text-raketa-foreground/70 mt-2">
127
+ <% if (Object.keys(_context.query).some(k => k.startsWith('f:'))) { %>
128
+ No tasks match the selected filters. Try adjusting your filters.
129
+ <% } else { %>
130
+ No translation workflow tasks have been created yet.
131
+ <% } %>
132
+ </p>
133
+ </div>
134
+ <% } %>
135
+
136
+ <%- pagination(items.pagination, _context) %>
137
+ </div>
138
+
139
+ <div data-sidebar="1" class="search-sidebar <%= Object.keys(_context.query).some(k => k.startsWith('f:')) ? '' : 'hidden' %>">
140
+ <span class="heading-2">Filter by</span>
141
+
142
+ <form action="/<%= _context.locale %>/content_tasks" method="GET" class="form mt-4">
143
+ <input type="hidden" name="order_direction" value="<%= _context.query.order_direction %>" />
144
+ <input type="hidden" name="order_by" value="<%= _context.query.order_by %>" />
145
+
146
+ <div class="mb-4">
147
+ <label for="f:locale" class="font-semibold hover:cursor-pointer body-3">Locale</label>
148
+ <select name="f:locale" id="f:locale">
149
+ <option value="">All Locales</option>
150
+ <% Object.keys(_context.locales).forEach(key => { %>
151
+ <option value="<%= key %>" <%= _context.query['f:locale'] === key ? 'selected' : '' %>>
152
+ <%= _context.locales[key].name %> (<%= key.toUpperCase() %>)
153
+ </option>
154
+ <% }) %>
155
+ </select>
156
+ </div>
157
+
158
+ <div class="mb-4">
159
+ <label for="f:author" class="font-semibold hover:cursor-pointer body-3">Author</label>
160
+ <select name="f:author" id="f:author">
161
+ <option value="">All Authors</option>
162
+ <% editors.forEach(editor => { %>
163
+ <option value="<%= editor.id %>" <%= _context.query['f:author'] === editor.id ? 'selected' : '' %>>
164
+ <%= editor.name %>
165
+ </option>
166
+ <% }) %>
167
+ </select>
168
+ </div>
169
+
170
+ <div class="mb-4">
171
+ <label for="f:status" class="font-semibold hover:cursor-pointer body-3">Status</label>
172
+ <select name="f:status" id="f:status">
173
+ <option value="">All Statuses</option>
174
+ <option value="IN_PROGRESS" <%= _context.query['f:status'] === 'IN_PROGRESS' ? 'selected' : '' %>>
175
+ In Progress
176
+ </option>
177
+ <option value="COMPLETED" <%= _context.query['f:status'] === 'COMPLETED' ? 'selected' : '' %>>
178
+ Completed
179
+ </option>
180
+ <option value="FAILED" <%= _context.query['f:status'] === 'FAILED' ? 'selected' : '' %>>
181
+ Failed
182
+ </option>
183
+ </select>
184
+ </div>
185
+
186
+ <div class="btn-toolbar">
187
+ <button type="submit" class="btn-primary">Filter</button>
188
+ <a href="/<%= _context.locale %>/content_tasks" class="btn-text">Clear filters</a>
189
+ </div>
190
+ </form>
191
+ </div>
@@ -66,7 +66,8 @@
66
66
 
67
67
  <div class="my-4">
68
68
  <% _context.layout.sidebar.forEach((menu, idx) => { %>
69
- <% if (menu.submenus) { %>
69
+ <% if (!menu) { %>
70
+ <% } else if (menu.submenus) { %>
70
71
  <% const pathMatchesMenuItem = menu.submenus.some(item => _context.path.includes(item.link)) %>
71
72
 
72
73
  <div class="relative group-[.expanded]:hidden group/item">
@@ -1,7 +1,14 @@
1
1
  <div>
2
+ <%
3
+ const hasActiveContentTask = utils.feature("contentTasks", _context) &&
4
+ resource.activeContentTask &&
5
+ !resource.activeContentTask.completedAt &&
6
+ !resource.activeContentTask.failedAt &&
7
+ !resource.activeContentTask.rejectedAt;
8
+ %>
2
9
  <div class="actions-toolbar">
3
10
  <form method="POST" action="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/${_context.resourceConfig.resourceType}_revisions` %>" class="inline-block">
4
- <% if (resource.revisionsCount >= _context?.maxOpenRevisions) { %>
11
+ <% if (resource.revisionsCount >= _context?.maxOpenRevisions || hasActiveContentTask) { %>
5
12
  <button type="submit" class="btn-primary" disabled style="opacity: 50%;">
6
13
  Start revision
7
14
  </button>
@@ -12,6 +19,15 @@
12
19
  <% } %>
13
20
  </form>
14
21
 
22
+ <% if (hasActiveContentTask) { %>
23
+ <button type="button" class="btn-text has-icon" disabled style="opacity: 50%; cursor: not-allowed;">
24
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
25
+ <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
26
+ </svg>
27
+
28
+ Change URL
29
+ </button>
30
+ <% } else { %>
15
31
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/urls/new` %>" class="btn-text has-icon">
16
32
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
17
33
  <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
@@ -19,9 +35,10 @@
19
35
 
20
36
  Change URL
21
37
  </a>
38
+ <% } %>
22
39
 
23
40
  <form action="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/clones` %>" method="post" class="inline-block">
24
- <button type="submit" class="btn-text has-icon">
41
+ <button type="submit" class="btn-text has-icon" <%= hasActiveContentTask ? 'disabled style="opacity: 50%;"' : '' %>>
25
42
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
26
43
  <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
27
44
  </svg>
@@ -30,6 +47,15 @@
30
47
  </button>
31
48
  </form>
32
49
 
50
+ <% if (hasActiveContentTask) { %>
51
+ <button type="button" class="btn-danger has-icon" disabled style="opacity: 50%; cursor: not-allowed;">
52
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
53
+ <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
54
+ </svg>
55
+
56
+ Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.resourceType)) %>
57
+ </button>
58
+ <% } else { %>
33
59
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/deletes/new` %>" class="btn-danger has-icon">
34
60
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
35
61
  <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
@@ -37,9 +63,14 @@
37
63
 
38
64
  Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.resourceType)) %>
39
65
  </a>
66
+ <% } %>
40
67
  </div>
41
68
 
42
69
  <% if (_context.query?.revisions_count_error) { %>
43
70
  <div class="text-red-600">Cannot open a new revision. Current limit is <%= _context.maxOpenRevisions %>. </div>
44
71
  <% } %>
72
+
73
+ <% if (hasActiveContentTask) { %>
74
+ <div class="text-orange-700 mt-2 dark:text-orange-300">Actions are disabled while a content task is in progress.</div>
75
+ <% } %>
45
76
  </div>
@@ -0,0 +1,71 @@
1
+ <div class="flex flex-wrap items-center gap-1 mb-6 -my-4">
2
+ <%
3
+ const availableLocales = Object.keys(_context.locales);
4
+ const totalPossibleTranslations = availableLocales.length - 1;
5
+ const existingTranslationsCount = Object.keys(resource.translations).length;
6
+ const canBeTranslated = existingTranslationsCount < totalPossibleTranslations;
7
+ %>
8
+
9
+ <% Object.keys(_context.locales).forEach(key => { %>
10
+ <% const { name, _code, flag} = _context.locales[key] %>
11
+ <% if (key === _context.locale) { %>
12
+ <%
13
+ const currentHasActiveTask = utils.feature("contentTasks", _context) &&
14
+ resource.activeContentTask &&
15
+ !resource.activeContentTask.completedAt &&
16
+ !resource.activeContentTask.failedAt &&
17
+ !resource.activeContentTask.rejectedAt;
18
+
19
+ const contentTaskMessage = currentHasActiveTask ? `${name}: ${Inflector.capitalize(resource.activeContentTask.workflow)} task in progress` : '';
20
+ %>
21
+
22
+ <span class="relative inline-block" title="<%= currentHasActiveTask ? contentTaskMessage : "" %>">
23
+ <img
24
+ title="<%= currentHasActiveTask ? contentTaskMessage : name %>"
25
+ src="<%= flag %>"
26
+ alt="<%= name %>"
27
+ class="border rounded-full size-6 dark:border-neutral-700"
28
+ />
29
+
30
+ <% if (currentHasActiveTask) { %>
31
+ <span class="absolute -top-0.5 -right-0.5 flex h-3 w-3">
32
+ <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-raketa-primary opacity-75"></span>
33
+ <span class="relative inline-flex rounded-full h-3 w-3 bg-raketa-primary"></span>
34
+ </span>
35
+ <% } %>
36
+ </span>
37
+ <% } else if (resource.translations[key]) { %>
38
+ <% const hasActiveTask = resource.translations[key].active_content_task; %>
39
+ <% const contentTaskMessage = hasActiveTask ? `${name}: ${Inflector.capitalize(resource.translations[key].active_content_task.workflow)} task in progress` : ""; %>
40
+ <a
41
+ href="<%= `/${key}/${_context.resourceConfig.resourceTypePlural}/${resource.translations[key].id}` %>"
42
+ class="relative inline-block"
43
+ title="<%= hasActiveTask ? contentTaskMessage : "" %>"
44
+ >
45
+ <img
46
+ title="<%= hasActiveTask ? contentTaskMessage : name %>"
47
+ src="<%= flag %>"
48
+ alt="<%= name %>"
49
+ class="border rounded-full size-6 dark:border-neutral-700"
50
+ />
51
+
52
+ <% if (hasActiveTask) { %>
53
+ <span class="absolute -top-0.5 -right-0.5 flex h-3 w-3">
54
+ <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-raketa-primary opacity-75"></span>
55
+ <span class="relative inline-flex rounded-full h-3 w-3 bg-raketa-primary"></span>
56
+ </span>
57
+ <% } %>
58
+ </a>
59
+ <% } else { %>
60
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/translations/new?target_locale=${key}` %>">
61
+ <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="<%= [" transition-all ease-in-out duration-300 size-6 rounded-full border hover:opacity-100 dark:border-neutral-700", key === _context.locale ? "opacity-100" : "opacity-10"].join(" ") %>" />
62
+ </a>
63
+ <% } %>
64
+ <% }); %>
65
+
66
+ <% if (canBeTranslated) { %>
67
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/translations/new` %>" class="ml-2 btn-text">
68
+ Translate
69
+ </a>
70
+ <% } %>
71
+ </div>
@@ -40,6 +40,11 @@
40
40
  <% if (item.revisionsCount > 0) { %>
41
41
  <span class="bg-orange-100 chip text-orange-700 dark:bg-orange-900/50 dark:text-orange-300"><%= item.revisionsCount !== 1 ? `${item.revisionsCount} open revisions` : 'Open revision' %></span>
42
42
  <% } %>
43
+ <% if (utils.feature("contentTasks", _context) && item.activeContentTask && !item.activeContentTask.completedAt && !item.activeContentTask.failedAt && !item.activeContentTask.rejectedAt) { %>
44
+ <span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" title="<%= item.activeContentTask.workflow ? `${item.activeContentTask.workflow} workflow` : 'Translation in progress' %>">
45
+ Content task in progress
46
+ </span>
47
+ <% } %>
43
48
 
44
49
  <% if (item.visibility !== "private") { %>
45
50
  <div class="mt-1">
@@ -4,35 +4,7 @@
4
4
  <%- render_partial('./_shared/_toolbar.ejs') %>
5
5
  </div>
6
6
 
7
- <div class="flex flex-wrap items-center gap-1 mb-6 -my-4">
8
- <%
9
- const availableLocales = Object.keys(_context.locales);
10
- const totalPossibleTranslations = availableLocales.length - 1;
11
- const existingTranslationsCount = Object.keys(resource.translations).length;
12
- const canBeTranslated = existingTranslationsCount < totalPossibleTranslations;
13
- %>
14
-
15
- <% Object.keys(_context.locales).forEach(key => { %>
16
- <% const { name, _code, flag} = _context.locales[key] %>
17
- <% if (key === _context.locale) { %>
18
- <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="border rounded-full size-6 dark:border-neutral-700" />
19
- <% } else if (resource.translations[key]) { %>
20
- <a href="<%= `/${key}/${_context.resourceConfig.resourceTypePlural}/${resource.translations[key].id}` %>">
21
- <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="border rounded-full size-6 dark:border-neutral-700" />
22
- </a>
23
- <% } else { %>
24
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/translations/new?target_locale=${key}` %>">
25
- <img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="<%= [" transition-all ease-in-out duration-300 size-6 rounded-full border hover:opacity-100 dark:border-neutral-700", key === _context.locale ? "opacity-100" : "opacity-10"].join(" ") %>" />
26
- </a>
27
- <% } %>
28
- <% }); %>
29
-
30
- <% if (canBeTranslated) { %>
31
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/translations/new` %>" class="ml-2 btn-text">
32
- Translate
33
- </a>
34
- <% } %>
35
- </div>
7
+ <%- render_partial('./_shared/_translations_strip.ejs') %>
36
8
 
37
9
  <%
38
10
  const openedRevisions = []
@@ -30,6 +30,19 @@
30
30
  <% }) %>
31
31
  </div>
32
32
 
33
+ <% if (utils.feature("contentTasks", _context) && _context.locale === _context.defaultLocale) { %>
34
+ <div class="mb-4">
35
+ <label for="workflow" class="font-semibold hover:cursor-pointer body-3">Translation workflow</label>
36
+ <select name="resource[workflow]" id="workflow">
37
+ <option value="">Default (no workflow)</option>
38
+ <option value="lokalise" <%= resource.workflow === 'lokalise' ? 'selected' : '' %>>Lokalise workflow</option>
39
+ </select>
40
+ <p class="text-sm text-raketa-foreground/50 mt-1">
41
+ Select a workflow for the translation process. Lokalise workflow initiates a content task for external translation.
42
+ </p>
43
+ </div>
44
+ <% } %>
45
+
33
46
  <% if (errors?.resource && targetLocale) { %>
34
47
  <div>
35
48
  <% if (targetLocale !== _context.locale) { %>