@crowdin/app-project-module 0.108.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/out/app-test/integration/get-integration-files.js +16 -4
  2. package/out/app-test/integration/types.d.ts +1 -1
  3. package/out/app-test/integration/update-crowdin.js +2 -2
  4. package/out/app-test/integration/update-integration.js +2 -2
  5. package/out/index.d.ts +2 -1
  6. package/out/index.js +73 -30
  7. package/out/middlewares/auto-credentials-masker.d.ts +4 -0
  8. package/out/middlewares/auto-credentials-masker.js +45 -0
  9. package/out/middlewares/crowdin-client.d.ts +1 -1
  10. package/out/middlewares/integration-credentials.d.ts +2 -2
  11. package/out/middlewares/integration-credentials.js +7 -4
  12. package/out/middlewares/render-ui-module.d.ts +3 -3
  13. package/out/middlewares/render-ui-module.js +10 -13
  14. package/out/middlewares/ui-module.d.ts +1 -1
  15. package/out/middlewares/ui-module.js +11 -1
  16. package/out/modules/about.d.ts +2 -1
  17. package/out/modules/about.js +10 -3
  18. package/out/modules/ai-prompt-provider/handlers/compile.d.ts +1 -1
  19. package/out/modules/ai-prompt-provider/handlers/compile.js +7 -1
  20. package/out/modules/ai-prompt-provider/index.js +2 -2
  21. package/out/modules/ai-prompt-provider/types.d.ts +7 -1
  22. package/out/modules/ai-provider/handlers/chat-completions.d.ts +1 -1
  23. package/out/modules/ai-provider/handlers/chat-completions.js +1 -1
  24. package/out/modules/ai-provider/handlers/get-model-list.d.ts +1 -1
  25. package/out/modules/ai-provider/index.js +2 -2
  26. package/out/modules/ai-provider/types.d.ts +4 -4
  27. package/out/modules/ai-request-processors/handler.d.ts +1 -1
  28. package/out/modules/ai-request-processors/handler.js +12 -2
  29. package/out/modules/ai-request-processors/types.d.ts +12 -2
  30. package/out/modules/ai-tools/handlers/tool-calls.d.ts +1 -1
  31. package/out/modules/ai-tools/handlers/tool-calls.js +1 -1
  32. package/out/modules/ai-tools/index.js +1 -1
  33. package/out/modules/ai-tools/types.d.ts +1 -1
  34. package/out/modules/auth-guard/handlers/verify.d.ts +1 -1
  35. package/out/modules/auth-guard/index.js +1 -1
  36. package/out/modules/automation-action/handlers/execute.d.ts +1 -1
  37. package/out/modules/automation-action/handlers/execute.js +1 -1
  38. package/out/modules/automation-action/handlers/input-schema.d.ts +1 -1
  39. package/out/modules/automation-action/handlers/output-schema.d.ts +1 -1
  40. package/out/modules/automation-action/handlers/validate-settings.d.ts +1 -1
  41. package/out/modules/automation-action/handlers/validate-settings.js +1 -1
  42. package/out/modules/automation-action/index.js +1 -1
  43. package/out/modules/automation-action/types.d.ts +4 -4
  44. package/out/modules/automation-action/util/index.js +2 -5
  45. package/out/modules/context-menu/index.js +2 -2
  46. package/out/modules/custom-mt/handlers/translate.d.ts +1 -1
  47. package/out/modules/custom-mt/handlers/translate.js +25 -9
  48. package/out/modules/custom-mt/index.js +3 -3
  49. package/out/modules/custom-mt/types.d.ts +10 -2
  50. package/out/modules/custom-spell-check/handlers/get-languages-list.d.ts +1 -1
  51. package/out/modules/custom-spell-check/handlers/spell-check.d.ts +1 -1
  52. package/out/modules/custom-spell-check/index.js +4 -4
  53. package/out/modules/editor-right-panel/index.js +1 -1
  54. package/out/modules/external-qa-check/handlers/validate.d.ts +1 -1
  55. package/out/modules/external-qa-check/index.js +2 -2
  56. package/out/modules/file-processing/handlers/custom-file-format.d.ts +7 -2
  57. package/out/modules/file-processing/handlers/custom-file-format.js +62 -22
  58. package/out/modules/file-processing/handlers/file-download.d.ts +1 -1
  59. package/out/modules/file-processing/handlers/file-download.js +5 -0
  60. package/out/modules/file-processing/handlers/pre-post-process.d.ts +1 -1
  61. package/out/modules/file-processing/handlers/pre-post-process.js +41 -15
  62. package/out/modules/file-processing/handlers/translations-alignment.d.ts +1 -1
  63. package/out/modules/file-processing/handlers/translations-alignment.js +10 -4
  64. package/out/modules/file-processing/index.js +12 -2
  65. package/out/modules/file-processing/types.d.ts +36 -5
  66. package/out/modules/file-processing/util/defaults.js +50 -6
  67. package/out/modules/file-processing/util/files.js +2 -1
  68. package/out/modules/form-data-display.d.ts +1 -1
  69. package/out/modules/form-data-save.d.ts +1 -1
  70. package/out/modules/install.d.ts +1 -1
  71. package/out/modules/integration/handlers/crowdin-file-progress.d.ts +1 -1
  72. package/out/modules/integration/handlers/crowdin-file-progress.js +5 -1
  73. package/out/modules/integration/handlers/crowdin-files-target-languages.d.ts +5 -0
  74. package/out/modules/integration/handlers/crowdin-files-target-languages.js +103 -0
  75. package/out/modules/integration/handlers/crowdin-files.d.ts +1 -1
  76. package/out/modules/integration/handlers/crowdin-files.js +7 -1
  77. package/out/modules/integration/handlers/crowdin-project.d.ts +1 -1
  78. package/out/modules/integration/handlers/crowdin-update.d.ts +1 -1
  79. package/out/modules/integration/handlers/crowdin-update.js +59 -18
  80. package/out/modules/integration/handlers/crowdin-webhook.d.ts +1 -1
  81. package/out/modules/integration/handlers/crowdin-webhook.js +15 -8
  82. package/out/modules/integration/handlers/integration-data.d.ts +1 -1
  83. package/out/modules/integration/handlers/integration-data.js +13 -6
  84. package/out/modules/integration/handlers/integration-login.d.ts +1 -1
  85. package/out/modules/integration/handlers/integration-logout.d.ts +1 -1
  86. package/out/modules/integration/handlers/integration-logout.js +8 -3
  87. package/out/modules/integration/handlers/integration-update.d.ts +1 -1
  88. package/out/modules/integration/handlers/integration-update.js +44 -6
  89. package/out/modules/integration/handlers/integration-webhook.d.ts +1 -1
  90. package/out/modules/integration/handlers/invite-users.d.ts +1 -1
  91. package/out/modules/integration/handlers/job-cancel.d.ts +1 -1
  92. package/out/modules/integration/handlers/job-info-deprecated.d.ts +1 -1
  93. package/out/modules/integration/handlers/job-info-deprecated.js +3 -2
  94. package/out/modules/integration/handlers/job-info.d.ts +1 -1
  95. package/out/modules/integration/handlers/job-list.d.ts +1 -1
  96. package/out/modules/integration/handlers/main.d.ts +1 -1
  97. package/out/modules/integration/handlers/main.js +32 -2
  98. package/out/modules/integration/handlers/oauth-login.d.ts +1 -1
  99. package/out/modules/integration/handlers/oauth-login.js +28 -6
  100. package/out/modules/integration/handlers/oauth-polling.d.ts +1 -1
  101. package/out/modules/integration/handlers/oauth-url.d.ts +1 -1
  102. package/out/modules/integration/handlers/settings-save.d.ts +1 -1
  103. package/out/modules/integration/handlers/settings-save.js +9 -9
  104. package/out/modules/integration/handlers/settings.d.ts +1 -1
  105. package/out/modules/integration/handlers/sync-settings-save.d.ts +1 -1
  106. package/out/modules/integration/handlers/sync-settings-save.js +5 -9
  107. package/out/modules/integration/handlers/sync-settings.d.ts +1 -1
  108. package/out/modules/integration/handlers/user-errors.d.ts +1 -1
  109. package/out/modules/integration/handlers/users.d.ts +1 -1
  110. package/out/modules/integration/index.js +19 -33
  111. package/out/modules/integration/types.d.ts +133 -29
  112. package/out/modules/integration/types.js +1 -0
  113. package/out/modules/integration/util/cron.d.ts +2 -2
  114. package/out/modules/integration/util/cron.js +40 -24
  115. package/out/modules/integration/util/defaults.d.ts +4 -0
  116. package/out/modules/integration/util/defaults.js +175 -15
  117. package/out/modules/integration/util/files.d.ts +2 -1
  118. package/out/modules/integration/util/files.js +19 -8
  119. package/out/modules/integration/util/job.js +4 -3
  120. package/out/modules/integration/util/snapshot.js +19 -3
  121. package/out/modules/integration/util/types.d.ts +1 -0
  122. package/out/modules/integration/util/types.js +1 -0
  123. package/out/modules/integration/util/webhooks.d.ts +8 -8
  124. package/out/modules/integration/util/webhooks.js +34 -17
  125. package/out/modules/manifest.js +12 -12
  126. package/out/modules/modal/index.js +2 -2
  127. package/out/modules/organization-menu/index.js +5 -4
  128. package/out/modules/organization-settings-menu/index.js +5 -4
  129. package/out/modules/profile-resources-menu/index.js +5 -4
  130. package/out/modules/profile-settings-menu/index.js +5 -4
  131. package/out/modules/project-menu/index.js +1 -1
  132. package/out/modules/project-menu-crowdsource/index.js +1 -1
  133. package/out/modules/project-reports/index.js +3 -2
  134. package/out/modules/project-tools/index.js +3 -2
  135. package/out/modules/status.d.ts +1 -1
  136. package/out/modules/status.js +12 -3
  137. package/out/modules/subscription-paid.d.ts +1 -1
  138. package/out/modules/uninstall.d.ts +1 -1
  139. package/out/modules/uninstall.js +1 -1
  140. package/out/modules/webhooks/handlers/webhook-handler.d.ts +2 -2
  141. package/out/modules/webhooks/handlers/webhook-handler.js +33 -22
  142. package/out/modules/webhooks/types.d.ts +7 -0
  143. package/out/modules/workflow-step-type/handlers/delete-step.d.ts +1 -1
  144. package/out/modules/workflow-step-type/handlers/step-settings-save.d.ts +1 -1
  145. package/out/modules/workflow-step-type/index.js +2 -2
  146. package/out/modules/workflow-step-type/util/index.js +3 -6
  147. package/out/static/js/dependent.js +16 -7
  148. package/out/static/ui/error.bundle.js +474 -0
  149. package/out/static/ui/error.bundle.js.map +1 -0
  150. package/out/static/ui/install.bundle.js +459 -0
  151. package/out/static/ui/install.bundle.js.map +1 -0
  152. package/out/static/ui/login.bundle.js +630 -0
  153. package/out/static/ui/login.bundle.js.map +1 -0
  154. package/out/static/ui/main.bundle.js +2109 -0
  155. package/out/static/ui/main.bundle.js.map +1 -0
  156. package/out/static/ui/oauth.bundle.js +467 -0
  157. package/out/static/ui/oauth.bundle.js.map +1 -0
  158. package/out/storage/d1.d.ts +107 -0
  159. package/out/storage/d1.js +831 -0
  160. package/out/storage/index.js +8 -1
  161. package/out/storage/mysql.js +22 -19
  162. package/out/storage/postgre.d.ts +0 -1
  163. package/out/storage/postgre.js +20 -30
  164. package/out/storage/sqlite.d.ts +2 -5
  165. package/out/storage/sqlite.js +29 -69
  166. package/out/types.d.ts +78 -18
  167. package/out/util/connection.js +1 -1
  168. package/out/util/credentials-masker.d.ts +3 -3
  169. package/out/util/credentials-masker.js +14 -20
  170. package/out/util/cron.d.ts +29 -0
  171. package/out/util/cron.js +87 -0
  172. package/out/util/index.d.ts +14 -1
  173. package/out/util/index.js +121 -10
  174. package/out/util/jsx-renderer.d.ts +14 -0
  175. package/out/util/jsx-renderer.js +35 -0
  176. package/out/util/logger.d.ts +1 -0
  177. package/out/util/logger.js +2 -2
  178. package/out/util/static-files.d.ts +19 -0
  179. package/out/util/static-files.js +87 -0
  180. package/out/util/subscription.js +1 -1
  181. package/out/views/AboutPage.d.ts +10 -0
  182. package/out/views/AboutPage.js +76 -0
  183. package/out/views/FormPage.d.ts +14 -0
  184. package/out/views/FormPage.js +28 -0
  185. package/out/views/SubscriptionPage.d.ts +7 -0
  186. package/out/views/SubscriptionPage.js +26 -0
  187. package/out/views/index.d.ts +8 -0
  188. package/out/views/index.js +15 -0
  189. package/out/views/layout/Head.d.ts +9 -0
  190. package/out/views/layout/Head.js +54 -0
  191. package/package.json +43 -41
  192. package/out/util/handlebars.d.ts +0 -1
  193. package/out/util/handlebars.js +0 -46
  194. package/out/views/about.handlebars +0 -102
  195. package/out/views/error.handlebars +0 -54
  196. package/out/views/form.handlebars +0 -31
  197. package/out/views/install.handlebars +0 -16
  198. package/out/views/login.handlebars +0 -332
  199. package/out/views/main.handlebars +0 -2042
  200. package/out/views/oauth.handlebars +0 -11
  201. package/out/views/partials/head.handlebars +0 -53
  202. package/out/views/subscription.handlebars +0 -26
