@payloadcms/figma 0.1.0-alpha.4 → 0.1.0-alpha.5
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/dist/db-content-api/index.js +31 -17
- package/dist/db-content-api/utilities/joins.js +5 -1
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
- package/dist/db-content-api/utilities/where.d.ts +2 -6
- package/dist/db-content-api/utilities/where.js +20 -11
- package/dist/exports/views.d.ts +1 -0
- package/dist/exports/views.js +1 -0
- package/dist/imageSizeOptions.d.ts +23 -0
- package/dist/imageSizeOptions.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/oauth/defaults.d.ts +1 -0
- package/dist/oauth/defaults.js +1 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
- package/dist/oauth/index.js +24 -9
- package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
- package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
- package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
- package/dist/plugin/build-config.js +22 -9
- package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
- package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
- package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
- package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
- package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
- package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
- package/dist/plugin/cloud-limits/routes.d.ts +2 -0
- package/dist/plugin/cloud-limits/routes.js +4 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
- package/dist/plugin/cloud-limits/shared.d.ts +1 -1
- package/dist/plugin/cloud-limits/shared.js +25 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
- package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
- package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
- package/package.json +8 -1
- package/dist/db-content-api/utilities/data/stringifyNumericIds.d.ts +0 -1
- package/dist/db-content-api/utilities/data/stringifyNumericIds.js +0 -19
|
@@ -7,6 +7,20 @@ const knownCloudLimitCodes = Object.values(cloudLimitErrorCodes);
|
|
|
7
7
|
export function isCloudLimitErrorCode(code) {
|
|
8
8
|
return typeof code === 'string' && knownCloudLimitCodes.includes(code);
|
|
9
9
|
}
|
|
10
|
+
const cloudLimitCodeByMessagePattern = [
|
|
11
|
+
{
|
|
12
|
+
code: cloudLimitErrorCodes.documentCount,
|
|
13
|
+
pattern: /document count/i
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: cloudLimitErrorCodes.documentDataSize,
|
|
17
|
+
pattern: /document data size/i
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: cloudLimitErrorCodes.assetStorage,
|
|
21
|
+
pattern: /asset storage limit/i
|
|
22
|
+
}
|
|
23
|
+
];
|
|
10
24
|
export function getCloudLimitErrorCode(json) {
|
|
11
25
|
const errors = json?.errors;
|
|
12
26
|
if (!Array.isArray(errors)) {
|
|
@@ -17,11 +31,22 @@ export function getCloudLimitErrorCode(json) {
|
|
|
17
31
|
if (isCloudLimitErrorCode(code)) {
|
|
18
32
|
return code;
|
|
19
33
|
}
|
|
34
|
+
const message = err?.message;
|
|
35
|
+
if (message?.startsWith('Content API ')) {
|
|
36
|
+
// Payload's bulk-upload response currently drops APIError.data.code. Match only the
|
|
37
|
+
// identifying phrase from each Content API message so variable values and minor wording
|
|
38
|
+
// changes do not break the fallback. Prefer the structured code above whenever present.
|
|
39
|
+
const match = cloudLimitCodeByMessagePattern.find(({ pattern })=>pattern.test(message));
|
|
40
|
+
if (match) {
|
|
41
|
+
return match.code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
20
44
|
}
|
|
21
45
|
return undefined;
|
|
22
46
|
}
|
|
23
47
|
export const cloudLimitDocumentCountModalSlug = 'figma-cloud-limit-document-count';
|
|
24
48
|
export const cloudLimitToastKeyByCode = {
|
|
25
49
|
[cloudLimitErrorCodes.assetStorage]: 'figma:cloudLimitAssetStorageError',
|
|
50
|
+
[cloudLimitErrorCodes.documentCount]: 'figma:cloudLimitDocumentCountError',
|
|
26
51
|
[cloudLimitErrorCodes.documentDataSize]: 'figma:cloudLimitDocumentDataSizeError'
|
|
27
52
|
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { toast } from '@payloadcms/ui';
|
|
3
|
+
import { formatAdminURL } from 'payload/shared';
|
|
4
|
+
import { getCloudLimitErrorCode } from '../shared.js';
|
|
5
|
+
import { showCloudLimitToast } from './toasts.js';
|
|
6
|
+
const ERROR_SUPPRESSION_MS = 1_000;
|
|
7
|
+
const SUMMARY_SUPPRESSION_MS = 60_000;
|
|
8
|
+
function restoreBulkErrorCodes(response, json) {
|
|
9
|
+
if (!json || typeof json !== 'object' || !Array.isArray(json.docs) || !Array.isArray(json.errors)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
let hasLimitError = false;
|
|
13
|
+
const errors = json.errors.map((error)=>{
|
|
14
|
+
const code = getCloudLimitErrorCode({
|
|
15
|
+
errors: [
|
|
16
|
+
error
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
if (!code || !error || typeof error !== 'object') {
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
hasLimitError = true;
|
|
23
|
+
const data = error.data;
|
|
24
|
+
return {
|
|
25
|
+
...error,
|
|
26
|
+
data: {
|
|
27
|
+
...typeof data === 'object' && data ? data : {},
|
|
28
|
+
code
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
if (!hasLimitError) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const { message: _aggregateMessage, ...result } = json;
|
|
36
|
+
const headers = new Headers(response.headers);
|
|
37
|
+
headers.delete('content-encoding');
|
|
38
|
+
headers.delete('content-length');
|
|
39
|
+
return new Response(JSON.stringify({
|
|
40
|
+
...result,
|
|
41
|
+
errors
|
|
42
|
+
}), {
|
|
43
|
+
headers,
|
|
44
|
+
status: response.status,
|
|
45
|
+
statusText: response.statusText
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function cloudLimitInterceptor(apiRoute, translate) {
|
|
49
|
+
const originalFetch = window.fetch;
|
|
50
|
+
const originalToastError = toast.error;
|
|
51
|
+
const suppressions = new Map();
|
|
52
|
+
let suppressSummary = false;
|
|
53
|
+
let summaryTimeout;
|
|
54
|
+
const patchedToastError = (...args)=>{
|
|
55
|
+
const message = typeof args[0] === 'string' ? args[0] : args[0]?.props?.errorMessage ?? '';
|
|
56
|
+
const match = [
|
|
57
|
+
...suppressions
|
|
58
|
+
].find(([rawMessage])=>message.includes(rawMessage));
|
|
59
|
+
if (match) {
|
|
60
|
+
const [, suppression] = match;
|
|
61
|
+
// Keep the suppression for its full lifetime so overlapping requests with the same raw
|
|
62
|
+
// error cannot leak duplicate Payload error toasts. The stable info-toast ID deduplicates
|
|
63
|
+
// the replacement notification.
|
|
64
|
+
showCloudLimitToast(suppression.code, translate);
|
|
65
|
+
if (suppression.isBulkUpload) {
|
|
66
|
+
suppressSummary = true;
|
|
67
|
+
window.clearTimeout(summaryTimeout);
|
|
68
|
+
summaryTimeout = window.setTimeout(()=>suppressSummary = false, SUMMARY_SUPPRESSION_MS);
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
if (suppressSummary && /^Failed to save \d+ files$/.test(message)) {
|
|
73
|
+
suppressSummary = false;
|
|
74
|
+
window.clearTimeout(summaryTimeout);
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
return originalToastError(...args);
|
|
78
|
+
};
|
|
79
|
+
const patchedFetch = async (...args)=>{
|
|
80
|
+
const response = await originalFetch(...args);
|
|
81
|
+
if (response.status < 400) {
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const json = await response.clone().json();
|
|
86
|
+
const bulkResponse = response.status === 400 ? restoreBulkErrorCodes(response, json) : undefined;
|
|
87
|
+
if (bulkResponse) {
|
|
88
|
+
return bulkResponse;
|
|
89
|
+
}
|
|
90
|
+
const input = args[0];
|
|
91
|
+
const pathname = new URL(input instanceof Request ? input.url : String(input), window.location.origin).pathname;
|
|
92
|
+
const method = (input instanceof Request ? input.method : args[1]?.method ?? 'GET').toUpperCase();
|
|
93
|
+
const apiPath = formatAdminURL({
|
|
94
|
+
apiRoute,
|
|
95
|
+
path: ''
|
|
96
|
+
}).replace(/\/$/, '');
|
|
97
|
+
const endpoint = pathname.startsWith(`${apiPath}/`) ? pathname.slice(apiPath.length + 1) : '';
|
|
98
|
+
const isUploadInstructions = endpoint === 'upload-instructions';
|
|
99
|
+
const isCollectionCreate = method === 'POST' && endpoint !== '' && !endpoint.includes('/');
|
|
100
|
+
if (!isUploadInstructions && !isCollectionCreate) {
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
const code = getCloudLimitErrorCode(json);
|
|
104
|
+
const rawMessage = json?.errors?.[0]?.message;
|
|
105
|
+
if (code && rawMessage) {
|
|
106
|
+
const previous = suppressions.get(rawMessage);
|
|
107
|
+
window.clearTimeout(previous?.timeout);
|
|
108
|
+
const suppression = {
|
|
109
|
+
code,
|
|
110
|
+
isBulkUpload: isCollectionCreate,
|
|
111
|
+
timeout: 0
|
|
112
|
+
};
|
|
113
|
+
suppression.timeout = window.setTimeout(()=>suppressions.delete(rawMessage), ERROR_SUPPRESSION_MS);
|
|
114
|
+
suppressions.set(rawMessage, suppression);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// Leave Payload's default handling intact for malformed error responses.
|
|
118
|
+
}
|
|
119
|
+
return response;
|
|
120
|
+
};
|
|
121
|
+
window.fetch = patchedFetch;
|
|
122
|
+
toast.error = patchedToastError;
|
|
123
|
+
return ()=>{
|
|
124
|
+
suppressions.forEach(({ timeout })=>window.clearTimeout(timeout));
|
|
125
|
+
window.clearTimeout(summaryTimeout);
|
|
126
|
+
if (window.fetch === patchedFetch) {
|
|
127
|
+
window.fetch = originalFetch;
|
|
128
|
+
}
|
|
129
|
+
if (toast.error === patchedToastError) {
|
|
130
|
+
;
|
|
131
|
+
toast.error = originalToastError;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CloudLimitErrorCode } from '../shared.js';
|
|
2
|
+
export type Translate = (key: string) => string;
|
|
3
|
+
export declare function dismissSubmittingToast(label: string): void;
|
|
4
|
+
export declare function showCloudLimitToast(code: CloudLimitErrorCode, translate: Translate): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { toast } from '@payloadcms/ui';
|
|
3
|
+
import { cloudLimitToastKeyByCode } from '../shared.js';
|
|
4
|
+
const TOAST_ID_PREFIX = 'figma-cloud-limit';
|
|
5
|
+
export function dismissSubmittingToast(label) {
|
|
6
|
+
const activeToasts = toast.getToasts();
|
|
7
|
+
for(let index = activeToasts.length - 1; index >= 0; index--){
|
|
8
|
+
const candidate = activeToasts[index];
|
|
9
|
+
if (candidate && !('dismiss' in candidate) && candidate.promise !== undefined && candidate.title === label && candidate.type === 'loading') {
|
|
10
|
+
toast.dismiss(candidate.id);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function showCloudLimitToast(code, translate) {
|
|
16
|
+
toast.info(translate(cloudLimitToastKeyByCode[code]), {
|
|
17
|
+
id: `${TOAST_ID_PREFIX}-${code}`
|
|
18
|
+
});
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/figma",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
"import": "./dist/exports/client.js",
|
|
14
14
|
"types": "./dist/exports/client.d.ts",
|
|
15
15
|
"default": "./dist/exports/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./views": {
|
|
18
|
+
"import": "./dist/exports/views.js",
|
|
19
|
+
"types": "./dist/exports/views.d.ts",
|
|
20
|
+
"default": "./dist/exports/views.js"
|
|
16
21
|
}
|
|
17
22
|
},
|
|
18
23
|
"main": "./dist/index.js",
|
|
@@ -28,6 +33,7 @@
|
|
|
28
33
|
],
|
|
29
34
|
"dependencies": {
|
|
30
35
|
"@clack/prompts": "^0.11.0",
|
|
36
|
+
"@cloudflare/workers-types": "5.20260716.1",
|
|
31
37
|
"@figma/rest-api-spec": "^0.34.0",
|
|
32
38
|
"@sindresorhus/slugify": "^3.0.0",
|
|
33
39
|
"archiver": "7.0.1",
|
|
@@ -105,6 +111,7 @@
|
|
|
105
111
|
"test:int:watch": "TEST_INT=true vitest --config vitest.config.int.ts",
|
|
106
112
|
"test:e2e": "TEST_E2E=true vitest run --config vitest.config.e2e.ts",
|
|
107
113
|
"test:e2e:watch": "TEST_E2E=true vitest --config vitest.config.e2e.ts",
|
|
114
|
+
"test:types": "tsc --project tsconfig.imageSizeOptions.json",
|
|
108
115
|
"test:watch": "TEST_UNIT=true vitest"
|
|
109
116
|
}
|
|
110
117
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function stringifyNumericIds(value: unknown): unknown;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// The Content API stores every document id as a string, but Payload keeps a
|
|
2
|
-
// collection's custom numeric id as a `number`. Stringify numbers so an id filter
|
|
3
|
-
// matches the stored value.
|
|
4
|
-
// Has two shapes it accepts:
|
|
5
|
-
// - Scalar values (most operators)
|
|
6
|
-
// - Arrays of values (for `in`/`not_in` operators)
|
|
7
|
-
// Non-numeric entries (already-string ids) pass through untouched.
|
|
8
|
-
export function stringifyNumericIds(value) {
|
|
9
|
-
if (typeof value === 'number') {
|
|
10
|
-
return stringifyNumericId(value);
|
|
11
|
-
}
|
|
12
|
-
if (Array.isArray(value)) {
|
|
13
|
-
return value.map((entry)=>stringifyNumericId(entry));
|
|
14
|
-
}
|
|
15
|
-
return value;
|
|
16
|
-
}
|
|
17
|
-
function stringifyNumericId(value) {
|
|
18
|
-
return typeof value === 'number' ? String(value) : value;
|
|
19
|
-
}
|