@forge/bridge 4.0.0 → 4.0.1-next.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/CHANGELOG.md +6 -0
- package/out/fetch/fetch.js +16 -13
- package/out/flag/flag.js +7 -5
- package/out/i18n/index.js +16 -17
- package/out/invoke-remote/invoke-remote.js +4 -5
- package/out/modal/modal.js +15 -18
- package/out/router/router.js +3 -4
- package/out/utils/index.js +2 -3
- package/out/view/close.js +3 -4
- package/out/view/createHistory.js +3 -4
- package/out/view/frame.js +6 -7
- package/out/view/getContext.js +3 -4
- package/out/view/refresh.js +3 -4
- package/out/view/submit.js +3 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/out/fetch/fetch.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.productFetchApi = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const blobParser_1 = require("../utils/blobParser");
|
|
6
|
-
const parseFormData = (form) =>
|
|
5
|
+
const parseFormData = async (form) => {
|
|
7
6
|
const parsed = {};
|
|
8
7
|
for (const [key, value] of form.entries()) {
|
|
9
8
|
if (key === 'file') {
|
|
10
9
|
const fileName = value.name;
|
|
11
10
|
const fileType = value.type;
|
|
12
|
-
parsed['file'] =
|
|
11
|
+
parsed['file'] = await (0, blobParser_1.blobToBase64)(value);
|
|
13
12
|
parsed['__fileName'] = fileName;
|
|
14
13
|
parsed['__fileType'] = fileType;
|
|
15
14
|
}
|
|
@@ -18,35 +17,39 @@ const parseFormData = (form) => tslib_1.__awaiter(void 0, void 0, void 0, functi
|
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
return JSON.stringify(parsed);
|
|
21
|
-
}
|
|
22
|
-
const parseRequest = (init) =>
|
|
20
|
+
};
|
|
21
|
+
const parseRequest = async (init) => {
|
|
23
22
|
const isFormData = (init === null || init === void 0 ? void 0 : init.body) instanceof FormData ? true : false;
|
|
24
|
-
const requestBody = isFormData ?
|
|
23
|
+
const requestBody = isFormData ? await parseFormData(init === null || init === void 0 ? void 0 : init.body) : init === null || init === void 0 ? void 0 : init.body;
|
|
25
24
|
const req = new Request('', { body: requestBody, method: init === null || init === void 0 ? void 0 : init.method, headers: init === null || init === void 0 ? void 0 : init.headers });
|
|
26
25
|
const headers = Object.fromEntries(req.headers.entries());
|
|
27
|
-
const body = req.method !== 'GET' ?
|
|
26
|
+
const body = req.method !== 'GET' ? await req.text() : null;
|
|
28
27
|
return {
|
|
29
28
|
body,
|
|
30
29
|
headers: new Headers(headers),
|
|
31
30
|
isMultipartFormData: isFormData
|
|
32
31
|
};
|
|
33
|
-
}
|
|
32
|
+
};
|
|
34
33
|
const productFetchApi = (callBridge) => {
|
|
35
|
-
const fetch = (product, restPath, init) =>
|
|
36
|
-
const { body: requestBody, headers: requestHeaders, isMultipartFormData } =
|
|
34
|
+
const fetch = async (product, restPath, init) => {
|
|
35
|
+
const { body: requestBody, headers: requestHeaders, isMultipartFormData } = await parseRequest(init);
|
|
37
36
|
if (!requestHeaders.has('X-Atlassian-Token')) {
|
|
38
37
|
requestHeaders.set('X-Atlassian-Token', 'no-check');
|
|
39
38
|
}
|
|
40
39
|
const fetchPayload = {
|
|
41
40
|
product,
|
|
42
41
|
restPath,
|
|
43
|
-
fetchRequestInit:
|
|
42
|
+
fetchRequestInit: {
|
|
43
|
+
...init,
|
|
44
|
+
body: requestBody,
|
|
45
|
+
headers: [...requestHeaders.entries()]
|
|
46
|
+
},
|
|
44
47
|
isMultipartFormData
|
|
45
48
|
};
|
|
46
|
-
const { body, headers, statusText, status, isAttachment } =
|
|
49
|
+
const { body, headers, statusText, status, isAttachment } = await callBridge('fetchProduct', fetchPayload);
|
|
47
50
|
const responseBody = isAttachment ? (0, blobParser_1.base64ToBlob)(body, headers['content-type']) : body;
|
|
48
51
|
return new Response(responseBody || null, { headers, status, statusText });
|
|
49
|
-
}
|
|
52
|
+
};
|
|
50
53
|
return {
|
|
51
54
|
requestConfluence: (restPath, fetchOptions) => fetch('confluence', restPath, fetchOptions),
|
|
52
55
|
requestJira: (restPath, fetchOptions) => fetch('jira', restPath, fetchOptions),
|
package/out/flag/flag.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.showFlag = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const errors_1 = require("../errors");
|
|
7
6
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
@@ -10,12 +9,15 @@ const showFlag = (options) => {
|
|
|
10
9
|
if (!options.id) {
|
|
11
10
|
throw new errors_1.BridgeAPIError('"id" must be defined in flag options');
|
|
12
11
|
}
|
|
13
|
-
const result = callBridge('showFlag',
|
|
12
|
+
const result = callBridge('showFlag', {
|
|
13
|
+
...options,
|
|
14
|
+
type: (_a = options.type) !== null && _a !== void 0 ? _a : 'info'
|
|
15
|
+
});
|
|
14
16
|
return {
|
|
15
|
-
close: () =>
|
|
16
|
-
|
|
17
|
+
close: async () => {
|
|
18
|
+
await result;
|
|
17
19
|
return callBridge('closeFlag', { id: options.id });
|
|
18
|
-
}
|
|
20
|
+
}
|
|
19
21
|
};
|
|
20
22
|
};
|
|
21
23
|
exports.showFlag = showFlag;
|
package/out/i18n/index.js
CHANGED
|
@@ -1,50 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTranslationFunction = exports.getTranslations = exports.resetTranslationsCache = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const i18n_1 = require("@forge/i18n");
|
|
6
5
|
const view_1 = require("../view");
|
|
7
6
|
const frontendResourcesAccessor = {
|
|
8
|
-
getI18nInfoConfig: () =>
|
|
9
|
-
const resp =
|
|
7
|
+
getI18nInfoConfig: async () => {
|
|
8
|
+
const resp = await fetch(`./${i18n_1.I18N_BUNDLE_FOLDER_NAME}/${i18n_1.I18N_INFO_FILE_NAME}`);
|
|
10
9
|
if (!resp.ok) {
|
|
11
10
|
throw new Error('Failed to get i18n info config: ' + resp.statusText);
|
|
12
11
|
}
|
|
13
|
-
const info = (
|
|
12
|
+
const info = (await resp.json());
|
|
14
13
|
return info.config;
|
|
15
|
-
}
|
|
16
|
-
getTranslationResource: (locale) =>
|
|
17
|
-
const resp =
|
|
14
|
+
},
|
|
15
|
+
getTranslationResource: async (locale) => {
|
|
16
|
+
const resp = await fetch(`./${i18n_1.I18N_BUNDLE_FOLDER_NAME}/${locale}.json`);
|
|
18
17
|
if (!resp.ok) {
|
|
19
18
|
throw new Error(`Failed to get translation resource for locale: ${locale}`);
|
|
20
19
|
}
|
|
21
20
|
return resp.json();
|
|
22
|
-
}
|
|
21
|
+
}
|
|
23
22
|
};
|
|
24
23
|
const translationsGetter = new i18n_1.TranslationsGetter(frontendResourcesAccessor);
|
|
25
24
|
const resetTranslationsCache = () => {
|
|
26
25
|
translationsGetter.reset();
|
|
27
26
|
};
|
|
28
27
|
exports.resetTranslationsCache = resetTranslationsCache;
|
|
29
|
-
const getTranslations = (locale = null, options = {
|
|
28
|
+
const getTranslations = async (locale = null, options = {
|
|
30
29
|
fallback: true
|
|
31
|
-
}) =>
|
|
30
|
+
}) => {
|
|
32
31
|
let targetLocale = locale;
|
|
33
32
|
if (!targetLocale) {
|
|
34
|
-
const context =
|
|
33
|
+
const context = await view_1.view.getContext();
|
|
35
34
|
targetLocale = context.locale;
|
|
36
35
|
}
|
|
37
|
-
return
|
|
38
|
-
}
|
|
36
|
+
return await translationsGetter.getTranslations(targetLocale, options);
|
|
37
|
+
};
|
|
39
38
|
exports.getTranslations = getTranslations;
|
|
40
|
-
const createTranslationFunction = (locale = null) =>
|
|
39
|
+
const createTranslationFunction = async (locale = null) => {
|
|
41
40
|
let targetLocale = locale;
|
|
42
41
|
if (!targetLocale) {
|
|
43
|
-
const context =
|
|
42
|
+
const context = await view_1.view.getContext();
|
|
44
43
|
targetLocale = context.locale;
|
|
45
44
|
}
|
|
46
45
|
const translator = new i18n_1.Translator(targetLocale, translationsGetter);
|
|
47
|
-
|
|
46
|
+
await translator.init();
|
|
48
47
|
return (i18nKey, defaultValue) => { var _a, _b; return (_b = (_a = translator.translate(i18nKey)) !== null && _a !== void 0 ? _a : defaultValue) !== null && _b !== void 0 ? _b : i18nKey; };
|
|
49
|
-
}
|
|
48
|
+
};
|
|
50
49
|
exports.createTranslationFunction = createTranslationFunction;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.invokeRemote = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const errors_1 = require("../errors");
|
|
7
6
|
const utils_1 = require("../utils");
|
|
@@ -15,11 +14,11 @@ const validatePayload = (payload) => {
|
|
|
15
14
|
throw new errors_1.BridgeAPIError('Passing functions as part of the payload is not supported!');
|
|
16
15
|
}
|
|
17
16
|
};
|
|
18
|
-
const _invokeRemote = (input) =>
|
|
17
|
+
const _invokeRemote = async (input) => {
|
|
19
18
|
var _a;
|
|
20
19
|
validatePayload(input);
|
|
21
|
-
const { success, payload, error } = (_a = (
|
|
22
|
-
const response =
|
|
20
|
+
const { success, payload, error } = (_a = (await callBridge('invoke', input))) !== null && _a !== void 0 ? _a : {};
|
|
21
|
+
const response = { ...(success ? payload : error) };
|
|
23
22
|
if (response && response.headers) {
|
|
24
23
|
for (const header in response.headers) {
|
|
25
24
|
if (Array.isArray(response.headers[header])) {
|
|
@@ -28,5 +27,5 @@ const _invokeRemote = (input) => tslib_1.__awaiter(void 0, void 0, void 0, funct
|
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
return response;
|
|
31
|
-
}
|
|
30
|
+
};
|
|
32
31
|
exports.invokeRemote = (0, utils_1.withRateLimiter)(_invokeRemote, MAX_NUM_OPERATIONS, OPERATION_INTERVAL_MS, 'Remote invocation calls are rate limited at 500req/25s');
|
package/out/modal/modal.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Modal = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const errors_1 = require("../errors");
|
|
7
6
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
@@ -16,25 +15,23 @@ class Modal {
|
|
|
16
15
|
this.closeOnEscape = (_a = opts === null || opts === void 0 ? void 0 : opts.closeOnEscape) !== null && _a !== void 0 ? _a : true;
|
|
17
16
|
this.closeOnOverlayClick = (_b = opts === null || opts === void 0 ? void 0 : opts.closeOnOverlayClick) !== null && _b !== void 0 ? _b : true;
|
|
18
17
|
}
|
|
19
|
-
open() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (success === false) {
|
|
31
|
-
throw new errors_1.BridgeAPIError('Unable to open modal.');
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
18
|
+
async open() {
|
|
19
|
+
try {
|
|
20
|
+
const success = await callBridge('openModal', {
|
|
21
|
+
resource: this.resource,
|
|
22
|
+
onClose: this.onClose,
|
|
23
|
+
size: this.size,
|
|
24
|
+
context: this.context,
|
|
25
|
+
closeOnEscape: this.closeOnEscape,
|
|
26
|
+
closeOnOverlayClick: this.closeOnOverlayClick
|
|
27
|
+
});
|
|
28
|
+
if (success === false) {
|
|
35
29
|
throw new errors_1.BridgeAPIError('Unable to open modal.');
|
|
36
30
|
}
|
|
37
|
-
}
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
throw new errors_1.BridgeAPIError('Unable to open modal.');
|
|
34
|
+
}
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
exports.Modal = Modal;
|
package/out/router/router.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.router = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
7
|
-
const navigate = (url) =>
|
|
8
|
-
const open = (url) =>
|
|
9
|
-
const reload = () =>
|
|
6
|
+
const navigate = async (url) => callBridge('navigate', { url, type: 'same-tab' });
|
|
7
|
+
const open = async (url) => callBridge('navigate', { url, type: 'new-tab' });
|
|
8
|
+
const reload = async () => callBridge('reload');
|
|
10
9
|
exports.router = {
|
|
11
10
|
navigate,
|
|
12
11
|
open,
|
package/out/utils/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.withRateLimiter = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const errors_1 = require("../errors");
|
|
6
5
|
const withRateLimiter = (wrappedFn, maxOps, intervalInMs, exceededErrorMessage) => {
|
|
7
6
|
let start = Date.now();
|
|
8
7
|
let numOps = 0;
|
|
9
|
-
return (...args) =>
|
|
8
|
+
return async (...args) => {
|
|
10
9
|
const now = Date.now();
|
|
11
10
|
const elapsed = now - start;
|
|
12
11
|
if (elapsed > intervalInMs) {
|
|
@@ -18,6 +17,6 @@ const withRateLimiter = (wrappedFn, maxOps, intervalInMs, exceededErrorMessage)
|
|
|
18
17
|
}
|
|
19
18
|
numOps = numOps + 1;
|
|
20
19
|
return wrappedFn(...args);
|
|
21
|
-
}
|
|
20
|
+
};
|
|
22
21
|
};
|
|
23
22
|
exports.withRateLimiter = withRateLimiter;
|
package/out/view/close.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.close = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const errors_1 = require("../errors");
|
|
7
6
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
8
|
-
const close = (payload) =>
|
|
7
|
+
const close = async (payload) => {
|
|
9
8
|
try {
|
|
10
|
-
const success =
|
|
9
|
+
const success = await callBridge('close', payload);
|
|
11
10
|
if (success === false) {
|
|
12
11
|
throw new errors_1.BridgeAPIError("this resource's view is not closable.");
|
|
13
12
|
}
|
|
@@ -15,5 +14,5 @@ const close = (payload) => tslib_1.__awaiter(void 0, void 0, void 0, function* (
|
|
|
15
14
|
catch (e) {
|
|
16
15
|
throw new errors_1.BridgeAPIError("this resource's view is not closable.");
|
|
17
16
|
}
|
|
18
|
-
}
|
|
17
|
+
};
|
|
19
18
|
exports.close = close;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createHistory = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
7
|
-
const createHistory = () =>
|
|
8
|
-
const history =
|
|
6
|
+
const createHistory = async () => {
|
|
7
|
+
const history = await callBridge('createHistory');
|
|
9
8
|
history.listen((location) => {
|
|
10
9
|
history.location = location;
|
|
11
10
|
});
|
|
12
11
|
return history;
|
|
13
|
-
}
|
|
12
|
+
};
|
|
14
13
|
exports.createHistory = createHistory;
|
package/out/view/frame.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.frame = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const internal_1 = require("../internal");
|
|
6
5
|
const events_1 = require("../events");
|
|
7
6
|
const frameEventKey = (eventKey, frameId) => `frame:${eventKey}:${frameId}`;
|
|
8
7
|
const EVENT_KEY_FRAME_PROPS = 'props';
|
|
9
8
|
const EVENT_KEY_FRAME_LOADED = 'loaded';
|
|
10
|
-
const onPropsUpdate = (handler) =>
|
|
11
|
-
const frameId =
|
|
9
|
+
const onPropsUpdate = async (handler) => {
|
|
10
|
+
const frameId = await (0, internal_1.getFrameId)();
|
|
12
11
|
if (!frameId) {
|
|
13
12
|
throw new Error('Frame could not be found, please ensure the custom props enabled Frame component is set up correctly.');
|
|
14
13
|
}
|
|
15
14
|
const sub = events_1.events.on(frameEventKey(EVENT_KEY_FRAME_PROPS, frameId), handler);
|
|
16
|
-
const unsubscribe = () =>
|
|
17
|
-
const subscription =
|
|
15
|
+
const unsubscribe = async () => {
|
|
16
|
+
const subscription = await sub;
|
|
18
17
|
subscription.unsubscribe();
|
|
19
|
-
}
|
|
18
|
+
};
|
|
20
19
|
void events_1.events.emit(frameEventKey(EVENT_KEY_FRAME_LOADED, frameId), { loaded: true });
|
|
21
20
|
return unsubscribe;
|
|
22
|
-
}
|
|
21
|
+
};
|
|
23
22
|
exports.frame = {
|
|
24
23
|
onPropsUpdate
|
|
25
24
|
};
|
package/out/view/getContext.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getContext = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const i18n_1 = require("@forge/i18n");
|
|
7
6
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
8
|
-
const getContext = () =>
|
|
7
|
+
const getContext = async () => {
|
|
9
8
|
var _a;
|
|
10
|
-
const context =
|
|
9
|
+
const context = await callBridge('getContext');
|
|
11
10
|
const locale = context === null || context === void 0 ? void 0 : context.locale;
|
|
12
11
|
if (locale) {
|
|
13
12
|
context.locale = (_a = (0, i18n_1.ensureLocale)(locale)) !== null && _a !== void 0 ? _a : locale;
|
|
14
13
|
}
|
|
15
14
|
return context;
|
|
16
|
-
}
|
|
15
|
+
};
|
|
17
16
|
exports.getContext = getContext;
|
package/out/view/refresh.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.refresh = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const errors_1 = require("../errors");
|
|
7
6
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
8
|
-
const refresh = (payload) =>
|
|
9
|
-
const success =
|
|
7
|
+
const refresh = async (payload) => {
|
|
8
|
+
const success = await callBridge('refresh', payload);
|
|
10
9
|
if (success === false) {
|
|
11
10
|
throw new errors_1.BridgeAPIError("this resource's view is not refreshable.");
|
|
12
11
|
}
|
|
13
|
-
}
|
|
12
|
+
};
|
|
14
13
|
exports.refresh = refresh;
|
package/out/view/submit.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.submit = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const bridge_1 = require("../bridge");
|
|
6
5
|
const errors_1 = require("../errors");
|
|
7
6
|
const callBridge = (0, bridge_1.getCallBridge)();
|
|
8
|
-
const submit = (payload) =>
|
|
9
|
-
const success =
|
|
7
|
+
const submit = async (payload) => {
|
|
8
|
+
const success = await callBridge('submit', payload);
|
|
10
9
|
if (success === false) {
|
|
11
10
|
throw new errors_1.BridgeAPIError("this resource's view is not submittable.");
|
|
12
11
|
}
|
|
13
|
-
}
|
|
12
|
+
};
|
|
14
13
|
exports.submit = submit;
|