@payloadcms/figma 0.1.0-alpha.1 → 0.1.0-alpha.3
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/api/control-plane.d.ts +1 -1
- package/dist/api/control-plane.js +1 -1
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +33 -33
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.js +22 -12
- package/dist/db-content-api/index.d.ts +1 -1
- package/dist/db-content-api/index.js +70 -40
- package/dist/db-content-api/utilities/schema/getDocumentSchema.d.ts +3 -0
- package/dist/db-content-api/utilities/schema/getDocumentSchema.js +24 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +2 -0
- package/dist/plugin/build-config.js +39 -4
- package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +9 -0
- package/dist/plugin/cloud-limits/CloudLimitModal/index.js +78 -0
- package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +51 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +7 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +169 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +2 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.js +3 -0
- package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +9 -0
- package/dist/plugin/cloud-limits/SlugTracker/index.js +23 -0
- package/dist/plugin/cloud-limits/icons/ExternalLinkIcon.d.ts +2 -0
- package/dist/plugin/cloud-limits/icons/ExternalLinkIcon.js +30 -0
- package/dist/plugin/cloud-limits/shared.d.ts +10 -0
- package/dist/plugin/cloud-limits/shared.js +27 -0
- package/dist/plugin/sandbox-upload-fetch.d.ts +32 -0
- package/dist/plugin/sandbox-upload-fetch.js +53 -0
- package/dist/translations/ar.d.ts +12 -0
- package/dist/translations/ar.js +12 -0
- package/dist/translations/az.d.ts +12 -0
- package/dist/translations/az.js +12 -0
- package/dist/translations/bg.d.ts +12 -0
- package/dist/translations/bg.js +12 -0
- package/dist/translations/bnBd.d.ts +12 -0
- package/dist/translations/bnBd.js +12 -0
- package/dist/translations/bnIn.d.ts +12 -0
- package/dist/translations/bnIn.js +12 -0
- package/dist/translations/ca.d.ts +12 -0
- package/dist/translations/ca.js +12 -0
- package/dist/translations/cs.d.ts +12 -0
- package/dist/translations/cs.js +12 -0
- package/dist/translations/da.d.ts +12 -0
- package/dist/translations/da.js +12 -0
- package/dist/translations/de.d.ts +12 -0
- package/dist/translations/de.js +12 -0
- package/dist/translations/en.d.ts +12 -0
- package/dist/translations/en.js +12 -0
- package/dist/translations/es.d.ts +12 -0
- package/dist/translations/es.js +12 -0
- package/dist/translations/et.d.ts +12 -0
- package/dist/translations/et.js +12 -0
- package/dist/translations/fa.d.ts +12 -0
- package/dist/translations/fa.js +12 -0
- package/dist/translations/fr.d.ts +12 -0
- package/dist/translations/fr.js +12 -0
- package/dist/translations/he.d.ts +12 -0
- package/dist/translations/he.js +12 -0
- package/dist/translations/hr.d.ts +12 -0
- package/dist/translations/hr.js +12 -0
- package/dist/translations/hu.d.ts +12 -0
- package/dist/translations/hu.js +12 -0
- package/dist/translations/hy.d.ts +12 -0
- package/dist/translations/hy.js +12 -0
- package/dist/translations/id.d.ts +12 -0
- package/dist/translations/id.js +12 -0
- package/dist/translations/index.d.ts +533 -0
- package/dist/translations/index.js +90 -0
- package/dist/translations/is.d.ts +12 -0
- package/dist/translations/is.js +12 -0
- package/dist/translations/it.d.ts +12 -0
- package/dist/translations/it.js +12 -0
- package/dist/translations/ja.d.ts +12 -0
- package/dist/translations/ja.js +12 -0
- package/dist/translations/ko.d.ts +12 -0
- package/dist/translations/ko.js +12 -0
- package/dist/translations/lt.d.ts +12 -0
- package/dist/translations/lt.js +12 -0
- package/dist/translations/lv.d.ts +12 -0
- package/dist/translations/lv.js +12 -0
- package/dist/translations/my.d.ts +12 -0
- package/dist/translations/my.js +12 -0
- package/dist/translations/nb.d.ts +12 -0
- package/dist/translations/nb.js +12 -0
- package/dist/translations/nl.d.ts +12 -0
- package/dist/translations/nl.js +12 -0
- package/dist/translations/pl.d.ts +12 -0
- package/dist/translations/pl.js +12 -0
- package/dist/translations/pt.d.ts +12 -0
- package/dist/translations/pt.js +12 -0
- package/dist/translations/ro.d.ts +12 -0
- package/dist/translations/ro.js +12 -0
- package/dist/translations/rs.d.ts +12 -0
- package/dist/translations/rs.js +12 -0
- package/dist/translations/rsLatin.d.ts +12 -0
- package/dist/translations/rsLatin.js +12 -0
- package/dist/translations/ru.d.ts +12 -0
- package/dist/translations/ru.js +12 -0
- package/dist/translations/sk.d.ts +12 -0
- package/dist/translations/sk.js +12 -0
- package/dist/translations/sl.d.ts +12 -0
- package/dist/translations/sl.js +12 -0
- package/dist/translations/sv.d.ts +12 -0
- package/dist/translations/sv.js +12 -0
- package/dist/translations/ta.d.ts +12 -0
- package/dist/translations/ta.js +12 -0
- package/dist/translations/th.d.ts +12 -0
- package/dist/translations/th.js +12 -0
- package/dist/translations/tr.d.ts +12 -0
- package/dist/translations/tr.js +12 -0
- package/dist/translations/uk.d.ts +12 -0
- package/dist/translations/uk.js +12 -0
- package/dist/translations/vi.d.ts +12 -0
- package/dist/translations/vi.js +12 -0
- package/dist/translations/zh.d.ts +12 -0
- package/dist/translations/zh.js +12 -0
- package/dist/translations/zhTw.d.ts +12 -0
- package/dist/translations/zhTw.js +12 -0
- package/dist/types.d.ts +2 -0
- package/dist/utils/adapters/nextjs.d.ts +3 -3
- package/dist/utils/adapters/nextjs.js +9 -7
- package/dist/utils/adapters/nitro.d.ts +3 -3
- package/dist/utils/adapters/nitro.js +73 -77
- package/dist/utils/adapters/vite.d.ts +1 -1
- package/dist/utils/adapters/vite.js +8 -7
- package/dist/utils/assert-never.d.ts +1 -0
- package/dist/utils/assert-never.js +3 -0
- package/dist/utils/asset-collection.d.ts +2 -2
- package/dist/utils/asset-collection.js +10 -15
- package/dist/utils/build-detection.d.ts +6 -1
- package/dist/utils/build-detection.js +68 -89
- package/dist/utils/build-lambda-zip.d.ts +5 -6
- package/dist/utils/build-lambda-zip.js +31 -48
- package/dist/utils/deploy-adapter.d.ts +4 -4
- package/dist/utils/deploy-adapter.js +12 -8
- package/dist/utils/deploy-output/inspectNextjsOutput/getNextRelativeAppDir.d.ts +1 -0
- package/dist/utils/deploy-output/inspectNextjsOutput/getNextRelativeAppDir.js +15 -0
- package/dist/utils/deploy-output/inspectNextjsOutput.d.ts +12 -0
- package/dist/utils/deploy-output/inspectNextjsOutput.js +28 -0
- package/dist/utils/deploy-output/inspectNitroOutput.d.ts +9 -0
- package/dist/utils/deploy-output/inspectNitroOutput.js +13 -0
- package/dist/utils/deploy-output/isFile.d.ts +1 -0
- package/dist/utils/deploy-output/isFile.js +4 -0
- package/dist/utils/deploy-output/readJson.d.ts +1 -0
- package/dist/utils/deploy-output/readJson.js +9 -0
- package/dist/utils/deploy-output/resolveNitroOutputLayout.d.ts +8 -0
- package/dist/utils/deploy-output/resolveNitroOutputLayout.js +16 -0
- package/dist/utils/deploy-output/resolveOutputPath.d.ts +2 -0
- package/dist/utils/deploy-output/resolveOutputPath.js +9 -0
- package/dist/utils/deploy-output/resolveWithin.d.ts +1 -0
- package/dist/utils/deploy-output/resolveWithin.js +12 -0
- package/dist/utils/messages.js +2 -0
- package/package.json +6 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
|
|
2
2
|
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
|
3
3
|
import { buildConfig as payloadBuildConfig } from 'payload';
|
|
4
|
+
import { deepMergeSimple } from 'payload/shared';
|
|
4
5
|
import { getBootstrapInfo } from '../api/control-plane.js';
|
|
5
6
|
import { getValidCredential } from '../auth/oauth-flow.js';
|
|
6
7
|
import { getTokenStore } from '../auth/token-store.js';
|
|
@@ -12,12 +13,14 @@ import { schema } from '../endpoints/schema.js';
|
|
|
12
13
|
import { oAuth2Plugin } from '../oauth/index.js';
|
|
13
14
|
import { createStorageClient } from '../storage-content-api/client.js';
|
|
14
15
|
import { contentApiStorageAdapter } from '../storage-content-api/index.js';
|
|
16
|
+
import { translations as figmaTranslations } from '../translations/index.js';
|
|
15
17
|
import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
16
18
|
import { getEnvVarSync } from '../utils/env-management.js';
|
|
17
19
|
import * as log from '../utils/log.js';
|
|
18
20
|
import { logMissingCliAuth } from './auth-preflight.js';
|
|
19
21
|
import { logMissingContentSystemId } from './bootstrap-preflight.js';
|
|
20
22
|
import { getDevCookieNames } from './dev-cookie-names.js';
|
|
23
|
+
import { createSandboxUploadFetchPlugin } from './sandbox-upload-fetch.js';
|
|
21
24
|
/**
|
|
22
25
|
* Worker-trusted marker injected by Figma's sites-worker after it validates the
|
|
23
26
|
* X-Figma-Job-Queues-Auth-Bypass secret on a Payload jobs endpoint. The default
|
|
@@ -322,7 +325,14 @@ export async function buildFigmaConfig(config) {
|
|
|
322
325
|
avatar: {
|
|
323
326
|
Component: '@payloadcms/figma/client#FigmaAvatar'
|
|
324
327
|
},
|
|
325
|
-
...config.admin
|
|
328
|
+
...config.admin,
|
|
329
|
+
components: {
|
|
330
|
+
...config.admin?.components,
|
|
331
|
+
providers: [
|
|
332
|
+
...config.admin?.components?.providers ?? [],
|
|
333
|
+
'@payloadcms/figma/client#FigmaCloudLimitProvider'
|
|
334
|
+
]
|
|
335
|
+
}
|
|
326
336
|
},
|
|
327
337
|
custom: {
|
|
328
338
|
figma: {
|
|
@@ -338,6 +348,10 @@ export async function buildFigmaConfig(config) {
|
|
|
338
348
|
health,
|
|
339
349
|
schema
|
|
340
350
|
],
|
|
351
|
+
i18n: {
|
|
352
|
+
...config.i18n,
|
|
353
|
+
translations: deepMergeSimple(figmaTranslations, config.i18n?.translations ?? {})
|
|
354
|
+
},
|
|
341
355
|
// Gate /api/payload-jobs/run and /api/payload-jobs/handle-schedules. Honor the
|
|
342
356
|
// sites-worker trust marker so Figma infra job runs never break, defer to the
|
|
343
357
|
// tenant's jobs.access.run if they set one, otherwise fall back to Payload's
|
|
@@ -392,21 +406,42 @@ export async function buildFigmaConfig(config) {
|
|
|
392
406
|
debug: !!process.env.DEBUG,
|
|
393
407
|
disabled: false
|
|
394
408
|
}),
|
|
395
|
-
scheduleExtractPlugin()
|
|
409
|
+
scheduleExtractPlugin(),
|
|
410
|
+
// Not folded into createStoragePlugin, which is skipped when
|
|
411
|
+
// figma.storage is false and would break remote uploads for those apps.
|
|
412
|
+
createSandboxUploadFetchPlugin({
|
|
413
|
+
userCollections: config.collections
|
|
414
|
+
})
|
|
396
415
|
]
|
|
397
416
|
};
|
|
398
417
|
// Inject figma fields into users collection
|
|
399
418
|
const collections = configWithFigmaDefaults.collections?.map((col)=>{
|
|
419
|
+
const withSlugTracker = {
|
|
420
|
+
...col,
|
|
421
|
+
admin: {
|
|
422
|
+
...col.admin,
|
|
423
|
+
components: {
|
|
424
|
+
...col.admin?.components,
|
|
425
|
+
edit: {
|
|
426
|
+
...col.admin?.components?.edit,
|
|
427
|
+
beforeDocumentControls: [
|
|
428
|
+
'@payloadcms/figma/client#FigmaCloudLimitSlugTracker',
|
|
429
|
+
...col.admin?.components?.edit?.beforeDocumentControls ?? []
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
400
435
|
if (col.slug === 'users') {
|
|
401
436
|
return {
|
|
402
|
-
...
|
|
437
|
+
...withSlugTracker,
|
|
403
438
|
fields: [
|
|
404
439
|
...col.fields || [],
|
|
405
440
|
...figmaUserFields
|
|
406
441
|
]
|
|
407
442
|
};
|
|
408
443
|
}
|
|
409
|
-
return
|
|
444
|
+
return withSlugTracker;
|
|
410
445
|
});
|
|
411
446
|
if (collections) {
|
|
412
447
|
configWithFigmaDefaults.collections = collections;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import './index.scss';
|
|
2
|
+
type Props = {
|
|
3
|
+
adminRoute: string;
|
|
4
|
+
collectionLabel?: string;
|
|
5
|
+
collectionSlug?: string;
|
|
6
|
+
onRetry: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function FigmaCloudLimitDocumentCountModal({ adminRoute, collectionLabel, collectionSlug, onRetry, }: Props): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button, DialogBody, DialogCancel, DialogFooter, DialogHeader, DialogModal, useModal, useTranslation } from '@payloadcms/ui';
|
|
4
|
+
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
import { ExternalLinkIcon } from '../icons/ExternalLinkIcon.js';
|
|
7
|
+
import { cloudLimitDocumentCountModalSlug } from '../shared.js';
|
|
8
|
+
import './index.scss';
|
|
9
|
+
const baseClass = 'figma-cloud-limit-modal';
|
|
10
|
+
export function FigmaCloudLimitDocumentCountModal({ adminRoute, collectionLabel, collectionSlug, onRetry }) {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const { closeModal } = useModal();
|
|
13
|
+
const router = useRouter();
|
|
14
|
+
const handleReviewClick = useCallback(()=>{
|
|
15
|
+
closeModal(cloudLimitDocumentCountModalSlug);
|
|
16
|
+
router.push(`${adminRoute}/collections/${collectionSlug}`);
|
|
17
|
+
}, [
|
|
18
|
+
adminRoute,
|
|
19
|
+
closeModal,
|
|
20
|
+
collectionSlug,
|
|
21
|
+
router
|
|
22
|
+
]);
|
|
23
|
+
return /*#__PURE__*/ _jsxs(DialogModal, {
|
|
24
|
+
size: "small",
|
|
25
|
+
slug: cloudLimitDocumentCountModalSlug,
|
|
26
|
+
children: [
|
|
27
|
+
/*#__PURE__*/ _jsx(DialogHeader, {
|
|
28
|
+
showClose: true,
|
|
29
|
+
title: t('figma:cloudLimitModalHeading')
|
|
30
|
+
}),
|
|
31
|
+
/*#__PURE__*/ _jsx(DialogBody, {
|
|
32
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
33
|
+
className: `${baseClass}__body`,
|
|
34
|
+
children: t('figma:cloudLimitModalBody')
|
|
35
|
+
})
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx(DialogFooter, {
|
|
38
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
39
|
+
className: `${baseClass}__footer`,
|
|
40
|
+
children: [
|
|
41
|
+
collectionLabel && collectionSlug ? /*#__PURE__*/ _jsxs("button", {
|
|
42
|
+
className: `${baseClass}__review-btn`,
|
|
43
|
+
onClick: handleReviewClick,
|
|
44
|
+
type: "button",
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ _jsx("span", {
|
|
47
|
+
className: `${baseClass}__review-btn-label`,
|
|
48
|
+
children: t('figma:cloudLimitModalReviewCollection', {
|
|
49
|
+
collection: collectionLabel
|
|
50
|
+
})
|
|
51
|
+
}),
|
|
52
|
+
/*#__PURE__*/ _jsx(ExternalLinkIcon, {})
|
|
53
|
+
]
|
|
54
|
+
}) : null,
|
|
55
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
56
|
+
className: `${baseClass}__actions`,
|
|
57
|
+
children: [
|
|
58
|
+
/*#__PURE__*/ _jsx(DialogCancel, {
|
|
59
|
+
label: t('figma:cloudLimitModalConfirm')
|
|
60
|
+
}),
|
|
61
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
62
|
+
buttonStyle: "primary",
|
|
63
|
+
margin: false,
|
|
64
|
+
onClick: ()=>{
|
|
65
|
+
closeModal(cloudLimitDocumentCountModalSlug);
|
|
66
|
+
onRetry();
|
|
67
|
+
},
|
|
68
|
+
size: "medium",
|
|
69
|
+
children: t('figma:cloudLimitModalRetry')
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.figma-cloud-limit-modal {
|
|
2
|
+
&__body {
|
|
3
|
+
margin: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
&__footer {
|
|
7
|
+
align-items: center;
|
|
8
|
+
display: flex;
|
|
9
|
+
gap: var(--spacer-4);
|
|
10
|
+
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&__review-btn {
|
|
14
|
+
align-items: center;
|
|
15
|
+
background: none;
|
|
16
|
+
border: none;
|
|
17
|
+
color: var(--color-text-brand);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
flex-shrink: 1;
|
|
21
|
+
font-size: var(--text-body-medium-font-size);
|
|
22
|
+
gap: var(--spacer-1);
|
|
23
|
+
min-width: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
|
|
26
|
+
&:hover {
|
|
27
|
+
color: var(--ramp-blue-700);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:active {
|
|
31
|
+
color: var(--ramp-blue-800);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&__review-btn-label {
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Prevents the icon from shrinking when the label ellipses.
|
|
42
|
+
&__review-btn svg {
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&__actions {
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: var(--spacer-2);
|
|
49
|
+
margin-inline-start: auto;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { getTranslation } from '@payloadcms/translations';
|
|
4
|
+
import { FormErrorHandlerContext, toast, useConfig, useFormErrorHandler, useModal, useTranslation } from '@payloadcms/ui';
|
|
5
|
+
import { hasAutosaveEnabled } from 'payload/shared';
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { FigmaCloudLimitDocumentCountModal } from '../CloudLimitModal/index.js';
|
|
8
|
+
import { cloudLimitDocumentCountModalSlug, cloudLimitErrorCodes, cloudLimitToastKeyByCode, getCloudLimitErrorCode } from '../shared.js';
|
|
9
|
+
import { FigmaCloudLimitSlugRegistryContext } from '../SlugRegistryContext/index.js';
|
|
10
|
+
const AUTOSAVE_TOAST_ID = 'figma-cloud-limit-doc-count';
|
|
11
|
+
let isUploadFetchPatched = false;
|
|
12
|
+
function slugFromPathSuffix(suffix) {
|
|
13
|
+
return /^\/collections\/([^/?#]+)/.exec(suffix)?.[1];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Surfaces Content API cloud-limit errors in the admin UI.
|
|
17
|
+
*/ export function FigmaCloudLimitProvider({ children }) {
|
|
18
|
+
const { openModal } = useModal();
|
|
19
|
+
const { i18n, t } = useTranslation();
|
|
20
|
+
const { config, getEntityConfig } = useConfig();
|
|
21
|
+
const parentErrorHandler = useFormErrorHandler();
|
|
22
|
+
const adminRouteRef = useRef(config.routes?.admin ?? '/admin');
|
|
23
|
+
const apiRouteRef = useRef(config.routes?.api ?? '/api');
|
|
24
|
+
const [collectionSlug, setCollectionSlug] = useState();
|
|
25
|
+
const [collectionLabel, setCollectionLabel] = useState();
|
|
26
|
+
const retryRef = useRef(undefined);
|
|
27
|
+
// Ref so the handler always calls the latest getEntityConfig without being recreated.
|
|
28
|
+
const getEntityConfigRef = useRef(getEntityConfig);
|
|
29
|
+
getEntityConfigRef.current = getEntityConfig;
|
|
30
|
+
const tRef = useRef(t);
|
|
31
|
+
tRef.current = t;
|
|
32
|
+
// Stack of active collection slugs. FigmaCloudLimitSlugTracker components push/pop as
|
|
33
|
+
// documents mount/unmount (including drawers). The last entry is the active form's collection.
|
|
34
|
+
const slugStackRef = useRef([]);
|
|
35
|
+
const registerCollectionSlug = useCallback((slug)=>{
|
|
36
|
+
const id = Symbol();
|
|
37
|
+
slugStackRef.current = [
|
|
38
|
+
...slugStackRef.current,
|
|
39
|
+
{
|
|
40
|
+
id,
|
|
41
|
+
slug
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
return ()=>{
|
|
45
|
+
slugStackRef.current = slugStackRef.current.filter((e)=>e.id !== id);
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
// Detect upload related cloud-limit that bypass form state
|
|
49
|
+
useEffect(()=>{
|
|
50
|
+
if (isUploadFetchPatched) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
isUploadFetchPatched = true;
|
|
54
|
+
const originalFetch = window.fetch.bind(window);
|
|
55
|
+
const originalToastError = toast.error;
|
|
56
|
+
const toastObj = toast;
|
|
57
|
+
const pendingSuppressions = [];
|
|
58
|
+
const removeSuppression = (suppression)=>{
|
|
59
|
+
window.clearTimeout(suppression.timeout);
|
|
60
|
+
const index = pendingSuppressions.indexOf(suppression);
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
pendingSuppressions.splice(index, 1);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
toastObj.error = (...args)=>{
|
|
66
|
+
// prevent the thrown error from sending a duplicate toast
|
|
67
|
+
const msg = typeof args[0] === 'string' ? args[0] : args[0]?.props?.errorMessage ?? '';
|
|
68
|
+
const suppression = pendingSuppressions.find(({ message })=>msg.includes(message));
|
|
69
|
+
if (suppression) {
|
|
70
|
+
removeSuppression(suppression);
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
return originalToastError(...args);
|
|
74
|
+
};
|
|
75
|
+
window.fetch = async function(...args) {
|
|
76
|
+
const response = await originalFetch(...args);
|
|
77
|
+
if (response.status === 409) {
|
|
78
|
+
const url = args[0] instanceof Request ? args[0].url : String(args[0]);
|
|
79
|
+
const uploadInstructionsPath = `${apiRouteRef.current.replace(/\/$/, '')}/upload-instructions`;
|
|
80
|
+
if (new URL(url, window.location.origin).pathname === uploadInstructionsPath) {
|
|
81
|
+
try {
|
|
82
|
+
const json = await response.clone().json();
|
|
83
|
+
const code = getCloudLimitErrorCode(json);
|
|
84
|
+
const messageKey = code !== undefined ? cloudLimitToastKeyByCode[code] : undefined;
|
|
85
|
+
if (messageKey) {
|
|
86
|
+
toast.info(tRef.current(messageKey));
|
|
87
|
+
const rawMessage = json?.errors?.[0]?.message;
|
|
88
|
+
if (rawMessage) {
|
|
89
|
+
const suppression = {
|
|
90
|
+
message: rawMessage,
|
|
91
|
+
timeout: 0
|
|
92
|
+
};
|
|
93
|
+
suppression.timeout = window.setTimeout(()=>removeSuppression(suppression), 0);
|
|
94
|
+
pendingSuppressions.push(suppression);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// ignore JSON parse failures
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return response;
|
|
103
|
+
};
|
|
104
|
+
return ()=>{
|
|
105
|
+
pendingSuppressions.forEach(({ timeout })=>window.clearTimeout(timeout));
|
|
106
|
+
window.fetch = originalFetch;
|
|
107
|
+
toastObj.error = originalToastError;
|
|
108
|
+
isUploadFetchPatched = false;
|
|
109
|
+
};
|
|
110
|
+
}, []);
|
|
111
|
+
const handleNonFieldError = useCallback((err, retry)=>{
|
|
112
|
+
const code = getCloudLimitErrorCode({
|
|
113
|
+
errors: [
|
|
114
|
+
err
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
if (!code) {
|
|
118
|
+
return parentErrorHandler?.(err, retry) ?? false;
|
|
119
|
+
}
|
|
120
|
+
if (code === cloudLimitErrorCodes.documentCount) {
|
|
121
|
+
// Prefer the tracker-provided slug (correct even in drawers) over URL parsing.
|
|
122
|
+
const stack = slugStackRef.current;
|
|
123
|
+
const slug = stack.length > 0 ? stack[stack.length - 1]?.slug : slugFromPathSuffix(window.location.pathname.slice(adminRouteRef.current.length));
|
|
124
|
+
const collectionConfig = slug ? getEntityConfigRef.current({
|
|
125
|
+
collectionSlug: slug
|
|
126
|
+
}) : undefined;
|
|
127
|
+
const isAutosave = collectionConfig ? hasAutosaveEnabled(collectionConfig) : false;
|
|
128
|
+
if (isAutosave) {
|
|
129
|
+
// Stable ID prevents a new toast stacking on every autosave attempt.
|
|
130
|
+
toast.info(t('figma:cloudLimitDocumentCountError'), {
|
|
131
|
+
id: AUTOSAVE_TOAST_ID
|
|
132
|
+
});
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
const label = collectionConfig?.labels?.plural != null ? getTranslation(collectionConfig.labels.plural, i18n) : slug;
|
|
136
|
+
setCollectionSlug(slug);
|
|
137
|
+
setCollectionLabel(label);
|
|
138
|
+
retryRef.current = retry;
|
|
139
|
+
openModal(cloudLimitDocumentCountModalSlug);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
const messageKey = cloudLimitToastKeyByCode[code];
|
|
143
|
+
if (messageKey) {
|
|
144
|
+
toast.info(t(messageKey));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}, [
|
|
149
|
+
i18n,
|
|
150
|
+
openModal,
|
|
151
|
+
parentErrorHandler,
|
|
152
|
+
t
|
|
153
|
+
]);
|
|
154
|
+
return /*#__PURE__*/ _jsx(FigmaCloudLimitSlugRegistryContext, {
|
|
155
|
+
value: registerCollectionSlug,
|
|
156
|
+
children: /*#__PURE__*/ _jsxs(FormErrorHandlerContext, {
|
|
157
|
+
value: handleNonFieldError,
|
|
158
|
+
children: [
|
|
159
|
+
children,
|
|
160
|
+
/*#__PURE__*/ _jsx(FigmaCloudLimitDocumentCountModal, {
|
|
161
|
+
adminRoute: adminRouteRef.current,
|
|
162
|
+
collectionLabel: collectionLabel,
|
|
163
|
+
collectionSlug: collectionSlug,
|
|
164
|
+
onRetry: ()=>retryRef.current?.()
|
|
165
|
+
})
|
|
166
|
+
]
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invisible component injected into every collection's
|
|
3
|
+
* `admin.components.edit.beforeDocumentControls`. Renders inside
|
|
4
|
+
* DocumentInfoProvider so it can read the correct collectionSlug even inside
|
|
5
|
+
* relationship drawers. Pushes to / pops from the slug stack owned by
|
|
6
|
+
* FigmaCloudLimitProvider so the error handler always knows which collection
|
|
7
|
+
* the active form belongs to.
|
|
8
|
+
*/
|
|
9
|
+
export declare function FigmaCloudLimitSlugTracker(): null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useDocumentInfo } from '@payloadcms/ui';
|
|
3
|
+
import { use, useEffect } from 'react';
|
|
4
|
+
import { FigmaCloudLimitSlugRegistryContext } from '../SlugRegistryContext/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Invisible component injected into every collection's
|
|
7
|
+
* `admin.components.edit.beforeDocumentControls`. Renders inside
|
|
8
|
+
* DocumentInfoProvider so it can read the correct collectionSlug even inside
|
|
9
|
+
* relationship drawers. Pushes to / pops from the slug stack owned by
|
|
10
|
+
* FigmaCloudLimitProvider so the error handler always knows which collection
|
|
11
|
+
* the active form belongs to.
|
|
12
|
+
*/ export function FigmaCloudLimitSlugTracker() {
|
|
13
|
+
const register = use(FigmaCloudLimitSlugRegistryContext);
|
|
14
|
+
const { collectionSlug } = useDocumentInfo();
|
|
15
|
+
useEffect(()=>{
|
|
16
|
+
const unregister = register(collectionSlug);
|
|
17
|
+
return unregister;
|
|
18
|
+
}, [
|
|
19
|
+
collectionSlug,
|
|
20
|
+
register
|
|
21
|
+
]);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/** External link icon (box with arrow pointing out). Used in cloud-limit UI. */ export function ExternalLinkIcon() {
|
|
4
|
+
return /*#__PURE__*/ _jsxs("svg", {
|
|
5
|
+
"aria-hidden": "true",
|
|
6
|
+
fill: "none",
|
|
7
|
+
height: "14",
|
|
8
|
+
stroke: "currentColor",
|
|
9
|
+
strokeLinecap: "round",
|
|
10
|
+
strokeLinejoin: "round",
|
|
11
|
+
strokeWidth: "2",
|
|
12
|
+
viewBox: "0 0 24 24",
|
|
13
|
+
width: "14",
|
|
14
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
15
|
+
children: [
|
|
16
|
+
/*#__PURE__*/ _jsx("path", {
|
|
17
|
+
d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
|
|
18
|
+
}),
|
|
19
|
+
/*#__PURE__*/ _jsx("polyline", {
|
|
20
|
+
points: "15 3 21 3 21 9"
|
|
21
|
+
}),
|
|
22
|
+
/*#__PURE__*/ _jsx("line", {
|
|
23
|
+
x1: "10",
|
|
24
|
+
x2: "21",
|
|
25
|
+
y1: "14",
|
|
26
|
+
y2: "3"
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const cloudLimitErrorCodes: {
|
|
2
|
+
readonly assetStorage: "cloud_limit.cms_assets_bytes_exceeded";
|
|
3
|
+
readonly documentCount: "cloud_limit.document_count_exceeded";
|
|
4
|
+
readonly documentDataSize: "cloud_limit.per_document_data_bytes_exceeded";
|
|
5
|
+
};
|
|
6
|
+
export type CloudLimitErrorCode = (typeof cloudLimitErrorCodes)[keyof typeof cloudLimitErrorCodes];
|
|
7
|
+
export declare function isCloudLimitErrorCode(code: unknown): code is CloudLimitErrorCode;
|
|
8
|
+
export declare function getCloudLimitErrorCode(json: unknown): CloudLimitErrorCode | undefined;
|
|
9
|
+
export declare const cloudLimitDocumentCountModalSlug = "figma-cloud-limit-document-count";
|
|
10
|
+
export declare const cloudLimitToastKeyByCode: Partial<Record<CloudLimitErrorCode, string>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const cloudLimitErrorCodes = {
|
|
2
|
+
assetStorage: 'cloud_limit.cms_assets_bytes_exceeded',
|
|
3
|
+
documentCount: 'cloud_limit.document_count_exceeded',
|
|
4
|
+
documentDataSize: 'cloud_limit.per_document_data_bytes_exceeded'
|
|
5
|
+
};
|
|
6
|
+
const knownCloudLimitCodes = Object.values(cloudLimitErrorCodes);
|
|
7
|
+
export function isCloudLimitErrorCode(code) {
|
|
8
|
+
return typeof code === 'string' && knownCloudLimitCodes.includes(code);
|
|
9
|
+
}
|
|
10
|
+
export function getCloudLimitErrorCode(json) {
|
|
11
|
+
const errors = json?.errors;
|
|
12
|
+
if (!Array.isArray(errors)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
for (const err of errors){
|
|
16
|
+
const code = err?.data?.code;
|
|
17
|
+
if (isCloudLimitErrorCode(code)) {
|
|
18
|
+
return code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
export const cloudLimitDocumentCountModalSlug = 'figma-cloud-limit-document-count';
|
|
24
|
+
export const cloudLimitToastKeyByCode = {
|
|
25
|
+
[cloudLimitErrorCodes.assetStorage]: 'figma:cloudLimitAssetStorageError',
|
|
26
|
+
[cloudLimitErrorCodes.documentDataSize]: 'figma:cloudLimitDocumentDataSizeError'
|
|
27
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Config } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* True inside the Make sandbox. agentplat injects `FIGMA=1`, absent locally and
|
|
4
|
+
* in the deployed Lambda; `FIGMA=true` matches the app template's vite config.
|
|
5
|
+
*
|
|
6
|
+
* KEEP_IN_SYNC(services/agentplat/scriptrun/script_runner.go): EnvFigma
|
|
7
|
+
*/
|
|
8
|
+
export declare function isMakeSandbox(): boolean;
|
|
9
|
+
type SandboxUploadFetchOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* The app's collections before any plugin runs. `plugin-cloud-storage` later
|
|
12
|
+
* rewrites `skipSafeFetch`, erasing whether the app set `false` or nothing, so
|
|
13
|
+
* intent must be read here.
|
|
14
|
+
*/
|
|
15
|
+
userCollections: Config['collections'];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Defaults upload collections to skip `safeFetch` in the Make sandbox, where DNS
|
|
19
|
+
* maps external hostnames to unique-local IPv6 addresses (`fc00::/7`) that
|
|
20
|
+
* `safeFetch` rejects as non-unicast. A no-op elsewhere.
|
|
21
|
+
*
|
|
22
|
+
* Only a default: a collection that set `skipSafeFetch` itself keeps its value.
|
|
23
|
+
* `externalFileHeaderFilter` is left unset on purpose, since it would replace
|
|
24
|
+
* the cookie logic that forwards credentials for relative URLs.
|
|
25
|
+
*
|
|
26
|
+
* SSRF risks, bounded by the routes' auth gate and sandbox network limits:
|
|
27
|
+
* `getExternalFile` re-validates redirects only against `pasteURL.allowList`,
|
|
28
|
+
* any session can GET an arbitrary URL and read the body, and request cookies
|
|
29
|
+
* reach the target.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createSandboxUploadFetchPlugin({ userCollections }: SandboxUploadFetchOptions): (config: Config) => Config;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as log from '../utils/log.js';
|
|
2
|
+
/**
|
|
3
|
+
* True inside the Make sandbox. agentplat injects `FIGMA=1`, absent locally and
|
|
4
|
+
* in the deployed Lambda; `FIGMA=true` matches the app template's vite config.
|
|
5
|
+
*
|
|
6
|
+
* KEEP_IN_SYNC(services/agentplat/scriptrun/script_runner.go): EnvFigma
|
|
7
|
+
*/ export function isMakeSandbox() {
|
|
8
|
+
return process.env.FIGMA === '1' || process.env.FIGMA === 'true';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Defaults upload collections to skip `safeFetch` in the Make sandbox, where DNS
|
|
12
|
+
* maps external hostnames to unique-local IPv6 addresses (`fc00::/7`) that
|
|
13
|
+
* `safeFetch` rejects as non-unicast. A no-op elsewhere.
|
|
14
|
+
*
|
|
15
|
+
* Only a default: a collection that set `skipSafeFetch` itself keeps its value.
|
|
16
|
+
* `externalFileHeaderFilter` is left unset on purpose, since it would replace
|
|
17
|
+
* the cookie logic that forwards credentials for relative URLs.
|
|
18
|
+
*
|
|
19
|
+
* SSRF risks, bounded by the routes' auth gate and sandbox network limits:
|
|
20
|
+
* `getExternalFile` re-validates redirects only against `pasteURL.allowList`,
|
|
21
|
+
* any session can GET an arbitrary URL and read the body, and request cookies
|
|
22
|
+
* reach the target.
|
|
23
|
+
*/ export function createSandboxUploadFetchPlugin({ userCollections }) {
|
|
24
|
+
const appOwnedSlugs = new Set((userCollections ?? []).filter((collection)=>typeof collection.upload === 'object' && 'skipSafeFetch' in collection.upload).map((collection)=>collection.slug));
|
|
25
|
+
return (config)=>{
|
|
26
|
+
if (!isMakeSandbox()) {
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
const modifiedSlugs = [];
|
|
30
|
+
const collections = (config.collections ?? []).map((collection)=>{
|
|
31
|
+
if (!collection.upload || appOwnedSlugs.has(collection.slug)) {
|
|
32
|
+
return collection;
|
|
33
|
+
}
|
|
34
|
+
modifiedSlugs.push(collection.slug);
|
|
35
|
+
const upload = collection.upload === true ? {} : collection.upload;
|
|
36
|
+
return {
|
|
37
|
+
...collection,
|
|
38
|
+
upload: {
|
|
39
|
+
...upload,
|
|
40
|
+
skipSafeFetch: true
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
if (modifiedSlugs.length === 0) {
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
log.debug(`Make sandbox detected: upload collections skip safeFetch (${modifiedSlugs.join(', ')})`);
|
|
48
|
+
return {
|
|
49
|
+
...config,
|
|
50
|
+
collections
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const arTranslations: {
|
|
2
|
+
figma: {
|
|
3
|
+
cloudLimitAssetStorageError: string;
|
|
4
|
+
cloudLimitDocumentCountError: string;
|
|
5
|
+
cloudLimitDocumentDataSizeError: string;
|
|
6
|
+
cloudLimitModalBody: string;
|
|
7
|
+
cloudLimitModalConfirm: string;
|
|
8
|
+
cloudLimitModalHeading: string;
|
|
9
|
+
cloudLimitModalRetry: string;
|
|
10
|
+
cloudLimitModalReviewCollection: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const arTranslations = {
|
|
2
|
+
figma: {
|
|
3
|
+
cloudLimitAssetStorageError: 'يتعذر التحميل. يرجى تحرير بعض المساحة للمتابعة.',
|
|
4
|
+
cloudLimitDocumentCountError: 'غير قادر على إنشاء وثيقة. تم الوصول إلى الحد الأقصى لسعة التخزين.',
|
|
5
|
+
cloudLimitDocumentDataSizeError: 'تعذر حفظ المستند، النص كبير جدًا.',
|
|
6
|
+
cloudLimitModalBody: 'لقد وصلت إلى الحد الأقصى لعدد الوثائق. يرجى حذف بعض الوثائق الحالية لمواصلة الإنشاء.',
|
|
7
|
+
cloudLimitModalConfirm: 'تم فهم ذلك',
|
|
8
|
+
cloudLimitModalHeading: 'تعذر حفظ المستند',
|
|
9
|
+
cloudLimitModalRetry: 'إعادة المحاولة',
|
|
10
|
+
cloudLimitModalReviewCollection: 'مراجعة {{collection}}'
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const azTranslations: {
|
|
2
|
+
figma: {
|
|
3
|
+
cloudLimitAssetStorageError: string;
|
|
4
|
+
cloudLimitDocumentCountError: string;
|
|
5
|
+
cloudLimitDocumentDataSizeError: string;
|
|
6
|
+
cloudLimitModalBody: string;
|
|
7
|
+
cloudLimitModalConfirm: string;
|
|
8
|
+
cloudLimitModalHeading: string;
|
|
9
|
+
cloudLimitModalRetry: string;
|
|
10
|
+
cloudLimitModalReviewCollection: string;
|
|
11
|
+
};
|
|
12
|
+
};
|