Skip to content
Merged
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
67 changes: 67 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

TinyMCE for Flow — a Vaadin 25 add-on that wraps the TinyMCE rich text editor as a Java server-side component. The component value is plain HTML and it implements `HasValue` for Vaadin Binder compatibility.

**Maven coordinates:** `org.parttio:tinymce-for-flow`
**Java package:** `org.vaadin.tinymce`
**Java version:** 21
**Stack:** Vaadin 25, Spring Boot 4

## Build & Test Commands

```bash
mvn clean package # Full build (compile + test + package)
mvn compile # Compile only
mvn test # Run all tests (requires Playwright/Chromium)
mvn test -Dtest=MopoSmokeTest # Run a single test class
mvn test -Dtest=MopoSmokeTest#smokeTest # Run a single test method
```

The test suite uses Spring Boot with `RANDOM_PORT` and Playwright (via [Mopo](https://github.com/nickvdyck/mopo)) for browser-based E2E tests. Tests launch a real Chromium browser.

## Running the Demo Server

```bash
mvn spring-boot:run -pl . -Dspring-boot.run.main-class=org.vaadin.tinymce.Application
```

Test/demo views are `@Route`-annotated classes in `src/test/java` (e.g., `DemoView`, `EditorInDialog`, `ImageUploadsEnabledView`).

## Architecture

### Server-side (Java)

- **`TinyMce`** (`src/main/java/.../TinyMce.java`) — The main component. Extends `AbstractCompositeField<Div, TinyMce, String>` so it works with Vaadin's data binding. Manages editor lifecycle (attach/detach/reattach), config assembly, and JS connector initialization.
- **`Plugin`**, **`Toolbar`**, **`Menubar`** — Enums providing type-safe constants for TinyMCE plugin names, toolbar buttons, and menu sections.
- **`Language`** — Enum for supported TinyMCE UI languages.
- **`ValueChangeMode`** — Enum controlling when value changes propagate (BLUR, TIMEOUT, CHANGE).

Configuration is accumulated into a `tools.jackson.databind.node.ObjectNode config` field via `configure()` / `configurePlugin()` / `configureToolbar()` / `configureMenubar()` methods and serialized inline into the JS connector call on attach. A raw JS config string can also be set via `setConfig(String)`.

### Jackson 3

Vaadin 25 ships with **Jackson 3**, which uses `tools.jackson.*` packages — not the older `com.fasterxml.jackson.*`. Use `tools.jackson.databind.node.ObjectNode`, `ArrayNode`, `JsonNodeFactory`, `JsonNode` for any JSON handling. Vaadin's own `DomEvent.getEventData()` also returns a `tools.jackson.databind.JsonNode` in Vaadin 25.

### Client-side (JavaScript)

- **`tinymceConnector.js`** (`src/main/resources/META-INF/resources/frontend/`) — The connector that bridges Vaadin's server-side component to the TinyMCE JS library. Handles `initLazy`, content sync via custom DOM events (`tchange`, `tblur`, `tfocus`), and Dialog/shadow-DOM workarounds.
- **Bundled TinyMCE** — A full TinyMCE distribution lives under `src/main/resources/META-INF/resources/frontend/tinymce_addon/tinymce/`. This is vendored; do not edit these files.

### Key design notes

- The component does **not** use Shadow DOM by default. The deprecated `TinyMce(boolean shadowRoot)` constructor exists for legacy Dialog support but is no longer needed.
- On detach, `tinymce.get(id).remove()` is called to clean up the TinyMCE instance. On reattach, the connector re-initializes.
- TinyMCE doesn't work well inside Shadow DOM — menu overlays and keyboard navigation require special handling (see the `isInDialog` workaround in the connector).
- The `vaadin-dev` dependency (optional, with `copilot` excluded) is required in the main dependencies for the Vaadin dev-mode server to start during tests. Without it, `'vaadin-dev-server' not found` is thrown at servlet context initialization.

## Release Process

```bash
mvn release:prepare release:clean
```

A GitHub Action handles `release:perform` — it builds and pushes to Maven Central automatically on tagged commits.
76 changes: 60 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,77 @@
# TinyMCE for Flow

Vaadin 25 Java integration for TinyMCE text editor.
A Vaadin 25 Java integration for TinyMCE, a popular open-source rich text editor. The component implements `HasValue` and works with Vaadin's data binding. Content value is plain HTML.

Works with binder as the component implements HasValue interfaces. The value is plain HTML. If you can't trust your clients, apply converter that filters the input with e.g. JSOUP library.
## Usage

Builds will be available from https://vaadin.com/directory
Add the dependency to your `pom.xml`:

## Limitations
```xml
<dependency>
<groupId>org.parttio</groupId>
<artifactId>tinymce-for-flow</artifactId>
<version>VERSION</version>
</dependency>
```

TinyMCE (like most traditional wysiwyg editors) don't work inside shadow DOM. You most probably have issues if you use templates or use the editor in Dialog.
Create and use the editor:

## Development instructions
```java
TinyMce editor = new TinyMce();
editor.setValue("<p>Hello, world</p>");

Starting the test/demo server:
// Or use a preconfigured setup
TinyMce basicEditor = new TinyMce()
.configureToolbar(true, Toolbar.BOLD, Toolbar.ITALIC, Toolbar.UNDERLINE)
.configurePlugin(true, Plugin.LISTS);
```
mvn spring-boot:test-run

## Limitations

TinyMCE 7 defaults to sandboxing any iframes in editor content with `sandbox_iframes: true`. If your content includes iframes and they appear broken, disable sandboxing:

```java
editor.configure("sandbox_iframes", false);
```

This deploys demo at http://localhost:9998
If you cannot trust your users' HTML input, apply a converter that filters it (e.g., using the JSOUP library) before storing or displaying it.

## Upgrade Guide

## Cutting a release
When upgrading to version 5.x (Vaadin 25 / TinyMCE 7):

Before cutting a release, make sure the build passes properly locally and in GitHub Actions based verification build.
- **Java 21 and Vaadin 25** are now required
- **Plugin.ADVLIST** is no longer needed — the `lists` plugin handles it automatically
- **Plugin.TEMPLATE** and **Plugin.TABFOCUS** are removed in TinyMCE 7
- **Toolbar.FONTNAME** has been renamed to **Toolbar.FONT_FAMILY**; new **Toolbar.FONT_SIZE_INPUT** is available
- **sandbox_iframes** defaults to `true` — note if iframe content looks broken
- Dialog usage: no special configuration needed anymore

To tag a release and increment versions, issue:
## Development

mvn release:prepare release:clean
### Starting the demo server

```bash
mvn spring-boot:run -pl . -Dspring-boot.run.main-class=org.vaadin.tinymce.Application
```

Answer questions, defaults most often fine.
Note that `release:perform` is not needed as there is a GitHub Action is set up build and to push release to Maven Central automatically.
Demo views are `@Route`-annotated classes in `src/test/java`. Access them at http://localhost:8080.

### Running tests

```bash
mvn test # Run all tests
mvn test -Dtest=MopoSmokeTest # Run a specific test class
```

Tests use Spring Boot with Playwright (via Mopo) for browser-based E2E testing.

## Release Process

To tag a release and increment versions:

```bash
mvn release:prepare release:clean
```

Directory will automatically pick up new releases within about half an hour, but if browser or Vaadin version support change, be sure to adjust the metadata in Vaadin Directory UI.
Answer the prompts (defaults are usually fine). A GitHub Action automatically builds and deploys the release to Maven Central.
55 changes: 54 additions & 1 deletion src/main/java/org/vaadin/tinymce/Plugin.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package org.vaadin.tinymce;

public enum Plugin {
//@formatter:off
//@formatter:off
ACCORDION("accordion"),
/**
* @deprecated Merged into the {@code lists} plugin in TinyMCE 7. Use {@link #LISTS} instead.
*/
@Deprecated
ADVLIST("advlist"),
ANCHOR("anchor"),
AUTOLINK("autolink"),
Expand All @@ -10,35 +15,83 @@ public enum Plugin {
CHARMAP("charmap"),
CODE("code"),
CODESAMPLE("codesample"),
/**
* @deprecated Removed in TinyMCE 5.
*/
@Deprecated
COLORPICKER("colorpicker"),
/**
* @deprecated Removed in TinyMCE 5.
*/
@Deprecated
CONTEXTMENU("contextmenu"),
DIRECTIONALITY("directionality"),
EMOTICONS("emoticons"),
/**
* @deprecated Removed in TinyMCE 6.
*/
@Deprecated
FULLPAGE("fullpage"),
FULLSCREEN("fullscreen"),
HELP("help"),
/**
* @deprecated Removed in TinyMCE 6.
*/
@Deprecated
HR("hr"),
IMAGE("image"),
/**
* @deprecated Removed in TinyMCE 6.
*/
@Deprecated
IMAGE_TOOLS("imagetools"),
IMPORT_CSS("importcss"),
INSERT_DATETIME("insertdatetime"),
/**
* @deprecated Removed in TinyMCE 6.
*/
@Deprecated
LEGACYOUTPUT("legacyoutput"),
LINK("link"),
LISTS("lists"),
MEDIA("media"),
NONBREAKING("nonbreaking"),
NONEDITABLE("noneditable"),
PAGEBREAK("pagebreak"),
/**
* @deprecated Merged into core in TinyMCE 6.
*/
@Deprecated
PASTE("paste"),
PREVIEW("preview"),
/**
* @deprecated Removed in TinyMCE 6.
*/
@Deprecated
PRINT("print"),
QUICKBARS("quickbars"),
SAVE("save"),
SEARCH_REPLACE("searchreplace"),
/**
* @deprecated Cloud-only since TinyMCE 6, not available in self-hosted.
*/
@Deprecated
SPELLCHECKER("spellchecker"),
/**
* @deprecated Removed in TinyMCE 7.
*/
@Deprecated
TABFOCUS("tabfocus"),
TABLE("table"),
/**
* @deprecated Removed in TinyMCE 7. Use the premium {@code advtemplate} plugin.
*/
@Deprecated
TEMPLATE("template"),
/**
* @deprecated Removed in TinyMCE 6.
*/
@Deprecated
TEXT_COLOR("textcolor"),
TEXT_PATTERN("textpattern"),
VISUAL_BLOCKS("visualblocks"),
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/vaadin/tinymce/TinyMce.java
Original file line number Diff line number Diff line change
Expand Up @@ -383,12 +383,12 @@ private TinyMce createBasicTinyMce() {
setValue("");
this.configure("branding", false);
this.basicTinyMCECreated = true;
this.configurePlugin(false, Plugin.ADVLIST, Plugin.AUTOLINK,
this.configurePlugin(false, Plugin.AUTOLINK,
Plugin.LISTS, Plugin.SEARCH_REPLACE);
this.configureMenubar(false, Menubar.FILE, Menubar.EDIT, Menubar.VIEW,
Menubar.FORMAT);
this.configureToolbar(false, Toolbar.UNDO, Toolbar.REDO,
Toolbar.SEPARATOR, Toolbar.FORMAT_SELECT, Toolbar.SEPARATOR,
Toolbar.SEPARATOR, Toolbar.BLOCKS, Toolbar.SEPARATOR,
Toolbar.BOLD, Toolbar.ITALIC, Toolbar.SEPARATOR,
Toolbar.ALIGN_LEFT, Toolbar.ALIGN_CENTER, Toolbar.ALIGN_RIGHT,
Toolbar.ALIGN_JUSTIFY, Toolbar.SEPARATOR, Toolbar.OUTDENT,
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/org/vaadin/tinymce/Toolbar.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public enum Toolbar {
SEPARATOR("|"),
UNDO("undo"),
REDO("redo"),
/**
* @deprecated Renamed to {@code blocks} in TinyMCE 6. Use {@link #BLOCKS} instead.
*/
@Deprecated
FORMAT_SELECT("formatselect"),
BLOCKS("blocks"),
BOLD("bold"),
Expand All @@ -17,8 +21,14 @@ public enum Toolbar {
ALIGN_CENTER("aligncenter"),
ALIGN_RIGHT("alignright"),
ALIGN_JUSTIFY("alignjustify"),
/**
* @deprecated Renamed to {@code fontfamily} in TinyMCE 7. Use {@link #FONT_FAMILY} instead.
*/
@Deprecated
FONTNAME("fontname"),
FONT_FAMILY("fontfamily"),
FONTSIZE("fontsize"),
FONT_SIZE_INPUT("fontsizeinput"),
BLOCKQUOTE("blockquote"),
NUMLIST("numlist"),
BULLIST("bullist"),
Expand All @@ -36,12 +46,24 @@ public enum Toolbar {
TABLE_DELETE_ROW("tabledeleterow"),
TABLE_INSERT_COL_BEFORE("tableinsertcolbefore"),
TABLE_INSERT_COL_AFTER("tableinsertcolafter"),
/**
* @deprecated String value has wrong case and was never functional. Use {@link #FONT_FAMILY} instead.
*/
@Deprecated
FONTSELECT("FONTSELECT"),
/**
* @deprecated String value has wrong case and was never functional. Use {@link #FONTSIZE} instead.
*/
@Deprecated
FONTSIZESELECT("FONTSIZESELECT"),
EMOTICONS("emoticons"),
LINK("link"),
IMAGE("image"),
MEDIA("media"),
/**
* @deprecated The print plugin was removed in TinyMCE 6.
*/
@Deprecated
PRINT("print"),
INSERT_DATETIME("insertdatetime");
//@formatter:on
Expand Down
Loading
Loading