Skip to content

10.8.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 13:38
· 274 commits to main since this release
a9c84dd

@dextinity/brevo-admin@10.8.0

Patch Changes

  • 8c41f26: Allow typing the email campaign schedule date

    The schedule field used the deprecated FinalFormDateTimePicker from @dextinity/admin-date-time, which renders a read-only input, so the date and time could only be picked from the calendar. It now uses DateTimePickerField from @dextinity/admin, which accepts keyboard input.

    @dextinity/brevo-admin no longer depends on @dextinity/admin-date-time.

@dextinity/cms-admin@10.8.0

Minor Changes

  • 9109aa3: TipTap Rich Text Block: replace the paragraph and heading options with textBlocks

    The text block type select could only ever offer a fixed paragraph entry plus every enabled heading level, in a fixed order. textBlocks configures 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 → one textBlocks entry per text block type (tag: "p" for the paragraph, h1-h6 for 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 replaces paragraph: false.

    • heading: { levels } → the textBlocks entries 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: textBlock names the text block the block becomes (instead of the tag the previous textBlockType named), textBlockStyle stays optional. Where two text blocks share a heading tag, textBlockMap has to name the one a DraftJS header-one…header-six becomes, unless it is the defaultTextBlock — 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 textBlock node that names its text block, instead of a paragraph/heading node with a level:

      { "type": "textBlock", "attrs": { "textBlock": "heading-2" }, "content": [{ "type": "text", "text": "Headline" }] }

      The tag lives in the configuration, so changing a text block's tag takes effect without a migration, while renaming or removing one invalidates the content that names it.

      Content stored by an earlier version holds paragraph and heading nodes. 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 before textBlocks existed.

      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, renderTipTapRichText renders a textBlock by its name: heading-1 to heading-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 AzureAiTranslatorProvider via useContentLanguage

    The targetLanguage sent to the Azure AI translation queries was read directly from scope.language.
    That only works in projects whose content scope happens to contain a language dimension.
    It is now resolved through the contentLanguage config, like the rest of the admin.

  • 350294d: Allow typing the DAM licence duration dates

    The licence duration fields used the deprecated FinalFormDatePicker from @dextinity/admin-date-time, which renders a read-only input, so a date could only be picked from the calendar. They now use DatePickerField from @dextinity/admin, which accepts keyboard input.

    The fields are labelled "From" and "To" instead of using untranslated from/to placeholders, and the calendar icon moved from the end to the start of the input.

    @dextinity/cms-admin no longer depends on @dextinity/admin-date-time.

  • 748dd34: Apply content changes from outside to the TipTap rich text block's editor

    useEditor only applies its content option 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 useContentLanguage

    When 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 a language dimension.
    The language is now resolved through the contentLanguage config, consistent with createEditPageNode, which already slugifies using useContentLanguage.

@dextinity/api-generator@10.8.0

Patch Changes

  • 07bf882: Add tsconfig-paths as a dependency

    dextinity-api-generator registers tsconfig-paths/register at runtime, but the package only worked because tsconfig-paths happened 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 createBrevoEmailCampaign to the campaign

    The brevoTargetGroups input field was spread into the entity as-is, but the entity property is named targetGroups, 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 .tsbuildinfo files that TypeScript writes next to the build output for incremental builds — 1.3 MB of build cache in @dextinity/cms-api alone, which has no use outside the repository.

  • 36f7f17: Replace the deprecated lodash.isequal dependency with Node's built-in util

    lodash.isequal is deprecated and isn't needed — Node's isDeepStrictEqual from util covers 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 the lodash.isequal dependency.

@dextinity/cms-api@10.8.0

Minor Changes

  • 383e9ed: Add a description option to createBlock

    A 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 createBlocksBlock and createOneOfBlock, accept the description in their options as well.

  • 0076f28: Add vendor migrations to blocks

    A block's version is 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: createTipTapRichTextBlock with migrateFromDraftJs took 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 the migrate option the application fills. They form a chain of their own, counting from 1 independently of the block's version, 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, DamVideoBlock and the Draft.js migration of createTipTapRichTextBlock — 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 migrateFromDraftJs blocks

    The 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 toVersion of each of those migrations by one as well.

  • 9109aa3: TipTap Rich Text Block: replace the paragraph and heading options with textBlocks

    The text block type select could only ever offer a fixed paragraph entry plus every enabled heading level, in a fixed order. textBlocks configures 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 → one textBlocks entry per text block type (tag: "p" for the paragraph, h1-h6 for 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 replaces paragraph: false.

    • heading: { levels } → the textBlocks entries 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: textBlock names the text block the block becomes (instead of the tag the previous textBlockType named), textBlockStyle stays optional. Where two text blocks share a heading tag, textBlockMap has to name the one a DraftJS header-one…header-six becomes, unless it is the defaultTextBlock — 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 textBlock node that names its text block, instead of a paragraph/heading node with a level:

      { "type": "textBlock", "attrs": { "textBlock": "heading-2" }, "content": [{ "type": "text", "text": "Headline" }] }

      The tag lives in the configuration, so changing a text block's tag takes effect without a migration, while renaming or removing one invalidates the content that names it.

      Content stored by an earlier version holds paragraph and heading nodes. 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 before textBlocks existed.

      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, renderTipTapRichText renders a textBlock by its name: heading-1 to heading-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 userPermissionsDeletePermission mutation

    Previously, the mutation returned true before 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: Accept in DAM image responses

    The image format depends on the Accept header. Without Vary, 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 Vary by 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 .tsbuildinfo files that TypeScript writes next to the build output for incremental builds — 1.3 MB of build cache in @dextinity/cms-api alone, 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 hasha dependency with Node's built-in crypto

    hasha isn't needed — Node's crypto covers everything we use it for. Hashing now uses crypto directly, producing identical hex-encoded MD5 hashes, so existing contentHash values and scaled-image cache paths remain valid.

  • 36f7f17: Replace the deprecated lodash.isequal dependency with Node's built-in util

    lodash.isequal is deprecated and isn't needed — Node's isDeepStrictEqual from util covers 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 the lodash.isequal dependency.

@dextinity/mail-react@10.8.0

Minor Changes

  • a3a66a7: Add id="body" to the <body> tag of every rendered mail

    Some email clients replace the <body> tag with a <div>, so a body selector no longer reaches it. The id does.

@dextinity/site-react@10.8.0

Minor Changes

  • 9109aa3: TipTap Rich Text Block: replace the paragraph and heading options with textBlocks

    The text block type select could only ever offer a fixed paragraph entry plus every enabled heading level, in a fixed order. textBlocks configures 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 → one textBlocks entry per text block type (tag: "p" for the paragraph, h1-h6 for 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 replaces paragraph: false.

    • heading: { levels } → the textBlocks entries 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: textBlock names the text block the block becomes (instead of the tag the previous textBlockType named), textBlockStyle stays optional. Where two text blocks share a heading tag, textBlockMap has to name the one a DraftJS header-one…header-six becomes, unless it is the defaultTextBlock — 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 textBlock node that names its text block, instead of a paragraph/heading node with a level:

      { "type": "textBlock", "attrs": { "textBlock": "heading-2" }, "content": [{ "type": "text", "text": "Headline" }] }

      The tag lives in the configuration, so changing a text block's tag takes effect without a migration, while renaming or removing one invalidates the content that names it.

      Content stored by an earlier version holds paragraph and heading nodes. 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 before textBlocks existed.

      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, renderTipTapRichText renders a textBlock by its name: heading-1 to heading-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.isequal dependency with fast-equals