@@ -1,2042 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- {{> head}}
4
-
5
- <body>
6
- <div class="i_w">
7
- <div class="top">
8
- {{#if notice}}
9
- <crowdin-alert
10
- id="notice"
11
- title="{{notice.title}}"
12
- {{#unless notice.icon}}
13
- no-icon="true"
14
- {{/unless}}
15
- {{#if notice.type}}
16
- type={{notice.type}}
17
- {{/if}}
18
- style="display: none; margin-bottom: 12px;"
19
- >
20
- <div class="box-center">
21
- <p class="m-0">{{{notice.content}}}</p>
22
- </div>
23
- {{#if notice.close}}
24
- <crowdin-button onclick="closeAlert(this, 'notice')" class="dismiss-alert" icon>close</crowdin-button>
25
- {{/if}}
26
- </crowdin-alert>
27
- {{/if}}
28
- {{#if checkSubscription}}
29
- <crowdin-alert id="subscription-info" no-icon="true" type="warning" style="display: none; margin-bottom: 12px;">
30
- <div class="box-center">
31
- <p class="m-0"></p>
32
- <crowdin-button class="ml-1" primary onclick="window.open(subscriptionLink,'_blank')">Subscribe</crowdin-button>
33
- </div>
34
- </crowdin-alert>
35
- {{/if}}
36
- <div id="buttons">
37
- <crowdin-button id="show-integration-btn" class="hidden" icon-before="arrow_back" onclick="showIntegration();">Integration</crowdin-button>
38
- <crowdin-button id="show-error-logs-btn" icon-before="list" onclick="showErrorLogs();">Error logs</crowdin-button>
39
- {{#if isManager}}
40
- <crowdin-button icon-before="link" onclick="showPermissionsDialog()">Share</crowdin-button>
41
- {{/if}}
42
- {{#if infoModal}}
43
- <crowdin-button icon-before="info" onclick="openModal(infoModal);">{{infoModal.title}}</crowdin-button>
44
- {{/if}}
45
- {{#if configurationFields}}
46
- <crowdin-button icon-before="settings" onclick="openModal(settingsModal);fillSettingsForm();">Settings</crowdin-button>
47
- {{/if}}
48
- <crowdin-button
49
- {{#unless isOwner}}
50
- disabled
51
- title="Only the owner can log out"
52
- {{/unless}}
53
- icon-before="account_circle"
54
- onclick="integrationLogout()"
55
- >Log out</crowdin-button>
56
- </div>
57
- </div>
58
- <crowdin-simple-integration
59
- async-progress
60
- {{#if syncNewElements.crowdin}}
61
- skip-crowdin-auto-schedule
62
- {{/if}}
63
- {{#if syncNewElements.integration}}
64
- skip-integration-auto-schedule
65
- {{/if}}
66
- {{#or withCronSync.crowdin webhooks.crowdin}}
67
- crowdin-schedule="true"
68
- {{/or}}
69
- {{#or withCronSync.integration webhooks.integration}}
70
- integration-schedule="true"
71
- {{/or}}
72
- {{#if integrationOneLevelFetching}}
73
- integration-one-level-fetching="true"
74
- {{/if}}
75
- {{#if integrationSearchListener}}
76
- allow-integration-filter-change-listener="true"
77
- {{/if}}
78
- {{#if integrationPagination}}
79
- integration-load-more-files="true"
80
- {{/if}}
81
- integration-name="{{name}}"
82
- integration-logo="logo.png"
83
- {{#if uploadTranslations}}
84
- {{#if excludedTargetLanguages}}
85
- {{#if forcePushSources}}
86
- integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}, {"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}, {"label":"Force Sync", "title":"Force sync files to Crowdin", "action":"forcePushSources"}]'
87
- {{else}}
88
- integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}, {"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}]'
89
- {{/if}}
90
- {{else}}
91
- {{#if forcePushSources}}
92
- integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}, {"label":"Force Sync", "title":"Force sync files to Crowdin", "action":"forcePushSources"}]'
93
- {{else}}
94
- integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}]'
95
- {{/if}}
96
- {{/if}}
97
- {{else}}
98
- {{#if excludedTargetLanguages}}
99
- {{#if forcePushSources}}
100
- integration-button-menu-items='[{"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}, {"label":"Force Sync", "title":"Force sync files to Crowdin", "action":"forcePushSources"}]'
101
- {{else}}
102
- integration-button-menu-items='[{"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}]'
103
- {{/if}}
104
- {{else}}
105
- {{#if forcePushSources}}
106
- integration-button-menu-items='[{"label":"Force Sync", "title":"Force sync files to Crowdin", "action":"forcePushSources"}]'
107
- {{/if}}
108
- {{/if}}
109
- {{/if}}
110
- {{#if forcePushTranslations}}
111
- crowdin-button-menu-items='[{"label":"Force Sync Translations", "action":"forcePushTranslations"}]'
112
- {{/if}}
113
- {{#if filtering.crowdinLanguages}}
114
- crowdin-filter
115
- {{/if}}
116
- {{#if filtering.integrationFilterConfig}}
117
- integration-filter
118
- integration-filter-config='{{filtering.integrationFilterConfig}}'
119
- {{/if}}
120
- >
121
- </crowdin-simple-integration>
122
- <div id="user-errors" class="hidden">
123
- <crowdin-show-as-table
124
- is-loading
125
- id="user-errors-table"
126
- is-searchable
127
- total-records="25"
128
- search-placeholder="Search something"
129
- ></crowdin-show-as-table>
130
- <crowdin-alert>This table displays the most recent error logs from the past month. Logs older than one month will be automatically deleted.</crowdin-alert>
131
- </div>
132
- </div>
133
- <crowdin-toasts></crowdin-toasts>
134
- <crowdin-async-progress
135
- cancelAsyncAction=""
136
- ></crowdin-async-progress>
137
- <crowdin-modal
138
- style="display: none;"
139
- id="subscription-modal"
140
- modal-width="50"
141
- close-button="false"
142
- >
143
- <div>
144
- <crowdin-alert type="warning">Subscribe to continue using the {{name}} app.</crowdin-alert>
145
- </div>
146
- <div slot="footer">
147
- <crowdin-button primary onclick="window.open(subscriptionLink,'_blank')">Subscribe</crowdin-button>
148
- <crowdin-button outlined onclick="window.open('https://crowdin.com/contacts','_blank')">Contact us</crowdin-button>
149
- <crowdin-button class="ml-10" secondary onclick="integrationLogout()">Log out</crowdin-button>
150
- </div>
151
- </crowdin-modal>
152
-
153
- <crowdin-modal
154
- style="display: none;"
155
- id="permissions-modal"
156
- modal-title="Share"
157
- close-button-title="Close"
158
- close-button
159
- body-overflow-unset
160
- >
161
- <div class="loader hidden">
162
- <crowdin-progress-indicator></crowdin-progress-indicator>
163
- </div>
164
- <div class="permissions-modal-content">
165
- <div class="select-users-block">
166
- <crowdin-users-select
167
- allow-new-options
168
- is-multi
169
- is-searchable
170
- id="users"
171
- key="users"
172
- label="Users"
173
- help-text="Search for members by name, username, email, or invite new ones using their email."
174
- is-position-fixed
175
- onchange="checkUsers()"
176
- >
177
- </crowdin-select>
178
- </div>
179
- <div class="select-users-info">
180
- <div class="confirm-users-block mt-2 hidden">
181
- <table>
182
- <thead>
183
- <tr>
184
- <th style="width: 40%;">Access</th>
185
- <th style="width: 35%;">Users</th>
186
- <th class="status" style="width: 20%;">Upcoming changes</th>
187
- </tr>
188
- </thead>
189
- <tbody>
190
- {{#if hasOrganization}}
191
- <tr class="organization-invite">
192
- <td>
193
- <crowdin-p>Registration in Organization</crowdin-p>
194
- <div class="permission-description"></div>
195
- </td>
196
- <td class="affected-users"></td>
197
- <td class="status"></td>
198
- </tr>
199
- {{/if}}
200
- <tr class="project-invite">
201
- <td>
202
- <crowdin-p>Project Manager Access</crowdin-p>
203
- </td>
204
- <td class="affected-users"></td>
205
- <td class="status"></td>
206
- </tr>
207
- <tr class="application-settings-invite">
208
- <td>
209
- <crowdin-p>Application Visibility Access</crowdin-p>
210
- <div class="permission-description"></div>
211
- </td>
212
- <td class="affected-users"></td>
213
- <td class="status"></td>
214
- </tr>
215
- <tr class="application-credentials-invite">
216
- <td>
217
- <crowdin-p>Access to the Integration</crowdin-p>
218
- <div class="permission-description">This provides access to sync files and modify the current integration settings.</div>
219
- </td>
220
- <td class="affected-users"></td>
221
- <td class="status"></td>
222
- </tr>
223
- </tbody>
224
- </table>
225
- </div>
226
- {{#if zenModeUrl}}
227
- <div class="mt-2">
228
- <crowdin-input
229
- with-fixed-height
230
- label="Zen Mode link"
231
- help-text="This focused view allows you to concentrate solely on the Integrations section, eliminating other distractions."
232
- value="{{zenModeUrl}}"
233
- name="zenModeLink"
234
- with-copy-button
235
- ></crowdin-input>
236
- </div>
237
- {{/if}}
238
- </div>
239
- </div>
240
-
241
- <div slot="footer">
242
- <crowdin-button id="confirm-users-btn" outlined onclick="inviteUsers()">Save</crowdin-button>
243
- </div>
244
- </crowdin-modal>
245
-
246
- {{#if excludedTargetLanguages}}
247
- <crowdin-modal
248
- modal-width="50"
249
- modal-title="Select Target Languages for Translation"
250
- id="excluded-languages"
251
- >
252
- </crowdin-modal>
253
- {{/if}}
254
-
255
- {{#if infoModal}}
256
- <crowdin-modal
257
- style="display: none;"
258
- id="info-modal"
259
- modal-width="50"
260
- modal-title="{{infoModal.title}}"
261
- close-button-title="Close"
262
- >
263
- <div>
264
- {{{infoModal.content}}}
265
- </div>
266
- </crowdin-modal>
267
- {{/if}}
268
-
269
- {{#if configurationFields}}
270
- <crowdin-modal
271
- style="display: none;"
272
- id="settings-modal"
273
- modal-width="65"
274
- modal-title="Settings"
275
- close-button-title="Close"
276
- >
277
- <div class="loader hidden">
278
- <crowdin-progress-indicator></crowdin-progress-indicator>
279
- </div>
280
- <div id="modal-content">
281
- <crowdin-tabs tabs-config='[{{#each configurationFields}}"{{name}}"{{#unless @last}},{{/unless}}{{/each}}]' active-tab="1">
282
- {{#each configurationFields}}
283
- <div slot="tab-{{incrementedIndex @index}}">
284
- <div class="crowdin-form">
285
- {{#each fields}}
286
- {{#if key}}
287
- {{#ifeq type "checkbox"}}
288
- <crowdin-checkbox
289
- id="{{key}}-settings"
290
- key="{{key}}"
291
- label="{{label}}"
292
- value="false"
293
- use-switch
294
- {{#if helpText}}
295
- help-text="{{helpText}}"
296
- {{/if}}
297
- {{#if helpTextHtml}}
298
- help-text-html="{{helpTextHtml}}"
299
- {{/if}}
300
- {{#ifeq defaultValue true}}
301
- checked="{{defaultValue}}"
302
- {{/ifeq}}
303
- {{#if dependencySettings}}
304
- data-dependency="{{dependencySettings}}"
305
- {{/if}}
306
- onchange="settingsFieldChange(this)"
307
- >
308
- </crowdin-checkbox>
309
- {{/ifeq}}
310
- {{#ifeq type "select"}}
311
- <crowdin-select
312
- {{#if isMulti}}
313
- is-multi
314
- close-on-select="false"
315
- {{/if}}
316
- {{#if isSearchable}}
317
- is-searchable
318
- {{/if}}
319
- id="{{key}}-settings"
320
- key="{{key}}"
321
- label="{{label}}"
322
- {{#if helpText}}
323
- help-text="{{helpText}}"
324
- {{/if}}
325
- {{#if helpTextHtml}}
326
- help-text-html="{{helpTextHtml}}"
327
- {{/if}}
328
- {{#if dependencySettings}}
329
- data-dependency="{{dependencySettings}}"
330
- {{/if}}
331
- is-position-fixed
332
- onchange="settingsFieldChange(this)"
333
- >
334
- {{#each options}}
335
- <option
336
- {{#if ../defaultValue}}
337
- {{#ifeq ../defaultValue value}} selected {{/ifeq}}
338
- {{#in value ../defaultValue}} selected {{/in}}
339
- {{/if}}
340
- value="{{value}}">{{label}}
341
- </option>
342
- {{/each}}
343
- </crowdin-select>
344
- {{/ifeq}}
345
- {{#ifeq type "text"}}
346
- <crowdin-input
347
- id="{{key}}-settings"
348
- label="{{label}}"
349
- key="{{key}}"
350
- with-fixed-height
351
- {{#if helpText}}
352
- help-text="{{helpText}}"
353
- {{/if}}
354
- {{#if helpTextHtml}}
355
- help-text-html="{{helpTextHtml}}"
356
- {{/if}}
357
- {{#if dependencySettings}}
358
- data-dependency="{{dependencySettings}}"
359
- {{/if}}
360
- value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
361
- onchange="settingsFieldChange(this)"
362
- >
363
- </crowdin-input>
364
- {{/ifeq}}
365
- {{#ifeq type "textarea"}}
366
- <crowdin-textarea
367
- id="{{key}}-settings"
368
- label="{{label}}"
369
- key="{{key}}"
370
- {{#if helpText}}
371
- help-text="{{helpText}}"
372
- {{/if}}
373
- {{#if helpTextHtml}}
374
- help-text-html="{{helpTextHtml}}"
375
- {{/if}}
376
- {{#if dependencySettings}}
377
- data-dependency="{{dependencySettings}}"
378
- {{/if}}
379
- value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
380
- onchange="settingsFieldChange(this)"
381
- >
382
- </crowdin-textarea>
383
- {{/ifeq}}
384
- {{#ifeq type "notice"}}
385
- <crowdin-alert
386
- {{#if noticeType}}
387
- type="{{noticeType}}"
388
- {{/if}}
389
- {{#if label}}
390
- title="{{label}}"
391
- {{/if}}
392
- {{#if noIcon}}
393
- no-icon="{{noIcon}}"
394
- {{/if}}
395
- {{#if dependencySettings}}
396
- data-dependency="{{dependencySettings}}"
397
- {{/if}}
398
- >
399
- {{{helpText}}}
400
- </crowdin-alert>
401
- {{/ifeq}}
402
- {{else}}
403
- {{#if labelHtml}}
404
- <crowdin-p
405
- {{#if dependencySettings}}
406
- data-dependency="{{dependencySettings}}"
407
- {{/if}}
408
- >
409
- {{{labelHtml}}}
410
- </crowdin-p>
411
- {{else}}
412
- <crowdin-p
413
- {{#if dependencySettings}}
414
- data-dependency="{{dependencySettings}}"
415
- {{/if}}
416
- >
417
- {{label}}
418
- </crowdin-p>
419
- {{/if}}
420
- {{/if}}
421
- <div
422
- style="padding: 8px"
423
- {{#if dependencySettings}}
424
- data-dependency="{{dependencySettings}}"
425
- {{/if}}
426
- ></div>
427
- {{/each}}
428
- </div>
429
- </div>
430
- {{/each}}
431
- </crowdin-tabs>
432
- </div>
433
- <div slot="footer">
434
- <crowdin-button id="settings-save-btn" outlined onclick="saveSettings()">Save</crowdin-button>
435
- </div>
436
- </crowdin-modal>
437
- {{/if}}
438
- {{#or syncNewElements.crowdin syncNewElements.integration}}
439
- <crowdin-modal
440
- style="display: none;"
441
- id="confirm-schedule-modal"
442
- modal-width="50"
443
- modal-title="Synchronization options"
444
- close-button-title="Close"
445
- close-button="true"
446
- body-overflow-unset
447
- >
448
- <crowdin-checkbox
449
- id="selected-files"
450
- name="selected-files"
451
- label="Selected files"
452
- class="hydrated"
453
- onchange="onChangeAutoSynchronizationOptions(this)"
454
- >
455
- </crowdin-checkbox>
456
- <crowdin-checkbox
457
- id="all-folder-files"
458
- name="all-folder-files"
459
- label="All files in the folder"
460
- class="hydrated"
461
- hidden
462
- onchange="onChangeAutoSynchronizationOptions(this)"
463
- >
464
- </crowdin-checkbox>
465
- <crowdin-checkbox
466
- id="new-files"
467
- name="new-files"
468
- label="New files"
469
- class="hydrated"
470
- onchange="onChangeAutoSynchronizationOptions(this)"
471
- >
472
- </crowdin-checkbox>
473
- <div slot="footer">
474
- <crowdin-button outlined id="save-schedule-sync" onclick="saveScheduleSync()">Save</crowdin-button>
475
- </div>
476
- </crowdin-modal>
477
- {{/or}}
478
-
479
- <crowdin-modal
480
- style="display: none;"
481
- id="user-error-detail"
482
- close-button-title="Close"
483
- close-button="true"
484
- >
485
- </crowdin-modal>
486
- </body>
487
- <script type="text/javascript">
488
- document.body.addEventListener('refreshFilesList', (e) => {
489
- if (e.detail.refreshIntegration) {
490
- getIntegrationData({ hardReload: true });
491
- } else if (e.detail.refreshCrowdin) {
492
- getCrowdinData();
493
- }
494
- });
495
- document.body.addEventListener('crowdinFilesFolderToggled', (event) => {
496
- if (event.detail.componentId === 'crowdin-files' && event.detail.isOpen && event.detail.type === '1') {
497
- getFileProgress(event.detail.id);
498
- }
499
- {{#if integrationOneLevelFetching}}
500
- if (event.detail.componentId === 'integration-files' && event.detail.isOpen) {
501
- getIntegrationData({ parentId: event.detail.id, recursion: event.detail.recursion });
502
- }
503
- {{/if}}
504
- });
505
- document.body.addEventListener('uploadFilesToCrowdin', uploadFilesToCrowdin);
506
- document.body.addEventListener('uploadFilesToIntegration', uploadFilesToIntegration);
507
- document.body.addEventListener('cancelAsyncAction', (e) => {
508
- cancelJob(e.detail);
509
- });
510
- {{#if integrationSearchListener}}
511
- document.body.addEventListener("integrationFilterChange", async (event) => {
512
- const isEmptySearch = !event.detail || event.detail.trim() === '';
513
-
514
- if (isEmptySearch && savedIntegrationData && hasLoadMoreElements) {
515
- appComponent.setIntegrationFilesData(savedIntegrationData);
516
- appComponent.setIntegrationOpenedFolders(currentOpenedFolders);
517
- return;
518
- }
519
-
520
- if (!isEmptySearch && hasLoadMoreElements) {
521
- savedIntegrationData = await appComponent.getIntegrationFilesData();
522
- currentOpenedFolders = await appComponent.getIntegrationOpenedFolders();
523
- }
524
-
525
- getIntegrationData({ search: event.detail });
526
- })
527
- {{/if}}
528
- const appComponent = document.querySelector('crowdin-simple-integration');
529
- const subscriptionModal = document.getElementById('subscription-modal');
530
-
531
- const folderType = '0';
532
- const fileType = '1';
533
- const branchType = '2';
534
-
535
- const JOB_TYPE = {
536
- updateCrowdin: 'updateCrowdin',
537
- updateIntegration: 'updateIntegration',
538
- integrationSyncSettingsSave: 'integrationSyncSettingsSave',
539
- crowdinSyncSettingsSave: 'crowdinSyncSettingsSave',
540
- };
541
-
542
- const JOB_STATUS = {
543
- created: 'created',
544
- inProgress: 'inProgress',
545
- failed: 'failed',
546
- canceled: 'canceled',
547
- finished: 'finished',
548
- };
549
-
550
- const silentJobs = [
551
- JOB_TYPE.integrationSyncSettingsSave,
552
- JOB_TYPE.crowdinSyncSettingsSave,
553
- ];
554
-
555
- const asyncJobs = {};
556
-
557
- let project = {};
558
- let crowdinData = [];
559
- let fileToSync = [];
560
- let hasLoadMoreElements = false;
561
- let savedIntegrationData = null;
562
- let currentOpenedFolders = null;
563
-
564
- getCrowdinData();
565
- getIntegrationData({});
566
- getActiveJobs();
567
-
568
- function integrationLogout() {
569
- checkOrigin()
570
- .then(queryParams => fetch(`api/logout${queryParams}`, { method: 'POST' }))
571
- .then(checkResponse)
572
- .then(reloadLocation)
573
- .then(localStorage.removeItem('revised_{{name}}'))
574
- .catch(e => catchRejection(e, e.error || 'Looks like you are not logged in'));
575
- }
576
-
577
- function transformCrowdinItemToTree(e) {
578
- const item = {
579
- parent_id: e.parentId ? e.parentId : '0',
580
- name: e.name,
581
- id: e.id,
582
- customContent: e.customContent,
583
- labels: e.labels,
584
- disabled: e.disabled,
585
- tooltip: e.tooltip,
586
- };
587
- if (e.type) {
588
- item.type = e.type;
589
- item.node_type = fileType;
590
- item.failed = e.failed;
591
- item.excludedTargetLanguages = e.excludedTargetLanguages;
592
- } else {
593
- item.node_type = e.nodeType || folderType;
594
- }
595
- return item;
596
- }
597
-
598
- function getCrowdinData() {
599
- appComponent.setAttribute('is-crowdin-loading', true);
600
-
601
- {{#if progressiveCrowdinFilesLoading}}
602
- checkOrigin()
603
- .then(restParams => {
604
- const dirPromise = fetch('api/crowdin/files' + restParams + '&mode=directories')
605
- .then(checkResponse)
606
- .then((directories) => {
607
- const dirTree = directories.map(transformCrowdinItemToTree);
608
- crowdinData = dirTree;
609
- appComponent.setCrowdinFilesData(dirTree);
610
- return dirTree;
611
- });
612
-
613
- const filesPromise = fetch('api/crowdin/files' + restParams + '&mode=files')
614
- .then(checkResponse);
615
-
616
- const projectPromise = fetch('api/crowdin/project' + restParams)
617
- .then(checkResponse);
618
-
619
- return Promise.all([dirPromise, filesPromise, projectPromise]);
620
- })
621
- .then(([dirTree, files, projectData]) => {
622
- const fileTree = files.map(transformCrowdinItemToTree);
623
- crowdinData = [...dirTree, ...fileTree];
624
- appComponent.setCrowdinFilesData(crowdinData);
625
-
626
- project = projectData;
627
- const languagesSorted = project.targetLanguages.map(language => ({
628
- sourceLanguageId: projectData.sourceLanguage.editorCode,
629
- projectEditorLink: projectData.projectEditorLink,
630
- ...language,
631
- })).sort((a, b) => a.name.localeCompare(b.name));
632
-
633
- if (project.inContext && config?.inContext) {
634
- languagesSorted.push({...project.inContextPseudoLanguage, inContext: true});
635
- }
636
-
637
- setLanguagesForTranslation(languagesSorted);
638
- appComponent.setCrowdinLanguagesData(languagesSorted);
639
- })
640
- {{else}}
641
- checkOrigin()
642
- .then(restParams => {
643
- const filesPromise = fetch('api/crowdin/files' + restParams)
644
- .then(checkResponse);
645
-
646
- const projectPromise = fetch('api/crowdin/project' + restParams)
647
- .then(checkResponse);
648
-
649
- return Promise.all([filesPromise, projectPromise]);
650
- })
651
- .then(([files, projectData]) => {
652
- const tree = files.map(transformCrowdinItemToTree);
653
- crowdinData = tree;
654
- appComponent.setCrowdinFilesData(tree);
655
-
656
- project = projectData;
657
- const languagesSorted = project.targetLanguages.map(language => ({
658
- sourceLanguageId: projectData.sourceLanguage.editorCode,
659
- projectEditorLink: projectData.projectEditorLink,
660
- ...language,
661
- })).sort((a, b) => a.name.localeCompare(b.name));
662
-
663
- if (project.inContext && config?.inContext) {
664
- languagesSorted.push({...project.inContextPseudoLanguage, inContext: true});
665
- }
666
-
667
- setLanguagesForTranslation(languagesSorted);
668
- appComponent.setCrowdinLanguagesData(languagesSorted);
669
- })
670
- {{/if}}
671
- {{#or withCronSync webhooks}}
672
- .then(() => getSyncSettings('crowdin'))
673
- {{/or}}
674
- .catch(e => catchRejection(e, 'Can\'t fetch Crowdin data'))
675
- .finally(() => (appComponent.setAttribute('is-crowdin-loading', false)));
676
- }
677
-
678
- function getIntegrationData(
679
- {
680
- hardReload = false,
681
- parentId = '',
682
- search = '',
683
- page = 0,
684
- recursion = false,
685
- paginationData,
686
- } = {},
687
- recursionState = null
688
- ) {
689
- if (!paginationData) {
690
- appComponent.setAttribute('is-integration-loading', true);
691
- }
692
-
693
- return checkOrigin()
694
- .then(restParams => {
695
- let url = `api/integration/data${restParams}&parent_id=${encodeURIComponent(parentId)}&search=${encodeURIComponent(search)}&page=${page}`;
696
- if (paginationData) {
697
- url += `&paginationData=${encodeURIComponent(JSON.stringify(paginationData))}`;
698
- }
699
- return fetch(url);
700
- })
701
- .then(checkResponse)
702
- .then(async (res) => {
703
- const files = res.data;
704
- const stopPagination = res.stopPagination;
705
- const tree = files.map(e => {
706
- const item = {
707
- parent_id: e.parentId ? e.parentId : '0',
708
- name: e.name,
709
- id: e.id,
710
- customContent: e.customContent,
711
- labels: e.labels,
712
- disabled: e.disabled,
713
- tooltip: e.tooltip,
714
- pagination: e.pagination,
715
- };
716
- if (e.type) {
717
- item.isNew = e.isNew;
718
- item.isUpdated = e.isUpdated;
719
- item.notSynced = e.notSynced;
720
- item.synced = e.synced;
721
- item.type = e.type;
722
- item.node_type = fileType;
723
- } else {
724
- item.node_type = e.nodeType || folderType;
725
- }
726
- return item;
727
- });
728
-
729
- if ((!paginationData && !parentId && !search) || hardReload) {
730
- hasLoadMoreElements = tree.some(item => item.node_type === 'load-more');
731
- }
732
-
733
- const appIntegrationFiles = appComponent.querySelector('#integration-files');
734
- if (hardReload) {
735
- appComponent.setIntegrationFilesData(tree);
736
- } else if (tree.length) {
737
- appComponent.pushIntegrationFilesData(tree).then(() => {
738
- if (parentId) {
739
- appIntegrationFiles.getSelected().then(async selection => {
740
- const selectedIds = selection?.filter((node) => node).map(({id}) => id.toString());
741
- const filteredNodes = tree.filter((node) => !selectedIds.includes(node.id.toString()) && selectedIds.includes(node.parent_id.toString()));
742
-
743
- if (filteredNodes?.length) {
744
- const filteredNodesId = filteredNodes.map(({id}) => id.toString());
745
- await appIntegrationFiles.setSelected(filteredNodesId);
746
- }
747
- });
748
- }
749
- });
750
- }
751
- {{#if integrationPagination}}
752
- if (stopPagination) {
753
- appIntegrationFiles.setAttribute('load-more-disabled', true);
754
- } else {
755
- appIntegrationFiles.setAttribute('load-more-disabled', false);
756
- }
757
- {{/if}}
758
- if (search) {
759
- const openIds = files.filter(e => !e.type).map(e => e.id);
760
- appComponent.setIntegrationOpenedFolders(openIds);
761
- }
762
-
763
- if (recursion) {
764
- if (!recursionState) {
765
- recursionState = {
766
- openedFolders: new Set(),
767
- calls: 0,
768
- setLoading(isLoading) {
769
- appComponent.setAttribute('is-integration-loading', isLoading);
770
- },
771
- };
772
- recursionState.setLoading(true);
773
- }
774
-
775
- if (parentId) {
776
- recursionState.openedFolders.add(parentId);
777
- }
778
-
779
- const folderItems = files.filter(isFolder);
780
-
781
- if (folderItems.length === 0) {
782
- return Promise.resolve();
783
- }
784
-
785
- folderItems.forEach(folder => recursionState.openedFolders.add(folder.id));
786
-
787
- const currentOpenedFolders = await appComponent.getIntegrationOpenedFolders();
788
- appComponent.setIntegrationOpenedFolders([...currentOpenedFolders, ...recursionState.openedFolders]);
789
-
790
- const recursivePromises = folderItems.map(async folder => {
791
- recursionState.calls++;
792
-
793
- try {
794
- return getIntegrationData({ parentId: folder.id, recursion: true }, recursionState);
795
- } finally {
796
- recursionState.calls--;
797
- if (recursionState.calls === 0) {
798
- recursionState.setLoading(false);
799
- }
800
- }
801
- });
802
-
803
- return Promise.all(recursivePromises);
804
- }
805
- })
806
- {{#or withCronSync webhooks}}
807
- .then(() => getSyncSettings('integration'))
808
- {{/or}}
809
- .catch(e => catchRejection(e, 'Can\'t fetch {{name}} templates'))
810
- .finally(() => {
811
- if (!recursionState && !recursion) {
812
- appComponent.setAttribute('is-integration-loading', false);
813
- } else if (recursionState && recursionState.calls === 0) {
814
- appComponent.setAttribute('is-integration-loading', false);
815
- }
816
- });
817
- }
818
-
819
- function getSyncSettings(provider) {
820
- checkOrigin()
821
- .then(restParams => fetch(`/api/sync-settings/${provider}` + restParams))
822
- .then(checkResponse)
823
- .then((res) => {
824
- if (provider === 'crowdin') {
825
- {{#if syncNewElements.crowdin}}
826
- appComponent.setCrowdinScheduleSync(res, true, true);
827
- {{else}}
828
- appComponent.setCrowdinScheduleSync(res);
829
- {{/if}}
830
- } else {
831
- {{#if syncNewElements.integration}}
832
- appComponent.setIntegrationScheduleSync(res, true, true);
833
- {{else}}
834
- appComponent.setIntegrationScheduleSync(res);
835
- {{/if}}
836
-
837
- }
838
- })
839
- .catch(e => catchRejection(e, 'Can\'t fetch file progress'));
840
- }
841
-
842
- function getFileProgress(fileId) {
843
- checkOrigin()
844
- .then(restParams => fetch(`api/crowdin/file-progress/${fileId}` + restParams))
845
- .then(checkResponse)
846
- .then((res) => (appComponent.addCrowdinFileProgress(res)))
847
- .catch(e => catchRejection(e, 'Can\'t fetch file progress'));
848
- }
849
-
850
- function uploadFilesToCrowdin(event, languages = []) {
851
- let files = [];
852
- let uploadTranslations = false;
853
- let forcePushSources = false;
854
- if (event.detail?.action === 'uploadTranslations') {
855
- files = event.detail.files;
856
- uploadTranslations = true;
857
- forcePushSources = true;
858
- } else if (event.detail?.action === 'excludedTargetLanguages') {
859
- fileToSync = event.detail.files;
860
- openLanguageModal();
861
- return;
862
- } else if (event.detail?.action === 'forcePushSources') {
863
- files = event.detail.files;
864
- forcePushSources = true;
865
- } else {
866
- files = event.detail;
867
- }
868
-
869
- if (event.detail?.length === 0 || !event.detail) {
870
- showToast('Select templates which will be pushed to Crowdin');
871
- return;
872
- }
873
- appComponent.setAttribute('is-to-crowdin-process', true);
874
- let req;
875
- {{#if integrationOneLevelFetching}}
876
- req = files.map(f => ({
877
- name: f.name,
878
- id: f.id,
879
- type: f.type,
880
- parentId: f.parent_id,
881
- nodeType: f.node_type,
882
- isUpdated: f.isUpdated,
883
- }));
884
- {{else}}
885
- req = files.filter(f => f.node_type !== folderType).map(f => ({
886
- name: f.name,
887
- id: f.id,
888
- type: f.type,
889
- parentId: f.parent_id,
890
- isUpdated: f.isUpdated,
891
- }));
892
- {{/if}}
893
- checkOrigin()
894
- .then(restParams => fetch(`api/crowdin/update${restParams}&uploadTranslations=${uploadTranslations}&languages=${languages}&forcePushSources=${forcePushSources}`, {
895
- method: 'POST',
896
- headers: { 'Content-Type': 'application/json' },
897
- body: JSON.stringify(req)
898
- }))
899
- .then(checkResponse)
900
- .then((res) => {
901
- checkJob({
902
- jobId: res?.jobId,
903
- jobType: JOB_TYPE.updateCrowdin,
904
- })
905
- })
906
- .catch(e => catchRejection(e, 'Can\'t upload templates to Crowdin'))
907
- }
908
-
909
- function getActiveJobs() {
910
- checkOrigin()
911
- .then(restParams => fetch(`api/jobs${restParams}`))
912
- .then(checkResponse)
913
- .then((jobs) => {
914
- if (!Array.isArray(jobs)) {
915
- return;
916
- }
917
-
918
- jobs.forEach((job) => {
919
- switch (job.type) {
920
- case JOB_TYPE.updateCrowdin:
921
- appComponent.setAttribute('is-to-crowdin-process', true);
922
- break;
923
- case JOB_TYPE.updateIntegration:
924
- appComponent.setAttribute('is-to-integration-process', true);
925
- break;
926
- case JOB_TYPE.crowdinSyncSettingsSave:
927
- appComponent.setAttribute(`is-crowdin-sync-settings-in-progress`, true)
928
- break;
929
- case JOB_TYPE.integrationSyncSettingsSave:
930
- appComponent.setAttribute(`is-integration-sync-settings-in-progress`, true)
931
- break;
932
- default:
933
- }
934
-
935
- checkJob({
936
- jobId: job.id,
937
- jobType: job.type,
938
- })
939
- })
940
- })
941
- .catch(e => catchRejection(e, 'Sync status check failed'))
942
- }
943
-
944
- function cancelJob(jobId) {
945
- if (asyncJobs[jobId]?.isFailed) {
946
- return;
947
- }
948
-
949
- checkOrigin()
950
- .then(restParams => fetch('api/jobs' + restParams + '&jobId=' + jobId, {
951
- method: 'DELETE',
952
- headers: { 'Content-Type': 'application/json' },
953
- }))
954
- .then(checkResponse)
955
- .then(() => showToast('Cancellation…'))
956
- .catch(e => catchRejection(e, 'Sync cancellation failed'));
957
- }
958
-
959
- function checkJob({jobId, jobType, onSuccess, onError, onFinally}) {
960
- switch (jobType) {
961
- case JOB_TYPE.updateCrowdin:
962
- if (!onFinally) {
963
- onFinally = (() => {
964
- appComponent.setAttribute('is-to-crowdin-process', false);
965
- getCrowdinData();
966
- });
967
- }
968
- break;
969
- case JOB_TYPE.updateIntegration:
970
- if (!onFinally) {
971
- onFinally = (() => appComponent.setAttribute('is-to-integration-process', false));
972
- }
973
- break;
974
- case JOB_TYPE.crowdinSyncSettingsSave:
975
- if (!onFinally) {
976
- onFinally = (() => appComponent.setAttribute(`is-crowdin-sync-settings-in-progress`, false));
977
- }
978
- break;
979
- case JOB_TYPE.integrationSyncSettingsSave:
980
- if (!onFinally) {
981
- onFinally = (() => appComponent.setAttribute(`is-integration-sync-settings-in-progress`, false));
982
- }
983
- break;
984
- default:
985
- }
986
-
987
- checkOrigin()
988
- .then(restParams => fetch('api/jobs' + restParams + '&jobId=' + jobId))
989
- .then(checkResponse)
990
- .then((job) => {
991
- const isFailed = [JOB_STATUS.failed, JOB_STATUS.canceled].includes(job.status);
992
- const progress = isFailed || JOB_STATUS.finished === job.status ? 100 : job.progress;
993
- const info = JOB_STATUS.canceled === job.status ? `Cancelled\n${job.info || ''}` : job.info;
994
-
995
- !silentJobs.find((type) => type === jobType) && pushJobs([ {
996
- id: job.id,
997
- title: job.title,
998
- progress,
999
- info,
1000
- isFailed,
1001
- } ]);
1002
-
1003
- if (isFailed) {
1004
- onError && onError();
1005
- } else if ([JOB_STATUS.created, JOB_STATUS.inProgress].includes(job.status) && job.progress < 100) {
1006
- setTimeout(() => {
1007
- checkJob({jobId, jobType, onSuccess, onError, onFinally});
1008
- }, {{asyncProgress.checkInterval}});
1009
- return;
1010
- } else {
1011
- onSuccess && onSuccess(job);
1012
- }
1013
-
1014
- onFinally && onFinally(job);
1015
- })
1016
- .catch((e) => {
1017
- onFinally && onFinally();
1018
-
1019
- !silentJobs.find((type) => type === jobType) && pushJobs([ {
1020
- id: jobId,
1021
- isFailed: true,
1022
- } ]);
1023
-
1024
- showToast('Sync status check failed');
1025
- });
1026
- }
1027
-
1028
- function pushJobs(jobs) {
1029
- const el = document.querySelector('crowdin-async-progress');
1030
- if (el && el.pushJobs) {
1031
- el.pushJobs(jobs);
1032
- jobs.forEach((job) => (asyncJobs[job.id] = job));
1033
- } else {
1034
- setTimeout(() => {
1035
- pushJobs(jobs);
1036
- }, 300);
1037
- }
1038
- }
1039
-
1040
- function uploadFilesToIntegration(event) {
1041
- let files = {};
1042
- let forcePushTranslations = false;
1043
- if (event.detail?.action === 'forcePushTranslations') {
1044
- files = event.detail.files;
1045
- forcePushTranslations = true;
1046
- } else {
1047
- files = event.detail;
1048
- }
1049
-
1050
- if (Object.keys(event.detail).length === 0 || !event.detail) {
1051
- showToast('Select files which will be uploaded to {{name}}');
1052
- return;
1053
- }
1054
-
1055
- appComponent.setAttribute('is-to-integration-process', true);
1056
- const req = {};
1057
- Object.keys(files)
1058
- .filter(id => crowdinData.find(c => c.id === id).node_type === fileType)
1059
- .forEach(id => (req[id] = files[id]));
1060
- checkOrigin()
1061
- .then(restParams => fetch(`api/integration/update${restParams}&forcePushTranslations=${forcePushTranslations}`, {
1062
- method: 'POST',
1063
- headers: { 'Content-Type': 'application/json' },
1064
- body: JSON.stringify(req)
1065
- }))
1066
- .then(checkResponse)
1067
- .then((res) => {
1068
- checkJob({
1069
- jobId: res?.jobId,
1070
- jobType: JOB_TYPE.updateIntegration,
1071
- })
1072
- })
1073
- .catch(e => catchRejection(e, 'Can\'t upload files to {{name}}'))
1074
- }
1075
-
1076
- function openLanguageModal() {
1077
- openModal(languageModal);
1078
- }
1079
-
1080
- async function uploadFilesToCrowdinWithExcludedLanguages() {
1081
- const languageSelect = document.querySelector('#language-select');
1082
- const selectedLanguages = await languageSelect.getValue();
1083
- closeModal(languageModal);
1084
- uploadFilesToCrowdin({ detail: fileToSync }, selectedLanguages);
1085
- }
1086
-
1087
- async function setLanguagesForTranslation(languages) {
1088
- const languageIds = languages.map(language => language.id);
1089
- if (languageModal) {
1090
- let modalContent = `
1091
- <div>
1092
- <crowdin-select
1093
- is-multi
1094
- is-searchable
1095
- is-position-fixed
1096
- close-on-select="false"
1097
- name="languages"
1098
- id="language-select"
1099
- label="Languages"
1100
- >`;
1101
-
1102
- for (const language of languages) {
1103
- modalContent += `<option value="${language.id}">${language.name}</option>`;
1104
- }
1105
-
1106
- modalContent += `
1107
- </crowdin-select>
1108
- </div>
1109
- <div slot="footer">
1110
- <crowdin-button id="upload-exclude-languages" outlined onclick="uploadFilesToCrowdinWithExcludedLanguages()">
1111
- <div class="integration-icon-button">
1112
- <span>Sync to</span>
1113
- <crowdin-logo is-icon/>
1114
- </div>
1115
- </crowdin-button>
1116
- </div>
1117
- `;
1118
-
1119
- languageModal.innerHTML = modalContent;
1120
-
1121
- const select = document.querySelector('#language-select')
1122
- await select.setValue(languageIds);
1123
- }
1124
- }
1125
-
1126
- function hideConfirmUsersBlock() {
1127
- const confirmUsersBlock = document.querySelector('.confirm-users-block');
1128
- confirmUsersBlock.classList.add('hidden');
1129
-
1130
- const permissionsModal = document.getElementById('permissions-modal');
1131
- permissionsModal.setAttribute('body-overflow-unset', true)
1132
- }
1133
-
1134
- function getUserFullName(user) {
1135
- if (user.full_name) {
1136
- return user.full_name;
1137
- }
1138
-
1139
- const ownerFullName = user.name || ((user.first_name || '') + ' ' + (user.last_name || '')).trim();
1140
-
1141
- return !!ownerFullName && user?.login !== ownerFullName
1142
- ? `${ownerFullName} (${user?.login})`
1143
- : user?.login;
1144
- }
1145
-
1146
- function showPermissionsDialog() {
1147
- hideConfirmUsersBlock();
1148
- openModal(permissions)
1149
- setLoader('#permissions-modal');
1150
- const select = document.getElementById('users');
1151
-
1152
- select.value = '[]';
1153
-
1154
- Promise.all([
1155
- new Promise((resolve) => {
1156
- checkOrigin()
1157
- .then(restParams => fetch('api/users' + restParams))
1158
- .then(checkResponse)
1159
- .then((res) => {
1160
- resolve(JSON.stringify(res.data.managers));
1161
- })
1162
- }),
1163
- new Promise((resolve) => {
1164
- AP.getUsers(res => {
1165
- const users = Object.values(res).filter(user => user.id !== Number('{{userId}}'));
1166
-
1167
- resolve(users.map(user => `<option value="${user.id}">${sanitizeHTML(getUserFullName(user))}</option>`).join(''));
1168
- })
1169
- })
1170
- ])
1171
- .then(([managers, users]) => {
1172
- select.innerHTML = users;
1173
- select.value = managers;
1174
- })
1175
- .catch(e => catchRejection(e, 'Can\'t fetch users'))
1176
- .finally(() => unsetLoader('#permissions-modal'));
1177
- }
1178
-
1179
- function showConfirmUsersBlock() {
1180
- const confirmUsersBlock = document.querySelector('.confirm-users-block');
1181
- confirmUsersBlock.classList.remove('hidden');
1182
-
1183
- const permissionsModal = document.getElementById('permissions-modal');
1184
- permissionsModal.removeAttribute('body-overflow-unset')
1185
- }
1186
-
1187
- function checkUsers() {
1188
- setLoader('#permissions-modal');
1189
-
1190
- const select = document.getElementById('users');
1191
-
1192
- if (select.value === '[]') {
1193
- hideConfirmUsersBlock();
1194
- unsetLoader('#permissions-modal');
1195
- return;
1196
- }
1197
-
1198
- const users = JSON.parse(select.value);
1199
- const user_ids = users.filter(id => parseInt(id));
1200
- const emails = users.filter(id => !parseInt(id));
1201
-
1202
- const params = { user_ids, emails };
1203
-
1204
- Promise.all([
1205
- new Promise((resolve) => {
1206
- checkOrigin()
1207
- .then(restParams => fetch('api/users' + restParams))
1208
- .then(checkResponse)
1209
- .then((usersResponse) => {
1210
- resolve(usersResponse.data.managers);
1211
- });
1212
- }),
1213
- new Promise((resolve) => {
1214
- AP.validateUsers(params, (res) => {
1215
- resolve(res.data);
1216
- });
1217
- }),
1218
- new Promise((resolve) => {
1219
- AP.getUsers(res => {
1220
- resolve(res);
1221
- });
1222
- })
1223
- ])
1224
- .then(([appManagers, checkUsersResponse, allUsers]) => {
1225
- const newUserEmails = checkUsersResponse.newUserEmails.map(email => ({name: email}));
1226
-
1227
- const newProjectManagers = [
1228
- ...newUserEmails,
1229
- ...checkUsersResponse.newProjectManagers.map(user => ({
1230
- name: getUserFullName(user),
1231
- })),
1232
- ];
1233
-
1234
- const newApplicationAccessUsers = [
1235
- ...newUserEmails,
1236
- ...checkUsersResponse.newApplicationAccessUsers.map(user => ({
1237
- name: getUserFullName(user),
1238
- })),
1239
- ];
1240
-
1241
- const newApplicationManagers = [
1242
- ...newUserEmails,
1243
- ...Object.values(allUsers)
1244
- .filter(user => users.some(id => +id === user.id) && !appManagers.some(id => +id === user.id))
1245
- .map(user => ({name: getUserFullName(user)})),
1246
- ];
1247
-
1248
- prepareUsersConfirmBlock({
1249
- ...checkUsersResponse,
1250
- newUserEmails,
1251
- newProjectManagers,
1252
- newApplicationAccessUsers,
1253
- newApplicationManagers,
1254
- });
1255
- })
1256
- .catch(e => catchRejection(e, e?.error || 'Can\'t invite users'))
1257
- .finally(() => unsetLoader('#permissions-modal'));
1258
- }
1259
-
1260
- function inviteUsers() {
1261
- setLoader('#permissions-modal');
1262
-
1263
- const select = document.getElementById('users');
1264
- const users = JSON.parse(select.value);
1265
- const user_ids = users.filter(id => parseInt(id));
1266
- const emails = users.filter(id => !parseInt(id));
1267
-
1268
- const params = { user_ids, emails };
1269
-
1270
- AP.inviteUsers(params, (res) => {
1271
- if (!res.success) {
1272
- catchRejection(res, res?.error?.message || 'Can\'t invite users')
1273
- unsetLoader('#permissions-modal');
1274
- return;
1275
- }
1276
-
1277
- checkOrigin()
1278
- .then(restParams => fetch('api/invite-users' + restParams, {
1279
- method: 'POST',
1280
- headers: { 'Content-Type': 'application/json' },
1281
- body: JSON.stringify(res.data)
1282
- }))
1283
- .then(checkResponse)
1284
- .then((res) => {
1285
- if (!res.success) {
1286
- catchRejection(res, res?.message || 'Can\'t invite users');
1287
- return;
1288
- }
1289
-
1290
- showToast('Users successfully updated');
1291
- hideConfirmUsersBlock();
1292
- closeModal(permissions);
1293
- })
1294
- .catch((e) => catchRejection(e, e?.error || 'Can\'t invite users'))
1295
- .finally(() => unsetLoader('#permissions-modal'));
1296
- });
1297
- }
1298
-
1299
- function prepareUsersConfirmBlock(usersData) {
1300
- showConfirmUsersBlock();
1301
-
1302
- const organizationInvite = document.querySelector('.organization-invite');
1303
- const projectInvite = document.querySelector('.project-invite');
1304
- const applicationCredentialsInvite = document.querySelector('.application-credentials-invite');
1305
-
1306
- const grantedElement = '<crowdin-p>&horbar;</crowdin-p>';
1307
- const willGrantedElement = '<span class="badge badge-will-be-granted">Will Be Granted</span>';
1308
- const notAvailableElement = '<span class="badge badge-not-available">Action Required</span>';
1309
-
1310
- // only in enterprise
1311
- if (organizationInvite) {
1312
- const organizationWillGrantElement = `<span class="badge badge-will-be-granted">Will Be Registered</span>`;
1313
-
1314
- processUsersWhoWillBeInvitedToOrganization(organizationInvite, usersData, organizationWillGrantElement, grantedElement, notAvailableElement);
1315
- }
1316
-
1317
- processUsersWhoWillBeInvited(projectInvite, usersData.newProjectManagers, willGrantedElement, grantedElement);
1318
- processUsersWhoWillBeInvited(applicationCredentialsInvite, usersData.newApplicationManagers, willGrantedElement, grantedElement);
1319
-
1320
- processUsersWhoWillBeInvitedApplicationSettings(usersData, willGrantedElement, grantedElement, notAvailableElement);
1321
- }
1322
-
1323
- function processUsersWhoWillBeInvitedToOrganization(element, usersData, willGrantedElement, alreadyGrantedMessage, notAvailableElement) {
1324
- const tooltip = element.querySelector('.status');
1325
- const description = element.querySelector('.permission-description');
1326
- const userList = element.querySelector('.affected-users');
1327
-
1328
- description.classList.remove('text-warning');
1329
- description.innerText = '';
1330
-
1331
- const users = usersData.newUserEmails;
1332
-
1333
- if (users.length) {
1334
- if (usersData.inviteRestricted) {
1335
- description.classList.add('text-warning');
1336
- description.innerText = 'New user invitations are restricted to administrators.';
1337
-
1338
- tooltip.innerHTML = notAvailableElement;
1339
- } else {
1340
- tooltip.innerHTML = willGrantedElement;
1341
- }
1342
-
1343
- let affectedUsers = '<ul>';
1344
- for (const user of users) {
1345
- affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1346
- }
1347
- affectedUsers += '</ul>';
1348
- userList.innerHTML = affectedUsers;
1349
- } else {
1350
- tooltip.innerHTML = alreadyGrantedMessage;
1351
- userList.innerHTML = alreadyGrantedMessage;
1352
- }
1353
- }
1354
-
1355
- function processUsersWhoWillBeInvited(element, users, grantMessage, alreadyGrantedMessage) {
1356
- const tooltip = element.querySelector('.status');
1357
- const userList = element.querySelector('.affected-users');
1358
-
1359
- if (users.length) {
1360
- tooltip.innerHTML = grantMessage;
1361
-
1362
- let affectedUsers = '<ul>';
1363
- for (const user of users) {
1364
- affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1365
- }
1366
- affectedUsers += '</ul>';
1367
- userList.innerHTML = affectedUsers;
1368
- } else {
1369
- tooltip.innerHTML = alreadyGrantedMessage;
1370
- userList.innerHTML = alreadyGrantedMessage;
1371
- }
1372
- }
1373
-
1374
- function processUsersWhoWillBeInvitedApplicationSettings(usersData, willGrantedElement, grantedElement, notAvailableElement) {
1375
- const applicationSettingsInvite = document.querySelector('.application-settings-invite');
1376
-
1377
- const tooltip = applicationSettingsInvite.querySelector('.status');
1378
- const description = applicationSettingsInvite.querySelector('.permission-description');
1379
- const userList = applicationSettingsInvite.querySelector('.affected-users');
1380
-
1381
- let descriptionMessage = 'This can be configured in organization settings (Apps section).';
1382
- description.classList.remove('text-warning');
1383
- description.innerText = descriptionMessage;
1384
-
1385
- let affectedUsers = '<ul>';
1386
-
1387
- if (!usersData.newApplicationAccessUsers.length) {
1388
- tooltip.innerHTML = grantedElement;
1389
- userList.innerHTML = grantedElement;
1390
- } else {
1391
- if (usersData.isAdmin) {
1392
- tooltip.innerHTML = willGrantedElement;
1393
- for (const user of usersData.newApplicationAccessUsers) {
1394
- affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1395
- }
1396
- userList.innerHTML = affectedUsers + '</ul>';
1397
- } else {
1398
- descriptionMessage += ' Only organization admins have permission to update this setting.';
1399
-
1400
- description.classList.add('text-warning');
1401
- description.innerText = descriptionMessage;
1402
-
1403
- tooltip.innerHTML = notAvailableElement;
1404
- for (const user of usersData.newApplicationAccessUsers) {
1405
- affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1406
- }
1407
- userList.innerHTML = affectedUsers + '</ul>';
1408
- }
1409
- }
1410
- }
1411
-
1412
- function setLoader(id) {
1413
- const loader = document.querySelector(`${id} .loader`);
1414
- loader.classList.remove('hidden');
1415
- }
1416
-
1417
- function unsetLoader(id) {
1418
- const loader = document.querySelector(`${id} .loader`);
1419
- setTimeout(function() {
1420
- loader.classList.add('hidden');
1421
- }, 500)
1422
- }
1423
-
1424
- {{#if configurationFields}}
1425
- const settingsModal = document.getElementById('settings-modal');
1426
- const settingsSaveBtn = document.getElementById('settings-save-btn');
1427
- let config = JSON.parse('{{{config}}}'.replace(/\n/g, '\\n'));
1428
- const newCrowdinFiles = config?.['new-crowdin-files'];
1429
- const newIntegrationFiles = config?.['new-integration-files'];
1430
-
1431
- function triggerEvent(el, type) {
1432
- const e = document.createEvent('HTMLEvents');
1433
- e.initEvent(type, false, true);
1434
- el.dispatchEvent(e);
1435
- }
1436
-
1437
- function fillSettingsForm() {
1438
- Object.entries(config).forEach(([key, value]) => {
1439
- const el = document.getElementById(`${key}-settings`);
1440
- if (el && (value || el.tagName.toLowerCase() === 'crowdin-checkbox')) {
1441
- if (el.tagName.toLowerCase() === 'crowdin-select') {
1442
- if (el.hasAttribute('is-multi')) {
1443
- el.value = JSON.stringify(value);
1444
- } else {
1445
- el.value = JSON.stringify([value]);
1446
- }
1447
- } else if (el.tagName.toLowerCase() === 'crowdin-checkbox') {
1448
- el.checked = !!value;
1449
- } else if (el.tagName.toLowerCase() === 'crowdin-textarea') {
1450
- el.value = value;
1451
- el.setValue(value);
1452
- } else {
1453
- el.value = value;
1454
- }
1455
-
1456
- triggerEvent(el, 'change');
1457
- }
1458
- });
1459
- }
1460
-
1461
- function saveSettings() {
1462
- setLoader('#settings-modal');
1463
- const settingsElements = Array.from(document.querySelectorAll('#modal-content crowdin-tabs > div'));
1464
- const tags = ['crowdin-checkbox', 'crowdin-select', 'crowdin-input', 'crowdin-textarea'];
1465
- const configReq = {};
1466
-
1467
- settingsElements.forEach(tab => {
1468
- const formElements = Array.from(tab.querySelectorAll('.crowdin-form > *'));
1469
- formElements
1470
- .filter(e => tags.includes(e.tagName.toLowerCase()))
1471
- .forEach(e => {
1472
- const key = e.getAttribute('key');
1473
- let value;
1474
- if (e.tagName.toLowerCase() === 'crowdin-select') {
1475
- value = JSON.parse(e.value);
1476
- if (!e.hasAttribute('is-multi')) {
1477
- value = value.length > 0 ? value[0] : undefined;
1478
- }
1479
- } else if (e.tagName.toLowerCase() === 'crowdin-checkbox') {
1480
- value = !!e.checked;
1481
- } else {
1482
- value = e.value;
1483
- }
1484
- configReq[key] = value;
1485
- });
1486
- });
1487
-
1488
- let isValidationError = false;
1489
- settingsSaveBtn.setAttribute('disabled', true);
1490
- checkOrigin()
1491
- .then(restParams => fetch('api/settings' + restParams, {
1492
- method: 'POST',
1493
- headers: { 'Content-Type': 'application/json' },
1494
- body: JSON.stringify({ config: configReq })
1495
- }))
1496
- .then(checkResponse)
1497
- .then(() => {
1498
- showToast('Settings successfully saved');
1499
- config = configReq;
1500
- })
1501
- .catch(e => {
1502
- if (e?.error && e?.error?.type === 'validation_error') {
1503
- Object.keys(e?.error?.details).forEach(key => {
1504
- const el = document.getElementById(`${key}-settings`);
1505
- if (el) {
1506
- el.setAttribute('error', 'true');
1507
- el.setAttribute('error-text', e?.error?.details[key]);
1508
- }
1509
- });
1510
-
1511
- isValidationError = true;
1512
- showToast(e?.error?.details?.message || 'Can\'t save settings');
1513
- settingsSaveBtn.removeAttribute('disabled');
1514
- return;
1515
- }
1516
- catchRejection(e, 'Can\'t save settings')
1517
- })
1518
- .finally(() => {
1519
- unsetLoader('#settings-modal');
1520
- settingsSaveBtn.removeAttribute('disabled');
1521
- if (!isValidationError) {
1522
- closeModal(settingsModal);
1523
- {{#if reloadOnConfigSave}}
1524
- getIntegrationData({ hardReload: true });
1525
- getCrowdinData();
1526
- {{/if}}
1527
- }
1528
- });
1529
- }
1530
- {{else}}
1531
- const settingsModal = undefined;
1532
- {{/if}}
1533
-
1534
- const permissions = document.getElementById('permissions-modal');
1535
-
1536
- {{#if excludedTargetLanguages}}
1537
- const languageModal = document.getElementById('excluded-languages');
1538
- {{else}}
1539
- const languageModal = undefined;
1540
- {{/if}}
1541
-
1542
- {{#if infoModal}}
1543
- const infoModal = document.getElementById('info-modal');
1544
- {{else}}
1545
- const infoModal = undefined;
1546
- {{/if}}
1547
-
1548
- document.addEventListener('keydown', (event) => {
1549
- if (event.keyCode == 27) {
1550
- if (users) {
1551
- closeModal(permissions);
1552
- }
1553
- if (infoModal) {
1554
- closeModal(infoModal);
1555
- }
1556
- if (settingsModal) {
1557
- closeModal(settingsModal);
1558
- }
1559
- if (languageModal) {
1560
- closeModal(languageModal);
1561
- }
1562
- }
1563
- });
1564
-
1565
- {{#if integrationPagination}}
1566
- document.body.addEventListener('fileListEnd', (e) => {
1567
- getIntegrationData({ page: e.detail.page });
1568
- })
1569
- {{/if}}
1570
-
1571
- document.body.addEventListener('loadMoreClick', async (e) => {
1572
- const loadMoreFile = e.detail;
1573
- const fileComponent = document.getElementById('integration-files')
1574
-
1575
- await fileComponent.setLoadMoreLoading(loadMoreFile.id, true);
1576
-
1577
- getIntegrationData({ parentId: e.detail.parent_id, paginationData: e.detail.pagination })
1578
- .finally(async () => {
1579
- await fileComponent.setLoadMoreLoading(loadMoreFile.id, false);
1580
- });
1581
- });
1582
-
1583
- {{#or withCronSync webhooks}}
1584
- const scheduleModal = document.getElementById('confirm-schedule-modal');
1585
- let syncData;
1586
-
1587
- document.body.addEventListener('integrationScheduleSync', setIntegrationScheduleSync);
1588
- document.body.addEventListener('crowdinScheduleSync', setCrowdinScheduleSync);
1589
- document.body.addEventListener('crowdinDisableSync', disableCrowdinSync);
1590
- document.body.addEventListener('integrationDisableSync', disableIntegrationSync);
1591
-
1592
- function getIntegrationFoldersToExpand(allData, syncedFiles) {
1593
- return Array.isArray(allData)
1594
- ? allData.filter(
1595
- node => (node.node_type === folderType && !syncedFiles.find((syncedFile) => syncedFile.parent_id === node.id))
1596
- ).map(node => ({
1597
- ...node,
1598
- nodeType: node.node_type,
1599
- parentId: node.parent_id,
1600
- }))
1601
- : [];
1602
- }
1603
-
1604
- async function saveScheduleSync() {
1605
- const newFile = scheduleModal.querySelector('#new-files').checked || false;
1606
- const selectedFiles = scheduleModal.querySelector('#selected-files').checked || false;
1607
- const allFolderFiles = scheduleModal.querySelector('#all-folder-files').checked || false;
1608
-
1609
- const type = scheduleModal.getAttribute('data-type');
1610
-
1611
- if (type === 'crowdin') {
1612
- appComponent.setCrowdinScheduleSync(syncData, newFile, selectedFiles);
1613
- appComponent.setCrowdinFilesHasSyncOption(true);
1614
- const syncedFiles = await appComponent.getCrowdinScheduleSync(true);
1615
- appComponent.setAttribute('is-crowdin-sync-settings-in-progress', true);
1616
- updateSyncSettings(syncedFiles, 'schedule', 'crowdin');
1617
- } else if (type === 'integration') {
1618
- {{#if syncNewElements.integration}}
1619
- appComponent.setIntegrationScheduleSync(syncData, newFile, selectedFiles);
1620
- {{else}}
1621
- appComponent.setIntegrationScheduleSync(syncData);
1622
- {{/if}}
1623
- appComponent.setIntegrationFilesHasSyncOption(true);
1624
- const syncedFiles = await appComponent.getIntegrationScheduleSync(true);
1625
- appComponent.setAttribute('is-integration-sync-settings-in-progress', true);
1626
- {{#if integrationOneLevelFetching}}
1627
- updateSyncSettings(
1628
- syncedFiles,
1629
- 'schedule',
1630
- 'integration',
1631
- selectedFiles ? getIntegrationFoldersToExpand(syncData, syncedFiles) : [],
1632
- );
1633
- {{else}}
1634
- if (allFolderFiles) {
1635
- const integrationExpandedFolders = syncData.filter(file => file.node_type === folderType).map(file => ({
1636
- ...file,
1637
- nodeType: file.node_type,
1638
- parentId: file.parent_id,
1639
- }));
1640
- updateSyncSettings(syncedFiles, 'schedule', 'integration', integrationExpandedFolders);
1641
- } else {
1642
- updateSyncSettings(syncedFiles, 'schedule', 'integration');
1643
- }
1644
- {{/if}}
1645
- }
1646
-
1647
- closeModal(scheduleModal);
1648
- }
1649
-
1650
- async function openScheduleModal(type, options = {}) {
1651
- const { showAllFolderFiles = false, showNewFiles = true } = options;
1652
-
1653
- const newFile = scheduleModal.querySelector('#new-files')
1654
- const selectedFiles = scheduleModal.querySelector('#selected-files');
1655
- const allFolderFiles = scheduleModal.querySelector('#all-folder-files');
1656
- let newFileStatus = 'unChecked';
1657
-
1658
- const integrationSyncFolders = (await document.querySelector('crowdin-simple-integration').getIntegrationScheduleSync(true))
1659
- .filter(elements => elements.node_type === '0');
1660
-
1661
- const selectedIntegrationFolders = (await document.getElementById('integration-files').getSelected(true))
1662
- .filter(elements => elements.node_type === '0');
1663
-
1664
- for (let i = 0; i < selectedIntegrationFolders.length; i++) {
1665
- const selectedElement = selectedIntegrationFolders[i];
1666
-
1667
- const found = integrationSyncFolders.some(integrationElement =>
1668
- integrationElement.id === selectedElement.id
1669
- );
1670
-
1671
- if (found) {
1672
- newFileStatus = 'checked';
1673
- } else if (!found && newFileStatus === 'checked') {
1674
- newFileStatus = 'clear-selection';
1675
- break;
1676
- } else {
1677
- newFileStatus = 'unChecked';
1678
- }
1679
- }
1680
-
1681
- if (showNewFiles) {
1682
- newFile.removeAttribute('hidden');
1683
- if (newFileStatus === 'checked') {
1684
- newFile[newFileStatus] = true;
1685
- newFile.value = true;
1686
- newFile.removeAttribute('clear-selection');
1687
- } else if (newFileStatus === 'clear-selection') {
1688
- newFile.checked = false;
1689
- newFile.value = false;
1690
- newFile.setAttribute(newFileStatus, '');
1691
- } else {
1692
- newFile.checked = false;
1693
- newFile.value = false;
1694
- newFile.removeAttribute('clear-selection');
1695
- }
1696
- } else {
1697
- newFile.setAttribute('hidden', '');
1698
- newFile.checked = false;
1699
- }
1700
-
1701
- if (showAllFolderFiles) {
1702
- allFolderFiles.removeAttribute('hidden');
1703
- allFolderFiles.checked = false;
1704
- allFolderFiles.value = false;
1705
- } else {
1706
- allFolderFiles.setAttribute('hidden', '');
1707
- allFolderFiles.checked = false;
1708
- }
1709
-
1710
- selectedFiles.checked = true;
1711
- selectedFiles.value = true;
1712
- scheduleModal.querySelector('#save-schedule-sync').setAttribute('disabled', false);
1713
- scheduleModal.setAttribute('data-type', type);
1714
- openModal(scheduleModal);
1715
- }
1716
-
1717
- function onChangeAutoSynchronizationOptions() {
1718
- const newFiles = document.getElementById('new-files');
1719
- newFiles.removeAttribute('clear-selection');
1720
-
1721
- const newFilesStatus = newFiles.checked || false;
1722
- const selectedFiles = document.getElementById('selected-files').checked || false;
1723
- const allFolderFiles = document.getElementById('all-folder-files').checked || false;
1724
- const buttonSaveScheduleSync = document.getElementById('save-schedule-sync');
1725
-
1726
- if (newFilesStatus || selectedFiles || allFolderFiles) {
1727
- buttonSaveScheduleSync.removeAttribute('disabled');
1728
- } else {
1729
- buttonSaveScheduleSync.setAttribute('disabled', true);
1730
- }
1731
- }
1732
-
1733
- async function hasFolder(selectedFiles) {
1734
- let isFolder;
1735
- if (Array.isArray(selectedFiles)) {
1736
- isFolder = selectedFiles.find((file) => file.node_type === folderType || file.node_type === branchType);
1737
- } else {
1738
- const files = await appComponent.getCrowdinFilesData();
1739
- const folders = files.filter((file) => file.node_type === folderType || file.node_type === branchType);
1740
- isFolder = folders.find((folder) => selectedFiles.hasOwnProperty(folder.id));
1741
- }
1742
-
1743
- return isFolder !== undefined;
1744
- }
1745
-
1746
- async function setIntegrationScheduleSync(e) {
1747
- if (e.detail.length === 0) {
1748
- showToast('Select templates which will be pushed to Crowdin');
1749
- return;
1750
- }
1751
-
1752
- {{#if syncNewElements.integration}}
1753
- syncData = e.detail;
1754
- const isFolder = await hasFolder(e.detail);
1755
-
1756
- const shouldShowModal = isFolder && !newIntegrationFiles;
1757
- const showNewFiles = true; // завжди показуємо New files коли є syncNewElements.integration
1758
- const showAllFolderFiles = hasLoadMoreElements; // показуємо All files якщо є hasLoadMoreElements
1759
-
1760
- if (shouldShowModal) {
1761
- await openScheduleModal('integration', { showNewFiles, showAllFolderFiles });
1762
- return;
1763
- }
1764
-
1765
- appComponent.setIntegrationScheduleSync(e.detail);
1766
- appComponent.setIntegrationFilesHasSyncOption(true);
1767
- const syncedFiles = await appComponent.getIntegrationScheduleSync(true);
1768
- {{else}}
1769
- syncData = e.detail;
1770
- const isFolder = await hasFolder(e.detail);
1771
-
1772
- if (hasLoadMoreElements && isFolder) {
1773
- await openScheduleModal('integration', { showNewFiles: false, showAllFolderFiles: true });
1774
- return;
1775
- }
1776
-
1777
- const syncedFiles = await appComponent.getIntegrationScheduleSync();
1778
- {{/if}}
1779
-
1780
- appComponent.setAttribute('is-integration-sync-settings-in-progress', true);
1781
-
1782
- {{#if integrationOneLevelFetching}}
1783
- updateSyncSettings(syncedFiles, 'schedule', 'integration', getIntegrationFoldersToExpand(e.detail, syncedFiles));
1784
- {{else}}
1785
- if (hasLoadMoreElements) {
1786
- const integrationExpandedFolders = e.detail.filter(file => file.node_type === folderType).map(file => ({
1787
- ...file,
1788
- nodeType: file.node_type,
1789
- parentId: file.parent_id,
1790
- }));
1791
-
1792
- updateSyncSettings(syncedFiles, 'schedule', 'integration', integrationExpandedFolders);
1793
- } else {
1794
- updateSyncSettings(syncedFiles, 'schedule', 'integration');
1795
- }
1796
- {{/if}}
1797
- }
1798
-
1799
- async function setCrowdinScheduleSync(e) {
1800
- if (e.detail.length === 0) {
1801
- showToast('Select templates which will be pushed to Crowdin');
1802
- return;
1803
- }
1804
-
1805
- {{#if syncNewElements.crowdin}}
1806
- syncData = e.detail;
1807
- const isFolder = await hasFolder(e.detail);
1808
- if (isFolder && !newCrowdinFiles) {
1809
- await openScheduleModal('crowdin');
1810
- return;
1811
- }
1812
-
1813
- appComponent.setCrowdinScheduleSync(e.detail);
1814
- appComponent.setCrowdinFilesHasSyncOption(true);
1815
- const syncedFiles = await appComponent.getCrowdinScheduleSync(true);
1816
- {{else}}
1817
- const syncedFiles = await appComponent.getCrowdinScheduleSync();
1818
- {{/if}}
1819
- appComponent.setAttribute('is-crowdin-sync-settings-in-progress', true);
1820
- updateSyncSettings(syncedFiles, 'schedule', 'crowdin');
1821
- }
1822
-
1823
- async function disableIntegrationSync(e) {
1824
- if (e.detail.length === 0) {
1825
- showToast('Select templates which will be pushed to Crowdin');
1826
- return;
1827
- }
1828
- appComponent.setAttribute('is-integration-sync-settings-in-progress', true);
1829
- {{#if syncNewElements.integration}}
1830
- const syncedFiles = await appComponent.getIntegrationScheduleSync(true);
1831
- {{else}}
1832
- const syncedFiles = await appComponent.getIntegrationScheduleSync();
1833
- {{/if}}
1834
-
1835
- updateSyncSettings(syncedFiles, 'schedule', 'integration');
1836
- }
1837
-
1838
- async function disableCrowdinSync(e) {
1839
- if (e.detail.length === 0) {
1840
- showToast('Select templates which will be pushed to Crowdin');
1841
- return;
1842
- }
1843
- appComponent.setAttribute('is-crowdin-sync-settings-in-progress', true);
1844
- {{#if syncNewElements.crowdin}}
1845
- const syncedFiles = await appComponent.getCrowdinScheduleSync(true);
1846
- {{else}}
1847
- const syncedFiles = await appComponent.getCrowdinScheduleSync();
1848
- {{/if}}
1849
-
1850
- updateSyncSettings(syncedFiles, 'schedule', 'crowdin');
1851
- }
1852
-
1853
- function updateSyncSettings(files, type, provider, expandIntegrationFolders = []) {
1854
- checkOrigin()
1855
- .then(restParams => fetch('api/sync-settings' + restParams, {
1856
- method: 'POST',
1857
- headers: { 'Content-Type': 'application/json' },
1858
- body: JSON.stringify({ files, type, provider, expandIntegrationFolders, hasLoadMoreElements }),
1859
- }))
1860
- .then(checkResponse)
1861
- .then((res) => {
1862
- checkJob({
1863
- jobId: res?.jobId,
1864
- jobType: JOB_TYPE[`${provider}SyncSettingsSave`],
1865
- })
1866
- })
1867
- .catch(e => catchRejection(e, 'Can\'t save schedule sync settings'));
1868
- }
1869
- {{/or}}
1870
-
1871
- {{#if checkSubscription}}
1872
- const subscriptionInfo = document.getElementById('subscription-info');
1873
- const subscriptionInfoText = subscriptionInfo.getElementsByTagName('p')[0];
1874
- function getSubscriptionInfo() {
1875
- checkOrigin()
1876
- .then(restParams => fetch('api/subscription-info' + restParams))
1877
- .then(checkResponse)
1878
- .then((res) => {
1879
- if (res.showInfo) {
1880
- subscriptionLink = res.subscribeLink;
1881
- subscriptionInfoText.textContent = `Your trial expires in ${res.daysLeft} days.`
1882
- subscriptionInfo.style.display = 'block';
1883
- }
1884
- });
1885
- }
1886
-
1887
- getSubscriptionInfo();
1888
- {{/if}}
1889
-
1890
- function checkAlert(alert, suffix) {
1891
- const name = suffix ?? '{{name}}';
1892
- const revised = localStorage.getItem(`revised_${name}`) === '1';
1893
- if (!revised) {
1894
- alert.style.display = 'block';
1895
- }
1896
- }
1897
- function closeAlert(el, suffix) {
1898
- const name = suffix ?? '{{name}}';
1899
- const alert = el.closest('crowdin-alert');
1900
- alert.style.display = 'none';
1901
- localStorage.setItem(`revised_${name}`, 1);
1902
- }
1903
-
1904
- {{#if notice}}
1905
- const notice = document.getElementById('notice');
1906
- checkAlert(notice, 'notice');
1907
- {{/if}}
1908
-
1909
- function showErrorLogs() {
1910
- document.getElementById('show-error-logs-btn').classList.add('hidden');
1911
- document.getElementById('show-integration-btn').classList.remove('hidden');
1912
-
1913
- appComponent.classList.add('hidden');
1914
- document.getElementById('user-errors').classList.remove('hidden');
1915
-
1916
- checkOrigin()
1917
- .then(restParams => fetch('api/user-errors' + restParams))
1918
- .then(checkResponse)
1919
- .then((res) => {
1920
- const table = document.getElementById('user-errors-table');
1921
-
1922
- const clickRow = (field, index, item) => {
1923
- const modal = document.getElementById('user-error-detail');
1924
- openModal(modal);
1925
- modal.innerHTML = getUserErrorDetail(item);
1926
- };
1927
-
1928
- const formatDate = (field, index, item) => {
1929
- return item.formattedDate;
1930
- }
1931
-
1932
- table.setTableConfig && table.setTableConfig({
1933
- defaultSortingColumn: "createdAt",
1934
- defaultSortingDir: "desc",
1935
- headerFields: [
1936
- {
1937
- field: "createdAt",
1938
- name: "Date",
1939
- isSortable: true,
1940
- clickFn: clickRow,
1941
- formatFn: formatDate,
1942
- },
1943
- {
1944
- field: "action",
1945
- name: "Action",
1946
- clickFn: clickRow,
1947
- },
1948
- {
1949
- field: "message",
1950
- name: "Message",
1951
- clickFn: clickRow,
1952
- }
1953
- ],
1954
- });
1955
-
1956
- table.setTableData(JSON.stringify(res));
1957
- table.setAttribute(`is-loading`, false);
1958
- })
1959
- .catch(e => catchRejection(e, 'Can\'t fetch error logs'));
1960
- }
1961
-
1962
- function showIntegration() {
1963
- document.getElementById('show-error-logs-btn').classList.remove('hidden');
1964
- document.getElementById('show-integration-btn').classList.add('hidden');
1965
-
1966
- appComponent.classList.remove('hidden');
1967
- document.getElementById('user-errors').classList.add('hidden');
1968
- }
1969
-
1970
- function isJSON(string) {
1971
- try {
1972
- JSON.parse(string);
1973
- return true;
1974
- } catch (e) {
1975
- return false;
1976
- }
1977
- }
1978
-
1979
- function getUserErrorDetail(error) {
1980
- const data = JSON.parse(error.data);
1981
-
1982
- let html = '<div class="error-detail-table"><table>';
1983
- html += `<tr><td>Action</td><td>${sanitizeHTML(error.action)}</td></tr>`;
1984
- if (isJSON(error.message)) {
1985
- html += `<tr><td>Message</td><td><pre>${sanitizeHTML(error.message)}<pre></td></tr>`;
1986
- } else {
1987
- html += `<tr><td>Message</td><td>${sanitizeHTML(error.message)}</td></tr>`;
1988
- }
1989
- html += `<tr><td>Date/time</td><td>${sanitizeHTML(error.formattedDate)}</td></tr>`;
1990
-
1991
- if (data.requestParams) {
1992
- html += `<tr><td>Method</td><td>${sanitizeHTML(data.requestParams.method)}</td></tr>`;
1993
- }
1994
-
1995
- if (data.responseData) {
1996
- html += `<tr><td>Response Data</td><td><pre>${sanitizeHTML(JSON.stringify(data.responseData, null, 2))}</pre></td></tr>`;
1997
- }
1998
-
1999
- if (data.appData) {
2000
- if (Array.isArray(data.appData)) {
2001
- html += `<tr><td>App Data</td><td><pre>${sanitizeHTML(JSON.stringify(data.appData, null, 2))}</pre></td></tr>`;
2002
- } else if (typeof data.appData === 'object') {
2003
- for (const key in data.appData) {
2004
- html += `<tr><td>${sanitizeHTML(key.charAt(0).toUpperCase() + key.slice(1))}</td><td><pre>${sanitizeHTML(JSON.stringify(data.appData[key], null, 2))}</pre></td></tr>`;
2005
- }
2006
- } else {
2007
- html += `<tr><td>App Data</td><td>${sanitizeHTML(data.appData)}</td></tr>`;
2008
- }
2009
- }
2010
-
2011
- html += '</table></div>';
2012
-
2013
- return html;
2014
- }
2015
-
2016
- function sanitizeHTML(str) {
2017
- const tempDiv = document.createElement('div');
2018
- tempDiv.textContent = str;
2019
- return tempDiv.innerHTML;
2020
- }
2021
-
2022
- function openModal(modal) {
2023
- modal.style.display = 'block';
2024
- modal.open()
2025
- }
2026
-
2027
- function closeModal(modal) {
2028
- modal.style.display = 'none';
2029
- modal.close()
2030
- }
2031
-
2032
- function settingsFieldChange(el) {
2033
- el.removeAttribute('error');
2034
- el.removeAttribute('error-text');
2035
- }
2036
-
2037
- function isFolder(e) {
2038
- return !e.type && (e.nodeType === undefined || e.nodeType === folderType || e.nodeType === branchType);
2039
- }
2040
- </script>
2041
-
2042
- </html>