Skip to content
Open
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
4 changes: 4 additions & 0 deletions content/customising/server-configuration/storage/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ storage: {
The default configuration will return `{dateString}/{randomString}{ext}`, but you can use the provided parameters on `computeKey()`
to compose a key blueprint that matches your needs, e.g. separate media by projectId with `{projectId}/{dateString}/{randomString}{extension}`.

{{< warning >}}
Keys starting with `v/` are reserved for [image editing in documents]({{< ref "/guides/media-library/2025-behavior/#image-editing-in-documents" >}}) and must not be returned by a custom `computeKey` function.
{{< /warning >}}

## Strategies

#### Memory
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 57 additions & 8 deletions content/guides/media-library/2025-behavior/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ weight: 5

## Overview

The Media Library is being upgraded during 2025, and the main improvements include:
- Image editing (colour correction and redaction)
The `use2025Behavior` represents the future direction of the Media Library. It unlocks new capabilities that were previously not possible. We recommend that new setups always enable it, and existing setups are encouraged to migrate.

- Image editing (colour corrections, rotation and pixelation)
- Automated deletion routines
- Manual media library entry deletion
- Removal of the "Archive" functionality
Expand Down Expand Up @@ -35,7 +36,7 @@ Be aware that this also requires additional configuration steps such as modifyin

## Features

### Image variant Storage / Delivery
### Image Variant Storage / Delivery

{{< added-in "release-2025-03" block >}}

Expand Down Expand Up @@ -77,12 +78,38 @@ This endpoint delivers the image with its original dimensions, as long as it has
To avoid performance bottlenecks ensure you place a CDN or image proxy in front of Livingdocs, retrieving images via the new API. This prevents excessive load on the Livingdocs Server.

Whenever an asset gets modified, we emit the [`mediaLibraryEntry.update`]({{< ref "/customising/advanced/server-events/#media-library-entry" >}}) server event. This event can be used to purge a CDN or other image service. The `mediaLibraryEntry.update` event also occurs for metadata changes, so if you want to only handle asset changes you can filter the events by checking whether `payload.changes?.some((c) => c.event === 'mediaLibraryEntry.asset.update')`.

When purging a CDN cache for a revoked, invalidated, or updated entry, use `mediaLibraryApi.getAllKeysForMediaLibraryEntry({mediaLibraryEntry})` to retrieve all asset keys associated with the entry, including variant keys from [image editing in documents](#image-editing-in-documents):

```js
liServer.events.subscribe(
'mediaLibraryEntry.update',
async (event, {mediaLibraryEntry, changes}) => {
const isAssetUpdate = changes?.some((c) => c.event === 'mediaLibraryEntry.asset.update')
if (!isAssetUpdate) return
const keys = await mediaLibraryApi.getAllKeysForMediaLibraryEntry({mediaLibraryEntry})
// Purge all keys from your CDN
}
)

liServer.events.subscribe('mediaLibraryEntry.revoke', async (event, {mediaLibraryEntry}) => {
const keys = await mediaLibraryApi.getAllKeysForMediaLibraryEntry({mediaLibraryEntry})
// Purge all keys from your CDN
})

liServer.events.subscribe('mediaLibraryEntry.invalid', async (event, {mediaLibraryEntry}) => {
const keys = await mediaLibraryApi.getAllKeysForMediaLibraryEntry({mediaLibraryEntry})
// Purge all keys from your CDN
})
```

{{< /info >}}

{{< warning >}}
If you had a path (and not just a domain) in `serverConfig.mediaLibrary.images.publicUrl`, or if you have documents which were created before {{< release "release-2024-03" >}}, you will need to set `serverConfig.mediaLibrary.generateImageServiceUrlsOnRead: true`.

If both of the above conditions apply you will also need to define `serverConfig.mediaLibrary.images.storage.extractKey` as something like the following:

```js
extractKey(url) {
const path = new URL(url).pathname
Expand All @@ -92,21 +119,42 @@ extractKey(url) {
: path
}
```

{{< /warning >}}

### Image Editing

{{< added-in "release-2025-05" block >}}

Journalists are sometimes required to redact areas of an image, such as license plates or faces, or to perform color corrections, such as adjusting brightness, contrast, or saturation. To simplify this task and eliminate the need for external tools, Livingdocs supports basic image editing.
Livingdocs supports image editing to allow journalists to redact sensitive areas (such as license plates or faces) and apply colour corrections, without the need for external tools.

{{< img src="./image-editor-button.png" alt="Image Editor Button" >}}
Image editing is supported for jpg, png, and webp formats. Images must be requested via the public API endpoint [`GET /api/2025-03/mediaLibrary/serve-image/:key`]({{< ref "/reference/public-api/media-library/#serve-image" >}}) for the modifications to be applied.

Users can open the image editor by clicking the edit button in the media center detail view. It allows users to adjust brightness, contrast, or saturation, as well as blur parts of an image. The original image is always preserved and can be restored at any time. Users can also continue editing an image or selectively undo specific adjustments at a later point.
#### Image Editing in the Media Library

{{< img src="./image-editor.png" alt="Image Editor" >}}
Users can open the image editor by clicking the edit button in the media library detail view. Edits here apply globally to all documents that reference the image. The original is always preserved and can be restored at any time.

Image editing is supported for jpg, png, and webp formats. Accordingly, images must be requested via the public API endpoint [`GET /api/2025-03/mediaLibrary/serve-image/{key}`]({{< ref "/reference/public-api/media-library/#serve-image" >}}) for the modifications to be applied. The edited image will be delivered in place of the original. The original version remains stored and can be restored in the editor.
This supports:

- Redaction: pixelate areas of the image (e.g. faces, license plates)
- Colour corrections: brightness, contrast, and saturation ({{< removed-in "release-2026-03" >}})

{{< img src="image-editing-pixelate.png" alt="Pixelate image" width="400" caption="Pixelation remains in the media library and applies globally to all placements of the image." >}}

#### Image Editing in Documents

{{< added-in "release-2026-03" block >}}

Journalists can edit images directly within a document using the "Adjust" button on each image placement. Edits here only affect that specific placement within the document. Other documents using the same image are not affected. The original can always be restored by resetting the adjustments.

This supports:

- Colour corrections: brightness, contrast, and saturation
- Rotation

{{< img src="image-editing-adjust.png" alt="Adjust image" width="400" caption="The Adjust button lets users apply colour adjustments or fix a skewed horizon per placement." >}}

{{< img src="image-editing-rotate.png" alt="Rotate image" caption="Images can be rotated per placement directly within a document." >}}

### Archive/Revoke/Delete

Expand Down Expand Up @@ -173,6 +221,7 @@ To configure a deletion routine you need to add the `deletionRoutine` config to
The filters property uses our usual [Search Filters Query DSL]({{< ref "/reference/public-api/publications/search-filters" >}}) in the same way as a base filter. Any unused media library entry which matches will be deleted.

Livingdocs automatically handles the "unused" part which excludes media library entries that:

- are referenced by documents
- are referenced by other media library entries
- are currently in a document inbox
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 44 additions & 1 deletion content/operations/releases/release-2026-03.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ These are the release notes of the upcoming release (pull requests merged to the
- :fire: Integration against the upcoming release (currently `main` branch) is at your own risk

## PRs to Categorize

- [Bump minor version for release management](https://github.com/livingdocsIO/livingdocs-editor/pull/10779)
- [Bump minor version for release management](https://github.com/livingdocsIO/livingdocs-server/pull/8976)
- [Add image collections](https://github.com/livingdocsIO/livingdocs-editor/pull/10700)
Expand Down Expand Up @@ -168,7 +169,16 @@ No pre-deployment steps are required before rolling out this release.

#### Migrate the Postgres Database

No migrations are required for this release.
When upgrading, first run the database migrations. At Livingdocs, we run this command in an initContainer on Kubernetes.

All migrations should execute quickly and not lock write-heavy tables.

```sh
# 216-image-variants.js
# Creates the `media_library_entries_image_variants` table for image editing
# in documents.
livingdocs-server migrate up
```

### After the deployment

Expand Down Expand Up @@ -198,10 +208,42 @@ Or preferably to our recommended versions:

The media source plugin function `searchMediaImage` now requires `systemName` and `externalId` to be strings when returned. Previously, these properties were not validated and had no effect.

### Reserved `v/` Key Prefix for Image Editing in Documents :fire:

Image variant keys use the `v/` prefix (e.g. `v/2026/02/10/abc123`). If you have configured a custom [`storage.computeKey`]({{< ref "/customising/server-configuration/storage/#interface" >}}) function for image storage, ensure it does not return keys starting with `v/`.

## Deprecations

### `mediaLibrary.disableImageEditingInDocuments`

The `mediaLibrary.disableImageEditingInDocuments` server configuration option is deprecated and will be removed in `release-2026-09`. It is introduced alongside [image editing in documents]({{< ref "#image-editing-in-documents-gift" >}}) as a temporary opt-out.

## Features :gift:

### Image Editing in Documents :gift:

Images can now be edited directly within a document. An "Adjust" button on each image placement lets users rotate images and apply colour adjustments (brightness, contrast, saturation) per placement, without affecting the original image or other documents that reference it.

{{< img src="release-2026-03-image-editing-adjust.png" alt="Adjust image" width="400" caption="The Adjust button lets users apply colour adjustments per placement." >}}

As a result, colour adjustments are no longer available in the media library. The only image modification that remains in the media library is pixelation. Pixelation continues to be applied globally to all image placements.

{{< img src="release-2026-03-image-editing-pixelate.png" alt="Pixelate image" width="400" caption="Pixelation remains in the media library and applies globally to all placements of the image." >}}

The new behavior is enabled automatically for all setups that have `use2025Behavior: true`. Once enabled, reverting to the previous behavior is not supported.

If newsrooms need time to adapt, image editing in documents can be temporarily disabled using `mediaLibrary.disableImageEditingInDocuments`. Note that this option is deprecated and will be removed in {{< release "release-2026-09" >}}. This option must be set before users start editing images in documents. Disabling it afterwards will not remove existing variants, which will continue to be applied in their respective placements.

This change is non-breaking for deliveries. Images can continue to be fetched by key as before. However, since an image can now have multiple variants, ensure you use `mediaLibraryApi.getAllKeysForMediaLibraryEntry()` when purging CDN caches after a media library entry is revoked or invalidated, as it returns all associated keys including any variant keys.

See [Image Editing in Documents]({{< ref "/guides/media-library/2025-behavior/#image-editing-in-documents" >}}) for details.

### Image Rotation :gift:

Journalists can now rotate images directly within a document. This is particularly useful for correcting a skewed horizon. Rotation is applied per placement, so the original image and any other documents that reference it remain unchanged. Since rotating an image changes its dimensions, any existing crops on that placement are reset automatically.

{{< img src="release-2026-03-image-editing-rotate.png" alt="Rotate image" caption="Images can be rotated per placement directly within a document." >}}

### Reuse Already Imported Media Source Items :gift:

When a media source item has already been imported, Livingdocs now reuses the existing media library entry instead of importing it again. This allows keeping media libraries free of duplicates when using media from external systems.
Expand Down Expand Up @@ -259,6 +301,7 @@ Here is a list of all patches after the release has been announced.
### Livingdocs Server Patches

### Livingdocs Editor Patches

- [v123.10.8](https://github.com/livingdocsIO/livingdocs-editor/releases/tag/v123.10.8): fix(search): Limit query length and display understandable error
- [v123.10.7](https://github.com/livingdocsIO/livingdocs-editor/releases/tag/v123.10.7): fix(metadata): Fix undefined property error in user needs form
- [v123.10.6](https://github.com/livingdocsIO/livingdocs-editor/releases/tag/v123.10.6): fix: Fix errors for edge cases of certain configurations/behaviors
Expand Down
8 changes: 8 additions & 0 deletions themes/hugo-docs/assets/elements/img.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
figure.img {
margin: $space-m 0;

figcaption {
@extend %font-s-xs;
color: $color-grey-3;
}
}
1 change: 1 addition & 0 deletions themes/hugo-docs/assets/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@import 'elements/history';
@import 'elements/history-button';
@import 'elements/illu-teaser';
@import 'elements/img';
@import 'elements/info';
@import 'elements/input-field';
@import 'elements/warning';
Expand Down
52 changes: 28 additions & 24 deletions themes/hugo-docs/layouts/shortcodes/img.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
{{ $src := path.Join .Page.File.Dir (.Get "src") }}
{{ $caption := .Get "caption" }}

{{ if (hasPrefix (.Get "src") "https:") }}
{{ warnf "Please do not link to external images. The image in %s referenced %s" .Position (.Get "src") }}
<img
src="{{ .Get "src" }}"
{{ with .Get "alt" }}alt='{{ . }}'{{ end }}
/>
{{ else if (fileExists $src) }}
{{ $img := imageConfig (path.Join "content" $src) }}
<img
src="{{ $src | relURL }}"
width="{{ (or (.Get "width") $img.Width) }}"
height="{{ (or (.Get "height") $img.Height) }}"
{{ with .Get "alt" }}alt='{{ . }}'{{ end }}
/>
{{ else }}
{{ $notfound := resources.Get "svg/not-found.svg" | fingerprint }}
{{ warnf "Image '%s' not found. It was referenced from %s" $src .Position }}
<img
src="{{ $notfound.RelPermalink }}"
width="600"
height="300"
alt="Image Not Found"
/>
{{ end }}
<figure class="img">
{{ if (hasPrefix (.Get "src") "https:") }}
{{ warnf "Please do not link to external images. The image in %s referenced %s" .Position (.Get "src") }}
<img
src="{{ .Get "src" }}"
{{ with .Get "alt" }}alt='{{ . }}'{{ end }}
/>
{{ else if (fileExists $src) }}
{{ $img := imageConfig (path.Join "content" $src) }}
<img
src="{{ $src | relURL }}"
width="{{ (or (.Get "width") $img.Width) }}"
height="{{ (or (.Get "height") $img.Height) }}"
{{ with .Get "alt" }}alt='{{ . }}'{{ end }}
/>
{{ if $caption }}<figcaption>{{ $caption }}</figcaption>{{ end }}
{{ else }}
{{ $notfound := resources.Get "svg/not-found.svg" | fingerprint }}
{{ warnf "Image '%s' not found. It was referenced from %s" $src .Position }}
<img
src="{{ $notfound.RelPermalink }}"
width="600"
height="300"
alt="Image Not Found"
/>
{{ end }}
</figure>