diff --git a/.changeset/admin-dam-video-block-name.md b/.changeset/admin-dam-video-block-name.md new file mode 100644 index 00000000000..c5a4d237b30 --- /dev/null +++ b/.changeset/admin-dam-video-block-name.md @@ -0,0 +1,15 @@ +--- +"@dextinity/cms-admin": minor +--- + +Add `name` option to `createDamVideoBlock` + +A `DamVideoBlock` created with a custom `supports` needs a name of its own, because `DamVideo` is taken by the exported `DamVideoBlock`. The name must match the name of the block created with `createDamVideoBlock` in the API. + +**Example** + +```tsx +import { createDamVideoBlock } from "@dextinity/cms-admin"; + +export const TeaserVideoBlock = createDamVideoBlock({ name: "TeaserVideo", supports: [] }); +``` diff --git a/.changeset/api-dam-video-block-factory.md b/.changeset/api-dam-video-block-factory.md new file mode 100644 index 00000000000..7d6899651a1 --- /dev/null +++ b/.changeset/api-dam-video-block-factory.md @@ -0,0 +1,33 @@ +--- +"@dextinity/cms-api": minor +--- + +Add `createDamVideoBlock` factory + +The `DamVideoBlock` always stores everything it has (autoplay, loop, show controls, preview image), even for sites that don't use any of it. `createDamVideoBlock` is the API counterpart of the Admin factory of the same name: pass what the site supports via `supports`, anything left out is part of neither the block's data nor its input, so it doesn't show up in `blocks.generated.ts` and isn't stored for new content. +Values that were stored before an option was left out are kept and saved again, so narrowing `supports` doesn't remove them from existing content. + +`supports` takes: + +- `"controls"` — autoplay, loop and show controls, offered together +- `"previewImage"` — the poster image + +`DamVideoBlock` is now created from the factory with both supported and still exported next to it, so this is non-breaking. Since it occupies the block name `DamVideo`, a block created with the factory needs a name of its own. +The name is passed as the second parameter, the same `nameOrOptions` the other block factories take, so it can carry a `migrate` option as well. + +**Example** + +```ts +import { createDamVideoBlock } from "@dextinity/cms-api"; + +// For a site that only reads the video's URL +export const TeaserVideoBlock = createDamVideoBlock({ supports: [] }, "TeaserVideo"); +``` + +Use the same `supports` and the same name for the Admin block. + +**Preview image of existing content** + +A block created by the factory defaults a missing preview image to an empty one when it supports one. This matters when the factory replaces a block a project already has: content stored before the block had a preview image loads as a child block instead of leaving the field undefined, which the block's own meta declares as always present. + +The default applies on read, so it also reaches content whose version is already the block's latest — for instance content stored while `supports` left the preview image out, before it was widened to include it. diff --git a/docs/docs/2-core-concepts/2-blocks/4-factories.mdx b/docs/docs/2-core-concepts/2-blocks/4-factories.mdx index d4bdcefb037..e9e7159e84c 100644 --- a/docs/docs/2-core-concepts/2-blocks/4-factories.mdx +++ b/docs/docs/2-core-concepts/2-blocks/4-factories.mdx @@ -227,30 +227,90 @@ export const FullWidthImageBlock = createCompositeBlock({ }); ``` -## DamVideoBlock (Admin only) +## DamVideoBlock The DamVideoBlock factory creates a block for a video from the DAM. -Use `supports` to restrict what editors can set besides the video file itself: +Use `supports` to restrict what the block offers besides the video file itself: - `"controls"`: The playback options autoplay, loop and show controls. - `"previewImage"`: The poster image shown before playback. Both are supported by default. -`@dextinity/cms-admin` exports a ready-made `DamVideoBlock` created with those defaults, use the `createDamVideoBlock` factory to change them: +`@dextinity/cms-api` and `@dextinity/cms-admin` export a ready-made `DamVideoBlock` created with those defaults. +Use the `createDamVideoBlock` factory when a site doesn't need all of them, for instance an app integration that only reads the video's URL. -```tsx title="DamVideoBlock.tsx" +Give the block a name of its own — `DamVideo` is taken by the exported `DamVideoBlock` — and use that same name and the same `supports` in both the API and the Admin. + +### API + +Use the `createDamVideoBlock` factory: + +```ts title="teaser-video.block.ts" +import { createDamVideoBlock } from "@dextinity/cms-api"; + +// For a site that only reads the video's URL +export const TeaserVideoBlock = createDamVideoBlock({ supports: [] }, "TeaserVideo"); +``` + +Unsupported options are part of neither the block's data nor its input, so they don't show up in `blocks.generated.ts` and aren't stored for new content. +Values that were stored before an option was left out are kept: the block loads them and saves them again, so narrowing `supports` doesn't remove them from existing content. +Note that such a value is no longer treated as a child block, so a preview image kept this way isn't part of the block index either. + +#### Preview image of existing content + +A block that supports the preview image defaults a missing one to an empty preview image when it loads content. +So replacing a block a project already has works without a migration: content stored before the preview image existed loads as a child block instead of leaving the field undefined. + +The default applies on read, which also covers content that no migration reaches — for instance content stored while `supports` left the preview image out, before it was widened to include it. + +Run `migrateBlocks` to write the defaulted data back: + +```bash +pnpm console migrateBlocks +``` + +Without it the default still applies whenever a block is loaded, it just isn't persisted until the content is saved. + +Own migrations are passed via `migrate` and start with version 1, like for any other block: + +```ts title="teaser-video.block.ts" +import { createDamVideoBlock, typeSafeBlockMigrationPipe } from "@dextinity/cms-api"; + +export const TeaserVideoBlock = createDamVideoBlock( + { supports: ["controls"] }, + { name: "TeaserVideo", migrate: { version: 1, migrations: typeSafeBlockMigrationPipe([AddSomethingMigration]) } }, +); +``` + +### Admin + +Use the `createDamVideoBlock` factory: + +```tsx title="TeaserVideoBlock.tsx" import { createDamVideoBlock } from "@dextinity/cms-admin"; -// For a site that renders no poster image -export const DamVideoBlock = createDamVideoBlock({ supports: ["controls"] }); +export const TeaserVideoBlock = createDamVideoBlock({ name: "TeaserVideo", supports: [] }); ``` -:::note +Unsupported options are hidden from the editor. +Their stored values are kept as long as the API block supports them, so widening `supports` later brings them back. -Leaving out an option only hides it from the editor. -Values that are already stored are kept as they are. -The preview image in particular stays part of the block's data either way, since the API's child block is non-nullable. -::: +### Site + +Render the video with the fields the block supports: + +```tsx title="TeaserVideoBlock.tsx" +import { PropsWithData } from "@dextinity/site-nextjs"; +import { TeaserVideoBlockData } from "@src/blocks.generated"; + +export function TeaserVideoBlock({ data: { damFile } }: PropsWithData) { + if (!damFile) { + return null; + } + + return