@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.
Files changed (45) hide show
  1. package/dist/db-content-api/index.js +31 -17
  2. package/dist/db-content-api/utilities/joins.js +5 -1
  3. package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
  4. package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
  5. package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
  6. package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
  7. package/dist/db-content-api/utilities/where.d.ts +2 -6
  8. package/dist/db-content-api/utilities/where.js +20 -11
  9. package/dist/exports/views.d.ts +1 -0
  10. package/dist/exports/views.js +1 -0
  11. package/dist/imageSizeOptions.d.ts +23 -0
  12. package/dist/imageSizeOptions.js +26 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/oauth/defaults.d.ts +1 -0
  15. package/dist/oauth/defaults.js +1 -0
  16. package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
  17. package/dist/oauth/index.js +24 -9
  18. package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
  19. package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
  20. package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
  21. package/dist/plugin/build-config.js +22 -9
  22. package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
  23. package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
  24. package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
  25. package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
  26. package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
  27. package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
  28. package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
  29. package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
  30. package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
  31. package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
  32. package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
  33. package/dist/plugin/cloud-limits/routes.d.ts +2 -0
  34. package/dist/plugin/cloud-limits/routes.js +4 -0
  35. package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
  36. package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
  37. package/dist/plugin/cloud-limits/shared.d.ts +1 -1
  38. package/dist/plugin/cloud-limits/shared.js +25 -0
  39. package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
  40. package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
  41. package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
  42. package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
  43. package/package.json +8 -1
  44. package/dist/db-content-api/utilities/data/stringifyNumericIds.d.ts +0 -1
  45. 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,2 @@
1
+ import type { Translate } from './toasts.js';
2
+ export declare function cloudLimitInterceptor(apiRoute: string, translate: Translate): () => void;
@@ -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.4",
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
- }