@crowdin/app-project-module 0.102.2 → 0.104.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/index.js +2 -0
- package/out/modules/ai-prompt-provider/types.d.ts +9 -0
- package/out/modules/ai-prompt-provider/types.js +7 -0
- package/out/modules/auth-guard/handlers/verify.d.ts +5 -0
- package/out/modules/auth-guard/handlers/verify.js +57 -0
- package/out/modules/auth-guard/index.d.ts +6 -0
- package/out/modules/auth-guard/index.js +36 -0
- package/out/modules/auth-guard/types.d.ts +101 -0
- package/out/modules/auth-guard/types.js +2 -0
- package/out/modules/integration/handlers/integration-data.js +11 -7
- package/out/modules/integration/handlers/sync-settings-save.js +7 -3
- package/out/modules/integration/types.d.ts +5 -1
- package/out/modules/integration/util/defaults.js +11 -5
- package/out/modules/integration/util/files.d.ts +1 -0
- package/out/modules/integration/util/files.js +76 -6
- package/out/modules/manifest.js +36 -3
- package/out/types.d.ts +9 -0
- package/out/views/main.handlebars +142 -23
- package/package.json +1 -1
package/out/index.js
CHANGED
|
@@ -88,6 +88,7 @@ const webhooks = __importStar(require("./modules/webhooks"));
|
|
|
88
88
|
const workflowStepType = __importStar(require("./modules/workflow-step-type"));
|
|
89
89
|
const aiRequestProcessors = __importStar(require("./modules/ai-request-processors"));
|
|
90
90
|
const automationAction = __importStar(require("./modules/automation-action"));
|
|
91
|
+
const authGuard = __importStar(require("./modules/auth-guard"));
|
|
91
92
|
const subscription_1 = require("./util/subscription");
|
|
92
93
|
var types_2 = require("./types");
|
|
93
94
|
Object.defineProperty(exports, "ProjectPermissions", { enumerable: true, get: function () { return types_2.ProjectPermissions; } });
|
|
@@ -222,6 +223,7 @@ function addCrowdinEndpoints(app, clientConfig) {
|
|
|
222
223
|
workflowStepType.register({ config, app });
|
|
223
224
|
aiRequestProcessors.register({ config, app });
|
|
224
225
|
automationAction.register({ config, app });
|
|
226
|
+
authGuard.register({ config, app });
|
|
225
227
|
addFormSchema({ config, app });
|
|
226
228
|
return Object.assign(Object.assign({}, exports.metadataStore), { storage: storage.getStorage(), establishCrowdinConnection: (authRequest, moduleKey) => {
|
|
227
229
|
let jwtToken = '';
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { CrowdinContextInfo, UiModule } from '../../types';
|
|
2
2
|
import Crowdin from '@crowdin/crowdin-api-client';
|
|
3
|
+
export declare enum PromptActions {
|
|
4
|
+
PRE_TRANSLATE = "pre_translate",
|
|
5
|
+
ASSIST = "assist",
|
|
6
|
+
QA_CHECK = "qa_check"
|
|
7
|
+
}
|
|
3
8
|
export interface AiPromptProviderModule extends UiModule {
|
|
4
9
|
/**
|
|
5
10
|
* generates prompt text based on provided options
|
|
6
11
|
*/
|
|
7
12
|
compile: (options: any, payload: any, client: Crowdin, context: CrowdinContextInfo, requestData: any) => Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Represents a collection of supported actions. If not provided, all actions are supported.
|
|
15
|
+
*/
|
|
16
|
+
actions?: PromptActions[] | `custom:${string}`[];
|
|
8
17
|
}
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PromptActions = void 0;
|
|
4
|
+
var PromptActions;
|
|
5
|
+
(function (PromptActions) {
|
|
6
|
+
PromptActions["PRE_TRANSLATE"] = "pre_translate";
|
|
7
|
+
PromptActions["ASSIST"] = "assist";
|
|
8
|
+
PromptActions["QA_CHECK"] = "qa_check";
|
|
9
|
+
})(PromptActions = exports.PromptActions || (exports.PromptActions = {}));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/// <reference types="qs" />
|
|
2
|
+
import { Response } from 'express';
|
|
3
|
+
import { CrowdinClientRequest } from '../../../types';
|
|
4
|
+
import { AuthGuardModule } from '../types';
|
|
5
|
+
export default function verifyHandler(authGuardModules: AuthGuardModule[]): (req: CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const util_1 = require("../../../util");
|
|
13
|
+
function verifyHandler(authGuardModules) {
|
|
14
|
+
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
const { userId, organizationId, ipAddress, moduleKey, code } = req.body;
|
|
16
|
+
req.logInfo(`Auth guard verification request for module: ${moduleKey}`);
|
|
17
|
+
// Find the auth-guard module by key
|
|
18
|
+
const module = authGuardModules.find((m) => m.key === moduleKey);
|
|
19
|
+
if (!module) {
|
|
20
|
+
req.logError(`Auth guard module not found: ${moduleKey}`);
|
|
21
|
+
return res.status(404).json({
|
|
22
|
+
error: `Auth guard module not found: ${moduleKey}`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
// Call the user-provided verification logic
|
|
27
|
+
const result = yield module.verify({
|
|
28
|
+
client: req.crowdinApiClient,
|
|
29
|
+
context: req.crowdinContext,
|
|
30
|
+
userId,
|
|
31
|
+
organizationId,
|
|
32
|
+
ipAddress,
|
|
33
|
+
moduleKey,
|
|
34
|
+
code,
|
|
35
|
+
});
|
|
36
|
+
if (!result || typeof result.success !== 'boolean') {
|
|
37
|
+
req.logError('Invalid response from auth guard verification function');
|
|
38
|
+
return res.status(500).json({
|
|
39
|
+
success: false,
|
|
40
|
+
message: 'Invalid response from verification function',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Return the result (always HTTP 200, success field indicates verification result)
|
|
44
|
+
req.logInfo(`Auth guard verification ${result.success ? 'succeeded' : 'failed'} for module: ${moduleKey}`);
|
|
45
|
+
return res.status(200).json(result);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
req.logError(`Auth guard verification error: ${error.message || error}`);
|
|
49
|
+
// Return HTTP 500 for internal errors
|
|
50
|
+
return res.status(500).json({
|
|
51
|
+
success: false,
|
|
52
|
+
message: 'Internal server error during verification',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
exports.default = verifyHandler;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.register = void 0;
|
|
7
|
+
const crowdin_client_1 = __importDefault(require("../../middlewares/crowdin-client"));
|
|
8
|
+
const json_response_1 = __importDefault(require("../../middlewares/json-response"));
|
|
9
|
+
const render_ui_module_1 = __importDefault(require("../../middlewares/render-ui-module"));
|
|
10
|
+
const ui_module_1 = __importDefault(require("../../middlewares/ui-module"));
|
|
11
|
+
const verify_1 = __importDefault(require("./handlers/verify"));
|
|
12
|
+
function register({ config, app }) {
|
|
13
|
+
if (!config.authGuard) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Normalize to array
|
|
17
|
+
const authGuardModules = Array.isArray(config.authGuard) ? config.authGuard : [config.authGuard];
|
|
18
|
+
if (!authGuardModules.length) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Register settings UI modules if provided
|
|
22
|
+
authGuardModules.forEach((module, index) => {
|
|
23
|
+
if (module.settingsUiModule) {
|
|
24
|
+
const moduleKey = module.key || `${config.identifier}-auth-guard-${index}`;
|
|
25
|
+
app.use(`/auth-guard/${moduleKey}/settings`, (0, ui_module_1.default)({ config, moduleType: moduleKey }), (0, render_ui_module_1.default)(module.settingsUiModule));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
// Register verification endpoint
|
|
29
|
+
app.post('/auth-guard/verify', json_response_1.default, (0, crowdin_client_1.default)({
|
|
30
|
+
config,
|
|
31
|
+
optional: false,
|
|
32
|
+
checkSubscriptionExpiration: false,
|
|
33
|
+
moduleKey: authGuardModules.map((m, i) => m.key || `${config.identifier}-auth-guard-${i}`),
|
|
34
|
+
}), (0, verify_1.default)(authGuardModules));
|
|
35
|
+
}
|
|
36
|
+
exports.register = register;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Crowdin from '@crowdin/crowdin-api-client';
|
|
2
|
+
import { CrowdinContextInfo, Environments, ModuleKey, UiModule } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Auth Guard verification type
|
|
5
|
+
*/
|
|
6
|
+
export type AuthGuardVerificationType = 'direct' | 'redirect' | 'iframe';
|
|
7
|
+
/**
|
|
8
|
+
* Auth Guard module options
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthGuardOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Verification type: direct (backend-only), redirect (external page), or iframe (embedded iframe)
|
|
13
|
+
* @default 'direct'
|
|
14
|
+
*/
|
|
15
|
+
type?: AuthGuardVerificationType;
|
|
16
|
+
/**
|
|
17
|
+
* Whether to apply this check to organization administrators
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
applyToAdmin?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* User-facing URL for redirect and iframe types
|
|
23
|
+
* Required if type is not 'direct'
|
|
24
|
+
*/
|
|
25
|
+
url?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Request body for auth guard verification
|
|
29
|
+
*/
|
|
30
|
+
export interface AuthGuardVerifyRequest {
|
|
31
|
+
/**
|
|
32
|
+
* User ID
|
|
33
|
+
*/
|
|
34
|
+
userId: number;
|
|
35
|
+
/**
|
|
36
|
+
* Organization ID
|
|
37
|
+
*/
|
|
38
|
+
organizationId: number;
|
|
39
|
+
/**
|
|
40
|
+
* User's IP address
|
|
41
|
+
*/
|
|
42
|
+
ipAddress: string;
|
|
43
|
+
/**
|
|
44
|
+
* Module key to identify which auth-guard module to invoke
|
|
45
|
+
*/
|
|
46
|
+
moduleKey: string;
|
|
47
|
+
/**
|
|
48
|
+
* Verification code (provided for redirect/iframe types after user interaction)
|
|
49
|
+
*/
|
|
50
|
+
code?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Response for auth guard verification
|
|
54
|
+
*/
|
|
55
|
+
export interface AuthGuardVerifyResponse {
|
|
56
|
+
/**
|
|
57
|
+
* Whether verification was successful
|
|
58
|
+
*/
|
|
59
|
+
success: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Optional error or informational message
|
|
62
|
+
*/
|
|
63
|
+
message?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Auth Guard module configuration
|
|
67
|
+
*/
|
|
68
|
+
export interface AuthGuardModule extends ModuleKey, Environments {
|
|
69
|
+
/**
|
|
70
|
+
* Module name displayed to users during verification
|
|
71
|
+
*/
|
|
72
|
+
name?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Module description (primarily for iframe type)
|
|
75
|
+
*/
|
|
76
|
+
description?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Endpoint URL for verification (relative to baseUrl or absolute)
|
|
79
|
+
*/
|
|
80
|
+
url?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Module configuration options
|
|
83
|
+
*/
|
|
84
|
+
options?: AuthGuardOptions;
|
|
85
|
+
/**
|
|
86
|
+
* Settings UI module for configuration
|
|
87
|
+
*/
|
|
88
|
+
settingsUiModule?: UiModule;
|
|
89
|
+
/**
|
|
90
|
+
* Verification logic function
|
|
91
|
+
*/
|
|
92
|
+
verify: (params: {
|
|
93
|
+
client?: Crowdin;
|
|
94
|
+
context?: CrowdinContextInfo;
|
|
95
|
+
userId: number;
|
|
96
|
+
organizationId: number;
|
|
97
|
+
ipAddress: string;
|
|
98
|
+
moduleKey: string;
|
|
99
|
+
code?: string;
|
|
100
|
+
}) => Promise<AuthGuardVerifyResponse>;
|
|
101
|
+
}
|
|
@@ -16,8 +16,8 @@ const types_1 = require("../types");
|
|
|
16
16
|
const storage_1 = require("../../../storage");
|
|
17
17
|
function handle(integration) {
|
|
18
18
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
20
|
-
const { parent_id: parentId, search, page } = req.query;
|
|
19
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
20
|
+
const { parent_id: parentId, search, page, paginationData } = req.query;
|
|
21
21
|
req.logInfo('Received request to get integration data');
|
|
22
22
|
let message;
|
|
23
23
|
let stopPagination;
|
|
@@ -25,7 +25,8 @@ function handle(integration) {
|
|
|
25
25
|
const { crowdinId, clientId } = req.crowdinContext;
|
|
26
26
|
try {
|
|
27
27
|
const appSettings = req.integrationSettings;
|
|
28
|
-
const
|
|
28
|
+
const parsedPaginationData = paginationData ? JSON.parse(paginationData) : undefined;
|
|
29
|
+
const result = yield integration.getIntegrationFiles(req.integrationCredentials, appSettings, parentId, search, page, parsedPaginationData);
|
|
29
30
|
if ((0, files_1.isExtendedResultType)(result)) {
|
|
30
31
|
files = result.data || [];
|
|
31
32
|
message = result.message;
|
|
@@ -46,20 +47,22 @@ function handle(integration) {
|
|
|
46
47
|
const needsFileStatus = ((_e = (_d = integration.filtering) === null || _d === void 0 ? void 0 : _d.integrationFileStatus) === null || _e === void 0 ? void 0 : _e.isNew) ||
|
|
47
48
|
((_g = (_f = integration.filtering) === null || _f === void 0 ? void 0 : _f.integrationFileStatus) === null || _g === void 0 ? void 0 : _g.isUpdated) ||
|
|
48
49
|
((_j = (_h = integration.filtering) === null || _h === void 0 ? void 0 : _h.integrationFileStatus) === null || _j === void 0 ? void 0 : _j.notSynced) ||
|
|
50
|
+
((_l = (_k = integration.filtering) === null || _k === void 0 ? void 0 : _k.integrationFileStatus) === null || _l === void 0 ? void 0 : _l.synced) ||
|
|
49
51
|
integration.forcePushSources === true;
|
|
50
52
|
if (needsFileStatus) {
|
|
51
53
|
const syncedData = yield (0, storage_1.getStorage)().getSyncedData(clientId, crowdinId, types_1.Provider.INTEGRATION);
|
|
52
54
|
const syncedFiles = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.files) ? JSON.parse(syncedData.files) : [];
|
|
53
55
|
const lastSyncTimestamp = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.updatedAt) ? Number(syncedData.updatedAt) : null;
|
|
54
56
|
files = files.map((file) => {
|
|
55
|
-
var _a, _b, _c, _d, _e, _f;
|
|
57
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
56
58
|
if (!file.type) {
|
|
57
59
|
return file;
|
|
58
60
|
}
|
|
59
61
|
const syncedFile = syncedFiles.find((syncedItem) => syncedItem.id === file.id);
|
|
60
62
|
const notSynced = ((_b = (_a = integration.filtering) === null || _a === void 0 ? void 0 : _a.integrationFileStatus) === null || _b === void 0 ? void 0 : _b.notSynced) === true ? !syncedFile : false;
|
|
63
|
+
const synced = ((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.synced) === true ? !!syncedFile : false;
|
|
61
64
|
const fileSyncTimestamp = (syncedFile === null || syncedFile === void 0 ? void 0 : syncedFile.syncedAt) ? Number(syncedFile.syncedAt) : lastSyncTimestamp;
|
|
62
|
-
const isNew = ((
|
|
65
|
+
const isNew = ((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.isNew) === true
|
|
63
66
|
? !syncedFile &&
|
|
64
67
|
fileSyncTimestamp &&
|
|
65
68
|
file.createdAt &&
|
|
@@ -67,7 +70,7 @@ function handle(integration) {
|
|
|
67
70
|
? new Date(file.createdAt).getTime()
|
|
68
71
|
: file.createdAt) > fileSyncTimestamp
|
|
69
72
|
: false;
|
|
70
|
-
const shouldCalculateIsUpdated = ((
|
|
73
|
+
const shouldCalculateIsUpdated = ((_h = (_g = integration.filtering) === null || _g === void 0 ? void 0 : _g.integrationFileStatus) === null || _h === void 0 ? void 0 : _h.isUpdated) === true ||
|
|
71
74
|
integration.forcePushSources === true;
|
|
72
75
|
const isUpdated = shouldCalculateIsUpdated
|
|
73
76
|
? syncedFile &&
|
|
@@ -78,7 +81,8 @@ function handle(integration) {
|
|
|
78
81
|
: false;
|
|
79
82
|
return Object.assign(Object.assign({}, file), { isNew,
|
|
80
83
|
notSynced,
|
|
81
|
-
isUpdated
|
|
84
|
+
isUpdated,
|
|
85
|
+
synced });
|
|
82
86
|
});
|
|
83
87
|
}
|
|
84
88
|
}
|
|
@@ -26,7 +26,7 @@ function checkAutoSyncSettings(integration, appSettings, provider) {
|
|
|
26
26
|
}
|
|
27
27
|
function handle(config, integration) {
|
|
28
28
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
29
|
-
const { files, provider, expandIntegrationFolders } = req.body;
|
|
29
|
+
const { files, provider, expandIntegrationFolders, hasLoadMoreElements } = req.body;
|
|
30
30
|
if (req.isApiCall) {
|
|
31
31
|
if (!files || !provider) {
|
|
32
32
|
return res.status(400).json({
|
|
@@ -54,9 +54,12 @@ function handle(config, integration) {
|
|
|
54
54
|
client: req.crowdinApiClient,
|
|
55
55
|
jobType: types_1.JobClientType.MANUAL,
|
|
56
56
|
jobStoreType: integration.jobStoreType,
|
|
57
|
-
jobCallback: () => __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
|
|
58
58
|
if (Array.isArray(expandIntegrationFolders) && expandIntegrationFolders.length) {
|
|
59
|
-
const
|
|
59
|
+
const expandedFiles = hasLoadMoreElements
|
|
60
|
+
? yield (0, files_1.expandFilesTreeWithPagination)(expandIntegrationFolders, req, integration, job)
|
|
61
|
+
: yield (0, files_1.expandFilesTree)(expandIntegrationFolders, req, integration, job);
|
|
62
|
+
const allFiles = expandedFiles.map((node) => ({
|
|
60
63
|
id: node.id,
|
|
61
64
|
name: node.name,
|
|
62
65
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
|
@@ -67,6 +70,7 @@ function handle(config, integration) {
|
|
|
67
70
|
sync: false,
|
|
68
71
|
type: node.type,
|
|
69
72
|
}));
|
|
73
|
+
req.logInfo(`Expanded ${allFiles.length} files for auto-sync`);
|
|
70
74
|
yield (0, cron_1.createOrUpdateSyncSettings)({
|
|
71
75
|
req,
|
|
72
76
|
files: (0, lodash_uniqby_1.default)([...files, ...allFiles], 'id'),
|
|
@@ -40,7 +40,7 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
40
40
|
/**
|
|
41
41
|
* function to get data from integration
|
|
42
42
|
*/
|
|
43
|
-
getIntegrationFiles: (apiCredentials: any, config?: any, parentId?: any, search?: any, page?: any) => Promise<TreeItem[] | ExtendedResult<TreeItem[]>>;
|
|
43
|
+
getIntegrationFiles: (apiCredentials: any, config?: any, parentId?: any, search?: any, page?: any, paginationData?: any) => Promise<TreeItem[] | ExtendedResult<TreeItem[]>>;
|
|
44
44
|
/**
|
|
45
45
|
* function to update crowdin files (e.g. pull integration data to crowdin source files)
|
|
46
46
|
*/
|
|
@@ -146,6 +146,10 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
146
146
|
* Enable file filtering by "notSynced" status
|
|
147
147
|
*/
|
|
148
148
|
notSynced?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Enable file filtering by "synced" status
|
|
151
|
+
*/
|
|
152
|
+
synced?: boolean;
|
|
149
153
|
};
|
|
150
154
|
};
|
|
151
155
|
/**
|
|
@@ -58,7 +58,7 @@ function getOauthRoute(integration) {
|
|
|
58
58
|
}
|
|
59
59
|
exports.getOauthRoute = getOauthRoute;
|
|
60
60
|
function applyIntegrationModuleDefaults(config, integration) {
|
|
61
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
61
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
62
62
|
if (!integration.getCrowdinFiles) {
|
|
63
63
|
integration.getCrowdinFiles = (projectId, client, rootFolder, config, mode) => __awaiter(this, void 0, void 0, function* () {
|
|
64
64
|
const allBranches = (yield client.sourceFilesApi.withFetchAll().listProjectBranches(projectId)).data.map((d) => d.data);
|
|
@@ -152,7 +152,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
152
152
|
}
|
|
153
153
|
const getUserSettings = integration.getConfiguration;
|
|
154
154
|
integration.getConfiguration = (projectId, crowdinClient, integrationCredentials) => __awaiter(this, void 0, void 0, function* () {
|
|
155
|
-
var
|
|
155
|
+
var _p, _q, _r;
|
|
156
156
|
let fields = [];
|
|
157
157
|
const project = (yield crowdinClient.projectsGroupsApi.getProject(projectId));
|
|
158
158
|
if (getUserSettings) {
|
|
@@ -171,7 +171,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
171
171
|
if (integration.withCronSync || integration.webhooks) {
|
|
172
172
|
const userSchedule = fields.find((field) => 'key' in field && field.key === 'schedule');
|
|
173
173
|
if (userSchedule) {
|
|
174
|
-
userSchedule.position = (
|
|
174
|
+
userSchedule.position = (_p = userSchedule.position) !== null && _p !== void 0 ? _p : 0;
|
|
175
175
|
userSchedule.category = types_1.DefaultCategory.SYNC;
|
|
176
176
|
}
|
|
177
177
|
else {
|
|
@@ -211,7 +211,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
211
211
|
],
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
|
-
if ((
|
|
214
|
+
if ((_q = integration.syncNewElements) === null || _q === void 0 ? void 0 : _q.crowdin) {
|
|
215
215
|
defaultSettings.push({
|
|
216
216
|
key: 'new-crowdin-files',
|
|
217
217
|
label: 'Automatically sync new translations from Crowdin',
|
|
@@ -221,7 +221,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
221
221
|
position: 1,
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
|
-
if ((
|
|
224
|
+
if ((_r = integration.syncNewElements) === null || _r === void 0 ? void 0 : _r.integration) {
|
|
225
225
|
defaultSettings.push({
|
|
226
226
|
key: 'new-integration-files',
|
|
227
227
|
label: `Automatically sync new content from ${config.name}`,
|
|
@@ -350,6 +350,12 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
350
350
|
label: 'Never Synced',
|
|
351
351
|
});
|
|
352
352
|
}
|
|
353
|
+
if ((_o = (_m = integration.filtering) === null || _m === void 0 ? void 0 : _m.integrationFileStatus) === null || _o === void 0 ? void 0 : _o.synced) {
|
|
354
|
+
filterItems.push({
|
|
355
|
+
value: 'synced',
|
|
356
|
+
label: 'Previously Synced',
|
|
357
|
+
});
|
|
358
|
+
}
|
|
353
359
|
integration.filtering = Object.assign(Object.assign({}, (integration.filtering || {})), { integrationFilterConfig: filterItems.length > 1
|
|
354
360
|
? [
|
|
355
361
|
{
|
|
@@ -5,6 +5,7 @@ import { SourceFilesModel } from '@crowdin/crowdin-api-client';
|
|
|
5
5
|
export declare function skipFilesByRegex(files: TreeItem[] | undefined, skipIntegrationNodes?: SkipIntegrationNodes): TreeItem[];
|
|
6
6
|
export declare function expandFilesTree(nodes: IntegrationFile[], req: IntegrationRequest, integration: IntegrationLogic, job?: JobClient): Promise<IntegrationFile[]>;
|
|
7
7
|
export declare function attachFileStatus(files: any[], clientId: string, crowdinId: string, integration: IntegrationLogic): Promise<any[]>;
|
|
8
|
+
export declare function expandFilesTreeWithPagination(nodes: IntegrationFile[], req: IntegrationRequest, integration: IntegrationLogic, job?: JobClient): Promise<IntegrationFile[]>;
|
|
8
9
|
export declare function isExtendedResultType<T>(data?: T | ExtendedResult<T>): data is ExtendedResult<T>;
|
|
9
10
|
export declare function markUnsyncedFiles({ integrationId, crowdinId, client, files, }: {
|
|
10
11
|
integrationId: string;
|
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.filterSyncedData = exports.updateSyncedData = exports.filterFilesByPath = exports.filterFilesByPatterns = exports.getParentFiles = exports.getParentPaths = exports.buildPath = exports.prepareSyncFiles = exports.uniqueFileLanguages = exports.filterLanguages = exports.getExcludedTargetLanguages = exports.markUnsyncedFiles = exports.isExtendedResultType = exports.attachFileStatus = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
|
|
15
|
+
exports.filterSyncedData = exports.updateSyncedData = exports.filterFilesByPath = exports.filterFilesByPatterns = exports.getParentFiles = exports.getParentPaths = exports.buildPath = exports.prepareSyncFiles = exports.uniqueFileLanguages = exports.filterLanguages = exports.getExcludedTargetLanguages = exports.markUnsyncedFiles = exports.isExtendedResultType = exports.expandFilesTreeWithPagination = exports.attachFileStatus = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
|
|
16
16
|
const types_1 = require("../types");
|
|
17
17
|
const types_2 = require("./types");
|
|
18
18
|
const storage_1 = require("../../../storage");
|
|
@@ -38,7 +38,7 @@ function skipFilesByRegex(files, skipIntegrationNodes) {
|
|
|
38
38
|
}
|
|
39
39
|
exports.skipFilesByRegex = skipFilesByRegex;
|
|
40
40
|
function expandFilesTree(nodes, req, integration, job) {
|
|
41
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
41
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
42
42
|
return __awaiter(this, void 0, void 0, function* () {
|
|
43
43
|
if (job && types_2.JobStatus.CANCELED === ((_a = (yield job.get())) === null || _a === void 0 ? void 0 : _a.status)) {
|
|
44
44
|
throw new Error('Job canceled');
|
|
@@ -72,6 +72,7 @@ function expandFilesTree(nodes, req, integration, job) {
|
|
|
72
72
|
const needsFileStatus = ((_c = (_b = integration.filtering) === null || _b === void 0 ? void 0 : _b.integrationFileStatus) === null || _c === void 0 ? void 0 : _c.isNew) ||
|
|
73
73
|
((_e = (_d = integration.filtering) === null || _d === void 0 ? void 0 : _d.integrationFileStatus) === null || _e === void 0 ? void 0 : _e.isUpdated) ||
|
|
74
74
|
((_g = (_f = integration.filtering) === null || _f === void 0 ? void 0 : _f.integrationFileStatus) === null || _g === void 0 ? void 0 : _g.notSynced) ||
|
|
75
|
+
((_j = (_h = integration.filtering) === null || _h === void 0 ? void 0 : _h.integrationFileStatus) === null || _j === void 0 ? void 0 : _j.synced) ||
|
|
75
76
|
integration.forcePushSources === true;
|
|
76
77
|
if (needsFileStatus) {
|
|
77
78
|
const { crowdinId, clientId } = req.crowdinContext;
|
|
@@ -82,11 +83,12 @@ function expandFilesTree(nodes, req, integration, job) {
|
|
|
82
83
|
}
|
|
83
84
|
exports.expandFilesTree = expandFilesTree;
|
|
84
85
|
function attachFileStatus(files, clientId, crowdinId, integration) {
|
|
85
|
-
var _a, _b, _c, _d, _e, _f;
|
|
86
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
86
87
|
return __awaiter(this, void 0, void 0, function* () {
|
|
87
88
|
const needsFileStatus = ((_b = (_a = integration.filtering) === null || _a === void 0 ? void 0 : _a.integrationFileStatus) === null || _b === void 0 ? void 0 : _b.isNew) ||
|
|
88
89
|
((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.isUpdated) ||
|
|
89
90
|
((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.notSynced) ||
|
|
91
|
+
((_h = (_g = integration.filtering) === null || _g === void 0 ? void 0 : _g.integrationFileStatus) === null || _h === void 0 ? void 0 : _h.synced) ||
|
|
90
92
|
integration.forcePushSources === true;
|
|
91
93
|
if (!needsFileStatus) {
|
|
92
94
|
return files;
|
|
@@ -95,21 +97,22 @@ function attachFileStatus(files, clientId, crowdinId, integration) {
|
|
|
95
97
|
const syncedFiles = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.files) ? JSON.parse(syncedData.files) : [];
|
|
96
98
|
const lastSyncTimestamp = (syncedData === null || syncedData === void 0 ? void 0 : syncedData.updatedAt) ? Number(syncedData.updatedAt) : null;
|
|
97
99
|
return files.map((file) => {
|
|
98
|
-
var _a, _b, _c, _d, _e, _f;
|
|
100
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
99
101
|
if (!file.type) {
|
|
100
102
|
return file;
|
|
101
103
|
}
|
|
102
104
|
const syncedFile = syncedFiles.find((syncedItem) => syncedItem.id === file.id);
|
|
103
105
|
const notSynced = ((_b = (_a = integration.filtering) === null || _a === void 0 ? void 0 : _a.integrationFileStatus) === null || _b === void 0 ? void 0 : _b.notSynced) === true ? !syncedFile : false;
|
|
106
|
+
const synced = ((_d = (_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.integrationFileStatus) === null || _d === void 0 ? void 0 : _d.synced) === true ? !!syncedFile : false;
|
|
104
107
|
const fileSyncTimestamp = (syncedFile === null || syncedFile === void 0 ? void 0 : syncedFile.syncedAt) ? Number(syncedFile.syncedAt) : lastSyncTimestamp;
|
|
105
|
-
const isNew = ((
|
|
108
|
+
const isNew = ((_f = (_e = integration.filtering) === null || _e === void 0 ? void 0 : _e.integrationFileStatus) === null || _f === void 0 ? void 0 : _f.isNew) === true
|
|
106
109
|
? !syncedFile &&
|
|
107
110
|
fileSyncTimestamp &&
|
|
108
111
|
file.createdAt &&
|
|
109
112
|
(typeof file.createdAt === 'string' ? new Date(file.createdAt).getTime() : file.createdAt) >
|
|
110
113
|
fileSyncTimestamp
|
|
111
114
|
: false;
|
|
112
|
-
const shouldCalculateIsUpdated = ((
|
|
115
|
+
const shouldCalculateIsUpdated = ((_h = (_g = integration.filtering) === null || _g === void 0 ? void 0 : _g.integrationFileStatus) === null || _h === void 0 ? void 0 : _h.isUpdated) === true || integration.forcePushSources === true;
|
|
113
116
|
const isUpdated = file.isUpdated !== undefined
|
|
114
117
|
? file.isUpdated
|
|
115
118
|
: shouldCalculateIsUpdated
|
|
@@ -121,11 +124,78 @@ function attachFileStatus(files, clientId, crowdinId, integration) {
|
|
|
121
124
|
: false;
|
|
122
125
|
return Object.assign(Object.assign({}, file), { isNew,
|
|
123
126
|
notSynced,
|
|
127
|
+
synced,
|
|
124
128
|
isUpdated });
|
|
125
129
|
});
|
|
126
130
|
});
|
|
127
131
|
}
|
|
128
132
|
exports.attachFileStatus = attachFileStatus;
|
|
133
|
+
function expandFilesTreeWithPagination(nodes, req, integration, job) {
|
|
134
|
+
var _a, _b;
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
if (job && types_2.JobStatus.CANCELED === ((_a = (yield job.get())) === null || _a === void 0 ? void 0 : _a.status)) {
|
|
137
|
+
throw new Error('Job canceled');
|
|
138
|
+
}
|
|
139
|
+
const files = nodes.filter((file) => file.nodeType === undefined || file.nodeType === '1');
|
|
140
|
+
const folders = nodes.filter((folder) => folder.nodeType === '0' && !nodes.find((node) => node.parentId === folder.id));
|
|
141
|
+
for (const folder of folders) {
|
|
142
|
+
const allFolderFiles = [];
|
|
143
|
+
let paginationData = undefined;
|
|
144
|
+
let hasMore = true;
|
|
145
|
+
let pageCount = 0;
|
|
146
|
+
while (hasMore) {
|
|
147
|
+
if (job && types_2.JobStatus.CANCELED === ((_b = (yield job.get())) === null || _b === void 0 ? void 0 : _b.status)) {
|
|
148
|
+
throw new Error('Job canceled');
|
|
149
|
+
}
|
|
150
|
+
pageCount++;
|
|
151
|
+
try {
|
|
152
|
+
if (job && pageCount > 1) {
|
|
153
|
+
yield job.update({
|
|
154
|
+
info: `Loading files from ${folder.name} (page ${pageCount})`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const result = yield integration.getIntegrationFiles(req.integrationCredentials, req.integrationSettings, folder.id, '', 0, paginationData);
|
|
158
|
+
let integrationTreeItems = [];
|
|
159
|
+
if (isExtendedResultType(result)) {
|
|
160
|
+
integrationTreeItems = result.data || [];
|
|
161
|
+
if (result.stopPagination === true) {
|
|
162
|
+
hasMore = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
integrationTreeItems = result || [];
|
|
167
|
+
}
|
|
168
|
+
const loadMoreElement = integrationTreeItems.find((item) => item.nodeType === 'load-more');
|
|
169
|
+
const actualFiles = integrationTreeItems.filter((item) => item.nodeType !== 'load-more');
|
|
170
|
+
allFolderFiles.push(...actualFiles);
|
|
171
|
+
if (loadMoreElement && loadMoreElement.pagination) {
|
|
172
|
+
paginationData = loadMoreElement.pagination;
|
|
173
|
+
}
|
|
174
|
+
else if (!isExtendedResultType(result) || result.stopPagination !== false) {
|
|
175
|
+
hasMore = false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
180
|
+
if (job) {
|
|
181
|
+
const currentJob = yield job.get();
|
|
182
|
+
const currentErrors = (currentJob === null || currentJob === void 0 ? void 0 : currentJob.errors) ? JSON.parse(currentJob.errors) : [];
|
|
183
|
+
yield job.update({ errors: [...currentErrors, errorMessage] });
|
|
184
|
+
}
|
|
185
|
+
req.logError(`Error loading page ${pageCount} for folder ${folder.id}: ${errorMessage}`);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const checkNodes = allFolderFiles
|
|
190
|
+
.filter((item) => item.id !== folder.id)
|
|
191
|
+
.map((item) => (Object.assign(Object.assign({}, item), { nodeType: item.nodeType || ('type' in item ? '1' : '0') })));
|
|
192
|
+
const expandedResult = yield expandFilesTreeWithPagination(checkNodes, req, integration, job);
|
|
193
|
+
files.push(...expandedResult);
|
|
194
|
+
}
|
|
195
|
+
return files;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
exports.expandFilesTreeWithPagination = expandFilesTreeWithPagination;
|
|
129
199
|
function isExtendedResultType(data) {
|
|
130
200
|
const dataTyped = data;
|
|
131
201
|
return !!dataTyped && !Array.isArray(dataTyped);
|
package/out/modules/manifest.js
CHANGED
|
@@ -295,7 +295,11 @@ function handle(config) {
|
|
|
295
295
|
// prevent possible overrides of the other modules
|
|
296
296
|
config.aiPromptProvider = Object.assign(Object.assign({}, config.aiPromptProvider), { key: config.identifier + '-ai-prompt-provider' });
|
|
297
297
|
modules['ai-prompt-provider'] = [
|
|
298
|
-
Object.assign({ key: config.aiPromptProvider.key, name: config.aiPromptProvider.name || config.name, logo: (0, util_1.getLogoUrl)(config.aiPromptProvider, '/ai-prompt-provider'), compileUrl: '/prompt-provider/compile' }, (config.aiPromptProvider.
|
|
298
|
+
Object.assign(Object.assign({ key: config.aiPromptProvider.key, name: config.aiPromptProvider.name || config.name, logo: (0, util_1.getLogoUrl)(config.aiPromptProvider, '/ai-prompt-provider'), compileUrl: '/prompt-provider/compile' }, (config.aiPromptProvider.actions
|
|
299
|
+
? {
|
|
300
|
+
actions: config.aiPromptProvider.actions,
|
|
301
|
+
}
|
|
302
|
+
: {})), (config.aiPromptProvider.formSchema || config.aiPromptProvider.uiPath
|
|
299
303
|
? {
|
|
300
304
|
configuratorUrl: '/prompt-provider/settings/' + (config.aiPromptProvider.fileName || 'index.html'),
|
|
301
305
|
}
|
|
@@ -407,6 +411,35 @@ function handle(config) {
|
|
|
407
411
|
: {})), { validateSettingsUrl: (0, index_1.getAutomationActionUrl)('/automation-action/validate-settings', automationAction), executeUrl: (0, index_1.getAutomationActionUrl)('/automation-action/execute', automationAction), invocationWaitMode: automationAction.invocationWaitMode || 'sync' }), (uiModule ? { url: (0, index_1.getAutomationActionUrl)('/automation-action', automationAction) } : {})));
|
|
408
412
|
}
|
|
409
413
|
}
|
|
414
|
+
if (config.authGuard) {
|
|
415
|
+
// prevent possible overrides of the other modules
|
|
416
|
+
if (Array.isArray(config.authGuard)) {
|
|
417
|
+
config.authGuard = config.authGuard.map((authGuard) => (Object.assign({}, authGuard)));
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
config.authGuard = Object.assign({}, config.authGuard);
|
|
421
|
+
}
|
|
422
|
+
const authGuards = Array.isArray(config.authGuard) ? config.authGuard : [config.authGuard];
|
|
423
|
+
modules['auth-guard'] = [];
|
|
424
|
+
for (let i = 0; i < authGuards.length; i++) {
|
|
425
|
+
if (!authGuards[i].key) {
|
|
426
|
+
authGuards[i].key = config.identifier + '-auth-guard-' + i;
|
|
427
|
+
}
|
|
428
|
+
const options = authGuards[i].options || {};
|
|
429
|
+
const manifestEntry = {
|
|
430
|
+
key: authGuards[i].key,
|
|
431
|
+
name: authGuards[i].name || config.name,
|
|
432
|
+
url: '/auth-guard/verify',
|
|
433
|
+
};
|
|
434
|
+
if (authGuards[i].description) {
|
|
435
|
+
manifestEntry.description = authGuards[i].description;
|
|
436
|
+
}
|
|
437
|
+
if (options.type || options.applyToAdmin !== undefined || options.url) {
|
|
438
|
+
manifestEntry.options = Object.assign(Object.assign(Object.assign({}, (options.type && { type: options.type })), (options.applyToAdmin !== undefined && { applyToAdmin: options.applyToAdmin })), (options.url && { url: options.url }));
|
|
439
|
+
}
|
|
440
|
+
modules['auth-guard'].push(manifestEntry);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
410
443
|
const events = {
|
|
411
444
|
installed: '/installed',
|
|
412
445
|
uninstall: '/uninstall',
|
|
@@ -415,10 +448,10 @@ function handle(config) {
|
|
|
415
448
|
events['subscription_paid'] = '/subscription-paid';
|
|
416
449
|
}
|
|
417
450
|
return (_req, res) => {
|
|
418
|
-
const manifest = Object.assign(Object.assign(Object.assign(Object.assign({ identifier: config.identifier, name: config.name, logo: (0, util_1.getLogoUrl)(), baseUrl: config.baseUrl, authentication: {
|
|
451
|
+
const manifest = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ identifier: config.identifier, name: config.name, logo: (0, util_1.getLogoUrl)(), baseUrl: config.baseUrl, authentication: {
|
|
419
452
|
type: config.authenticationType || types_1.AuthenticationType.APP,
|
|
420
453
|
clientId: config.clientId,
|
|
421
|
-
}, restrictAiToSameApp: config.restrictAiToSameApp }, (config.agent && { agent: config.agent })), { events, scopes: config.scopes ? config.scopes : [types_1.Scope.PROJECTS] }), (config.defaultPermissions && { default_permissions: config.defaultPermissions })), { modules });
|
|
454
|
+
}, restrictAiToSameApp: config.restrictAiToSameApp }, (config.stringBasedAvailable !== undefined && { stringBasedAvailable: config.stringBasedAvailable })), (config.agent && { agent: config.agent })), { events, scopes: config.scopes ? config.scopes : [types_1.Scope.PROJECTS] }), (config.defaultPermissions && { default_permissions: config.defaultPermissions })), { modules });
|
|
422
455
|
res.send(manifest);
|
|
423
456
|
};
|
|
424
457
|
}
|
package/out/types.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { Webhook } from './modules/webhooks/types';
|
|
|
20
20
|
import { WorkflowStepTypeModule } from './modules/workflow-step-type/types';
|
|
21
21
|
import { AiRequestProcessorModule, AiStreamProcessorModule } from './modules/ai-request-processors/types';
|
|
22
22
|
import { AutomationActionModule } from './modules/automation-action/types';
|
|
23
|
+
import { AuthGuardModule } from './modules/auth-guard/types';
|
|
23
24
|
export interface ClientConfig extends ImagePath {
|
|
24
25
|
/**
|
|
25
26
|
* Authentication Crowdin App type: "authorization_code", "crowdin_app", "crowdin_agent". Default: "crowdin_app"
|
|
@@ -264,10 +265,18 @@ export interface ClientConfig extends ImagePath {
|
|
|
264
265
|
* property that tells backend that AiProvider and AiPromptProvider modules can cooperate only with each one
|
|
265
266
|
*/
|
|
266
267
|
restrictAiToSameApp?: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Flag to indicate if the app works with string-based projects
|
|
270
|
+
*/
|
|
271
|
+
stringBasedAvailable?: boolean;
|
|
267
272
|
/**
|
|
268
273
|
* Automation action module
|
|
269
274
|
*/
|
|
270
275
|
automationAction?: AutomationActionModule | AutomationActionModule[];
|
|
276
|
+
/**
|
|
277
|
+
* Auth guard module for custom authentication/authorization checks
|
|
278
|
+
*/
|
|
279
|
+
authGuard?: AuthGuardModule | AuthGuardModule[];
|
|
271
280
|
}
|
|
272
281
|
export interface Environments {
|
|
273
282
|
environments?: Environment | Environment[];
|
|
@@ -453,6 +453,15 @@
|
|
|
453
453
|
onchange="onChangeAutoSynchronizationOptions(this)"
|
|
454
454
|
>
|
|
455
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>
|
|
456
465
|
<crowdin-checkbox
|
|
457
466
|
id="new-files"
|
|
458
467
|
name="new-files"
|
|
@@ -499,8 +508,21 @@
|
|
|
499
508
|
cancelJob(e.detail);
|
|
500
509
|
});
|
|
501
510
|
{{#if integrationSearchListener}}
|
|
502
|
-
document.body.addEventListener("integrationFilterChange", (event) => {
|
|
503
|
-
|
|
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 });
|
|
504
526
|
})
|
|
505
527
|
{{/if}}
|
|
506
528
|
const appComponent = document.querySelector('crowdin-simple-integration');
|
|
@@ -535,6 +557,9 @@
|
|
|
535
557
|
let project = {};
|
|
536
558
|
let crowdinData = [];
|
|
537
559
|
let fileToSync = [];
|
|
560
|
+
let hasLoadMoreElements = false;
|
|
561
|
+
let savedIntegrationData = null;
|
|
562
|
+
let currentOpenedFolders = null;
|
|
538
563
|
|
|
539
564
|
getCrowdinData();
|
|
540
565
|
getIntegrationData({});
|
|
@@ -628,10 +653,29 @@
|
|
|
628
653
|
.finally(() => (appComponent.setAttribute('is-crowdin-loading', false)));
|
|
629
654
|
}
|
|
630
655
|
|
|
631
|
-
function getIntegrationData(
|
|
632
|
-
|
|
656
|
+
function getIntegrationData(
|
|
657
|
+
{
|
|
658
|
+
hardReload = false,
|
|
659
|
+
parentId = '',
|
|
660
|
+
search = '',
|
|
661
|
+
page = 0,
|
|
662
|
+
recursion = false,
|
|
663
|
+
paginationData,
|
|
664
|
+
} = {},
|
|
665
|
+
recursionState = null
|
|
666
|
+
) {
|
|
667
|
+
if (!paginationData) {
|
|
668
|
+
appComponent.setAttribute('is-integration-loading', true);
|
|
669
|
+
}
|
|
670
|
+
|
|
633
671
|
return checkOrigin()
|
|
634
|
-
.then(restParams =>
|
|
672
|
+
.then(restParams => {
|
|
673
|
+
let url = `api/integration/data${restParams}&parent_id=${encodeURIComponent(parentId)}&search=${encodeURIComponent(search)}&page=${page}`;
|
|
674
|
+
if (paginationData) {
|
|
675
|
+
url += `&paginationData=${encodeURIComponent(JSON.stringify(paginationData))}`;
|
|
676
|
+
}
|
|
677
|
+
return fetch(url);
|
|
678
|
+
})
|
|
635
679
|
.then(checkResponse)
|
|
636
680
|
.then(async (res) => {
|
|
637
681
|
const files = res.data;
|
|
@@ -645,11 +689,13 @@
|
|
|
645
689
|
labels: e.labels,
|
|
646
690
|
disabled: e.disabled,
|
|
647
691
|
tooltip: e.tooltip,
|
|
692
|
+
pagination: e.pagination,
|
|
648
693
|
};
|
|
649
694
|
if (e.type) {
|
|
650
695
|
item.isNew = e.isNew;
|
|
651
696
|
item.isUpdated = e.isUpdated;
|
|
652
697
|
item.notSynced = e.notSynced;
|
|
698
|
+
item.synced = e.synced;
|
|
653
699
|
item.type = e.type;
|
|
654
700
|
item.node_type = fileType;
|
|
655
701
|
} else {
|
|
@@ -658,6 +704,10 @@
|
|
|
658
704
|
return item;
|
|
659
705
|
});
|
|
660
706
|
|
|
707
|
+
if ((!paginationData && !parentId && !search) || hardReload) {
|
|
708
|
+
hasLoadMoreElements = tree.some(item => item.node_type === 'load-more');
|
|
709
|
+
}
|
|
710
|
+
|
|
661
711
|
const appIntegrationFiles = appComponent.querySelector('#integration-files');
|
|
662
712
|
if (hardReload) {
|
|
663
713
|
appComponent.setIntegrationFilesData(tree);
|
|
@@ -1496,6 +1546,18 @@
|
|
|
1496
1546
|
})
|
|
1497
1547
|
{{/if}}
|
|
1498
1548
|
|
|
1549
|
+
document.body.addEventListener('loadMoreClick', async (e) => {
|
|
1550
|
+
const loadMoreFile = e.detail;
|
|
1551
|
+
const fileComponent = document.getElementById('integration-files')
|
|
1552
|
+
|
|
1553
|
+
await fileComponent.setLoadMoreLoading(loadMoreFile.id, true);
|
|
1554
|
+
|
|
1555
|
+
getIntegrationData({ parentId: e.detail.parent_id, paginationData: e.detail.pagination })
|
|
1556
|
+
.finally(async () => {
|
|
1557
|
+
await fileComponent.setLoadMoreLoading(loadMoreFile.id, false);
|
|
1558
|
+
});
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1499
1561
|
{{#or withCronSync webhooks}}
|
|
1500
1562
|
const scheduleModal = document.getElementById('confirm-schedule-modal');
|
|
1501
1563
|
let syncData;
|
|
@@ -1520,6 +1582,7 @@
|
|
|
1520
1582
|
async function saveScheduleSync() {
|
|
1521
1583
|
const newFile = scheduleModal.querySelector('#new-files').checked || false;
|
|
1522
1584
|
const selectedFiles = scheduleModal.querySelector('#selected-files').checked || false;
|
|
1585
|
+
const allFolderFiles = scheduleModal.querySelector('#all-folder-files').checked || false;
|
|
1523
1586
|
|
|
1524
1587
|
const type = scheduleModal.getAttribute('data-type');
|
|
1525
1588
|
|
|
@@ -1530,7 +1593,11 @@
|
|
|
1530
1593
|
appComponent.setAttribute('is-crowdin-sync-settings-in-progress', true);
|
|
1531
1594
|
updateSyncSettings(syncedFiles, 'schedule', 'crowdin');
|
|
1532
1595
|
} else if (type === 'integration') {
|
|
1533
|
-
|
|
1596
|
+
{{#if syncNewElements.integration}}
|
|
1597
|
+
appComponent.setIntegrationScheduleSync(syncData, newFile, selectedFiles);
|
|
1598
|
+
{{else}}
|
|
1599
|
+
appComponent.setIntegrationScheduleSync(syncData);
|
|
1600
|
+
{{/if}}
|
|
1534
1601
|
appComponent.setIntegrationFilesHasSyncOption(true);
|
|
1535
1602
|
const syncedFiles = await appComponent.getIntegrationScheduleSync(true);
|
|
1536
1603
|
appComponent.setAttribute('is-integration-sync-settings-in-progress', true);
|
|
@@ -1542,16 +1609,28 @@
|
|
|
1542
1609
|
selectedFiles ? getIntegrationFoldersToExpand(syncData, syncedFiles) : [],
|
|
1543
1610
|
);
|
|
1544
1611
|
{{else}}
|
|
1545
|
-
|
|
1612
|
+
if (allFolderFiles) {
|
|
1613
|
+
const integrationExpandedFolders = syncData.filter(file => file.node_type === folderType).map(file => ({
|
|
1614
|
+
...file,
|
|
1615
|
+
nodeType: file.node_type,
|
|
1616
|
+
parentId: file.parent_id,
|
|
1617
|
+
}));
|
|
1618
|
+
updateSyncSettings(syncedFiles, 'schedule', 'integration', integrationExpandedFolders);
|
|
1619
|
+
} else {
|
|
1620
|
+
updateSyncSettings(syncedFiles, 'schedule', 'integration');
|
|
1621
|
+
}
|
|
1546
1622
|
{{/if}}
|
|
1547
1623
|
}
|
|
1548
1624
|
|
|
1549
1625
|
closeModal(scheduleModal);
|
|
1550
1626
|
}
|
|
1551
1627
|
|
|
1552
|
-
async function openScheduleModal(type) {
|
|
1628
|
+
async function openScheduleModal(type, options = {}) {
|
|
1629
|
+
const { showAllFolderFiles = false, showNewFiles = true } = options;
|
|
1630
|
+
|
|
1553
1631
|
const newFile = scheduleModal.querySelector('#new-files')
|
|
1554
1632
|
const selectedFiles = scheduleModal.querySelector('#selected-files');
|
|
1633
|
+
const allFolderFiles = scheduleModal.querySelector('#all-folder-files');
|
|
1555
1634
|
let newFileStatus = 'unChecked';
|
|
1556
1635
|
|
|
1557
1636
|
const integrationSyncFolders = (await document.querySelector('crowdin-simple-integration').getIntegrationScheduleSync(true))
|
|
@@ -1577,18 +1656,33 @@
|
|
|
1577
1656
|
}
|
|
1578
1657
|
}
|
|
1579
1658
|
|
|
1580
|
-
if (
|
|
1581
|
-
newFile
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1659
|
+
if (showNewFiles) {
|
|
1660
|
+
newFile.removeAttribute('hidden');
|
|
1661
|
+
if (newFileStatus === 'checked') {
|
|
1662
|
+
newFile[newFileStatus] = true;
|
|
1663
|
+
newFile.value = true;
|
|
1664
|
+
newFile.removeAttribute('clear-selection');
|
|
1665
|
+
} else if (newFileStatus === 'clear-selection') {
|
|
1666
|
+
newFile.checked = false;
|
|
1667
|
+
newFile.value = false;
|
|
1668
|
+
newFile.setAttribute(newFileStatus, '');
|
|
1669
|
+
} else {
|
|
1670
|
+
newFile.checked = false;
|
|
1671
|
+
newFile.value = false;
|
|
1672
|
+
newFile.removeAttribute('clear-selection');
|
|
1673
|
+
}
|
|
1588
1674
|
} else {
|
|
1675
|
+
newFile.setAttribute('hidden', '');
|
|
1589
1676
|
newFile.checked = false;
|
|
1590
|
-
|
|
1591
|
-
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
if (showAllFolderFiles) {
|
|
1680
|
+
allFolderFiles.removeAttribute('hidden');
|
|
1681
|
+
allFolderFiles.checked = false;
|
|
1682
|
+
allFolderFiles.value = false;
|
|
1683
|
+
} else {
|
|
1684
|
+
allFolderFiles.setAttribute('hidden', '');
|
|
1685
|
+
allFolderFiles.checked = false;
|
|
1592
1686
|
}
|
|
1593
1687
|
|
|
1594
1688
|
selectedFiles.checked = true;
|
|
@@ -1604,9 +1698,10 @@
|
|
|
1604
1698
|
|
|
1605
1699
|
const newFilesStatus = newFiles.checked || false;
|
|
1606
1700
|
const selectedFiles = document.getElementById('selected-files').checked || false;
|
|
1701
|
+
const allFolderFiles = document.getElementById('all-folder-files').checked || false;
|
|
1607
1702
|
const buttonSaveScheduleSync = document.getElementById('save-schedule-sync');
|
|
1608
1703
|
|
|
1609
|
-
if (newFilesStatus || selectedFiles) {
|
|
1704
|
+
if (newFilesStatus || selectedFiles || allFolderFiles) {
|
|
1610
1705
|
buttonSaveScheduleSync.removeAttribute('disabled');
|
|
1611
1706
|
} else {
|
|
1612
1707
|
buttonSaveScheduleSync.setAttribute('disabled', true);
|
|
@@ -1635,8 +1730,13 @@
|
|
|
1635
1730
|
{{#if syncNewElements.integration}}
|
|
1636
1731
|
syncData = e.detail;
|
|
1637
1732
|
const isFolder = await hasFolder(e.detail);
|
|
1638
|
-
|
|
1639
|
-
|
|
1733
|
+
|
|
1734
|
+
const shouldShowModal = isFolder && !newIntegrationFiles;
|
|
1735
|
+
const showNewFiles = true; // завжди показуємо New files коли є syncNewElements.integration
|
|
1736
|
+
const showAllFolderFiles = hasLoadMoreElements; // показуємо All files якщо є hasLoadMoreElements
|
|
1737
|
+
|
|
1738
|
+
if (shouldShowModal) {
|
|
1739
|
+
await openScheduleModal('integration', { showNewFiles, showAllFolderFiles });
|
|
1640
1740
|
return;
|
|
1641
1741
|
}
|
|
1642
1742
|
|
|
@@ -1644,14 +1744,33 @@
|
|
|
1644
1744
|
appComponent.setIntegrationFilesHasSyncOption(true);
|
|
1645
1745
|
const syncedFiles = await appComponent.getIntegrationScheduleSync(true);
|
|
1646
1746
|
{{else}}
|
|
1747
|
+
syncData = e.detail;
|
|
1748
|
+
const isFolder = await hasFolder(e.detail);
|
|
1749
|
+
|
|
1750
|
+
if (hasLoadMoreElements && isFolder) {
|
|
1751
|
+
await openScheduleModal('integration', { showNewFiles: false, showAllFolderFiles: true });
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1647
1755
|
const syncedFiles = await appComponent.getIntegrationScheduleSync();
|
|
1648
1756
|
{{/if}}
|
|
1649
1757
|
|
|
1650
1758
|
appComponent.setAttribute('is-integration-sync-settings-in-progress', true);
|
|
1759
|
+
|
|
1651
1760
|
{{#if integrationOneLevelFetching}}
|
|
1652
1761
|
updateSyncSettings(syncedFiles, 'schedule', 'integration', getIntegrationFoldersToExpand(e.detail, syncedFiles));
|
|
1653
1762
|
{{else}}
|
|
1654
|
-
|
|
1763
|
+
if (hasLoadMoreElements) {
|
|
1764
|
+
const integrationExpandedFolders = e.detail.filter(file => file.node_type === folderType).map(file => ({
|
|
1765
|
+
...file,
|
|
1766
|
+
nodeType: file.node_type,
|
|
1767
|
+
parentId: file.parent_id,
|
|
1768
|
+
}));
|
|
1769
|
+
|
|
1770
|
+
updateSyncSettings(syncedFiles, 'schedule', 'integration', integrationExpandedFolders);
|
|
1771
|
+
} else {
|
|
1772
|
+
updateSyncSettings(syncedFiles, 'schedule', 'integration');
|
|
1773
|
+
}
|
|
1655
1774
|
{{/if}}
|
|
1656
1775
|
}
|
|
1657
1776
|
|
|
@@ -1714,7 +1833,7 @@
|
|
|
1714
1833
|
.then(restParams => fetch('api/sync-settings' + restParams, {
|
|
1715
1834
|
method: 'POST',
|
|
1716
1835
|
headers: { 'Content-Type': 'application/json' },
|
|
1717
|
-
body: JSON.stringify({ files, type, provider, expandIntegrationFolders })
|
|
1836
|
+
body: JSON.stringify({ files, type, provider, expandIntegrationFolders, hasLoadMoreElements }),
|
|
1718
1837
|
}))
|
|
1719
1838
|
.then(checkResponse)
|
|
1720
1839
|
.then((res) => {
|
package/package.json
CHANGED