diff --git a/README.md b/README.md
index 0d9e221..06b932e 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ This extension provides visual editing features for content elements in TYPO3 CM
- 🔦 **Finding editable areas:** use Spotlight to highlight editable text, rich text, images, and content elements.
- 👻 **Showing empty fields:** use "show empty" when editable but currently empty fields are hard to see.
- ↔️ **Moving content:** drag content elements by their handle. Hold Ctrl while dropping to copy instead of moving.
+- ✍️ **Editing special text characters:** type `` for a soft hyphen and ` ` for a non-breaking space. Entity-like text that starts with `&`, such as ` `, is shown with `&` while editing so it stays literal text.
## Template Integration
@@ -77,6 +78,16 @@ If you do not have a Record object yet, you can create one with the `record-tran
lib.contentElement.dataProcessing.1768551979 = record-transformation
````
+#### Editable text entities
+For plain editable text fields, Visual Editor makes some otherwise hard-to-see characters explicit while the field is focused.
+Soft hyphens are shown as ``, and non-breaking spaces are shown as ` `.
+Plain ampersands stay visible as `&`, but ampersands that start an entity-like sequence, such as ` `, ` `, or ` `, are shown as `&` while editing so the sequence stays literal text.
+When the editor changes or leaves the field, these values are converted back before validation and storage.
+
+````html
+
{record -> f:render.text(field: 'header')}
+````
+
#### Fluid components
When you use Fluid components, render the editable text outside the component and pass the rendered value into the component.
This keeps the component decoupled from records and TCA fields, and lets callers pass either a plain string or the result of `f:render.text`.
diff --git a/Resources/Public/JavaScript/Frontend/components/ve-editable-text.js b/Resources/Public/JavaScript/Frontend/components/ve-editable-text.js
index d7c7596..6e210db 100644
--- a/Resources/Public/JavaScript/Frontend/components/ve-editable-text.js
+++ b/Resources/Public/JavaScript/Frontend/components/ve-editable-text.js
@@ -373,6 +373,28 @@ export class VeEditableText extends LitElement {
return this.shadowRoot?.querySelector('.slot');
}
+ /**
+ * @param {string} value
+ * @return {string}
+ */
+ #storedTextToEditableText(value) {
+ return value
+ .replace(/&(?=#\d+;|#x[0-9a-fA-F]+;|[a-zA-Z][a-zA-Z0-9]+;)/g, '&')
+ .replace(/\u00ad/g, '')
+ .replace(/\u00a0/g, ' ');
+ }
+
+ /**
+ * @param {string} value
+ * @return {string}
+ */
+ #editableTextToStoredText(value) {
+ return value
+ .replace(//gi, '\u00ad')
+ .replace(/ /gi, '\u00a0')
+ .replace(/&/gi, '&');
+ }
+
/**
* @param {HTMLElement} element
* @param {InputEvent} event
@@ -422,21 +444,22 @@ export class VeEditableText extends LitElement {
if (insertedText !== edit.insertedText) {
event.preventDefault();
insertTextAtSelection(element, insertedText);
- this.#validateAndStore(this.#getSlotText());
+ this.#validateAndStore(this.#editableTextToStoredText(this.#getSlotText()));
}
}
#handleInput() {
- this.#validateAndStore(this.#getSlotText());
+ this.#validateAndStore(this.#editableTextToStoredText(this.#getSlotText()));
}
#handleFocus() {
this.focused = true;
+ this.#setSlotText(this.#storedTextToEditableText(this.value));
}
#handleBlur() {
this.focused = false;
- this.#setSlotText(this.#validateAndStore(this.#getSlotText()));
+ this.#setSlotText(this.#validateAndStore(this.#editableTextToStoredText(this.#getSlotText())));
}
/**