Repository navigation
10.8.0
@dextinity/brevo-admin@10.8.0
Patch Changes
-
8c41f26: Allow typing the email campaign schedule date
The schedule field used the deprecated
FinalFormDateTimePickerfrom@dextinity/admin-date-time, which renders a read-only input, so the date and time could only be picked from the calendar. It now usesDateTimePickerFieldfrom@dextinity/admin, which accepts keyboard input.@dextinity/brevo-adminno longer depends on@dextinity/admin-date-time.
@dextinity/cms-admin@10.8.0
Minor Changes
-
9109aa3: TipTap Rich Text Block: replace the
paragraphandheadingoptions withtextBlocksThe text block type select could only ever offer a fixed paragraph entry plus every enabled heading level, in a fixed order.
textBlocksconfigures the text block types explicitly, which decouples a text block from the tag it is stored as and lets several text blocks share a tag, for instance a display headline next to a regular heading 1.Example
createTipTapRichTextBlock({ textBlocks: [ { name: "paragraph", label: "Paragraph", tag: "p" }, { name: "display", label: "Display", tag: "h1" }, { name: "heading-1", label: "Heading 1", tag: "h1" }, { name: "heading-2", label: "Heading 2", tag: "h2" }, ], defaultTextBlock: "paragraph", });
The API takes the same option without the labels.
Migrating an existing configuration
-
paragraph/heading→ onetextBlocksentry per text block type (tag: "p"for the paragraph,h1-h6for the headings). It defaults to a paragraph plus a heading for every level, so only a restricted set needs to be configured. Leaving the paragraph out replacesparagraph: false. -
heading: { levels }→ thetextBlocksentries for those levels. -
heading: { defaultLevel }→defaultTextBlock, which names the text block new content starts with and defaults to the first one. -
migrateFromDraftJs'textBlockStyleMap→textBlockMap, which now takes a{ textBlock, textBlockStyle }object for every DraftJS block type:textBlocknames the text block the block becomes (instead of the tag the previoustextBlockTypenamed),textBlockStylestays optional. Where two text blocks share a heading tag,textBlockMaphas to name the one a DraftJSheader-one…header-sixbecomes, unless it is thedefaultTextBlock— the block throws otherwise, because the conversion runs once and the DraftJS content is gone afterwards.The stored format changes
Every paragraph and heading is now one
textBlocknode that names its text block, instead of aparagraph/headingnode with alevel:{ "type": "textBlock", "attrs": { "textBlock": "heading-2" }, "content": [{ "type": "text", "text": "Headline" }] }The tag lives in the configuration, so changing a text block's
tagtakes effect without a migration, while renaming or removing one invalidates the content that names it.Content stored by an earlier version holds
paragraphandheadingnodes. A vendor migration converts it when the block is loaded, so a project needs no migration of its own. It resolves the text block from the node's tag, which is unambiguous for that content: two text blocks could not share a tag beforetextBlocksexisted.A project migration sees the converted nodes, because the vendor migrations run before the block's own. A migration written against
{ type: "heading", attrs: { level } }has to be changed to match{ type: "textBlock", attrs: { textBlock } }.On the site,
renderTipTapRichTextrenders atextBlockby its name:heading-1toheading-6— the default text blocks — as<h1>to<h6>, anything else as<p>. A block that renames a text block or adds one needs its own handler, which reads the name:const nodeMapping: Record<string, TipTapNodeHandler> = { textBlock: ({ node, children }) => <Headline variant={node.attrs?.textBlock}>{children}</Headline>, };
-
Patch Changes
-
f9ba8df: Resolve the target language of
AzureAiTranslatorProviderviauseContentLanguageThe
targetLanguagesent to the Azure AI translation queries was read directly fromscope.language.
That only works in projects whose content scope happens to contain alanguagedimension.
It is now resolved through thecontentLanguageconfig, like the rest of the admin. -
350294d: Allow typing the DAM licence duration dates
The licence duration fields used the deprecated
FinalFormDatePickerfrom@dextinity/admin-date-time, which renders a read-only input, so a date could only be picked from the calendar. They now useDatePickerFieldfrom@dextinity/admin, which accepts keyboard input.The fields are labelled "From" and "To" instead of using untranslated
from/toplaceholders, and the calendar icon moved from the end to the start of the input.@dextinity/cms-adminno longer depends on@dextinity/admin-date-time. -
748dd34: Apply content changes from outside to the TipTap rich text block's editor
useEditoronly applies itscontentoption once, so content set from outside the editor — for instance by an agent rewriting the text — was ignored while the editor was mounted. It is now synced into the editor, keeping the caret where it was. Content the editor emitted itself is skipped, so typing is unaffected. -
f9ba8df: Resolve the language for translated page slugs via
useContentLanguageWhen translating pages in the page tree, the locale used to slugify the translated page name was read directly from
scope.language.
That only works in projects whose content scope happens to contain alanguagedimension.
The language is now resolved through thecontentLanguageconfig, consistent withcreateEditPageNode, which already slugifies usinguseContentLanguage.
@dextinity/api-generator@10.8.0
Patch Changes
-
07bf882: Add
tsconfig-pathsas a dependencydextinity-api-generatorregisterstsconfig-paths/registerat runtime, but the package only worked becausetsconfig-pathshappened to be hoisted from another package's dependencies. Declaring it directly ensures the CLI works when installed standalone.
@dextinity/brevo-api@10.8.0
Patch Changes
-
b855b96: Assign the target groups passed to
createBrevoEmailCampaignto the campaignThe
brevoTargetGroupsinput field was spread into the entity as-is, but the entity property is namedtargetGroups, so newly created campaigns ended up without any target groups. -
4a1da4e: Exclude the TypeScript build cache from the published packages
The published tarballs contained the
.tsbuildinfofiles that TypeScript writes next to the build output for incremental builds — 1.3 MB of build cache in@dextinity/cms-apialone, which has no use outside the repository. -
36f7f17: Replace the deprecated
lodash.isequaldependency with Node's built-inutillodash.isequalis deprecated and isn't needed — Node'sisDeepStrictEqualfromutilcovers the deep comparisons we use it for, with the same results for the compared values (plain JSON block data and content scopes). Both packages drop thelodash.isequaldependency.
@dextinity/cms-api@10.8.0
Minor Changes
-
383e9ed: Add a
descriptionoption tocreateBlockA block's name alone often doesn't say what the block is meant for. The description holds that information, so that consumers of the block meta, such as editor tooling and AI agents, can tell blocks apart.
Example
export const HeadlineBlock = createBlock(HeadlineBlockData, HeadlineBlockInput, { name: "Headline", description: "A headline with an optional eyebrow text above it. Use it to introduce a section.", });
The block factories that take a name, for instance
createBlocksBlockandcreateOneOfBlock, accept the description in their options as well. -
0076f28: Add vendor migrations to blocks
A block's
versionis a single counter, so only one party can advance it. That doesn't work for a block that receives migrations from the library providing it as well as from the application using it:createTipTapRichTextBlockwithmigrateFromDraftJstook version 1, so the application had to know that and start its own migrations at 2 — and the numbers collided as soon as the library added a migration of its own.The migrations that ship with a block are therefore declared in
migrateVendor, next to themigrateoption the application fills. They form a chain of their own, counting from 1 independently of the block'sversion, stored per block instance in$$vendorVersion, and they run before the block's own migrations. Both options stay separate, so neither side can overwrite the other's migrations.Example
createBlock(VideoBlockData, VideoBlockInput, { name: "Video", migrate: { version: 1, migrations: typeSafeBlockMigrationPipe([ChangeTitleMigration]), }, migrateVendor: { version: 1, migrations: typeSafeBlockMigrationPipe([ChangeAspectRatioMigration]), }, });
The migrations of the blocks Dextinity ships —
ExternalLinkBlock,YouTubeVideoBlock,DamVideoBlockand the Draft.js migration ofcreateTipTapRichTextBlock— moved into their vendor chain. Existing content doesn't need to be touched: the version it was saved with is split into the two counters when it is loaded.A block that extends the data of a block with vendor migrations inherits them, so it runs them before its own migrations without declaring them, and its own migrations start at 1.
Deploy this before content is saved with it: once a block instance has been saved with
$$vendorVersion, an older Dextinity version reads its vendor chain as unmigrated and migrates it a second time.Migrating existing
migrateFromDraftJsblocksThe Draft.js → TipTap migration is now a vendor migration, so it no longer occupies version 1 of the block. Renumber the migrations of a block that uses
migrateFromDraftJs, so they start at 1 again:export const TipTapRichTextBlock = createTipTapRichTextBlock( { link: LinkBlock, migrateFromDraftJs: true }, { name: "TipTapRichText", migrate: { - version: 2, + version: 1, migrations: typeSafeBlockMigrationPipe([Heading1ToHeading2Migration]), }, }, );Lower the
toVersionof each of those migrations by one as well. -
9109aa3: TipTap Rich Text Block: replace the
paragraphandheadingoptions withtextBlocksThe text block type select could only ever offer a fixed paragraph entry plus every enabled heading level, in a fixed order.
textBlocksconfigures the text block types explicitly, which decouples a text block from the tag it is stored as and lets several text blocks share a tag, for instance a display headline next to a regular heading 1.Example
createTipTapRichTextBlock({ textBlocks: [ { name: "paragraph", label: "Paragraph", tag: "p" }, { name: "display", label: "Display", tag: "h1" }, { name: "heading-1", label: "Heading 1", tag: "h1" }, { name: "heading-2", label: "Heading 2", tag: "h2" }, ], defaultTextBlock: "paragraph", });
The API takes the same option without the labels.
Migrating an existing configuration
-
paragraph/heading→ onetextBlocksentry per text block type (tag: "p"for the paragraph,h1-h6for the headings). It defaults to a paragraph plus a heading for every level, so only a restricted set needs to be configured. Leaving the paragraph out replacesparagraph: false. -
heading: { levels }→ thetextBlocksentries for those levels. -
heading: { defaultLevel }→defaultTextBlock, which names the text block new content starts with and defaults to the first one. -
migrateFromDraftJs'textBlockStyleMap→textBlockMap, which now takes a{ textBlock, textBlockStyle }object for every DraftJS block type:textBlocknames the text block the block becomes (instead of the tag the previoustextBlockTypenamed),textBlockStylestays optional. Where two text blocks share a heading tag,textBlockMaphas to name the one a DraftJSheader-one…header-sixbecomes, unless it is thedefaultTextBlock— the block throws otherwise, because the conversion runs once and the DraftJS content is gone afterwards.The stored format changes
Every paragraph and heading is now one
textBlocknode that names its text block, instead of aparagraph/headingnode with alevel:{ "type": "textBlock", "attrs": { "textBlock": "heading-2" }, "content": [{ "type": "text", "text": "Headline" }] }The tag lives in the configuration, so changing a text block's
tagtakes effect without a migration, while renaming or removing one invalidates the content that names it.Content stored by an earlier version holds
paragraphandheadingnodes. A vendor migration converts it when the block is loaded, so a project needs no migration of its own. It resolves the text block from the node's tag, which is unambiguous for that content: two text blocks could not share a tag beforetextBlocksexisted.A project migration sees the converted nodes, because the vendor migrations run before the block's own. A migration written against
{ type: "heading", attrs: { level } }has to be changed to match{ type: "textBlock", attrs: { textBlock } }.On the site,
renderTipTapRichTextrenders atextBlockby its name:heading-1toheading-6— the default text blocks — as<h1>to<h6>, anything else as<p>. A block that renames a text block or adds one needs its own handler, which reads the name:const nodeMapping: Record<string, TipTapNodeHandler> = { textBlock: ({ node, children }) => <Headline variant={node.attrs?.textBlock}>{children}</Headline>, };
-
Patch Changes
-
44125b9: Wait for the permission to be deleted in the
userPermissionsDeletePermissionmutationPreviously, the mutation returned
truebefore the deletion was flushed, so errors during deletion weren't reported to the client. -
8538d98: Fix DAM file URLs (both inline "open in new tab" and download links) missing the file extension, which caused browsers to save downloaded files without their extension (e.g.
.pdf).Requests for a DAM file URL whose filename segment no longer matches the file (for instance, a URL generated before this fix) now get a permanent redirect to the current canonical URL instead of being served under the stale filename.
-
5d9bf35: Send
Vary: Acceptin DAM image responsesThe image format depends on the
Acceptheader. WithoutVary, a CDN could send a cached WebP image to a client that does not support WebP. For example, images in emails could fail to load in classic Outlook for Windows.Some CDNs ignore
Varyby default. Check if your CDN needs a setting to cache each image format separately. -
4a1da4e: Exclude the TypeScript build cache from the published packages
The published tarballs contained the
.tsbuildinfofiles that TypeScript writes next to the build output for incremental builds — 1.3 MB of build cache in@dextinity/cms-apialone, which has no use outside the repository. -
dddb222: Prevent the API from crashing when the dominant color calculation or the cleanup of outdated mail logs fails
Both run in the background without being awaited. A failure previously caused an unhandled promise rejection, which terminates the process by default. The error is now logged instead.
-
8fbfbce: Replace the
hashadependency with Node's built-incryptohashaisn't needed — Node'scryptocovers everything we use it for. Hashing now usescryptodirectly, producing identical hex-encoded MD5 hashes, so existingcontentHashvalues and scaled-image cache paths remain valid. -
36f7f17: Replace the deprecated
lodash.isequaldependency with Node's built-inutillodash.isequalis deprecated and isn't needed — Node'sisDeepStrictEqualfromutilcovers the deep comparisons we use it for, with the same results for the compared values (plain JSON block data and content scopes). Both packages drop thelodash.isequaldependency.
@dextinity/mail-react@10.8.0
Minor Changes
-
a3a66a7: Add
id="body"to the<body>tag of every rendered mailSome email clients replace the
<body>tag with a<div>, so abodyselector no longer reaches it. The id does.
@dextinity/site-react@10.8.0
Minor Changes
-
9109aa3: TipTap Rich Text Block: replace the
paragraphandheadingoptions withtextBlocksThe text block type select could only ever offer a fixed paragraph entry plus every enabled heading level, in a fixed order.
textBlocksconfigures the text block types explicitly, which decouples a text block from the tag it is stored as and lets several text blocks share a tag, for instance a display headline next to a regular heading 1.Example
createTipTapRichTextBlock({ textBlocks: [ { name: "paragraph", label: "Paragraph", tag: "p" }, { name: "display", label: "Display", tag: "h1" }, { name: "heading-1", label: "Heading 1", tag: "h1" }, { name: "heading-2", label: "Heading 2", tag: "h2" }, ], defaultTextBlock: "paragraph", });
The API takes the same option without the labels.
Migrating an existing configuration
-
paragraph/heading→ onetextBlocksentry per text block type (tag: "p"for the paragraph,h1-h6for the headings). It defaults to a paragraph plus a heading for every level, so only a restricted set needs to be configured. Leaving the paragraph out replacesparagraph: false. -
heading: { levels }→ thetextBlocksentries for those levels. -
heading: { defaultLevel }→defaultTextBlock, which names the text block new content starts with and defaults to the first one. -
migrateFromDraftJs'textBlockStyleMap→textBlockMap, which now takes a{ textBlock, textBlockStyle }object for every DraftJS block type:textBlocknames the text block the block becomes (instead of the tag the previoustextBlockTypenamed),textBlockStylestays optional. Where two text blocks share a heading tag,textBlockMaphas to name the one a DraftJSheader-one…header-sixbecomes, unless it is thedefaultTextBlock— the block throws otherwise, because the conversion runs once and the DraftJS content is gone afterwards.The stored format changes
Every paragraph and heading is now one
textBlocknode that names its text block, instead of aparagraph/headingnode with alevel:{ "type": "textBlock", "attrs": { "textBlock": "heading-2" }, "content": [{ "type": "text", "text": "Headline" }] }The tag lives in the configuration, so changing a text block's
tagtakes effect without a migration, while renaming or removing one invalidates the content that names it.Content stored by an earlier version holds
paragraphandheadingnodes. A vendor migration converts it when the block is loaded, so a project needs no migration of its own. It resolves the text block from the node's tag, which is unambiguous for that content: two text blocks could not share a tag beforetextBlocksexisted.A project migration sees the converted nodes, because the vendor migrations run before the block's own. A migration written against
{ type: "heading", attrs: { level } }has to be changed to match{ type: "textBlock", attrs: { textBlock } }.On the site,
renderTipTapRichTextrenders atextBlockby its name:heading-1toheading-6— the default text blocks — as<h1>to<h6>, anything else as<p>. A block that renames a text block or adds one needs its own handler, which reads the name:const nodeMapping: Record<string, TipTapNodeHandler> = { textBlock: ({ node, children }) => <Headline variant={node.attrs?.textBlock}>{children}</Headline>, };
-
Patch Changes
- ccffc10: Replace the deprecated
lodash.isequaldependency withfast-equals