Skip to content

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:

  1. List the paragraph bundles under Entity types and bundles.
  2. Add the extra_field_frontend_editing extra field to each paragraph bundle's view display.
  3. 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.

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.