Paragraphs¶
frontend_editing_paragraphs adds paragraph support. Enable it when the content editors work on is
built from paragraphs.
drush en frontend_editing_paragraphs
Dependencies: frontend_editing, and
Paragraphs Edit.
What it adds¶
Paragraph lineage. A paragraph does not know its own host page; it knows its immediate parent, which may be another paragraph. The sub-module walks that chain to find the root entity, so saving a paragraph nested three levels deep saves the right node.
An add form that knows paragraph types. Choosing a type in the add panel creates the paragraph and splices it into the parent field at the right position.
Real duplication. Paragraphs belong to their host entity, so two field items can never point at one paragraph. Duplicating clones the paragraph into a new entity rather than adding a second reference to the same one, which is what the base module does for a plain reference field.
Translation handling. Paragraph translations follow their host's language handling when edited from the frontend.
Content moderation. Changes to paragraphs of moderated content are saved as a draft of the host. See Content moderation below.
Setup¶
The same three steps as any other bundle, applied to paragraph types:
- List the paragraph bundles under Entity types and bundles.
- Add the
extra_field_frontend_editingextra field to each paragraph bundle's view display. - Turn on the operations you want on the parent field's formatter, which is usually the paragraphs field on the node.
Remember that step 1 gates the referenced bundle. A paragraphs field on a node needs its paragraph
bundles listed, not node.
Contextual links¶
Paragraphs Edit depends on core's Contextual module, so paragraphs get contextual links as well as Frontend Editing links. The Hide contextual links setting removes the duplication. See Editing content.
Content moderation¶
Paragraphs are revisioned together with their host, so the sub-module makes Paragraphs Edit work with content moderation. See Content moderation for what editors see.
| Piece | What it does |
|---|---|
ModerationAwareLineageRevisioner |
Decorates paragraphs_edit.lineage.revisioner. Saves a change to a published host as a draft of the host instead of a new published default revision, and checks the transition on the root host before saving anything, so a refused change leaves no orphan paragraph revision. |
ActiveRevisionLineageInspector |
Decorates paragraphs_edit.lineage.inspector. Resolves parents to their active (latest) revision instead of the default one, so a second edit builds on the pending draft. |
ParagraphsReferenceHelper::getRevisionRoot() |
Follows the host revision a paragraph is rendered from (_referringItem), then falls back to the active root parent. Nested paragraphs resolve to the top host. |
RouteSubscriber |
Marks the root_parent and paragraph parameters of the paragraph edit and delete routes as load_latest_revision, so submitting a form does not upcast the published revision and save over the draft. |
FrontendEditingParagraphsHooks::addSaveAndPublishButton() |
Adds Save and publish to the paragraph form on the frontend_editing.form route when RevisionHelperInterface::canPublishDirectly() allows it for the root host. Its submit handlers switch RevisionHelperInterface::setPublishDirectly() on around the form's own save, so ModerationAwareLineageRevisioner keeps the published state instead of moving to draft. It also adds the note and button tooltips that explain Save versus Save and publish. |
ParagraphDeleteForm |
Keeps the paragraph entity when the host has a pending revision, because the published revision still references it. |
Warning
Decorating paragraphs_edit.lineage.revisioner and the inspector changes behaviour for every
Paragraphs Edit consumer once the sub-module is enabled, not only for frontend editing links.
Danger
Do not apply the Paragraphs Edit patch from
issue 2914976. Its moderation
state copy overwrites the draft state, and the parent then reads a $time property the
decorator never sets. Nothing enforces this.
The decorators extend the concrete Paragraphs Edit classes and skip their parent constructors,
because the 3.x and 4.x signatures differ. This was verified against Paragraphs Edit 3.0.1. The
4.x route names (entity.paragraph.edit_form, entity.paragraph.delete_form), its
createRevision() clone and its ParagraphLineageInspectorInterface type hint were only read, not
run.
Save queue (experimental)¶
Normally each paragraph edit saves on its own. The save queue lets an editor make several paragraph edits and commit them in one go.
Warning
This is experimental. Verify it on a development environment before enabling it in production.
Enable it under Save queue settings on the settings form. It needs All Entity Preview, whose tempstore holds the queued values. Without that module the endpoints stay registered but do nothing.
flowchart LR
Edit[Edit a paragraph<br/>in the sidebar] --> Queue[Add to Save Queue]
Queue --> Store[(Preview tempstore)]
Store --> Page[Page shows<br/>the unsaved values]
Queue --> More[Edit another<br/>paragraph]
More --> Queue
Store --> Save[Save All]
Save --> Entities[Host entities saved,<br/>queue cleared]
Each paragraph form in the sidebar gains an Add to Save Queue button next to Save. Queued paragraphs render on the page with their pending values, and a floating Save All button reports how many are waiting. Its position relative to the frontend editing toggle is the "Save All" button position setting.
The panel has a Revision log message field. Its text is set on the new revision of each root entity. Left empty, the default "Updated with frontend editing." is kept. With content moderation, saving the queue creates a draft of a published host, and is refused for a user who may not make the transition.
Saving groups the queue by root entity and saves each group in its own database transaction, so one failing group does not half-write another. Items can also be discarded from the queue without saving.