Metadata-Version: 2.5
Name: collective.blicca.referencelist
Version: 1.0.0a5
Summary: Aurora Reference List block: picked content items rendered like a listing, with per-variation templates and item templates
Project-URL: Homepage, https://github.com/collective/collective.blicca.referencelist
Project-URL: Documentation, https://github.com/collective/collective.blicca.referencelist
Project-URL: Repository, https://github.com/collective/collective.blicca.referencelist.git
Project-URL: Issues, https://github.com/collective/collective.blicca.referencelist/issues
Author-email: Maik Derstappen <md@derico.de>
License: GPL-2.0-or-later
Keywords: CMS,Plone
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: Plone
Classifier: Framework :: Plone :: 6.0
Classifier: Framework :: Plone :: Addon
Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: plone-blicca-auroraeditor>=1.0.0a4
Requires-Dist: plone-restapi
Requires-Dist: plone>=6.0
Provides-Extra: test
Requires-Dist: plone-app-testing; extra == 'test'
Requires-Dist: plone-testing; extra == 'test'
Requires-Dist: pytest; extra == 'test'
Requires-Dist: pytest-cov; extra == 'test'
Requires-Dist: pytest-plone>=0.5.0; extra == 'test'
Description-Content-Type: text/markdown

# collective.blicca.referencelist

