@appsemble/utils 0.29.7 → 0.29.9
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/README.md +3 -3
- package/api/components/schemas/SecurityDefaultDefinition.js +1 -1
- package/api/paths/assets.js +10 -0
- package/assets.d.ts +29 -0
- package/assets.js +81 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +2 -2
- package/reference-schemas/remappers/data.js +3 -3
- package/reference-schemas/remappers/unsorted.js +5 -5
- package/validateAppMessages.d.ts +5 -0
- package/validateAppMessages.js +71 -0
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#  Appsemble Utilities
|
|
2
2
|
|
|
3
3
|
> Internal utility functions used across multiple Appsemble projects.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/utils)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.29.9)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -26,5 +26,5 @@ not guaranteed.
|
|
|
26
26
|
|
|
27
27
|
## License
|
|
28
28
|
|
|
29
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.29.
|
|
29
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.29.9/LICENSE.md) ©
|
|
30
30
|
[Appsemble](https://appsemble.com)
|
|
@@ -14,7 +14,7 @@ The following values are allowed:
|
|
|
14
14
|
- \`organization\`: Every authenticated user gets the default role if they are in the same organization as the app.
|
|
15
15
|
- \`invite\`: The user has to manually get a role assigned.
|
|
16
16
|
|
|
17
|
-
> **Important**: When [OAuth2](../
|
|
17
|
+
> **Important**: When [OAuth2](../guides/oauth2) or [SAML2.0](../guides/saml) is used in the
|
|
18
18
|
> app, you must set the policy to \`everyone\`. This will specifically allow every configured
|
|
19
19
|
> authentication method on the secrets page to be used as login method. If you do not want other
|
|
20
20
|
> Appsemble user accounts to be able to log in, you must \`disable\` the \`appsemble login\` options
|
package/api/paths/assets.js
CHANGED
|
@@ -187,6 +187,16 @@ export const paths = {
|
|
|
187
187
|
},
|
|
188
188
|
},
|
|
189
189
|
},
|
|
190
|
+
head: {
|
|
191
|
+
tags: ['asset'],
|
|
192
|
+
description: 'Get the headers for a single asset',
|
|
193
|
+
operationId: 'getAssetHeadersById',
|
|
194
|
+
responses: {
|
|
195
|
+
200: {
|
|
196
|
+
description: 'The asset that matches the given id.',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
190
200
|
delete: {
|
|
191
201
|
tags: ['asset'],
|
|
192
202
|
description: 'Remove an existing asset',
|
package/assets.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type IconName } from '@fortawesome/fontawesome-common-types';
|
|
2
|
+
export declare enum MimeTypeCategory {
|
|
3
|
+
Image = "image",
|
|
4
|
+
Video = "video",
|
|
5
|
+
PDF = "pdf",
|
|
6
|
+
Wordprocessing = "wordprocessing",
|
|
7
|
+
Spreadsheet = "spreadsheet",
|
|
8
|
+
Presentation = "presentation",
|
|
9
|
+
Archive = "archive"
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Puts a mimetype into one of the predefined categories.
|
|
13
|
+
*
|
|
14
|
+
* Wildcard behavior is supported.
|
|
15
|
+
*
|
|
16
|
+
* @param mimeType A mimetype to categorize
|
|
17
|
+
* @returns A category that the mimetype falls into or null if no category is found
|
|
18
|
+
*/
|
|
19
|
+
export declare function getMimeTypeCategory(mimeType: string): MimeTypeCategory | null;
|
|
20
|
+
export declare function getMimeTypeCategories(mimeTypes: string[]): MimeTypeCategory[];
|
|
21
|
+
export type FileIconName = IconName | `file-${string}`;
|
|
22
|
+
export declare function getMimeTypeIcon(category: MimeTypeCategory): FileIconName;
|
|
23
|
+
/**
|
|
24
|
+
* Extracts the filename from a Content-Disposition header string.
|
|
25
|
+
*
|
|
26
|
+
* @param contentDisposition The Content-Disposition header string
|
|
27
|
+
* @returns The name of the file or null if it's not present
|
|
28
|
+
*/
|
|
29
|
+
export declare function getFilenameFromContentDisposition(contentDisposition: string): string | null;
|
package/assets.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export var MimeTypeCategory;
|
|
2
|
+
(function (MimeTypeCategory) {
|
|
3
|
+
MimeTypeCategory["Image"] = "image";
|
|
4
|
+
MimeTypeCategory["Video"] = "video";
|
|
5
|
+
MimeTypeCategory["PDF"] = "pdf";
|
|
6
|
+
MimeTypeCategory["Wordprocessing"] = "wordprocessing";
|
|
7
|
+
MimeTypeCategory["Spreadsheet"] = "spreadsheet";
|
|
8
|
+
MimeTypeCategory["Presentation"] = "presentation";
|
|
9
|
+
MimeTypeCategory["Archive"] = "archive";
|
|
10
|
+
})(MimeTypeCategory || (MimeTypeCategory = {}));
|
|
11
|
+
const mimeTypeCategories = {
|
|
12
|
+
[MimeTypeCategory.Image]: ['image/jpeg', 'image/png', 'image/gif', 'image/*'],
|
|
13
|
+
[MimeTypeCategory.Video]: ['video/mp4', 'video/webm', 'video/ogg', 'video/*'],
|
|
14
|
+
[MimeTypeCategory.PDF]: ['application/pdf'],
|
|
15
|
+
[MimeTypeCategory.Wordprocessing]: [
|
|
16
|
+
'application/msword',
|
|
17
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
18
|
+
],
|
|
19
|
+
[MimeTypeCategory.Spreadsheet]: [
|
|
20
|
+
'application/vnd.ms-excel',
|
|
21
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
22
|
+
],
|
|
23
|
+
[MimeTypeCategory.Presentation]: [
|
|
24
|
+
'application/vnd.ms-powerpoint',
|
|
25
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
26
|
+
],
|
|
27
|
+
[MimeTypeCategory.Archive]: ['application/zip'],
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Puts a mimetype into one of the predefined categories.
|
|
31
|
+
*
|
|
32
|
+
* Wildcard behavior is supported.
|
|
33
|
+
*
|
|
34
|
+
* @param mimeType A mimetype to categorize
|
|
35
|
+
* @returns A category that the mimetype falls into or null if no category is found
|
|
36
|
+
*/
|
|
37
|
+
export function getMimeTypeCategory(mimeType) {
|
|
38
|
+
var _a;
|
|
39
|
+
const [mimeBaseType, mimeSubType] = mimeType.split('/');
|
|
40
|
+
const [category] = (_a = Object.entries(mimeTypeCategories).find(([, types]) => types.some((type) => {
|
|
41
|
+
const [baseType, subType] = type.split('/');
|
|
42
|
+
return baseType === mimeBaseType && (subType === '*' || subType === mimeSubType);
|
|
43
|
+
}))) !== null && _a !== void 0 ? _a : [null];
|
|
44
|
+
return category;
|
|
45
|
+
}
|
|
46
|
+
export function getMimeTypeCategories(mimeTypes) {
|
|
47
|
+
return Array.from(new Set(mimeTypes.map(getMimeTypeCategory).filter(Boolean)));
|
|
48
|
+
}
|
|
49
|
+
export function getMimeTypeIcon(category) {
|
|
50
|
+
switch (category) {
|
|
51
|
+
case MimeTypeCategory.Image:
|
|
52
|
+
case MimeTypeCategory.Video:
|
|
53
|
+
case MimeTypeCategory.PDF:
|
|
54
|
+
return `file-${category}`;
|
|
55
|
+
case MimeTypeCategory.Wordprocessing:
|
|
56
|
+
return 'file-word';
|
|
57
|
+
case MimeTypeCategory.Spreadsheet:
|
|
58
|
+
return 'file-excel';
|
|
59
|
+
case MimeTypeCategory.Presentation:
|
|
60
|
+
return 'file-powerpoint';
|
|
61
|
+
case MimeTypeCategory.Archive:
|
|
62
|
+
return 'file-zipper';
|
|
63
|
+
default:
|
|
64
|
+
return 'file';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extracts the filename from a Content-Disposition header string.
|
|
69
|
+
*
|
|
70
|
+
* @param contentDisposition The Content-Disposition header string
|
|
71
|
+
* @returns The name of the file or null if it's not present
|
|
72
|
+
*/
|
|
73
|
+
export function getFilenameFromContentDisposition(contentDisposition) {
|
|
74
|
+
const filenameRegex = /filename="([^"]+)"/;
|
|
75
|
+
const matches = filenameRegex.exec(contentDisposition);
|
|
76
|
+
if (matches != null && matches[1]) {
|
|
77
|
+
return matches[1].replaceAll(/["']/g, '');
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=assets.js.map
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -29,4 +29,6 @@ export * from './serverActions.js';
|
|
|
29
29
|
export * from './validation.js';
|
|
30
30
|
export * from './serializeResource.js';
|
|
31
31
|
export * from './convertToCsv.js';
|
|
32
|
+
export * from './assets.js';
|
|
33
|
+
export * from './validateAppMessages.js';
|
|
32
34
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/utils",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.9",
|
|
4
4
|
"description": "Utility functions used in Appsemble internally",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test": "vitest"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@appsemble/types": "0.29.
|
|
40
|
+
"@appsemble/types": "0.29.9",
|
|
41
41
|
"axios": "^1.0.0",
|
|
42
42
|
"cron-parser": "^4.0.0",
|
|
43
43
|
"date-fns": "^2.0.0",
|
|
@@ -61,7 +61,7 @@ Clicking on the first item would log \`0\`, the second item \`1\` and so on.
|
|
|
61
61
|
},
|
|
62
62
|
history: {
|
|
63
63
|
type: 'integer',
|
|
64
|
-
description: `> **Note:** This remapper is explained more in depth in the [History](/docs/
|
|
64
|
+
description: `> **Note:** This remapper is explained more in depth in the [History](/docs/remappers/history) page
|
|
65
65
|
|
|
66
66
|
Gives the data at the history entry at the specified history index. The history at specified index
|
|
67
67
|
is the data that is passed to that action.
|
|
@@ -166,7 +166,7 @@ Example:
|
|
|
166
166
|
\`\`\`
|
|
167
167
|
|
|
168
168
|
The page data only works in the context of a flow page. Let’s say you have a
|
|
169
|
-
[“FlowPage”](/docs/
|
|
169
|
+
[“FlowPage”](/docs/reference/app#-flow-page-definition) type with multiple subpages. Whenever you
|
|
170
170
|
navigate to the next page it adds the data from that page to the flow page’s data. The page remapper
|
|
171
171
|
allows you to access this cumulative data.
|
|
172
172
|
|
|
@@ -281,7 +281,7 @@ ${schemaExample('static', { exclude: ['input'] })}
|
|
|
281
281
|
},
|
|
282
282
|
translate: {
|
|
283
283
|
type: 'string',
|
|
284
|
-
description: `> **Note:** This is explained much more in depth at [Translating](/docs/
|
|
284
|
+
description: `> **Note:** This is explained much more in depth at [Translating](/docs/guides/translating)
|
|
285
285
|
|
|
286
286
|
This remapper allows you to easily add translations to your app. To make this remapper work, replace
|
|
287
287
|
any static text with \`translate: {name}\`. Then, in your app’s Translations page pick the language
|
|
@@ -2,7 +2,7 @@ export const unsortedRemappers = {
|
|
|
2
2
|
ics: {
|
|
3
3
|
type: 'object',
|
|
4
4
|
description: `Create a calendar event. This event can be downloaded as an \`.ics\` file and
|
|
5
|
-
uploaded to your agenda using the [download](../
|
|
5
|
+
uploaded to your agenda using the [download](../reference/action#download) action.
|
|
6
6
|
|
|
7
7
|
For example, the following input:
|
|
8
8
|
\`\`\`yaml
|
|
@@ -160,12 +160,12 @@ The options represent the level of logging that will show in the console.
|
|
|
160
160
|
- \`appId\`: ID of the application,
|
|
161
161
|
- \`url\`: Absolute URL of the page where the remapper was fired,
|
|
162
162
|
- \`appUrl\`: Base URL of the application,
|
|
163
|
-
- \`pageData\`: Current page data of a FlowPage (See [page remapper](./
|
|
164
|
-
- \`userInfo\`: User's information if they are logged in (See [user remapper](./
|
|
163
|
+
- \`pageData\`: Current page data of a FlowPage (See [page remapper](./data#page)),
|
|
164
|
+
- \`userInfo\`: User's information if they are logged in (See [user remapper](./data#user)),
|
|
165
165
|
- \`context\`: Internal context
|
|
166
|
-
- \`history\`: Complete list of this remapper’s history (See [history remapper](./
|
|
166
|
+
- \`history\`: Complete list of this remapper’s history (See [history remapper](./history))
|
|
167
167
|
- \`locale\`: The user’s locale,
|
|
168
|
-
- \`stepRef\`: In a loop page, gives the properties from the loop’s current data index (See [step remapper](./
|
|
168
|
+
- \`stepRef\`: In a loop page, gives the properties from the loop’s current data index (See [step remapper](./data#step))
|
|
169
169
|
|
|
170
170
|
For example:
|
|
171
171
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AppDefinition, type AppsembleMessages } from '@appsemble/types';
|
|
2
|
+
export declare class AppMessageValidationError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function validateMessages(messages: AppsembleMessages, app: AppDefinition): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { extractAppMessages } from './appMessages.js';
|
|
2
|
+
import { normalizeBlockName } from './blockUtils.js';
|
|
3
|
+
import { has } from './has.js';
|
|
4
|
+
export class AppMessageValidationError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'AppMessageValidationError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function validateMessages(messages, app) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const blockMessageKeys = {};
|
|
13
|
+
const extractedMessages = extractAppMessages(app, (block) => {
|
|
14
|
+
const type = normalizeBlockName(block.type);
|
|
15
|
+
if (blockMessageKeys[type]) {
|
|
16
|
+
blockMessageKeys[type][block.version] = {};
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
blockMessageKeys[type] = {
|
|
20
|
+
[block.version]: {},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
if (messages.messageIds) {
|
|
25
|
+
Object.keys(messages.messageIds).map((key) => {
|
|
26
|
+
if (typeof messages.messageIds[key] !== 'string') {
|
|
27
|
+
throw new AppMessageValidationError(`Not allowed to have non-string message ${messages.messageIds[key]}`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (messages.app) {
|
|
32
|
+
Object.keys(messages.app).map((key) => {
|
|
33
|
+
const match = /^(pages\.[\dA-Za-z-]+(\..+)?)\.blocks\.\d+.+/.exec(key);
|
|
34
|
+
if ((!has(extractedMessages === null || extractedMessages === void 0 ? void 0 : extractedMessages.app, key) || typeof messages.app[key] !== 'string') && !match) {
|
|
35
|
+
throw new AppMessageValidationError(`Invalid key ${key}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const blockMessages = {};
|
|
40
|
+
if (messages.blocks) {
|
|
41
|
+
for (const key of Object.keys(messages.blocks)) {
|
|
42
|
+
if (!has(blockMessageKeys, key)) {
|
|
43
|
+
throw new AppMessageValidationError(`Invalid translation key: blocks.${key}\nThis block is not used in the app`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const coreMessages = (_a = messages.core) !== null && _a !== void 0 ? _a : {};
|
|
48
|
+
for (const [key, value] of Object.entries(coreMessages)) {
|
|
49
|
+
if (!value || typeof value !== 'string') {
|
|
50
|
+
throw new AppMessageValidationError(`Invalid translation key: core.${key}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const [blockName] of Object.entries(blockMessageKeys)) {
|
|
54
|
+
if ((_b = messages.blocks) === null || _b === void 0 ? void 0 : _b[blockName]) {
|
|
55
|
+
blockMessages[blockName] = {};
|
|
56
|
+
for (const [version, oldValues] of Object.entries(messages.blocks[blockName])) {
|
|
57
|
+
if (!has(blockMessageKeys[blockName], version)) {
|
|
58
|
+
throw new AppMessageValidationError(`Invalid translation key: blocks.${blockName}.${version}
|
|
59
|
+
This block version is not used in the app`);
|
|
60
|
+
}
|
|
61
|
+
for (const [oldValueKey, oldValue] of Object.entries(messages.blocks[blockName][version])) {
|
|
62
|
+
if (typeof oldValue !== 'string') {
|
|
63
|
+
throw new AppMessageValidationError(`Invalid translation key: blocks.${blockName}.${version}.${oldValueKey}`);
|
|
64
|
+
}
|
|
65
|
+
blockMessages[blockName][version] = oldValues;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=validateAppMessages.js.map
|