Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions packages/pluggableWidgets/rich-text-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- We added new configuration to allow users to use class names instead of inline styling in generated HTML to support strict CSP.

### Fixed

- We fixed an issue where the editor pasting back the whole sentence instead of the single copied word

### Changed

- We removed codemirror from code dialog viewer due to unsupported strict CSP policy. A simple internally built code editor using highlightjs is now replacing it.

## [4.12.0] - 2026-04-22

### Added
Expand Down
32 changes: 32 additions & 0 deletions packages/pluggableWidgets/rich-text-web/e2e/RichText.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from "@mendix/run-e2e/fixtures";
import { waitForMendixApp } from "@mendix/run-e2e/mendix-helpers";

test.describe("RichText", () => {
test.describe.configure({ mode: "serial" });
test("compares with a screenshot baseline and checks if inline basic mode are rendered as expected", async ({
page
}) => {
Expand Down Expand Up @@ -115,6 +116,37 @@
await expect(page.locator(".mx-name-richText6")).toHaveScreenshot(`readOnlyModeReadPanel.png`);
});

test("compares with a screenshot baseline and checks if class mode editor is rendered as expected", async ({
page
}) => {
await page.goto("/p/classmode");
await page.waitForLoadState("networkidle");
await expect(page.locator(".mx-name-richText1")).toBeVisible();
await expect(page.locator(".mx-name-richText1")).toHaveScreenshot(`classModeEditor.png`, { threshold: 0.4 });
});

test("checks that class mode editor output uses CSS classes instead of inline styles", async ({ page }) => {
await page.goto("/p/classmode");
await page.waitForLoadState("networkidle");
const html = await page.locator(".mx-name-richText1 .ql-editor").innerHTML();
expect(html).toMatch(/class="ql-color-/);
expect(html).toMatch(/class="ql-bg-/);
expect(html).toMatch(/class="ql-indent-/);
expect(html).toMatch(/data-style-format="class"/);
expect(html).not.toMatch(/style="color:/);
expect(html).not.toMatch(/style="background-color:/);
expect(html).not.toMatch(/style="padding-left:/);
});

test("compares with a screenshot baseline of the View/Edit Code dialog in class mode", async ({ page }) => {
await page.goto("/p/classmode");
await page.waitForLoadState("networkidle");
await page.click(".mx-name-richText1 .ql-toolbar button.ql-view-code");
await expect(page.locator(".widget-rich-text .widget-rich-text-modal-body").first()).toHaveScreenshot(
`classModeViewCodeDialog.png`
);
});

test("compares with a screenshot for rich text inside modal popup layout", async ({ page }) => {
await page.goto("/");
await waitForMendixApp(page);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-05
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
## Context

The rich text editor uses TipTap, which already supports column widths via the `colwidth` attribute on table cells. TipTap's base `TableCell` extension includes this attribute, and the `TableBackgroundColor` extension reads it to generate `<colgroup>` elements for column sizing.

Currently:

- `colwidth` is an array of pixel values (e.g., `[150]` for single column, `[100, 150, 200]` for colspan)
- TipTap has a `resizable: true` option that should enable drag-to-resize, but the custom `TableBackgroundColorNodeView` doesn't implement resize handles
- Users can click column borders, which sets a width, but cannot drag to adjust
- No UI control exists to set exact pixel widths

The existing cell configuration dropdown already has sections for background color, border color/style/width using a `ConfigurationSection` pattern.

## Goals / Non-Goals

**Goals:**

- Add a number input control to the cell configuration dropdown for setting column width
- Support exact pixel values (25-1000px range)
- Provide clear button to reset to auto width
- Handle colspan cells by setting only the first column width
- Follow existing configuration dropdown patterns and styling
- Validate input and clamp to acceptable ranges

**Non-Goals:**

- Implementing drag-to-resize handles (would require significant NodeView work)
- Supporting percentage or other CSS units (only pixels for now)
- Setting widths for all columns in a colspan cell (only first column)
- Syncing widths across all rows (only first row widths apply per TipTap's design)
- Adding column width presets dropdown (keep it simple with number input only)

## Decisions

### Decision 1: Use number input type (not dropdown with presets)

**Rationale:** User requested "exact pixel values" in requirements. A number input provides precision without limiting users to preset sizes.

**Alternatives considered:**

- Dropdown with presets (Small/Medium/Large): Too restrictive, doesn't meet "exact values" requirement
- Hybrid (input + preset buttons): Over-engineered for initial implementation
- Text input: Number input provides better UX with built-in increment/decrement and validation

**Implementation:** Add new `"numberInput"` type to `ConfigurationSection` interface alongside existing `"colorPicker"` and `"dropdown"` types.

---

### Decision 2: Set only first column width for colspan cells

**Rationale:**

- User explicitly requested "set only first column" in requirements
- Simplifies implementation and UX (no need for multi-column width editor)
- Matches TipTap's array-based `colwidth` structure where `colwidth[0]` is the first column

**Alternatives considered:**

- Set all spanned columns to same width: Could surprise users if they had different widths set
- Show per-column width array editor: Complex UI for edge case (most cells don't have colspan)
- Disable control for colspan cells: Too restrictive

**Implementation:** `onChange` handler creates `colwidth` array with single value: `[width]`

---

### Decision 3: Use setCellAttribute command (not custom command)

**Rationale:**

- `setCellAttribute("colwidth", value)` should work because `colwidth` is in TipTap's base TableCell
- Other cell properties (backgroundColor, borderColor, etc.) use `setCellAttribute` successfully
- No need to define custom commands in extensions

**Alternatives considered:**

- Custom `setCellWidth` command in `TableCellBackgroundColor`: Unnecessary abstraction
- Modify `TableCellBackgroundColor.addAttributes()` to re-declare colwidth: Already inherits via `...this.parent?.()`

**Implementation:** Direct call to `editor.chain().focus().setCellAttribute("colwidth", [width]).run()`

---

### Decision 4: Render clear button conditionally (when value exists)

**Rationale:**

- Provides explicit way to reset to auto width (null)
- Visual indicator that a custom width is set
- Follows common UI pattern (seen in search inputs, etc.)

**Alternatives considered:**

- Always show clear button: Clutters UI when empty
- Use empty string to mean auto: Less explicit, could be confusing
- Add separate "Auto" button: Redundant with clear functionality

**Implementation:** Render `×` button only when `currentValue !== null`

---

### Decision 5: Store null for auto width (not empty array or zero)

**Rationale:**

- TipTap treats `colwidth: null` as auto-sizing behavior
- Consistent with how TipTap's base extensions work
- `colwidth: []` or `colwidth: [0]` could cause layout issues

**Implementation:** When user clears input, call `setCellAttribute("colwidth", null)`

## Risks / Trade-offs

### Risk: Parent attribute inheritance might not work

If `TableCellBackgroundColor` doesn't properly inherit `colwidth` from base `TableCell`, `setCellAttribute` calls will fail silently.

**Mitigation:** The extension already uses `...this.parent?.()` in `addAttributes()`, which should preserve `colwidth`. If issues arise, explicitly re-declare `colwidth` in the extension's attributes.

---

### Risk: First-row-only behavior might confuse users

TipTap only uses `colwidth` from the first row of the table to generate `<colgroup>`. Setting widths on other rows has no effect.

**Mitigation:** Could add tooltip or helper text: "Note: Only first row column widths apply." For initial implementation, leave as-is since it matches TipTap's inherent behavior.

---

### Risk: Custom NodeView might interfere with colwidth updates

The `TableBackgroundColorNodeView` manually generates `<colgroup>` in `updateColgroup()`. If it's not reactive to `colwidth` changes, widths might not update visually.

**Mitigation:** The NodeView's `update()` method already calls `updateColgroup()` on node changes, so it should react to `colwidth` updates. Test thoroughly during implementation.

---

### Trade-off: No drag-to-resize (only manual input)

Users lose the convenience of dragging column borders to resize.

**Mitigation:** The number input provides precision that drag handles lack. If drag-to-resize is needed later, it would require integrating ProseMirror's `columnResizing` plugin into the custom NodeView (~200 lines of code). For now, manual input meets the stated requirements.

---

### Trade-off: Pixel-only units (no percentages)

Users cannot set responsive column widths using percentages.

**Mitigation:** Percentage support would require custom rendering logic (TipTap's `colwidth` is pixel-only). Tables in rich text editors typically use fixed layouts. Can be added later if users request it.

## Migration Plan

No migration needed. This is a purely additive feature:

- Existing tables without `colwidth` continue to work (auto width)
- Existing tables with `colwidth` set (via TipTap's base functionality) display correctly
- No data model changes or breaking API changes

Deployment:

1. Merge code changes
2. Run build
3. Deploy widget to Mendix project
4. Feature is immediately available in cell configuration dropdown

Rollback:

- If issues arise, remove the new configuration section from `createCellConfigurationSections()`
- No data corruption risk (colwidth attribute is standard TipTap)

## Open Questions

1. **Should we show the actual rendered column width vs. the set value?**
- The set `colwidth` might differ from actual rendered width due to table layout constraints
- For initial implementation: Show the set value only (simpler)
- Can add "measured width" tooltip later if needed

2. **Should we prevent setting widths on non-first-row cells?**
- Pro: Avoids confusion about why widths don't apply
- Con: Restricts user control, might not match mental model
- Decision: Allow setting on any cell (simpler), rely on TipTap's first-row behavior

3. **Should we add keyboard shortcuts for common widths?**
- Out of scope for initial implementation
- Can be added later if users request it
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Why

Users need precise control over table column widths in the rich text editor. Currently, while TipTap supports column widths via the `colwidth` attribute, there's no UI control to set them. Users can only click column borders which sets widths without drag-to-resize capability, making it difficult to achieve desired layouts.

## What Changes

- Add a "Column Width" number input control to the cell configuration dropdown
- Support setting exact pixel widths (25-1000px range)
- Provide "Clear" button to reset to auto width
- Handle colspan cells by setting only the first column width
- Integrate with existing `setCellAttribute` command for `colwidth` attribute

## Capabilities

### New Capabilities

- `table-column-width-control`: UI control in cell configuration dropdown allowing users to set precise column widths in pixels, with validation, auto width support, and colspan handling

### Modified Capabilities

<!-- No existing spec requirements are changing - this is a new UI feature using existing TipTap colwidth infrastructure -->

## Impact

**Affected Files:**

- `src/components/toolbars/components/ConfigurationDropdown.tsx` - Add number input type support
- `src/components/toolbars/components/ConfigurationDropdown.scss` - Add number input styling
- `src/components/toolbars/ToolbarConfig.ts` - Update ConfigurationSection type definition
- `src/components/toolbars/helpers/configurationHelpers.ts` - Add column width section

**User Impact:**

- Positive: Users gain precise control over table column widths via familiar number input UI
- No breaking changes: Feature is additive, existing tables continue to work

**Technical Impact:**

- Uses existing TipTap `colwidth` attribute mechanism (no changes to data model)
- No impact on existing table resize behavior (custom NodeView remains unchanged)
- Follows established configuration dropdown patterns
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
## ADDED Requirements

### Requirement: Column width input control

The cell configuration dropdown SHALL include a number input control labeled "Column Width" that allows users to set the column width in pixels.

#### Scenario: Opening cell configuration shows current width

- **WHEN** user selects a table cell and opens the cell configuration dropdown
- **THEN** the "Column Width" input displays the current column width value if set, or shows empty with "Auto" placeholder if width is auto

#### Scenario: Setting a valid column width

- **WHEN** user enters a numeric value between 25 and 1000 in the "Column Width" input
- **THEN** the column width is set to that exact pixel value and the column resizes accordingly

#### Scenario: Clearing column width for auto sizing

- **WHEN** user clicks the clear button or deletes the value from the "Column Width" input
- **THEN** the column width is reset to auto (null) and the column resizes to fit content

### Requirement: Column width validation

The system SHALL validate column width input to ensure values are within acceptable ranges.

#### Scenario: Entering value below minimum

- **WHEN** user enters a value less than 25 pixels
- **THEN** the system clamps the value to 25 pixels

#### Scenario: Entering value above maximum

- **WHEN** user enters a value greater than 1000 pixels
- **THEN** the system clamps the value to 1000 pixels

#### Scenario: Entering invalid input

- **WHEN** user enters non-numeric text
- **THEN** the system ignores the input and retains the previous value

### Requirement: Colspan cell width handling

The system SHALL set only the first column width when the selected cell has a colspan greater than 1.

#### Scenario: Setting width on colspan cell

- **WHEN** user sets column width on a cell with colspan=3 that currently has colwidth=[100, 150, 200]
- **THEN** the system sets colwidth=[new_value] affecting only the first spanned column

#### Scenario: Reading width from colspan cell

- **WHEN** user opens cell configuration for a cell with colspan=3 and colwidth=[100, 150, 200]
- **THEN** the "Column Width" input displays 100 (the first column's width)

### Requirement: Number input UI component

The configuration dropdown SHALL support a "numberInput" type with number-specific controls.

#### Scenario: Number input with unit label

- **WHEN** a configuration section has type "numberInput" with unit "px"
- **THEN** the UI renders a number input field with "px" unit label beside it

#### Scenario: Number input with clear button

- **WHEN** a configuration section has type "numberInput" and the field has a value
- **THEN** the UI displays a clear button (×) that resets the field to empty when clicked

#### Scenario: Number input with placeholder

- **WHEN** a configuration section has type "numberInput" with placeholder "Auto"
- **THEN** the empty input field shows "Auto" as placeholder text

### Requirement: Column width persistence

The system SHALL persist column width values in the document's colwidth attribute.

#### Scenario: Width persists after save and reload

- **WHEN** user sets a column width to 150 pixels, saves the document, and reloads the page
- **THEN** the column width remains 150 pixels and is displayed correctly in the cell configuration

#### Scenario: Width persists across editing sessions

- **WHEN** user sets column widths on multiple columns and closes the editor
- **THEN** reopening the document displays all columns at their configured widths

### Requirement: Visual feedback

The system SHALL provide clear visual feedback when column width changes.

#### Scenario: Immediate column resize on input

- **WHEN** user changes the column width value in the input field
- **THEN** the table column resizes immediately without requiring additional confirmation

#### Scenario: Clear button visibility

- **WHEN** the column width input has a value
- **THEN** the clear button is visible
- **WHEN** the column width input is empty
- **THEN** the clear button is hidden
Loading
Loading