diff --git a/package-lock.json b/package-lock.json
index c9700417c..4b05c059f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15209,7 +15209,7 @@
"name": "cms-starter",
"version": "0.0.0",
"dependencies": {
- "framer-plugin": "^3.3.0",
+ "framer-plugin": "^3.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
@@ -15424,9 +15424,9 @@
}
},
"starters/cms/node_modules/framer-plugin": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/framer-plugin/-/framer-plugin-3.3.0.tgz",
- "integrity": "sha512-8zrW63Oml0tUFNHoKjA55OJg2YyXCyeL9D5Y8lSZ334A45kOJOwdRJK731iGdtW/7PjctLRYa5IP/XQ3iGHEYw==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/framer-plugin/-/framer-plugin-3.3.1.tgz",
+ "integrity": "sha512-uIPLoGS/0wQqhl2dnnsNLs/b4h/vLI1Tvu8FeUt3VBhNwUSu1FO8SsSsQyncq9L2EsbiBP3Mnz7lK9jB/b2DYg==",
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
diff --git a/starters/cms/package.json b/starters/cms/package.json
index a2ce10845..b20e26c78 100644
--- a/starters/cms/package.json
+++ b/starters/cms/package.json
@@ -14,7 +14,7 @@
"typecheck": "tsc"
},
"dependencies": {
- "framer-plugin": "^3.3.0",
+ "framer-plugin": "^3.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
diff --git a/starters/cms/src/FieldMapping.tsx b/starters/cms/src/FieldMapping.tsx
index cc5c49f45..51eabf713 100644
--- a/starters/cms/src/FieldMapping.tsx
+++ b/starters/cms/src/FieldMapping.tsx
@@ -1,6 +1,6 @@
-import { type ManagedCollectionFieldInput, framer, type ManagedCollection } from "framer-plugin"
+import { type ManagedCollectionFieldInput, framer, type ManagedCollection, useIsAllowedTo } from "framer-plugin"
import { useEffect, useState } from "react"
-import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection } from "./data"
+import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection, syncMethods } from "./data"
interface FieldMappingRowProps {
field: ManagedCollectionFieldInput
@@ -8,6 +8,7 @@ interface FieldMappingRowProps {
isIgnored: boolean
onToggleDisabled: (fieldId: string) => void
onNameChange: (fieldId: string, name: string) => void
+ disabled: boolean
}
function FieldMappingRow({
@@ -16,6 +17,7 @@ function FieldMappingRow({
isIgnored,
onToggleDisabled,
onNameChange,
+ disabled,
}: FieldMappingRowProps) {
return (
<>
@@ -23,6 +25,7 @@ function FieldMappingRow({
type="button"
className={`source-field ${isIgnored ? "ignored" : ""}`}
onClick={() => onToggleDisabled(field.id)}
+ disabled={disabled}
>
{originalFieldName ?? field.id}
@@ -40,7 +43,7 @@ function FieldMappingRow({
onNameChange(field.id, event.target.value)}
@@ -136,6 +139,8 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
})
}
+ const isAllowedToManage = useIsAllowedTo("ManagedCollection.setFields", ...syncMethods)
+
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault()
@@ -150,8 +155,11 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
try {
setStatus("syncing-collection")
- const fieldsToSync = fields.filter(field => !ignoredFieldIds.has(field.id))
+ const fieldsToSync = fields
+ .filter(field => !ignoredFieldIds.has(field.id))
+ .map(field => ({ ...field, name: field.name.trim() || field.id }))
+ await collection.setFields(fieldsToSync)
await syncCollection(collection, dataSource, fieldsToSync, selectedSlugField)
await framer.closePlugin("Synchronization successful", { variant: "success" })
} catch (error) {
@@ -189,6 +197,7 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
if (!selectedField) return
setSelectedSlugField(selectedField)
}}
+ disabled={!isAllowedToManage}
>
{possibleSlugFields.map(possibleSlugField => {
return (
@@ -211,13 +220,18 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
isIgnored={ignoredFieldIds.has(field.id)}
onToggleDisabled={toggleFieldDisabledState}
onNameChange={changeFieldName}
+ disabled={!isAllowedToManage}
/>
))}
diff --git a/starters/cms/src/data.ts b/starters/cms/src/data.ts
index 24daa29e2..8d8069314 100644
--- a/starters/cms/src/data.ts
+++ b/starters/cms/src/data.ts
@@ -4,6 +4,7 @@ import {
framer,
type ManagedCollection,
type ManagedCollectionItemInput,
+ type ProtectedMethod,
} from "framer-plugin"
export const PLUGIN_KEYS = {
@@ -101,11 +102,6 @@ export async function syncCollection(
fields: readonly ManagedCollectionFieldInput[],
slugField: ManagedCollectionFieldInput
) {
- const sanitizedFields = fields.map(field => ({
- ...field,
- name: field.name.trim() || field.id,
- }))
-
const items: ManagedCollectionItemInput[] = []
const unsyncedItems = new Set(await collection.getItemIds())
@@ -123,7 +119,7 @@ export async function syncCollection(
const fieldData: FieldDataInput = {}
for (const [fieldName, value] of Object.entries(item)) {
- const field = sanitizedFields.find(field => field.id === fieldName)
+ const field = fields.find(field => field.id === fieldName)
// Field is in the data but skipped based on selected fields.
if (!field) continue
@@ -141,7 +137,6 @@ export async function syncCollection(
})
}
- await collection.setFields(sanitizedFields)
await collection.removeItems(Array.from(unsyncedItems))
await collection.addItems(items)
@@ -149,6 +144,12 @@ export async function syncCollection(
await collection.setPluginData(PLUGIN_KEYS.SLUG_FIELD_ID, slugField.id)
}
+export const syncMethods = [
+ "ManagedCollection.removeItems",
+ "ManagedCollection.addItems",
+ "ManagedCollection.setPluginData",
+] as const satisfies ProtectedMethod[]
+
export async function syncExistingCollection(
collection: ManagedCollection,
previousDataSourceId: string | null,
@@ -162,6 +163,10 @@ export async function syncExistingCollection(
return { didSync: false }
}
+ if (!framer.isAllowedTo(...syncMethods)) {
+ return { didSync: false }
+ }
+
try {
const dataSource = await getDataSource(previousDataSourceId)
const existingFields = await collection.getFields()