Skip to content

Commit

Permalink
feat: add Contentful field package
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvxd committed Mar 25, 2024
1 parent 9fa2428 commit d944288
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 2 deletions.
2 changes: 0 additions & 2 deletions apps/docs/pages/docs/extending-puck/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ The plugin API enables developers to distribute plugins to customize the Puck in

- [`heading-analyzer`](https://github.com/measuredco/puck/tree/main/packages/plugin-heading-analyzer): Analyze the heading outline of your page and be warned when you're not respecting WCAG 2 accessibility standards.

## Community plugins

Please see the [awesome-puck repo](https://github.com/measuredco/awesome-puck) for a full list of community plugins.

## Developing a plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ const config = {
};
```

## External data packages

We provide helper packages to load data from common data sources.

- [`contentful`](https://github.com/measuredco/puck/tree/main/packages/field-contentful): Select content entries from a [Contentful](https://www.contentful.com) space.

## Further reading

- [`external` field API reference](/docs/api-reference/configuration/fields/external)
Expand Down
132 changes: 132 additions & 0 deletions packages/field-contentful/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# field-contentful

Select [entries](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries) from a [Contentful](https://www.contentful.com) space.

## Quick start

```sh
npm i @measured/puck-field-contentful
```

```jsx
import createFieldContentful from "@measured/puck-field-contentful";

const config = {
components: {
Example: {
fields: {
movie: createFieldContentful("movies", {
space: "my_space",
accessToken: "abcdefg123456",
}),
},
render: ({ data }) => {
return <p>{data?.fields.title || "No data selected"}</p>;
},
},
},
};
```
## Args
| Param | Example | Type | Status |
| ----------------------------- | -------- | ------ | -------- |
| [`contentType`](#contenttype) | `movies` | String | Required |
| [`options`](#options) | `{}` | Object | Required |
### Required args
#### `contentType`
ID of the Contentful [Content Type](https://www.contentful.com/help/content-model-and-content-type/) to query.
#### `options`
| Param | Example | Type | Status |
| ------------------------------------------ | --------------------------------------- | --------------------------------------------------------------- | ------------------------------ |
| [`accessToken`](#optionsaccesstoken) | `"abc123"` | String | Required (unless using client) |
| [`space`](#optionsspace) | `"my-space"` | String | Required (unless using client) |
| [`client`](#optionsclient) | `createClient()` | [ContentfulClientApi](https://www.npmjs.com/package/contentful) | - |
| [`filterFields`](#optionsfilterfields) | `{ "rating[gte]": { type: "number" } }` | Object | - |
| [`initialFilters`](#optionsinitialfilters) | `{ "rating[gte]": 1 }` | Object | - |
| [`titleField`](#optionstitlefield) | `"name"` | String | - |
##### `options.accessToken`
Your Contentful access token.
##### `options.space`
The id for the Contentful space that contains your content.
##### `options.client`
A Contentful client as created by the [`contentful` Node.js package](https://www.npmjs.com/package/contentful). You can use this instead of `accessToken` and `space` if you want to reuse your client, or customise it more fully.
##### `options.filterFields`
An object describing which [`filterFields`](https://puckeditor.com/docs/api-reference/configuration/fields/external#filterfields) to render and pass the result directly to Contentful as [search parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters).
```jsx
createFieldContentful("movies", {
// ...
filterFields: {
// Filter the "rating" field by value greater than the user input
"fields.rating[gte]": {
type: "number",
},
},
});
```
##### `options.initialFilters`
The initial values for the filters defined in [`filterFields`](#optionsfilterfields). This data is passed directly directly to Contentful as [search parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters).
```jsx
createFieldContentful("movies", {
// ...
initialFilters: {
"fields.rating[gte]": 1,
select: "name,rating", // Can include search parameters not included in filterFields
},
});
```
##### `options.titleField`
The field to use as the title for the selected item. Defaults to `"title"`.
```jsx
createFieldContentful("movies", {
// ...
titleField: "name",
});
```
## Returns
An [External field](https://puckeditor.com/docs/api-reference/configuration/fields/external) type that loads Contentful [entries](https://contentful.github.io/contentful.js/contentful/10.6.16/types/Entry.html).
## TypeScript
You can use the `Entry` type for data loaded via Contentful:
```tsx
import createFieldContentful, { Entry } from "@/field-contentful";

type MyProps = {
Example: {
movie: Entry<{ title: string; description: string; rating: number }>;
};
};

const config: Config<MyProps> = {
// ...
};
```
## License
MIT © [Measured Corporation Ltd](https://measured.co)
64 changes: 64 additions & 0 deletions packages/field-contentful/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ExternalField } from "@/core/types/Config";

import { BaseEntry, ContentfulClientApi, createClient } from "contentful";

export { createClient };

export type Entry<Fields extends Record<string, any> = {}> = BaseEntry & {
fields: Fields;
};

export function createFieldContentful<T extends Entry = Entry>(
contentType: string,
options: {
client?: ContentfulClientApi<undefined>;
space?: string;
accessToken?: string;
titleField?: string;
filterFields?: ExternalField["filterFields"];
initialFilters?: ExternalField["initialFilters"];
} = {}
) {
const {
space,
accessToken,
titleField = "title",
filterFields,
initialFilters,
} = options;

if (!options.client) {
if (!space || !accessToken) {
throw new Error(
'field-contentful: Must either specify "client", or "space" and "accessToken"'
);
}
}

const client =
options.client ||
createClient({ space: space!, accessToken: accessToken! });

const field: ExternalField<T> = {
type: "external",
placeholder: "Select from Contentful",
showSearch: true,
fetchList: async ({ query, filters = {} }) => {
const entries = await client.getEntries({
...filters,
content_type: contentType,
query,
});

return entries.items;
},
mapRow: ({ fields }) => fields,
getItemSummary: (item) => item.fields[titleField],
filterFields,
initialFilters,
};

return field;
}

export default createFieldContentful;
37 changes: 37 additions & 0 deletions packages/field-contentful/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@measured/puck-field-contentful",
"version": "0.13.1",
"author": "Measured Corporation Ltd <hello@measured.co>",
"repository": "measuredco/puck",
"bugs": "https://github.com/measuredco/puck/issues",
"homepage": "https://puckeditor.com",
"private": false,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "MIT",
"scripts": {
"lint": "eslint \"**/*.ts*\"",
"build": "rm -rf dist && tsup index.ts",
"prepare": "yarn build"
},
"files": [
"dist"
],
"devDependencies": {
"@measured/puck": "^0.13.1",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"eslint": "^7.32.0",
"eslint-config-custom": "*",
"tsconfig": "*",
"tsup-config": "*",
"typescript": "^4.5.2"
},
"dependencies": {
"react-from-json": "^0.8.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"contentful": "^10.0.0"
}
}
11 changes: 11 additions & 0 deletions packages/field-contentful/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "tsconfig/react-library.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"],
"compilerOptions": {
"paths": {
"@/core": ["../core"],
"@/core/*": ["../core/*"]
}
}
}
4 changes: 4 additions & 0 deletions packages/field-contentful/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineConfig } from "tsup";
import tsupconfig from "../tsup-config";

export default defineConfig(tsupconfig);

0 comments on commit d944288

Please sign in to comment.