diff --git a/content/customising/server-configuration/storage/_index.md b/content/customising/server-configuration/storage/_index.md index a24b455fc5..1a66db70f1 100644 --- a/content/customising/server-configuration/storage/_index.md +++ b/content/customising/server-configuration/storage/_index.md @@ -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 diff --git a/content/guides/media-library/2025-behavior/image-editing-adjust.png b/content/guides/media-library/2025-behavior/image-editing-adjust.png new file mode 100644 index 0000000000..b433842bfe Binary files /dev/null and b/content/guides/media-library/2025-behavior/image-editing-adjust.png differ diff --git a/content/guides/media-library/2025-behavior/image-editing-pixelate.png b/content/guides/media-library/2025-behavior/image-editing-pixelate.png new file mode 100644 index 0000000000..d875609808 Binary files /dev/null and b/content/guides/media-library/2025-behavior/image-editing-pixelate.png differ diff --git a/content/guides/media-library/2025-behavior/image-editing-rotate.png b/content/guides/media-library/2025-behavior/image-editing-rotate.png new file mode 100644 index 0000000000..996e85275a Binary files /dev/null and b/content/guides/media-library/2025-behavior/image-editing-rotate.png differ diff --git a/content/guides/media-library/2025-behavior/index.md b/content/guides/media-library/2025-behavior/index.md index 410c69cae2..6bf13bfaa0 100644 --- a/content/guides/media-library/2025-behavior/index.md +++ b/content/guides/media-library/2025-behavior/index.md @@ -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 @@ -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 >}} @@ -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 @@ -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 @@ -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 diff --git a/content/operations/releases/release-2026-03-image-editing-adjust.png b/content/operations/releases/release-2026-03-image-editing-adjust.png new file mode 100644 index 0000000000..b433842bfe Binary files /dev/null and b/content/operations/releases/release-2026-03-image-editing-adjust.png differ diff --git a/content/operations/releases/release-2026-03-image-editing-pixelate.png b/content/operations/releases/release-2026-03-image-editing-pixelate.png new file mode 100644 index 0000000000..d875609808 Binary files /dev/null and b/content/operations/releases/release-2026-03-image-editing-pixelate.png differ diff --git a/content/operations/releases/release-2026-03-image-editing-rotate.png b/content/operations/releases/release-2026-03-image-editing-rotate.png new file mode 100644 index 0000000000..996e85275a Binary files /dev/null and b/content/operations/releases/release-2026-03-image-editing-rotate.png differ diff --git a/content/operations/releases/release-2026-03.md b/content/operations/releases/release-2026-03.md index e6b3ef05b0..5c512dae51 100644 --- a/content/operations/releases/release-2026-03.md +++ b/content/operations/releases/release-2026-03.md @@ -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) @@ -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 @@ -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. @@ -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 diff --git a/themes/hugo-docs/assets/elements/img.scss b/themes/hugo-docs/assets/elements/img.scss new file mode 100644 index 0000000000..9fea0eb1be --- /dev/null +++ b/themes/hugo-docs/assets/elements/img.scss @@ -0,0 +1,8 @@ +figure.img { + margin: $space-m 0; + + figcaption { + @extend %font-s-xs; + color: $color-grey-3; + } +} diff --git a/themes/hugo-docs/assets/style.scss b/themes/hugo-docs/assets/style.scss index a44c586288..d94abdb64b 100644 --- a/themes/hugo-docs/assets/style.scss +++ b/themes/hugo-docs/assets/style.scss @@ -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'; diff --git a/themes/hugo-docs/layouts/shortcodes/img.html b/themes/hugo-docs/layouts/shortcodes/img.html index 8ad94194d7..7bf6034e4f 100644 --- a/themes/hugo-docs/layouts/shortcodes/img.html +++ b/themes/hugo-docs/layouts/shortcodes/img.html @@ -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") }} - -{{ else if (fileExists $src) }} - {{ $img := imageConfig (path.Join "content" $src) }} - -{{ else }} - {{ $notfound := resources.Get "svg/not-found.svg" | fingerprint }} - {{ warnf "Image '%s' not found. It was referenced from %s" $src .Position }} - Image Not Found -{{ end }} +
+ {{ if (hasPrefix (.Get "src") "https:") }} + {{ warnf "Please do not link to external images. The image in %s referenced %s" .Position (.Get "src") }} + + {{ else if (fileExists $src) }} + {{ $img := imageConfig (path.Join "content" $src) }} + + {{ if $caption }}
{{ $caption }}
{{ end }} + {{ else }} + {{ $notfound := resources.Get "svg/not-found.svg" | fingerprint }} + {{ warnf "Image '%s' not found. It was referenced from %s" $src .Position }} + Image Not Found + {{ end }} +