Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
876887b
cms-admin: Fix order of link and special character buttons in TipTap …
VPS-Andreas Aug 27, 2026
4433140
Mark v10 and later as supported in the security policy (#6257)
VPS-thodax Aug 31, 2026
66cb98a
DAM: Allow replacing files with same category instead of same mimetyp…
VPS-julia Aug 31, 2026
697f72a
Update Brevo setup guide to v10 (#6143)
VPS-julia Aug 31, 2026
6c15f3e
Enable TypeScript strict mode in the packages that need no fixes (#6255)
VPS-thodax Aug 31, 2026
2fb03f8
eslint-plugin: Enable TypeScript strict mode (#6250)
VPS-thodax Aug 31, 2026
43c6a3c
storybook: Enable TypeScript strict mode (#6261)
VPS-thodax Aug 31, 2026
594c512
docs: Add DamVideoBlock factory documentation (#6251)
VPS-julia Aug 31, 2026
8922319
Recommend upgrading the ESLint config before the v9 migration (#6252)
VPS-thodax Aug 31, 2026
c61435a
Update dependency next to v16.2.11 [SECURITY] (#6267)
renovate[bot] Aug 31, 2026
4d6408f
Fix `FormMutation` throwing when rendered (#6253)
VPS-thodax Aug 31, 2026
c2f55d8
mail-react: Keep columns side by side without the `<style>` block (#6…
VPS-Ricky Aug 31, 2026
1e24462
Show the mail addon UI in a composed Storybook (#6258)
VPS-Ricky Aug 31, 2026
12be273
cms-admin: Build the hovered block's route from the sub-route prefix …
SebiVPS Sep 1, 2026
ddea65d
Remove the Permissions and Scopes columns from the user permissions u…
fraxachun Sep 1, 2026
b873623
mail-react: Minify the registered styles (#6236)
VPS-Ricky Sep 1, 2026
0c9109e
mail-react: Stop relying on CSS that Gmail drops (#6283)
VPS-Ricky Sep 1, 2026
e17c855
Reduce Renovate memory usage to prevent OOM-killed jobs (#6286)
VPS-Obi Sep 1, 2026
3ffe174
cms-api: Validate UUID format of AffectedEntity id args before loadin…
VP-DS Sep 1, 2026
f3f00f2
mail-react: Fix crash on a rounded image block with no alt text or li…
VPS-Ricky Sep 1, 2026
0c211e9
Stop deduplicating content scopes in the user permissions API (#6112)
fraxachun Sep 1, 2026
504c97f
cms-admin: Expose setPageState in the usePage hook (#6284)
VPS-Obi Sep 1, 2026
93e6f7e
Version Packages (#6245)
github-actions[bot] Sep 1, 2026
a00f0b2
Support wildcard values for content scope dimensions (#6114)
fraxachun Sep 2, 2026
bf769da
Explain how to round corners in classic Outlook (#6298)
VPS-Ricky Sep 2, 2026
583dd13
mail-react: Fix the corner radius of rounded images in classic Outloo…
VPS-Ricky Sep 2, 2026
bb5d22a
mail-react: Give the VML shape the public URL in Storybook previews (…
VPS-Ricky Sep 3, 2026
348049a
Configure CodeRabbit in code (#6300)
VPS-Obi Sep 3, 2026
d60e328
Add pre-merge checks and request changes workflow to CodeRabbit (#6301)
nsams Sep 3, 2026
0e88bab
site-react: Fix block preview outlines not ending at the block's righ…
SebiVPS Sep 3, 2026
4b9ead5
cms-admin: Move custom TipTap inline styles into the toolbar's more-o…
VPS-Andreas Sep 3, 2026
47bc1f1
Make "Requires human review" a warning, so coderabbit doesn't (hopefu…
nsams Sep 3, 2026
3ae7d61
Revert "Make "Requires human review" a warning, so coderabbit doesn't…
nsams Sep 3, 2026
b6cbbd9
cms-admin: Move icons to the start of the TipTap "More options" menu …
VPS-Andreas Sep 3, 2026
fc55084
Add .playwright-cli/ to gitignore (#6306)
nsams Sep 3, 2026
16f2510
Disable CodeRabbit issue assessment features (#6308)
VPS-Obi Sep 3, 2026
85a5023
Update Coderabbit Required human review instruction (#6310)
nsams Sep 3, 2026
0dba1b0
Document two CSS limits of Yahoo Mail (#6309)
VPS-Ricky Sep 3, 2026
98c1097
mail-react: Fix head CSS lost after the first media block in Outlook …
VPS-Ricky Sep 4, 2026
234469b
Version Packages (#6291)
github-actions[bot] Sep 4, 2026
f4d091f
Allow declaring content scope dimensions at runtime (#6115)
fraxachun Sep 4, 2026
72d0ba7
Replace axios with native fetch API for campaign content requests (#6…
nsams Sep 4, 2026
02bba49
cms-api: Validate PageTreeNode scope DTO name in PageTreeModule (#6173)
VPS-Obi Sep 7, 2026
9284971
Fix package name in changeset (#6321)
VPS-Obi Sep 7, 2026
0be2f59
Replace the TipTap Rich Text Block's `supports` array with one option…
VPS-Obi Sep 7, 2026
82dfa79
mail-react: Fix the size of rounded images in classic Outlook (#6323)
VPS-Ricky Sep 7, 2026
5540bdb
Clarify which changes need docs and skill updates (#6327)
VPS-Ricky Sep 7, 2026
675dd62
Merge main into next
invalid-email-address Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/brevo-api-replace-axios-with-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@dextinity/brevo-api": patch
---

Replace `axios` with the native `fetch` API when requesting the email campaign content from the frontend

This removes the `@nestjs/axios` and `axios` dependencies from the package.
22 changes: 22 additions & 0 deletions .changeset/content-scope-dimensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@dextinity/cms-api": minor
---

Allow declaring content scope dimensions at runtime

Add an optional `availableContentScopeDimensions` option to the `UserPermissionsModule` to declare the content scope dimensions (with optional labels). When omitted, the dimensions are derived from the keys of `availableContentScopes`.

A content scope may hold any value (including the `"*"` wildcard) for a dimension that is not part of `availableContentScopes`. Content scopes are no longer validated against `availableContentScopes`; access is enforced per request by `isAllowed`, which compares the requested scope against the user's granted scopes.

**Example**

```ts
UserPermissionsModule.forRootAsync({
useFactory: () => ({
availableContentScopes: [ ... ],
availableContentScopeDimensions: [{ name: "domain", label: "Domain (Website)" }, { name: "language" }, { name: "product" }],
// ...
}),
// ...
});
```
18 changes: 0 additions & 18 deletions .changeset/generate-block-types-tiptap-node-type.md

This file was deleted.

7 changes: 7 additions & 0 deletions .changeset/outlook-vml-shape-reserved-pixel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@dextinity/mail-react": patch
---

Fix rounded images rendering a pixel too large in classic Outlook

Two images side by side made their section wider than the body width. Affects `MjmlImage`, `HtmlImage`, `MjmlPixelImageBlock` and `HtmlPixelImageBlock`.
29 changes: 29 additions & 0 deletions .changeset/tiptap-feature-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@dextinity/cms-admin": minor
"@dextinity/cms-api": minor
---

Replace the TipTap Rich Text Block's `supports` array with one option per feature

`createTipTapRichTextBlock` now takes a single root options object with one option per editor feature, similar to TipTap's `StarterKit` configuration. Feature-specific options move into a nested options object of the feature they belong to, so `headingLevels` becomes `heading: { levels: [...] }`.

Every feature is enabled by default (except `underline`) and is disabled by passing `false`, so a configuration only has to state what deviates from the defaults instead of repeating every supported feature. Links stay the exception: they are enabled by passing the link block as `link`.

**Example**

```ts
// Before
createTipTapRichTextBlock({
supports: ["bold", "italic", "strike", "sub", "sup", "heading", "ordered-list", "unordered-list"],
headingLevels: [2, 3],
});

// After
createTipTapRichTextBlock({
nonBreakingSpace: false,
softHyphen: false,
heading: { levels: [2, 3] },
});
```

The features are named after their option: `bold`, `italic`, `underline`, `strike`, `sub`, `sup`, `heading`, `orderedList`, `unorderedList`, `nonBreakingSpace`, `softHyphen` and `link`. Additionally, `undoRedoButtons` (Admin only) shows or hides the undo/redo buttons in the toolbar; the keyboard shortcuts work regardless. The document-level limits `maxTextBlocks` and `listLevelMax` are unchanged.
7 changes: 7 additions & 0 deletions .changeset/validate-page-tree-node-scope-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@dextinity/cms-api": patch
---

Validate the GraphQL type names of a custom `PageTreeNode` scope passed to `PageTreeModule.forRoot()`

`PageTreeModule.forRoot()` now throws an error at startup if the provided `Scope` class isn't decorated with `@ObjectType("PageTreeNodeScope")` and `@InputType("PageTreeNodeScopeInput")`, mirroring the existing validation for `DamModule`'s `Scope` option. This prevents runtime GraphQL schema errors caused by an accidentally renamed scope type.
33 changes: 33 additions & 0 deletions .coderabbit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
reviews:
auto_review:
enabled: false
high_level_summary_in_walkthrough: true
request_changes_workflow: true
# We don't use GitHub issues and CodeRabbit has no access to Jira, so there is never a linked issue to assess or relate.
assess_linked_issues: false
related_issues: false
pre_merge_checks:
override_requested_reviewers_only: true # author can't dismiss failing checks
issue_assessment:
mode: "off"
custom_checks:
- name: "Requires human review"
mode: "error"
instructions: |
FAIL if the PR touches one of the following:
- authentication
- authorization
- DB migrations
- CI workflow files
- secret or credential handling
- changes public API
- deletes tests
- coderabbit config

FAIL if the PR adds or modifies more than 300 lines of hand-written source.
When counting those lines, ignore deleted lines, and ignore changes to
tests (*.spec.*, *.test.*, __snapshots__/), docs (docs/, *.md,
.changeset/) and generated output (**/generated/, schema.gql,
block-meta.json, pnpm-lock.yaml).
Otherwise PASS.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ junit-storybook.xml
.context
.playwright
.playwright-mcp
.playwright-cli/

# Agent files
.agents/skills/
Expand Down
8 changes: 6 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
The versions of the project that are currently supported with security updates.

| Version | Supported |
|--------:|:--------------------------------------|
| 9.x | :white_check_mark: |
| ------: | :------------------------------------ |
| ≥ 10.x | :white_check_mark: |
| 9.x | :x: (use 10.x instead, see note) |
| 8.x | :white_check_mark: (until 2027-07-07) |
| < 8.x | :x: |

> [!NOTE]
> Version 9.x is not supported. Use 10.x instead, which is largely compatible with 9.x: every breaking change in 10.x is part of the COMET -> Dextinity rename. See the [migration guide from v9 to v10](https://cms-docs.dextinity.com/docs/migration-guide/migration-from-v9-to-v10).

## Reporting a vulnerability

You can report a vulnerability by contacting us via email at [security@vivid-planet.com](mailto:security@vivid-planet.com).
3 changes: 3 additions & 0 deletions demo/admin/src/common/blocks/TipTapRichTextBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RteHighlight, Tag } from "@dextinity/admin-icons";
import { createTipTapRichTextBlock } from "@dextinity/cms-admin";
import { ProductPriceBlock } from "@src/products/blocks/ProductPriceBlock";
import { ProductTeaserBlock } from "@src/products/blocks/ProductTeaserBlock";
Expand Down Expand Up @@ -66,12 +67,14 @@ export const TipTapRichTextBlock = createTipTapRichTextBlock({
{
name: "highlight",
label: <FormattedMessage id="tipTapRichTextBlock.inlineStyle.highlight" defaultMessage="Highlight" />,
icon: RteHighlight,
element: (props: HTMLAttributes<HTMLElement>) => <span style={{ backgroundColor: "#fff3cd", padding: "0 2px" }} {...props} />,
},
{
name: "tag",
label: <FormattedMessage id="tipTapRichTextBlock.inlineStyle.tag" defaultMessage="Tag" />,
appliesTo: ["paragraph"],
icon: Tag,
element: (props: HTMLAttributes<HTMLElement>) => (
<span style={{ backgroundColor: "#e0f0ff", color: "#0066cc", padding: "0 4px", borderRadius: 4 }} {...props} />
),
Expand Down
4 changes: 2 additions & 2 deletions demo/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -2310,12 +2310,12 @@ enum UserPermissionSource {
}

type UserPermissionsUser {
contentScopesCount: Int!
contentScopesCount: Int! @deprecated(reason: "The Scopes column was removed from the users list. Will be removed in the next version.")
email: String!
id: String!
impersonationAllowed: Boolean!
name: String!
permissionsCount: Int!
permissionsCount: Int! @deprecated(reason: "The Permissions column was removed from the users list. Will be removed in the next version.")
}

input UserPermissionsUserFilter {
Expand Down
6 changes: 6 additions & 0 deletions demo/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ export class AppModule {
label: { domain: siteConfig.name },
})),
),
availableContentScopeDimensions: [
{ name: "domain", label: "Domain (Website)" },
{ name: "language", label: "Language" },
// "product" is declared here so it shows up in the admin panel although it is not part of availableContentScopes
{ name: "product", label: "Product Category" },
],
userService,
accessControlService,
systemUsers: [SYSTEM_USER_NAME],
Expand Down
4 changes: 2 additions & 2 deletions demo/api/src/auth/access-control.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe("AccessControlService", () => {

const contentScopes = service.getContentScopesForUser(nonAdminUser);

expect(contentScopes).toEqual([{ domain: "main", language: "en" }]);
expect(contentScopes).toEqual([{ domain: "main", language: "*", product: "*" }]);
});

it("should return limited content scopes for unknown non-admin user", () => {
Expand All @@ -74,7 +74,7 @@ describe("AccessControlService", () => {

const contentScopes = service.getContentScopesForUser(unknownUser);

expect(contentScopes).toEqual([{ domain: "main", language: "en" }]);
expect(contentScopes).toEqual([{ domain: "main", language: "*", product: "*" }]);
});
});
});
3 changes: 2 additions & 1 deletion demo/api/src/auth/access-control.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export class AccessControlService extends AbstractAccessControlService {
if (user.isAdmin) {
return UserPermissions.allContentScopes;
} else {
return [{ domain: "main", language: "en" }];
// Grant access to every language and product within the "main" domain using wildcard dimensions
return [{ domain: "main", language: "*", product: "*" }];
}
}
}
6 changes: 4 additions & 2 deletions demo/api/src/content-scope/content-scope.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ContentScope } from "@dextinity/cms-api";
import type { ContentScope as BaseContentScope } from "@src/site-configs";

declare module "@dextinity/cms-api" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface ContentScope extends BaseContentScope {}
interface ContentScope extends BaseContentScope {
// Optional dimension used only by certain resolvers. Not part of `availableContentScopes` as there can be thousands of product ids.
product?: string;
}
}
5 changes: 2 additions & 3 deletions demo/api/src/page-tree/dto/page-tree-node-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { Field, InputType, ObjectType } from "@nestjs/graphql";
import { IsString } from "class-validator";

@Embeddable()
@ObjectType("PageTreeNodeScope") // name must not be changed in the app
@InputType("PageTreeNodeScopeInput") // name must not be changed in the app
// @TODO: disguise @ObjectType("PageTreeContentScope") and @InputType("PageTreeContentScopeInput") decorators under a custom decorator: f.i. @PageTreeNodeScope
@ObjectType("PageTreeNodeScope")
@InputType("PageTreeNodeScopeInput")
export class PageTreeNodeScope {
@Property({ columnType: "text" })
@Field()
Expand Down
4 changes: 2 additions & 2 deletions dev-pm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export default defineConfig({
{
name: "mail-react-storybook",
script: "pnpm --filter @dextinity/mail-react run storybook",
group: ["mail-react"],
group: ["mail-react", "storybook", "docs"],
},

//group brevo
Expand Down Expand Up @@ -270,7 +270,7 @@ export default defineConfig({
name: "storybook",
script: "pnpm --filter dextinity-storybook run storybook",
group: ["storybook", "docs"],
waitOn: ["tcp:26646", "tcp:26647"], // storybook-dextinity-admin, storybook-dextinity-cms-admin
waitOn: ["tcp:26646", "tcp:26647", "tcp:6066"], // storybook-dextinity-admin, storybook-dextinity-cms-admin, mail-react-storybook
},
{
name: "docs",
Expand Down
25 changes: 25 additions & 0 deletions docs/docs/2-core-concepts/2-blocks/4-factories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,31 @@ export const FullWidthImageBlock = createCompositeBlock({
});
```

## DamVideoBlock (Admin only)

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:

- `"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:

```tsx title="DamVideoBlock.tsx"
import { createDamVideoBlock } from "@dextinity/cms-admin";

// For a site that renders no poster image
export const DamVideoBlock = createDamVideoBlock({ supports: ["controls"] });
```

:::note

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.
:::

## FinalFormBlock (Admin only)

The FinalFormBlock factory can be used to convert a block's `AdminComponent` API to the Final Form `Field` API.
Expand Down
Loading
Loading