A **Reference List** block for the Aurora editor in [Plone](https://plone.org) Blicca.
Blicca is the former Plone Classic UI. The author picks any number of content
items with the content browser, in the order they should appear, and the
block renders them the way the listing block renders a query result: as a
plain stack, as summary cards with a picture, or as a grid of those cards.

Typical uses:

- "See also" or "Related" lists curated by hand instead of by a query;
- a set of case studies, partners or services picked for one landing page;
- a grid of teasers to a handful of pages, without one teaser block each.

The block needs [plone.blicca.auroraeditor](https://github.com/derico-de/plone.blicca.auroraeditor),
which brings the Aurora editor to Blicca. Its editor half is also a plain
Aurora block package, `@plone-collective/aurora-referencelist-block`, see
[Using the block in Aurora](#using-the-block-in-aurora).

## Features

- **Hand-picked references.** Any number of items, kept in the site's own
  content browser: the picked items are always listed in the sidebar,
  several are added in one visit to the browser, and an item is removed or
  dragged to a new place right in that list.
- **Always current.** Only the reference is stored. On every page load the
  referenced items are looked up in the catalog, so a renamed or moved item
  shows its current title and picture, and a deleted or unpublished one
  disappears from the list.
- **The listing's three variations.** `default` stacks title and
  description, `summary` puts the item's picture over each card, `grid`
  lays the summary cards out in two or three columns depending on the
  block's width.
- **Headline and heading levels** as on the listing block: an optional
  headline as `h2` or `h3`, item titles one level below it.
- **Responsive pictures.** The card picture is served through the site's
  picture variants with `srcset`, `sizes` and an AVIF alternative, sized
  for the block's width, or for one column of it on the grid.
- **Templates and item templates per variation.** A theme replaces the
  list template, the item template, or a content type's card for one
  variation or for all of them by registering a named view. See
  [Templates](#templates).
- **Honest editor preview.** The canvas shows the referenced items as the
  page will, says when nothing is picked yet, and names a reference whose
  target no longer exists.
- **One markup, one stylesheet** for the public page, the editor canvas
  and an Aurora frontend. Block width and background colour come from the
  editor's regular block styling controls.

## Requirements

- Plone 6.0 or later
- `plone.blicca.auroraeditor` 1.0.0a4 or later

The JavaScript bundle is committed to the package. No Node is needed at
install time.

## Installation

Add the package to your project's dependencies:

```toml
# pyproject.toml
dependencies = [
    "collective.blicca.referencelist",
]
```

Then install **Collective Blicca Referencelist** from Plone's Add-ons
control panel, or apply the `collective.blicca.referencelist:default`
profile. Uninstalling removes the block registration again.

## Using the block

Insert the **Reference list** block in the Aurora editor and fill in the
sidebar. The canvas is a live preview; all editing happens in the sidebar.

| Field | What it does |
|---|---|
| Headline | Optional heading above the list |
| Headline tag | `H2` or `H3`. Item titles are one level below a visible headline, else at that level |
| Variation | `Default`, `Summary` or `Grid` |
| Link items | On by default: each card links to its item, picture and description included. Off, the titles are plain text |
| References | The picked items, in order, in the content browser's list. **Select** opens the browser: Shift-click or Ctrl-click picks several at once. Drag an item to reorder it, its × removes it |
| Block width | The editor's width control |
| Background | A background colour, if the site offers a palette |

Rules worth knowing:

- Items render in the order they are listed. The order is the author's;
  nothing is sorted.
- An item the visitor may not see is skipped silently on the page. In the
  editor a reference whose target no longer exists is named in a notice.
  The content browser cannot list it, so it leaves the block as soon as the
  block's settings are opened, as it leaves a relation field in a classic
  edit form.
- The summary and grid variations show an item's picture when it has one
  (a lead image, or the image of an Image item). Items without a picture
  keep their place without one.
- An empty list renders its headline and nothing else on the page.
- A block saved before **Link items** existed links its items. Its
  checkbox shows that with `plone.blicca.auroraeditor` later than 1.0.0a4;
  older versions show it unchecked until the author sets it.

## Rendered markup

```html
<div class="referencelist variation-summary">
  <h2 class="headline">Related</h2>                 <!-- only with a headline -->
  <div class="referencelist-items">                 <!-- only with items -->
    <article class="item summary">                  <!-- "item" on the default variation -->
      <picture>                                     <!-- summary and grid, only with a picture -->
        <source type="image/avif" srcset="…" sizes="…">   <!-- public page only -->
        <img src="…" srcset="…" sizes="…" width="…" height="…"
             alt="" loading="lazy" decoding="async">
      </picture>
      <div class="item-content">
        <h3><a href="…">Title</a></h3>              <!-- h2, h3 or h4; no <a> with Link items off -->
        <p>Description</p>                          <!-- only with a description -->
      </div>
    </article>
  </div>
</div>
```

- The root always carries `variation-<name>` with the variation in effect;
  an unknown stored value renders as `default`.
- The grid variation uses the same cards as the summary variation and lays
  them out in columns with a container query on the block's own width.
- The picture is decorative (`alt=""`): the title link beside it carries the
  item's name.
- The stylesheet stretches the title link over its card, so a click on the
  picture or the description follows it too. The link's name stays the
  title.
- Block width and background classes are added by the host on a wrapper
  around this markup, as for every Aurora block.

## Templates

The block renders in two steps, each through a named browser view on the
page's (context, request) pair, so a theme or add-on can replace one step
for one variation without touching the rest:

| Step | Looked up, most specific first |
|---|---|
| The list | `aurora-referencelist-<variation>`, then `aurora-referencelist` |
| Each item | `aurora-referencelist-item-<variation>-<portal_type>`, `aurora-referencelist-item-<variation>`, `aurora-referencelist-item-<portal_type>`, then `aurora-referencelist-item` |

The names without a suffix are the stock templates this package registers.
To replace one, register a view under a more specific name on your own
browser layer (`for="*"`, permission `zope2.View`) and subclass the base
class from `collective.blicca.referencelist.rendering`. A distinct name
always wins, so an override never depends on the order of browser layers.

The templates render the public page only. The editor canvas shows the
stock card for every item.

### An item template

An item template renders one referenced item. Its name says which items it
covers:

| Name | Renders |
|---|---|
| `aurora-referencelist-item-<portal_type>` | Items of one content type, on every variation |
| `aurora-referencelist-item-<variation>` | Every item on one variation |
| `aurora-referencelist-item-<variation>-<portal_type>` | Items of one content type on one variation |

The variation is `default`, `summary` or `grid`. The portal type goes into
the name verbatim, space included: `aurora-referencelist-item-News Item`.
A variation's template outranks a content type's, so a theme that
registers `aurora-referencelist-item-grid` renders every type with it on
the grid, whatever cards the types have elsewhere.

As an example, an Event card that shows the event's location below its
title, on every variation.

1. Register the view on your theme's or add-on's browser layer:

   ```xml
   <browser:page
       name="aurora-referencelist-item-Event"
       for="*"
       class=".referencelist.EventCard"
       template="referencelist_event_card.pt"
       permission="zope2.View"
       layer=".interfaces.IMyThemeLayer"
       />
   ```

2. Subclass `BaseReferenceListItemView` and derive what the card needs
   from `self.item`:

   ```python
   from collective.blicca.referencelist.rendering import BaseReferenceListItemView


   class EventCard(BaseReferenceListItemView):
       @property
       def css_class(self):
           return f"{super().css_class} event"

       @property
       def location(self):
           return (self.item or {}).get("location") or ""
   ```

3. Write the template. Start from the stock one,
   `views/reference_list_item_view.pt`, so the card keeps the block's
   markup and styles:

   ```html
   <article xmlns:tal="http://xml.zope.org/namespaces/tal"
            tal:attributes="class view/css_class">
     <tal:media tal:condition="view/shows_cards"
                tal:define="image view/image">
       <picture tal:condition="image">
         <source type="image/avif"
                 tal:condition="image/avif_srcset | nothing"
                 tal:attributes="srcset image/avif_srcset;
                                 sizes image/sizes | nothing" />
         <img alt="" loading="lazy" decoding="async"
              tal:attributes="src image/src;
                              srcset image/srcset | nothing;
                              sizes image/sizes | nothing;
                              width image/width;
                              height image/height" />
       </picture>
     </tal:media>
     <div class="item-content">
       <h2 tal:condition="python: view.title_tag == 'h2'"><a
           tal:attributes="href view/url"
           tal:omit-tag="not:view/linked"
           tal:content="view/title">title</a></h2>
       <h3 tal:condition="python: view.title_tag == 'h3'"><a
           tal:attributes="href view/url"
           tal:omit-tag="not:view/linked"
           tal:content="view/title">title</a></h3>
       <h4 tal:condition="python: view.title_tag == 'h4'"><a
           tal:attributes="href view/url"
           tal:omit-tag="not:view/linked"
           tal:content="view/title">title</a></h4>
       <p class="event-location"
          tal:condition="view/location"
          tal:content="view/location">location</p>
       <p tal:condition="view/description"
          tal:content="view/description">description</p>
     </div>
   </article>
   ```

What `BaseReferenceListItemView` gives a template:

| Attribute | Value |
|---|---|
| `item` | The item's JSON summary: every catalog metadata column, `image_scales` included |
| `data` | The block's data |
| `variation` | The variation in effect |
| `title_tag` | The heading level of the item title: `h2`, `h3` or `h4` |
| `position`, `total` | The item's place in the list (0-based) and the number of items |
| `portal_type` | The item's content type |
| `linked` | False when the block's **Link items** is off |
| `url` | The item's link, `None` when the target must not be linked or `linked` is false |
| `title`, `description` | The item's title (its id when it has none) and description |
| `shows_cards` | True on the `summary` and `grid` variations |
| `css_class` | `item`, plus `summary` when `shows_cards` |
| `image` | `src`, `srcset`, `avif_srcset`, `sizes`, `width` and `height` of the item's picture, sized for the block, or `None` |

Rules worth knowing:

- Keep `view/css_class` on the card's root. The block's dividers, card and
  grid styles hang on `item` and `summary`.
- Read extra fields from `self.item`, not from the object: the summary
  carries every catalog metadata column, so the card wakes no object.
  Dates are ISO 8601 strings.
- `tal:attributes` leaves out an attribute whose value is `None`, so a
  title link to a target that must not be linked renders without `href`.
- `tal:omit-tag="not:view/linked"` drops the title's anchor when the block
  does not link its items. Keep the anchor flush in its heading, as the
  stock template does, so the bare title carries no stray whitespace.
- The anchor directly inside the card's `h2`, `h3` or `h4` covers the whole
  card. Another link on the card needs `position: relative; z-index: 1` to
  stay clickable above it.
- `index` is the view's own template. Do not use the name for anything
  else.

### A list template

A list template renders the block's wrapper, headline and items. Register
it as `aurora-referencelist-<variation>` and subclass
`BaseReferenceListView`. It gets `data` and `items` (the referenced items'
JSON summaries, in stored order) and derives `variation`, `headline`,
`headline_tag`, `item_title_tag`, `shows_cards`, `css_class` and
`rendered_items`, the items already rendered through their item templates.
A list template emits its own wrapper and inserts `rendered_items` with
`structure`; start from the stock `views/reference_list_view.pt` and keep
`view/css_class` on the root.

## How it works

The block stores its headline, headline tag, variation, whether item titles
link, and the list of references. A reference is the picked item's `@id`
(which plone.restapi rewrites to `../resolveuid/<UID>` on save and back to a
URL on read), its UID, type and a snapshot of its title. Nothing else is
persisted.

1. On every load, a `plone.restapi` block serialization transformer resolves
   the references in the catalog and injects `items`, the full JSON summary
   of every target the current user may see, in stored order, plus
   `missing`, the references that resolved to nothing. A reference stored
   without a UID gets the one its `@id` names, since the content browser
   tracks its selection by UID.
2. On save, a matching deserialization transformer strips both keys again,
   so derived data is never written to the database.
3. The public renderer hands `items` to the variation's list template,
   which renders each one through its item template.
4. The React renderer reads `items` too. A freshly picked reference the
   server has not seen yet is previewed from the stored snapshot, and a
   reference in `missing` is left out and named in a notice.

The transformers are registered for content with the `IBlocks` behavior and
for the site root, so a reference list in a footer stored on the site root
works on every page. Nested blocks inside a text container are transformed
too.

Two renderers emit the same markup: the Chameleon templates behind the
`aurora-block-referencelist` view for the public page, and a React `view`
component for the editor canvas and for Aurora frontends. Both are held to
the shared fixture `tests/anatomy-cases.json` by their test suites.

## Theming

The stylesheet mirrors the listing block's: quiet dividers, a picture over
the card on the summary and grid variations, the grid's two and three
columns from container queries on the block's width. One custom property is
read, `--referencelist-divider`, which falls back to the editor's
`--aurora-divider`. Every rule descends from `.referencelist`, the block's
own root. The stylesheet is `@scope`-wrapped to the editor and the public
blocks view, so a theme that wants different cards registers a template
(see above) rather than fighting the rules.

## Using the block in Aurora

The editor half lives in `bundle-src/` as the npm package
`@plone-collective/aurora-referencelist-block` (not yet published). It
registers the block and its two widgets through the usual `install(config)`
entry point and resolves every other field to an upstream Aurora widget.

- **The Python package must still be installed on the backend.** The React
  `view` reads the `items` the server injects; without it the canvas shows
  the stored snapshots only.
- **Link items** is a plain `boolean` field, drawn by the host's checkbox
  widget. Aurora's own, Quanta's `Checkbox`, does not take the label and
  stored value the block sidebar passes yet, so there it shows unlabelled
  and unchecked.
- The references widget mounts the host's own `object_browser` widget in
  multiple mode, on the stored references, and stores the selection it
  reports. Aurora's can only be mounted inside the edit route, where the
  block sidebar lives.
- **Bring your own styling.** The stylesheet is scoped to the Blicca roots
  and does not apply in an Aurora frontend.
- The block has been verified against the registry the Aurora installers
  build, not inside a running Aurora application.

## Development

The JavaScript build output is committed into
`src/collective/blicca/referencelist/static/`. Rebuild and commit it whenever
`bundle-src/src/` changes; CI fails when the committed bundle does not match
the source.

```bash
cd bundle-src
pnpm install
pnpm build        # writes ../src/collective/blicca/referencelist/static/referencelist-block.{js,css}
pnpm test         # reads the built bundle, so build first
pnpm typecheck
```

```bash
uv run --extra test pytest
```

`e2e/` drives the block in a running site with playwright-core: in the
editor, picking six images in one visit to the content browser, removing
one, dragging one to the top, adding one more, saving and reloading; on the
page, clicking a card's picture and description. Each script builds its
fixture in the folder `AURORA_E2E_BASE` names and removes it again; the
add-on must be installed there.

```bash
cd e2e
pnpm install
AURORA_E2E_BASE=http://localhost:8080/Plone AURORA_E2E_PAGE_TYPE=Article pnpm e2e
```

`AURORA_E2E_USER`, `AURORA_E2E_PASSWORD`, `AURORA_E2E_CHROMIUM` (a Chromium
executable) and `AURORA_E2E_REROUTE` (`"<prefix>=><replacement>"`, a URL
rewrite for the mockup bundle) are optional; the header of
`e2e/references-picker.e2e.js` lists them.

Every change to a GenericSetup profile XML file needs an upgrade step, even
in an alpha release. Scaffold it with `plonecli add upgrade_step`.

## License

GPL-2.0-or-later

## Author

Maik Derstappen, [derico.de](https://derico.de), <md@derico.de>
