@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.
- package/out/app-test/integration/get-integration-files.js +16 -4
- package/out/app-test/integration/types.d.ts +1 -1
- package/out/app-test/integration/update-crowdin.js +2 -2
- package/out/app-test/integration/update-integration.js +2 -2
- package/out/index.d.ts +2 -1
- package/out/index.js +73 -30
- package/out/middlewares/auto-credentials-masker.d.ts +4 -0
- package/out/middlewares/auto-credentials-masker.js +45 -0
- package/out/middlewares/crowdin-client.d.ts +1 -1
- package/out/middlewares/integration-credentials.d.ts +2 -2
- package/out/middlewares/integration-credentials.js +7 -4
- package/out/middlewares/render-ui-module.d.ts +3 -3
- package/out/middlewares/render-ui-module.js +10 -13
- package/out/middlewares/ui-module.d.ts +1 -1
- package/out/middlewares/ui-module.js +11 -1
- package/out/modules/about.d.ts +2 -1
- package/out/modules/about.js +10 -3
- package/out/modules/ai-prompt-provider/handlers/compile.d.ts +1 -1
- package/out/modules/ai-prompt-provider/handlers/compile.js +7 -1
- package/out/modules/ai-prompt-provider/index.js +2 -2
- package/out/modules/ai-prompt-provider/types.d.ts +7 -1
- package/out/modules/ai-provider/handlers/chat-completions.d.ts +1 -1
- package/out/modules/ai-provider/handlers/chat-completions.js +1 -1
- package/out/modules/ai-provider/handlers/get-model-list.d.ts +1 -1
- package/out/modules/ai-provider/index.js +2 -2
- package/out/modules/ai-provider/types.d.ts +4 -4
- package/out/modules/ai-request-processors/handler.d.ts +1 -1
- package/out/modules/ai-request-processors/handler.js +12 -2
- package/out/modules/ai-request-processors/types.d.ts +12 -2
- package/out/modules/ai-tools/handlers/tool-calls.d.ts +1 -1
- package/out/modules/ai-tools/handlers/tool-calls.js +1 -1
- package/out/modules/ai-tools/index.js +1 -1
- package/out/modules/ai-tools/types.d.ts +1 -1
- package/out/modules/auth-guard/handlers/verify.d.ts +1 -1
- package/out/modules/auth-guard/index.js +1 -1
- package/out/modules/automation-action/handlers/execute.d.ts +1 -1
- package/out/modules/automation-action/handlers/execute.js +1 -1
- package/out/modules/automation-action/handlers/input-schema.d.ts +1 -1
- package/out/modules/automation-action/handlers/output-schema.d.ts +1 -1
- package/out/modules/automation-action/handlers/validate-settings.d.ts +1 -1
- package/out/modules/automation-action/handlers/validate-settings.js +1 -1
- package/out/modules/automation-action/index.js +1 -1
- package/out/modules/automation-action/types.d.ts +4 -4
- package/out/modules/automation-action/util/index.js +2 -5
- package/out/modules/context-menu/index.js +2 -2
- package/out/modules/custom-mt/handlers/translate.d.ts +1 -1
- package/out/modules/custom-mt/handlers/translate.js +25 -9
- package/out/modules/custom-mt/index.js +3 -3
- package/out/modules/custom-mt/types.d.ts +10 -2
- package/out/modules/custom-spell-check/handlers/get-languages-list.d.ts +1 -1
- package/out/modules/custom-spell-check/handlers/spell-check.d.ts +1 -1
- package/out/modules/custom-spell-check/index.js +4 -4
- package/out/modules/editor-right-panel/index.js +1 -1
- package/out/modules/external-qa-check/handlers/validate.d.ts +1 -1
- package/out/modules/external-qa-check/index.js +2 -2
- package/out/modules/file-processing/handlers/custom-file-format.d.ts +7 -2
- package/out/modules/file-processing/handlers/custom-file-format.js +62 -22
- package/out/modules/file-processing/handlers/file-download.d.ts +1 -1
- package/out/modules/file-processing/handlers/file-download.js +5 -0
- package/out/modules/file-processing/handlers/pre-post-process.d.ts +1 -1
- package/out/modules/file-processing/handlers/pre-post-process.js +41 -15
- package/out/modules/file-processing/handlers/translations-alignment.d.ts +1 -1
- package/out/modules/file-processing/handlers/translations-alignment.js +10 -4
- package/out/modules/file-processing/index.js +12 -2
- package/out/modules/file-processing/types.d.ts +36 -5
- package/out/modules/file-processing/util/defaults.js +50 -6
- package/out/modules/file-processing/util/files.js +2 -1
- package/out/modules/form-data-display.d.ts +1 -1
- package/out/modules/form-data-save.d.ts +1 -1
- package/out/modules/install.d.ts +1 -1
- package/out/modules/integration/handlers/crowdin-file-progress.d.ts +1 -1
- package/out/modules/integration/handlers/crowdin-file-progress.js +5 -1
- package/out/modules/integration/handlers/crowdin-files-target-languages.d.ts +5 -0
- package/out/modules/integration/handlers/crowdin-files-target-languages.js +103 -0
- package/out/modules/integration/handlers/crowdin-files.d.ts +1 -1
- package/out/modules/integration/handlers/crowdin-files.js +7 -1
- package/out/modules/integration/handlers/crowdin-project.d.ts +1 -1
- package/out/modules/integration/handlers/crowdin-update.d.ts +1 -1
- package/out/modules/integration/handlers/crowdin-update.js +59 -18
- package/out/modules/integration/handlers/crowdin-webhook.d.ts +1 -1
- package/out/modules/integration/handlers/crowdin-webhook.js +15 -8
- package/out/modules/integration/handlers/integration-data.d.ts +1 -1
- package/out/modules/integration/handlers/integration-data.js +13 -6
- package/out/modules/integration/handlers/integration-login.d.ts +1 -1
- package/out/modules/integration/handlers/integration-logout.d.ts +1 -1
- package/out/modules/integration/handlers/integration-logout.js +8 -3
- package/out/modules/integration/handlers/integration-update.d.ts +1 -1
- package/out/modules/integration/handlers/integration-update.js +44 -6
- package/out/modules/integration/handlers/integration-webhook.d.ts +1 -1
- package/out/modules/integration/handlers/invite-users.d.ts +1 -1
- package/out/modules/integration/handlers/job-cancel.d.ts +1 -1
- package/out/modules/integration/handlers/job-info-deprecated.d.ts +1 -1
- package/out/modules/integration/handlers/job-info-deprecated.js +3 -2
- package/out/modules/integration/handlers/job-info.d.ts +1 -1
- package/out/modules/integration/handlers/job-list.d.ts +1 -1
- package/out/modules/integration/handlers/main.d.ts +1 -1
- package/out/modules/integration/handlers/main.js +32 -2
- package/out/modules/integration/handlers/oauth-login.d.ts +1 -1
- package/out/modules/integration/handlers/oauth-login.js +28 -6
- package/out/modules/integration/handlers/oauth-polling.d.ts +1 -1
- package/out/modules/integration/handlers/oauth-url.d.ts +1 -1
- package/out/modules/integration/handlers/settings-save.d.ts +1 -1
- package/out/modules/integration/handlers/settings-save.js +9 -9
- package/out/modules/integration/handlers/settings.d.ts +1 -1
- package/out/modules/integration/handlers/sync-settings-save.d.ts +1 -1
- package/out/modules/integration/handlers/sync-settings-save.js +5 -9
- package/out/modules/integration/handlers/sync-settings.d.ts +1 -1
- package/out/modules/integration/handlers/user-errors.d.ts +1 -1
- package/out/modules/integration/handlers/users.d.ts +1 -1
- package/out/modules/integration/index.js +19 -33
- package/out/modules/integration/types.d.ts +133 -29
- package/out/modules/integration/types.js +1 -0
- package/out/modules/integration/util/cron.d.ts +2 -2
- package/out/modules/integration/util/cron.js +40 -24
- package/out/modules/integration/util/defaults.d.ts +4 -0
- package/out/modules/integration/util/defaults.js +175 -15
- package/out/modules/integration/util/files.d.ts +2 -1
- package/out/modules/integration/util/files.js +19 -8
- package/out/modules/integration/util/job.js +4 -3
- package/out/modules/integration/util/snapshot.js +19 -3
- package/out/modules/integration/util/types.d.ts +1 -0
- package/out/modules/integration/util/types.js +1 -0
- package/out/modules/integration/util/webhooks.d.ts +8 -8
- package/out/modules/integration/util/webhooks.js +34 -17
- package/out/modules/manifest.js +12 -12
- package/out/modules/modal/index.js +2 -2
- package/out/modules/organization-menu/index.js +5 -4
- package/out/modules/organization-settings-menu/index.js +5 -4
- package/out/modules/profile-resources-menu/index.js +5 -4
- package/out/modules/profile-settings-menu/index.js +5 -4
- package/out/modules/project-menu/index.js +1 -1
- package/out/modules/project-menu-crowdsource/index.js +1 -1
- package/out/modules/project-reports/index.js +3 -2
- package/out/modules/project-tools/index.js +3 -2
- package/out/modules/status.d.ts +1 -1
- package/out/modules/status.js +12 -3
- package/out/modules/subscription-paid.d.ts +1 -1
- package/out/modules/uninstall.d.ts +1 -1
- package/out/modules/uninstall.js +1 -1
- package/out/modules/webhooks/handlers/webhook-handler.d.ts +2 -2
- package/out/modules/webhooks/handlers/webhook-handler.js +33 -22
- package/out/modules/webhooks/types.d.ts +7 -0
- package/out/modules/workflow-step-type/handlers/delete-step.d.ts +1 -1
- package/out/modules/workflow-step-type/handlers/step-settings-save.d.ts +1 -1
- package/out/modules/workflow-step-type/index.js +2 -2
- package/out/modules/workflow-step-type/util/index.js +3 -6
- package/out/static/js/dependent.js +16 -7
- package/out/static/ui/error.bundle.js +474 -0
- package/out/static/ui/error.bundle.js.map +1 -0
- package/out/static/ui/install.bundle.js +459 -0
- package/out/static/ui/install.bundle.js.map +1 -0
- package/out/static/ui/login.bundle.js +630 -0
- package/out/static/ui/login.bundle.js.map +1 -0
- package/out/static/ui/main.bundle.js +2109 -0
- package/out/static/ui/main.bundle.js.map +1 -0
- package/out/static/ui/oauth.bundle.js +467 -0
- package/out/static/ui/oauth.bundle.js.map +1 -0
- package/out/storage/d1.d.ts +107 -0
- package/out/storage/d1.js +831 -0
- package/out/storage/index.js +8 -1
- package/out/storage/mysql.js +22 -19
- package/out/storage/postgre.d.ts +0 -1
- package/out/storage/postgre.js +20 -30
- package/out/storage/sqlite.d.ts +2 -5
- package/out/storage/sqlite.js +29 -69
- package/out/types.d.ts +78 -18
- package/out/util/connection.js +1 -1
- package/out/util/credentials-masker.d.ts +3 -3
- package/out/util/credentials-masker.js +14 -20
- package/out/util/cron.d.ts +29 -0
- package/out/util/cron.js +87 -0
- package/out/util/index.d.ts +14 -1
- package/out/util/index.js +121 -10
- package/out/util/jsx-renderer.d.ts +14 -0
- package/out/util/jsx-renderer.js +35 -0
- package/out/util/logger.d.ts +1 -0
- package/out/util/logger.js +2 -2
- package/out/util/static-files.d.ts +19 -0
- package/out/util/static-files.js +87 -0
- package/out/util/subscription.js +1 -1
- package/out/views/AboutPage.d.ts +10 -0
- package/out/views/AboutPage.js +76 -0
- package/out/views/FormPage.d.ts +14 -0
- package/out/views/FormPage.js +28 -0
- package/out/views/SubscriptionPage.d.ts +7 -0
- package/out/views/SubscriptionPage.js +26 -0
- package/out/views/index.d.ts +8 -0
- package/out/views/index.js +15 -0
- package/out/views/layout/Head.d.ts +9 -0
- package/out/views/layout/Head.js +54 -0
- package/package.json +43 -41
- package/out/util/handlebars.d.ts +0 -1
- package/out/util/handlebars.js +0 -46
- package/out/views/about.handlebars +0 -102
- package/out/views/error.handlebars +0 -54
- package/out/views/form.handlebars +0 -31
- package/out/views/install.handlebars +0 -16
- package/out/views/login.handlebars +0 -332
- package/out/views/main.handlebars +0 -2042
- package/out/views/oauth.handlebars +0 -11
- package/out/views/partials/head.handlebars +0 -53
- 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>―</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>
|