From fd13359ebf249fd981812c870831f8f58f20f334 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 13:21:28 +0000 Subject: [PATCH 01/12] Document URL parts, unresolved URLs and rich-text changes in lib-portal Adds urlBase, pageUrlParts, imageUrlParts, attachmentUrlParts and processHtmlParts with their types, the 404 URLs for pages and media that do not resolve, image styles named by application, macros resolved when processing, and the XP 8.2 deprecations of pageUrl and baseUrl project, branch and content parameters. Links to imageUrl and attachmentUrl now use their anchors. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/images/xp-820.svg | 1 + docs/libraries/lib-portal.adoc | 532 +++++++++++++++++++++++++++++++-- docs/upgrade.adoc | 66 +++- 3 files changed, 579 insertions(+), 20 deletions(-) create mode 100644 docs/images/xp-820.svg diff --git a/docs/images/xp-820.svg b/docs/images/xp-820.svg new file mode 100644 index 00000000..cf359793 --- /dev/null +++ b/docs/images/xp-820.svg @@ -0,0 +1 @@ +XPXP8.2.08.2.0 diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 9acb2ead..78b75856 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -26,6 +26,34 @@ image:xp-810.svg[XP 8.1.0,opts=inline] A configured Base URL is used *verbatim* To serve media from another host, configure `+media.defaultBaseUrl+` — see the https://developer.enonic.com/docs/platform/xp8/config/cms[portal configuration]. +[#url_parts] +=== URL parts + +image:xp-820.svg[XP 8.2.0,opts=inline] The URL functions follow the request: they generate the URL the current request leads to, through its virtual host. A frontend serving content from a host of its own, such as a headless one, needs URLs resolved from configuration alone instead. Each request-following function has a counterpart that returns such a URL as parts to assemble: + +[%header,cols="1,1"] +[frame="none"] +[grid="none"] +|=== +| Follows the request | From configuration alone +| <> | <> +| <> | <> +| <> | <> +| <> | <> +| <> | <> +|=== + +Page URLs and rich text belong to a site, or to a project. <> resolves that site or project once, and its result is passed as `base` to every `pageUrlParts()` and `processHtmlParts()` call of the same request. Its `baseUrl` is the Base URL configured there, or `null`. A page URL is `baseUrl + path + queryString`, where the caller supplies the origin the site is served from when `baseUrl` is `null`, and the path is relative to the site or project. + +A media URL carries its project and branch in its path and belongs to no site, so `imageUrlParts()` and `attachmentUrlParts()` take a `project` and `branch` instead of a base. A media URL is `mediaBaseUrl + path + queryString`, where the caller supplies the root of the media APIs as it serves them; `+media.defaultBaseUrl+` and the mounts of the media APIs do not apply. + +Parts are URL-escaped as they appear in the URL: assemble them without encoding them again. + +[#unresolved_urls] +=== URLs that do not resolve + +image:xp-820.svg[XP 8.2.0,opts=inline] When the content a URL names does not resolve - a missing content, a content that is not an image, an unknown attachment, or a deleted project or branch - <>, <>, <> and <> generate a URL answered with *404 Not Found*, and log a warning. A broken link in rich text therefore leaves the rest of the text intact. In <>, the element of a link or image that does not resolve holds such a URL, and its entry has no parts. + == Functions === apiUrl @@ -143,7 +171,7 @@ Parameters [.lead] Returns -*string* : The generated URL. +*string* : The generated URL; one answered with 404 when the attachment does not resolve — see <>. [.lead] Example @@ -158,14 +186,66 @@ const url = attachmentUrl({ }); ---- +=== attachmentUrlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the parts of an attachment URL from configuration alone: `mediaBaseUrl + path + queryString`, where `mediaBaseUrl` is the root of the media APIs as the caller serves them. See <>. + +The parts also hold the segments of the path - `context`, `id`, `fingerprint` and `name` - for a host that serves the media API with leading segments hidden by a virtual host mapping, such as `+https://cdn.example.com//:/+`. The attachment is picked as in <>. + +[.lead] +Parameters + +`attachmentUrlParts()` takes a single `AttachmentUrlPartsParams` object with these properties: + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| id | string | *Optional.* Id of the content holding the attachment. Either `id` or `path` is required. +| path | string | *Optional.* Path of the content holding the attachment, within the project. +| name | string | *Optional.* Name of the attachment. Picks the attachment by name, before `label`. +| label | string | *Optional.* Label of the attachment, used when `name` is absent. Defaults to `source`. +| download | boolean | *Optional.* Set to `true` to ask for the attachment to be downloaded. Defaults to `false`. +| project | string | *Optional.* Name of the project. Defaults to the project of the current context. +| branch | string | *Optional.* Name of the branch. Defaults to the branch of the current context. +| params | object | *Optional.* Custom query parameters of the URL. +|=== + +[.lead] +Returns + +*object* : (<>) The parts of the URL. + +[.lead] +Example + +[source,typescript] +---- +import {attachmentUrlParts} from '/lib/xp/portal'; + +// Parts of the URL of an attachment, to be downloaded +const parts = attachmentUrlParts({ + path: '/my-site/documents/report', + download: true, + project: 'myproject', + branch: 'draft' +}); + +// The root of the media APIs as the frontend serves them +const url = 'https://cdn.example.com/api' + parts.path + parts.queryString; +---- + === baseUrl -Generates a base URL. +Generates the base URL of the current site request: the address of the site - or project - the matched virtual host mapping points at, rewritten for that host. Page URLs following the request are relative to it, and so are routes the site serves outside the content tree, such as <<../web/sites/mappings#, mappings>>. Without a site request, it is the Base URL configured for the project of the current context, or else the address the site engine serves the project at. + +For the Base URL configured for a site or project, independent of the request, use <>. [.lead] Parameters -`baseUrl()` takes a single `BaseUrlParams` object with these properties: +`baseUrl()` takes a single, optional `BaseUrlParams` object with these properties: [%header,cols="1%,1%,98%a"] [frame="none"] @@ -173,10 +253,10 @@ Parameters |=== | Name | Type | Description | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute` or `websocket`. Default is `server`. Ignored when the site has a configured Base URL — see <>. -| id | string | *Optional.* ID of the content. -| path | string | *Optional.* Path to the content. -| project | string | *Optional.* Name of the project to use for resolving the URL. -| branch | string | *Optional.* Name of the branch to use for resolving the URL. +| id | string | *Deprecated.* Use <> with `key` instead. +| path | string | *Deprecated.* Use <> with `key` instead. +| project | string | *Deprecated.* Use <> with `project` instead. +| branch | string | *Deprecated.* Use <> with `branch` instead. |=== [.lead] @@ -191,11 +271,13 @@ Example ---- import {baseUrl} from '/lib/xp/portal'; -const url = baseUrl({ - type: 'server', - path: '/path', - project: 'explicit-project', - branch: 'explicit-branch' +// The address the current site request is served from: routes of the site are relative to it +const base = baseUrl(); +const searchUrl = base + '/search'; + +// The same, as an absolute URL, for a canonical link +const absoluteBase = baseUrl({ + type: 'absolute' }); ---- @@ -724,7 +806,7 @@ const expected = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACb [[imageUrl]] === imageUrl -Generates URL to an image. +Generates URL to an image: an image or a vector image. [.lead] Parameters @@ -753,7 +835,7 @@ Parameters [.lead] Returns -*string* : The generated URL. +*string* : The generated URL; one answered with 404 when the image does not resolve — see <>. [.lead] Example @@ -770,6 +852,61 @@ const url = imageUrl({ }); ---- +=== imageUrlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the parts of an image URL from configuration alone: `mediaBaseUrl + path + queryString`, where `mediaBaseUrl` is the root of the media APIs as the caller serves them. See <>. + +The parts also hold the segments of the path - `context`, `id`, `fingerprint`, `scale` and `name` - for a host that serves the media API with leading segments hidden by a virtual host mapping, such as `+https://img.example.com//://+`. + +The content has to be an image or a vector image; for other content an error is raised. An image the image API serves as stored, such as a vector image, has a single URL, with the `full` scale and none of the processing parameters. + +[.lead] +Parameters + +`imageUrlParts()` takes a single `ImageUrlPartsParams` object with these properties: + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| id | string | *Optional.* ID of the image content. Either `id` or `path` is required. +| path | string | *Optional.* Path of the image content within the project. +| scale | string | Options are `width(px)`, `height(px)`, `block(width,height)`, `square(px)`, `max(px)`, `wide(width,height)` and `full`. +| quality | number | *Optional.* Quality for JPEG images, ranges from 0 (max compression) to 100 (min compression). Defaults to `85`. +| background | string | *Optional.* Background color. +| format | string | *Optional.* Format of the image. +| filter | string | *Optional.* Filters to alter the image appearance, for example, `blur(3)`, `grayscale()`, `rounded(5)`. +| project | string | *Optional.* Name of the project. Defaults to the project of the current context. +| branch | string | *Optional.* Name of the branch. Defaults to the branch of the current context. +| params | object | *Optional.* Custom query parameters of the URL. +|=== + +[.lead] +Returns + +*object* : (<>) The parts of the URL. + +[.lead] +Example + +[source,typescript] +---- +import {imageUrlParts} from '/lib/xp/portal'; + +// Parts of the URL of a scaled image +const parts = imageUrlParts({ + id: '11cc4e09-0d9d-4a4d-9a4b-1a3a8c2b6f3e', + scale: 'block(1024,768)', + quality: 85, + project: 'myproject', + branch: 'master' +}); + +// The root of the media APIs as the frontend serves them +const url = 'https://cdn.example.com/api' + parts.path + parts.queryString; +---- + === loginUrl Generates URL to the login endpoint of an ID provider. @@ -836,15 +973,15 @@ Parameters | id | string | *Optional.* Id of a content. If id is set, then path is not used. | path | string | *Optional.* Path of a content. Relative paths are resolved based on the current context. | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute` or `websocket`. Default is `server`. Ignored when the site has a configured Base URL — see <>. -| project | string | *Optional.* Project of the context. -| branch | string | *Optional.* Branch of the project for context. +| project | string | *Deprecated.* Use <> with a `base` from <> with `project` instead. +| branch | string | *Deprecated.* Use <> with a `base` from <> with `branch` instead. | params | object | *Optional.* Custom query parameters to append to the URL. |=== [.lead] Returns -*string* : The generated URL. +*string* : The generated URL; one answered with 404 when the page does not resolve — see <>. [.lead] Example @@ -863,12 +1000,72 @@ const url = pageUrl({ }); ---- +=== pageUrlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the parts of a page URL from configuration alone, for the site or project `base` stands for: `baseUrl + path + queryString`. See <>. + +The page has to be inside the site or project `base` stands for, or be it; for a page elsewhere an error is raised. + +[.lead] +Parameters + +`pageUrlParts()` takes a single `PageUrlPartsParams` object with these properties: + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| id | string | *Optional.* Id of the page. Either `id` or `path` is required. +| path | string | *Optional.* Path of the page within the project. +| base | <> | *Optional.* The site or project the URL belongs to, resolved by <>; the page is looked up in its project and branch. Defaults to the project of the current context. +| params | object | *Optional.* Custom query parameters of the URL. +|=== + +[.lead] +Returns + +*object* : (<>) The parts of the URL. + +[.lead] +Example + +[source,typescript] +---- +import {pageUrlParts, urlBase} from '/lib/xp/portal'; + +// The site the URLs belong to: resolve it once, and pass it to every call of the same request +const base = urlBase({ + key: '/my-site', + project: 'myproject', + branch: 'master' +}); + +// Parts of the URL of a page, relative to the site it belongs to +const parts = pageUrlParts({ + path: '/my-site/posts/first-post', + base, + params: { + a: 1 + } +}); + +// The site's configured Base URL, or the origin the frontend serves the site from +const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.queryString; +---- + === processHtml [#processHtml] Resolves internal links to images and internal content items contained in an HTML text and replaces them with correct URLs. It will also process embedded macros. +image:xp-820.svg[XP 8.2.0,opts=inline] An image names its style in the `style` parameter of its `+image://+` link: `:` for the style of that application, or a name alone for the first style of that name. Styles come from the system application and the applications configured on the site. + +image:xp-820.svg[XP 8.2.0,opts=inline] A macro is resolved when the HTML is processed, among the applications configured on the site - by its name, then ignoring case - and then among the built-in macros; a macro no application provides stays as written. Post-processing instructions already present in the HTML are not executed. + +A link or image that does not resolve gets a URL answered with 404 — see <>. + TIP: When outputting processed HTML in Thymeleaf, use attribute `data-th-utext="${processedHtml}"`. [.lead] @@ -883,8 +1080,8 @@ Parameters | Name | Type | Description | value | string | Html value string to process. | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute`. Default is `server`. -| imageWidths | number[] | *Optional.* A comma-separated list of image widths. If this parameter is provided, all `++` tags will have an additional `srcset` attribute with image URLs generated for specified widths. -| imageSizes | string | *Optional.* Specifies the width for an image depending on browser dimensions. The value has the following format: `(media-condition) width`. Multiple sizes are comma-separated. +| imageWidths | number[] | *Optional.* A comma-separated list of image widths. If this parameter is provided, the `++` tags of images the image API scales will have an additional `srcset` attribute with image URLs generated for specified widths. +| imageSizes | string | *Optional.* Specifies the width for an image depending on browser dimensions. The value has the following format: `(media-condition) width`. Multiple sizes are comma-separated. Written only along with the `srcset` that `imageWidths` adds. |=== [.lead] @@ -909,6 +1106,77 @@ const html = processHtml({ }); ---- +=== processHtmlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Processes an HTML text like <>, from configuration alone, for the site or project `base` stands for, and returns the parts of every internal link, image and macro in it. See <>. + +Everything is resolved for the base: the Base URL, the path each content link is relative to, the project and branch contents and media are looked up in, and the applications image styles and macros come from. + +Internal links, images and macros in the returned HTML are placeholders, which the caller renders from their entries: + +* a link carries a `data-link-ref` attribute, and an image a `data-image-ref` attribute, naming its entry in `links` or `images`; +* a macro an application of the base provides becomes an `editor-macro` element holding its body, with a `data-macro-name` attribute holding the name of its descriptor and a `data-macro-ref` attribute naming its entry in `macros`. Other macros stay as written. + +A link or image that does not resolve keeps its ref, and its entry has no parts, so the caller decides how to render it. + +[.lead] +Parameters + +`processHtmlParts()` takes a single `ProcessHtmlPartsParams` object with these properties: + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| value | string | Html value string to process. +| base | <> | *Optional.* The site or project the HTML belongs to, resolved by <>. Defaults to the project of the current context. +| imageWidths | number[] | *Optional.* Image widths for the `srcset` attribute of `++` tags, for images the image API scales. +| imageSizes | string | *Optional.* Value of the `sizes` attribute of `++` tags. Written only along with the `srcset` that `imageWidths` adds. +|=== + +[.lead] +Returns + +*object* : (<>) The processed HTML, with the parts of its links and images, and its macros. + +[.lead] +Example + +[source,typescript] +---- +import {processHtmlParts, urlBase} from '/lib/xp/portal'; + +const base = urlBase({ + key: '/my-site', + project: 'myproject', + branch: 'master' +}); + +const result = processHtmlParts({ + value: 'Post[youtube videoid="abc"/]', + base +}); + +// The site's configured Base URL, or the origin the frontend serves the site from +const origin = result.baseUrl ?? 'https://www.example.com'; + +for (const link of result.links) { + // a link that does not resolve has no parts + if (link.type === 'content' && link.page) { + const href = origin + link.page.path + link.page.queryString + (link.fragment ? `#${link.fragment}` : ''); + // set href on the element whose data-link-ref is link.ref + } +} + +for (const macro of result.macros) { + if (macro.descriptor === 'com.example.myapp:youtube') { + const embedUrl = 'https://www.youtube.com/embed/' + macro.params.videoId[0]; + // render the editor-macro element whose data-macro-ref is macro.ref + } +} +---- + === sanitizeHtml Sanitizes an HTML string by stripping all potentially unsafe tags and attributes. @@ -1027,8 +1295,234 @@ const url = buildUrl({ }); ---- +=== urlBase + +image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the site - or the project - URLs belong to, from configuration alone, for <> and <>. Resolve it once and pass it as `base` to every call of the same request. See <>. + +[.lead] +Parameters + +`urlBase()` takes a single, optional `UrlBaseParams` object with these properties: + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| key | string | *Optional.* Id or path of the site, or of a content inside it. A key starting with `/` is a path. Defaults to `/`, which selects the project. +| project | string | *Optional.* Name of the project. Defaults to the project of the current context. +| branch | string | *Optional.* Name of the branch. Defaults to the branch of the current context. +|=== + +[.lead] +Returns + +*object* : (<>) The resolved site or project. + +[.lead] +Example + +[source,typescript] +---- +import {pageUrlParts, urlBase} from '/lib/xp/portal'; + +// The site URLs belong to, resolved once for every URL of the same request +const base = urlBase({ + key: '/my-site', + project: 'myproject', + branch: 'master' +}); + +// The site's configured Base URL, or the origin the frontend serves the site from +const origin = base.baseUrl ?? 'https://www.example.com'; + +const post = pageUrlParts({path: '/my-site/posts/first-post', base}); +const url = origin + post.path + post.queryString; +---- + == Type Definitions +[#url-base] +=== UrlBase + +image:xp-820.svg[XP 8.2.0,opts=inline] The site or project URLs belong to, resolved by <>. Pass it as is to <> and <>. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| baseUrl | string \| null | Base URL configured for the site or project, without a trailing slash; `null` when none is configured. +|=== + +[#page-url-parts] +=== PageUrlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Parts of a page URL: `baseUrl + path + queryString`. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| baseUrl | string \| null | Base URL configured for the site or project the URL belongs to, without a trailing slash; `null` when none is configured. +| path | string | URL-escaped content path relative to that site or project, with a leading slash; empty for the site itself, so that `baseUrl + path` is the Base URL. +| queryString | string | URL-escaped query string prefixed with `?`; empty when there are no parameters. +|=== + +[#image-url-parts] +=== ImageUrlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Parts of an image URL: `mediaBaseUrl + path + queryString`, with the media base supplied by the caller. All values are URL-escaped as they appear in the URL. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| path | string | The media API path with a leading slash: `+/media:image//://+`. +| queryString | string | Query string prefixed with `?`; empty when there are no parameters. +| context | string | Project context segment: `` on the master branch, `:` otherwise. +| id | string | Content id. +| fingerprint | string \| null | Media fingerprint, joined with the id as `:` in the path. +| scale | string | Scale segment, for example `max-300`. +| name | string | File name segment, with the requested format extension applied. +|=== + +[#attachment-url-parts] +=== AttachmentUrlParts + +image:xp-820.svg[XP 8.2.0,opts=inline] Parts of an attachment URL: `mediaBaseUrl + path + queryString`, with the media base supplied by the caller. All values are URL-escaped as they appear in the URL. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| path | string | The media API path with a leading slash: `+/media:attachment//:/+`. +| queryString | string | Query string prefixed with `?`, holding `download` when requested; empty when there are no parameters. +| context | string | Project context segment: `` on the master branch, `:` otherwise. +| id | string | Content id. +| fingerprint | string \| null | Media fingerprint, joined with the id as `:` in the path. +| name | string | Attachment file name segment. +|=== + +[#processed-html] +=== ProcessedHtml + +image:xp-820.svg[XP 8.2.0,opts=inline] Result of <>. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| html | string | The processed HTML, with placeholders for its internal links, images and macros. +| baseUrl | string \| null | Base URL configured for the site or project the HTML belongs to, without a trailing slash; `null` when none is configured. It is the `baseUrl` of the page parts of every content link. +| links | (<> \| <>)[] | Internal links to contents and attachments, in document order, told apart by their `type`. +| images | <>[] | Internal images, in document order. +| macros | <>[] | Macros, in document order. +|=== + +[#processed-html-content-link] +=== ProcessedHtmlContentLink + +image:xp-820.svg[XP 8.2.0,opts=inline] A link to a content page, written as `content://`. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| type | string | `content`. +| ref | string | Value of the `data-link-ref` attribute of the element. +| uri | string | The link as written in the HTML. +| contentId | string | Id of the linked content. +| page | <> \| null | Parts of the page URL, with the query string the link carries; `null` when the link does not resolve. +| fragment | string \| null | Fragment of the link, without `#`; `null` when it has none. +|=== + +[#processed-html-attachment-link] +=== ProcessedHtmlAttachmentLink + +image:xp-820.svg[XP 8.2.0,opts=inline] A link to the attachment of a media content, written as `media:///`. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| type | string | `attachment`. +| ref | string | Value of the `data-link-ref` attribute of the element. +| uri | string | The link as written in the HTML. +| contentId | string | Id of the media content. +| attachment | <> \| null | Parts of the attachment URL; `null` when the link does not resolve. +| download | boolean | Whether the link asks for the attachment to be downloaded. +|=== + +[#processed-html-image] +=== ProcessedHtmlImage + +image:xp-820.svg[XP 8.2.0,opts=inline] An internal image. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| ref | string | Value of the `data-image-ref` attribute of the element. +| contentId | string | Id of the image content. +| style | <> \| null | The image style applied; `null` for none. +| src | <> \| null | Parts of the URL in `src`; `null` when the image does not resolve. +| srcset | <>[] | Parts of the URLs in `srcset`, one for each image width; empty for an image the image API serves as stored. +|=== + +[#processed-html-image-style] +=== ProcessedHtmlImageStyle + +image:xp-820.svg[XP 8.2.0,opts=inline] The style applied to an image. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| application | string | Key of the application whose style descriptor holds the style. +| name | string | Name of the style in that style descriptor. +| aspectRatio | string \| null | Aspect ratio the image is cropped to, such as `16:9`; `null` for none. +| filter | string \| null | Image filter; `null` for none. +|=== + +[#processed-html-image-source] +=== ProcessedHtmlImageSource + +image:xp-820.svg[XP 8.2.0,opts=inline] A `srcset` candidate of an image. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| width | number | Width in pixels, the `w` descriptor of the candidate. +| url | <> | Parts of the URL of the image at that width. +|=== + +[#processed-html-macro] +=== ProcessedHtmlMacro + +image:xp-820.svg[XP 8.2.0,opts=inline] A macro, resolved among the applications of the site or project the HTML belongs to. + +[%header,cols="1%,1%,98%a"] +[frame="none"] +[grid="none"] +|=== +| Name | Type | Description +| ref | string | Value of the `data-macro-ref` attribute of the `editor-macro` element. +| descriptor | string | Key of the macro descriptor, such as `system:embed`. +| params | object | Parameters of the macro, each a list of its values in the order written. A parameter matching an input of the descriptor's form, ignoring case, is named as that input. +| body | string | Body of the macro as written; empty for a macro without one. +|=== + [#site] === Site diff --git a/docs/upgrade.adoc b/docs/upgrade.adoc index bc4a9605..3ed31782 100644 --- a/docs/upgrade.adoc +++ b/docs/upgrade.adoc @@ -47,6 +47,70 @@ The app builds and runs. What follows is work that can be done at your own pace. XP 8.0 is the baseline for this documentation. The <> covers changes to the build system, descriptors, application code and APIs when moving from XP 7. +[#xp-8-2] +== XP 8.2 + +No breaking changes for JavaScript application code. Every API deprecated in 8.2 still works, and every replacement ships with 8.2.0, apart from the Java macro instruction noted below. + +=== Deprecations + +[#xp-8-2-lib-portal] +==== lib-portal + +Setting a project or branch on a request-following URL function switched it from the request to configuration, even for the request's own project, so the URL left the virtual host the request came through. These parameters are deprecated in favour of the <> functions, which resolve URLs from configuration alone: + +* `project` and `branch` on <>: use <> with a `base` from <>. +* `id`, `path`, `project` and `branch` on <>: use <>. `baseUrl()` keeps `type`, and without parameters it is the base URL of the current site request. + +.Before +[source,typescript] +---- +import {pageUrl} from '/lib/xp/portal'; + +const url = pageUrl({ + path: '/my-site/posts/first-post', + project: 'myproject', + branch: 'master', + type: 'absolute' +}); +---- + +.After +[source,typescript] +---- +import {pageUrlParts, urlBase} from '/lib/xp/portal'; + +const base = urlBase({key: '/my-site', project: 'myproject', branch: 'master'}); +const parts = pageUrlParts({path: '/my-site/posts/first-post', base}); +const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.queryString; +---- + +The `project` and `branch` of <> and <> stay: a media URL carries them in its path. + +[#xp-8-2-java] +==== Java APIs + +`MacroService.postProcessInstructionSerialize()` is deprecated, and post-processing no longer executes the instructions it writes: an instruction runs only when it names the macro descriptor it was resolved to, which `processHtml()` writes for the macros of the HTML it processes. Process rich text with `processHtml()` instead of serializing macro instructions yourself. + +=== Behaviour changes + +URLs that do not resolve:: +<>, <>, <> and <> generate a URL answered with 404, rather than one answered with 500, when the content it names does not resolve. See <>. + +Rich text images:: +`processHtml()` writes `sizes` only along with a `srcset` of at least one width, and an image the image API serves as stored gets no `srcset`. + +Rich text macros:: +A macro in rich text is resolved when `processHtml()` processes it, and no longer needs a site request to render. A macro no application provides stays as written, and post-processing instructions already present in the HTML are not executed. + +=== Worth adopting + +URLs for a frontend of your own:: +A frontend that serves content from a host of its own builds its URLs from the <> functions: <>, <>, <> and <>, with the site or project resolved once by <>. + +Image styles of a given application:: +An image in rich text can name its style as `:`, where several applications define styles of the same name. + [#xp-8-1] == XP 8.1 @@ -173,7 +237,7 @@ Two differences to plan for. A named task takes its input as `config`, validated [#xp-8-1-lib-portal] ==== lib-portal -The `baseUrl` parameter is deprecated on <>, <> and <>. Where media or an API is served from is a deployment decision, and passing it per call means every call site has to agree on it. +The `baseUrl` parameter is deprecated on <>, <> and <>. Where media or an API is served from is a deployment decision, and passing it per call means every call site has to agree on it. .Before [source,typescript] From ab7aa24d8ed70a265037177c0bf09c3a6e770ce6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 13:46:55 +0000 Subject: [PATCH 02/12] Move the URL parts documentation to XP 8.1 The change lands in XP 8.1.0: badge the new functions and types 8.1.0, and fold the upgrade notes into the XP 8.1 section. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/images/xp-820.svg | 1 - docs/libraries/lib-portal.adoc | 40 +++++------ docs/upgrade.adoc | 117 +++++++++++++++------------------ 3 files changed, 72 insertions(+), 86 deletions(-) delete mode 100644 docs/images/xp-820.svg diff --git a/docs/images/xp-820.svg b/docs/images/xp-820.svg deleted file mode 100644 index cf359793..00000000 --- a/docs/images/xp-820.svg +++ /dev/null @@ -1 +0,0 @@ -XPXP8.2.08.2.0 diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 78b75856..81219103 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -29,7 +29,7 @@ To serve media from another host, configure `+media.defaultBaseUrl+` — see the [#url_parts] === URL parts -image:xp-820.svg[XP 8.2.0,opts=inline] The URL functions follow the request: they generate the URL the current request leads to, through its virtual host. A frontend serving content from a host of its own, such as a headless one, needs URLs resolved from configuration alone instead. Each request-following function has a counterpart that returns such a URL as parts to assemble: +image:xp-810.svg[XP 8.1.0,opts=inline] The URL functions follow the request: they generate the URL the current request leads to, through its virtual host. A frontend serving content from a host of its own, such as a headless one, needs URLs resolved from configuration alone instead. Each request-following function has a counterpart that returns such a URL as parts to assemble: [%header,cols="1,1"] [frame="none"] @@ -52,7 +52,7 @@ Parts are URL-escaped as they appear in the URL: assemble them without encoding [#unresolved_urls] === URLs that do not resolve -image:xp-820.svg[XP 8.2.0,opts=inline] When the content a URL names does not resolve - a missing content, a content that is not an image, an unknown attachment, or a deleted project or branch - <>, <>, <> and <> generate a URL answered with *404 Not Found*, and log a warning. A broken link in rich text therefore leaves the rest of the text intact. In <>, the element of a link or image that does not resolve holds such a URL, and its entry has no parts. +image:xp-810.svg[XP 8.1.0,opts=inline] When the content a URL names does not resolve - a missing content, a content that is not an image, an unknown attachment, or a deleted project or branch - <>, <>, <> and <> generate a URL answered with *404 Not Found*, and log a warning. A broken link in rich text therefore leaves the rest of the text intact. In <>, the element of a link or image that does not resolve holds such a URL, and its entry has no parts. == Functions @@ -188,7 +188,7 @@ const url = attachmentUrl({ === attachmentUrlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the parts of an attachment URL from configuration alone: `mediaBaseUrl + path + queryString`, where `mediaBaseUrl` is the root of the media APIs as the caller serves them. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the parts of an attachment URL from configuration alone: `mediaBaseUrl + path + queryString`, where `mediaBaseUrl` is the root of the media APIs as the caller serves them. See <>. The parts also hold the segments of the path - `context`, `id`, `fingerprint` and `name` - for a host that serves the media API with leading segments hidden by a virtual host mapping, such as `+https://cdn.example.com//:/+`. The attachment is picked as in <>. @@ -854,7 +854,7 @@ const url = imageUrl({ === imageUrlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the parts of an image URL from configuration alone: `mediaBaseUrl + path + queryString`, where `mediaBaseUrl` is the root of the media APIs as the caller serves them. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the parts of an image URL from configuration alone: `mediaBaseUrl + path + queryString`, where `mediaBaseUrl` is the root of the media APIs as the caller serves them. See <>. The parts also hold the segments of the path - `context`, `id`, `fingerprint`, `scale` and `name` - for a host that serves the media API with leading segments hidden by a virtual host mapping, such as `+https://img.example.com//://+`. @@ -1002,7 +1002,7 @@ const url = pageUrl({ === pageUrlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the parts of a page URL from configuration alone, for the site or project `base` stands for: `baseUrl + path + queryString`. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the parts of a page URL from configuration alone, for the site or project `base` stands for: `baseUrl + path + queryString`. See <>. The page has to be inside the site or project `base` stands for, or be it; for a page elsewhere an error is raised. @@ -1060,9 +1060,9 @@ const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.qu Resolves internal links to images and internal content items contained in an HTML text and replaces them with correct URLs. It will also process embedded macros. -image:xp-820.svg[XP 8.2.0,opts=inline] An image names its style in the `style` parameter of its `+image://+` link: `:` for the style of that application, or a name alone for the first style of that name. Styles come from the system application and the applications configured on the site. +image:xp-810.svg[XP 8.1.0,opts=inline] An image names its style in the `style` parameter of its `+image://+` link: `:` for the style of that application, or a name alone for the first style of that name. Styles come from the system application and the applications configured on the site. -image:xp-820.svg[XP 8.2.0,opts=inline] A macro is resolved when the HTML is processed, among the applications configured on the site - by its name, then ignoring case - and then among the built-in macros; a macro no application provides stays as written. Post-processing instructions already present in the HTML are not executed. +image:xp-810.svg[XP 8.1.0,opts=inline] A macro is resolved when the HTML is processed, among the applications configured on the site - by its name, then ignoring case - and then among the built-in macros; a macro no application provides stays as written. Post-processing instructions already present in the HTML are not executed. A link or image that does not resolve gets a URL answered with 404 — see <>. @@ -1108,7 +1108,7 @@ const html = processHtml({ === processHtmlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Processes an HTML text like <>, from configuration alone, for the site or project `base` stands for, and returns the parts of every internal link, image and macro in it. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Processes an HTML text like <>, from configuration alone, for the site or project `base` stands for, and returns the parts of every internal link, image and macro in it. See <>. Everything is resolved for the base: the Base URL, the path each content link is relative to, the project and branch contents and media are looked up in, and the applications image styles and macros come from. @@ -1297,7 +1297,7 @@ const url = buildUrl({ === urlBase -image:xp-820.svg[XP 8.2.0,opts=inline] Resolves the site - or the project - URLs belong to, from configuration alone, for <> and <>. Resolve it once and pass it as `base` to every call of the same request. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the site - or the project - URLs belong to, from configuration alone, for <> and <>. Resolve it once and pass it as `base` to every call of the same request. See <>. [.lead] Parameters @@ -1345,7 +1345,7 @@ const url = origin + post.path + post.queryString; [#url-base] === UrlBase -image:xp-820.svg[XP 8.2.0,opts=inline] The site or project URLs belong to, resolved by <>. Pass it as is to <> and <>. +image:xp-810.svg[XP 8.1.0,opts=inline] The site or project URLs belong to, resolved by <>. Pass it as is to <> and <>. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1358,7 +1358,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] The site or project URLs belong to, resol [#page-url-parts] === PageUrlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Parts of a page URL: `baseUrl + path + queryString`. +image:xp-810.svg[XP 8.1.0,opts=inline] Parts of a page URL: `baseUrl + path + queryString`. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1373,7 +1373,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] Parts of a page URL: `baseUrl + path + qu [#image-url-parts] === ImageUrlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Parts of an image URL: `mediaBaseUrl + path + queryString`, with the media base supplied by the caller. All values are URL-escaped as they appear in the URL. +image:xp-810.svg[XP 8.1.0,opts=inline] Parts of an image URL: `mediaBaseUrl + path + queryString`, with the media base supplied by the caller. All values are URL-escaped as they appear in the URL. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1392,7 +1392,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] Parts of an image URL: `mediaBaseUrl + pa [#attachment-url-parts] === AttachmentUrlParts -image:xp-820.svg[XP 8.2.0,opts=inline] Parts of an attachment URL: `mediaBaseUrl + path + queryString`, with the media base supplied by the caller. All values are URL-escaped as they appear in the URL. +image:xp-810.svg[XP 8.1.0,opts=inline] Parts of an attachment URL: `mediaBaseUrl + path + queryString`, with the media base supplied by the caller. All values are URL-escaped as they appear in the URL. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1410,7 +1410,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] Parts of an attachment URL: `mediaBaseUrl [#processed-html] === ProcessedHtml -image:xp-820.svg[XP 8.2.0,opts=inline] Result of <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Result of <>. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1427,7 +1427,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] Result of <`. +image:xp-810.svg[XP 8.1.0,opts=inline] A link to a content page, written as `content://`. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1445,7 +1445,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] A link to a content page, written as `con [#processed-html-attachment-link] === ProcessedHtmlAttachmentLink -image:xp-820.svg[XP 8.2.0,opts=inline] A link to the attachment of a media content, written as `media:///`. +image:xp-810.svg[XP 8.1.0,opts=inline] A link to the attachment of a media content, written as `media:///`. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1463,7 +1463,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] A link to the attachment of a media conte [#processed-html-image] === ProcessedHtmlImage -image:xp-820.svg[XP 8.2.0,opts=inline] An internal image. +image:xp-810.svg[XP 8.1.0,opts=inline] An internal image. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1480,7 +1480,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] An internal image. [#processed-html-image-style] === ProcessedHtmlImageStyle -image:xp-820.svg[XP 8.2.0,opts=inline] The style applied to an image. +image:xp-810.svg[XP 8.1.0,opts=inline] The style applied to an image. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1496,7 +1496,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] The style applied to an image. [#processed-html-image-source] === ProcessedHtmlImageSource -image:xp-820.svg[XP 8.2.0,opts=inline] A `srcset` candidate of an image. +image:xp-810.svg[XP 8.1.0,opts=inline] A `srcset` candidate of an image. [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1510,7 +1510,7 @@ image:xp-820.svg[XP 8.2.0,opts=inline] A `srcset` candidate of an image. [#processed-html-macro] === ProcessedHtmlMacro -image:xp-820.svg[XP 8.2.0,opts=inline] A macro, resolved among the applications of the site or project the HTML belongs to. +image:xp-810.svg[XP 8.1.0,opts=inline] A macro, resolved among the applications of the site or project the HTML belongs to. [%header,cols="1%,1%,98%a"] [frame="none"] diff --git a/docs/upgrade.adoc b/docs/upgrade.adoc index 3ed31782..d7619840 100644 --- a/docs/upgrade.adoc +++ b/docs/upgrade.adoc @@ -47,74 +47,10 @@ The app builds and runs. What follows is work that can be done at your own pace. XP 8.0 is the baseline for this documentation. The <> covers changes to the build system, descriptors, application code and APIs when moving from XP 7. -[#xp-8-2] -== XP 8.2 - -No breaking changes for JavaScript application code. Every API deprecated in 8.2 still works, and every replacement ships with 8.2.0, apart from the Java macro instruction noted below. - -=== Deprecations - -[#xp-8-2-lib-portal] -==== lib-portal - -Setting a project or branch on a request-following URL function switched it from the request to configuration, even for the request's own project, so the URL left the virtual host the request came through. These parameters are deprecated in favour of the <> functions, which resolve URLs from configuration alone: - -* `project` and `branch` on <>: use <> with a `base` from <>. -* `id`, `path`, `project` and `branch` on <>: use <>. `baseUrl()` keeps `type`, and without parameters it is the base URL of the current site request. - -.Before -[source,typescript] ----- -import {pageUrl} from '/lib/xp/portal'; - -const url = pageUrl({ - path: '/my-site/posts/first-post', - project: 'myproject', - branch: 'master', - type: 'absolute' -}); ----- - -.After -[source,typescript] ----- -import {pageUrlParts, urlBase} from '/lib/xp/portal'; - -const base = urlBase({key: '/my-site', project: 'myproject', branch: 'master'}); -const parts = pageUrlParts({path: '/my-site/posts/first-post', base}); -const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.queryString; ----- - -The `project` and `branch` of <> and <> stay: a media URL carries them in its path. - -[#xp-8-2-java] -==== Java APIs - -`MacroService.postProcessInstructionSerialize()` is deprecated, and post-processing no longer executes the instructions it writes: an instruction runs only when it names the macro descriptor it was resolved to, which `processHtml()` writes for the macros of the HTML it processes. Process rich text with `processHtml()` instead of serializing macro instructions yourself. - -=== Behaviour changes - -URLs that do not resolve:: -<>, <>, <> and <> generate a URL answered with 404, rather than one answered with 500, when the content it names does not resolve. See <>. - -Rich text images:: -`processHtml()` writes `sizes` only along with a `srcset` of at least one width, and an image the image API serves as stored gets no `srcset`. - -Rich text macros:: -A macro in rich text is resolved when `processHtml()` processes it, and no longer needs a site request to render. A macro no application provides stays as written, and post-processing instructions already present in the HTML are not executed. - -=== Worth adopting - -URLs for a frontend of your own:: -A frontend that serves content from a host of its own builds its URLs from the <> functions: <>, <>, <> and <>, with the site or project resolved once by <>. - -Image styles of a given application:: -An image in rich text can name its style as `:`, where several applications define styles of the same name. - [#xp-8-1] == XP 8.1 -No breaking changes for application code. Every API deprecated in 8.1 still works, and every replacement ships with 8.1.0. +No breaking changes for JavaScript application code. Every API deprecated in 8.1 still works, and every replacement ships with 8.1.0, apart from the Java macro instruction noted below. === Deprecations @@ -283,6 +219,36 @@ See https://developer.enonic.com/docs/platform/stable/config/vhosts#api-location Both are read at URL generation, so the same code produces the right URL in every environment - and in a preview, a task or a headless client, where there may be no request to anchor to. +Setting a project or branch on a request-following URL function switched it from the request to configuration, even for the request's own project, so the URL left the virtual host the request came through. These parameters are deprecated in favour of the <> functions, which resolve URLs from configuration alone: + +* `project` and `branch` on <>: use <> with a `base` from <>. +* `id`, `path`, `project` and `branch` on <>: use <>. `baseUrl()` keeps `type`, and without parameters it is the base URL of the current site request. + +.Before +[source,typescript] +---- +import {pageUrl} from '/lib/xp/portal'; + +const url = pageUrl({ + path: '/my-site/posts/first-post', + project: 'myproject', + branch: 'master', + type: 'absolute' +}); +---- + +.After +[source,typescript] +---- +import {pageUrlParts, urlBase} from '/lib/xp/portal'; + +const base = urlBase({key: '/my-site', project: 'myproject', branch: 'master'}); +const parts = pageUrlParts({path: '/my-site/posts/first-post', base}); +const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.queryString; +---- + +The `project` and `branch` of <> and <> stay: a media URL carries them in its path. + [#xp-8-1-lib-export] ==== lib-export @@ -313,6 +279,21 @@ There is no drop-in replacement, and no general rule - what to key on depends on `ApplicationListener` is deprecated. It misses applications that were already active when the listener registered. Track `Application` services with a service tracker instead: `addingService` replays every active application on open and delivers future ones, and `removedService` is delivered before any replacement is registered. +==== MacroService + +`MacroService.postProcessInstructionSerialize()` is deprecated, and post-processing no longer executes the instructions it writes: an instruction runs only when it names the macro descriptor it was resolved to, which `processHtml()` writes for the macros of the HTML it processes. Process rich text with `processHtml()` instead of serializing macro instructions yourself. + +=== Behaviour changes + +URLs that do not resolve:: +<>, <>, <> and <> generate a URL answered with 404, rather than one answered with 500, when the content it names does not resolve. See <>. + +Rich text images:: +`processHtml()` writes `sizes` only along with a `srcset` of at least one width, and an image the image API serves as stored gets no `srcset`. + +Rich text macros:: +A macro in rich text is resolved when `processHtml()` processes it, and no longer needs a site request to render. A macro no application provides stays as written, and post-processing instructions already present in the HTML are not executed. + === Worth adopting None of these are deprecations. They are places where 8.1 offers a supported way to do something apps have had to work around. @@ -328,3 +309,9 @@ Initialization guards:: Disposer registration:: `+__.disposer()+` registrations are remembered when they are made while the application is starting - from `main.ts`, or a module it loads during start-up. Registering one later is not dependable; move such registrations into start-up. + +URLs for a frontend of your own:: +A frontend that serves content from a host of its own builds its URLs from the <> functions: <>, <>, <> and <>, with the site or project resolved once by <>. + +Image styles of a given application:: +An image in rich text can name its style as `:`, where several applications define styles of the same name. From b88cb8114fa59d7eb23f064b0ca751e2435f4604 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 15:03:27 +0000 Subject: [PATCH 03/12] Drop the explanation of the media project and branch parameters Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 2 +- docs/upgrade.adoc | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 81219103..021f8db0 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -45,7 +45,7 @@ image:xp-810.svg[XP 8.1.0,opts=inline] The URL functions follow the request: the Page URLs and rich text belong to a site, or to a project. <> resolves that site or project once, and its result is passed as `base` to every `pageUrlParts()` and `processHtmlParts()` call of the same request. Its `baseUrl` is the Base URL configured there, or `null`. A page URL is `baseUrl + path + queryString`, where the caller supplies the origin the site is served from when `baseUrl` is `null`, and the path is relative to the site or project. -A media URL carries its project and branch in its path and belongs to no site, so `imageUrlParts()` and `attachmentUrlParts()` take a `project` and `branch` instead of a base. A media URL is `mediaBaseUrl + path + queryString`, where the caller supplies the root of the media APIs as it serves them; `+media.defaultBaseUrl+` and the mounts of the media APIs do not apply. +`imageUrlParts()` and `attachmentUrlParts()` take a `project` and `branch`. A media URL is `mediaBaseUrl + path + queryString`, where the caller supplies the root of the media APIs as it serves them; `+media.defaultBaseUrl+` and the mounts of the media APIs do not apply. Parts are URL-escaped as they appear in the URL: assemble them without encoding them again. diff --git a/docs/upgrade.adoc b/docs/upgrade.adoc index d7619840..f6ee388d 100644 --- a/docs/upgrade.adoc +++ b/docs/upgrade.adoc @@ -247,8 +247,6 @@ const parts = pageUrlParts({path: '/my-site/posts/first-post', base}); const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.queryString; ---- -The `project` and `branch` of <> and <> stay: a media URL carries them in its path. - [#xp-8-1-lib-export] ==== lib-export From 4476c13c3aed14a7e29aca274cff9b181e3e0df7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 15:09:14 +0000 Subject: [PATCH 04/12] Explain the macro changes in plain terms Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 2 +- docs/upgrade.adoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 021f8db0..c63785e6 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -1062,7 +1062,7 @@ It will also process embedded macros. image:xp-810.svg[XP 8.1.0,opts=inline] An image names its style in the `style` parameter of its `+image://+` link: `:` for the style of that application, or a name alone for the first style of that name. Styles come from the system application and the applications configured on the site. -image:xp-810.svg[XP 8.1.0,opts=inline] A macro is resolved when the HTML is processed, among the applications configured on the site - by its name, then ignoring case - and then among the built-in macros; a macro no application provides stays as written. Post-processing instructions already present in the HTML are not executed. +image:xp-810.svg[XP 8.1.0,opts=inline] A macro is resolved when the HTML is processed, among the applications configured on the site - by its name, then ignoring case - and then among the built-in macros; a macro no application provides stays as written. HTML comments in the text stay plain comments: they are never run as macros. A link or image that does not resolve gets a URL answered with 404 — see <>. diff --git a/docs/upgrade.adoc b/docs/upgrade.adoc index f6ee388d..0ac6be7f 100644 --- a/docs/upgrade.adoc +++ b/docs/upgrade.adoc @@ -279,7 +279,7 @@ There is no drop-in replacement, and no general rule - what to key on depends on ==== MacroService -`MacroService.postProcessInstructionSerialize()` is deprecated, and post-processing no longer executes the instructions it writes: an instruction runs only when it names the macro descriptor it was resolved to, which `processHtml()` writes for the macros of the HTML it processes. Process rich text with `processHtml()` instead of serializing macro instructions yourself. +`MacroService.postProcessInstructionSerialize()` is deprecated. It turned a macro into an HTML comment that XP replaced with the macro's output when rendering the page. XP no longer does that for these comments: the page shows the macro as written, such as `[mymacro]`. Pass rich text through `processHtml()` instead; the macros it finds are rendered with the page. === Behaviour changes @@ -290,7 +290,7 @@ Rich text images:: `processHtml()` writes `sizes` only along with a `srcset` of at least one width, and an image the image API serves as stored gets no `srcset`. Rich text macros:: -A macro in rich text is resolved when `processHtml()` processes it, and no longer needs a site request to render. A macro no application provides stays as written, and post-processing instructions already present in the HTML are not executed. +`processHtml()` now looks up each macro itself, so macros render on any page, also outside a site. A macro no application provides is shown as written. HTML comments in the rich text stay plain comments: they are never run as macros. === Worth adopting From 7d68d78fcd9cfe9be630514bc09dac8c834ae3b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 10:00:20 +0000 Subject: [PATCH 05/12] Note that a base has to come from urlBase() Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index c63785e6..ab87afd7 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -1345,7 +1345,7 @@ const url = origin + post.path + post.queryString; [#url-base] === UrlBase -image:xp-810.svg[XP 8.1.0,opts=inline] The site or project URLs belong to, resolved by <>. Pass it as is to <> and <>. +image:xp-810.svg[XP 8.1.0,opts=inline] The site or project URLs belong to, resolved by <>. Pass it as is to <> and <>; a `base` that `urlBase()` did not return raises an error. [%header,cols="1%,1%,98%a"] [frame="none"] From dfdfc3fd9fb77b6daa255458c9c4127ad90d53a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 18:19:57 +0000 Subject: [PATCH 06/12] Describe the URL base as standing in for a site request Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index ab87afd7..31c07ee5 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -43,7 +43,7 @@ image:xp-810.svg[XP 8.1.0,opts=inline] The URL functions follow the request: the | <> | <> |=== -Page URLs and rich text belong to a site, or to a project. <> resolves that site or project once, and its result is passed as `base` to every `pageUrlParts()` and `processHtmlParts()` call of the same request. Its `baseUrl` is the Base URL configured there, or `null`. A page URL is `baseUrl + path + queryString`, where the caller supplies the origin the site is served from when `baseUrl` is `null`, and the path is relative to the site or project. +Page URLs and rich text belong to a site, or to a project. The request-following functions take it from the request: its project, branch, site and the site's configuration. <> resolves the same from configuration alone, as a stand-in for a site request, and its result is passed as `base` to every `pageUrlParts()` and `processHtmlParts()` call for that site. Its `baseUrl` is the Base URL configured there, or `null`. A page URL is `baseUrl + path + queryString`, where the caller supplies the origin the site is served from when `baseUrl` is `null`, and the path is relative to the site or project. `imageUrlParts()` and `attachmentUrlParts()` take a `project` and `branch`. A media URL is `mediaBaseUrl + path + queryString`, where the caller supplies the root of the media APIs as it serves them; `+media.defaultBaseUrl+` and the mounts of the media APIs do not apply. @@ -1034,7 +1034,7 @@ Example ---- import {pageUrlParts, urlBase} from '/lib/xp/portal'; -// The site the URLs belong to: resolve it once, and pass it to every call of the same request +// The site the URLs belong to, in place of a site request: resolve it once, and pass it to every call for that site const base = urlBase({ key: '/my-site', project: 'myproject', @@ -1297,7 +1297,7 @@ const url = buildUrl({ === urlBase -image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the site - or the project - URLs belong to, from configuration alone, for <> and <>. Resolve it once and pass it as `base` to every call of the same request. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the site - or the project - URLs belong to, from configuration alone, for <> and <>: what a site request would provide them - the project, the branch, the site and its configuration - without one. Resolve it once and pass it as `base` to every call for that site. See <>. [.lead] Parameters @@ -1326,7 +1326,7 @@ Example ---- import {pageUrlParts, urlBase} from '/lib/xp/portal'; -// The site URLs belong to, resolved once for every URL of the same request +// The site URLs belong to, in place of a site request: resolved once for every URL of that site const base = urlBase({ key: '/my-site', project: 'myproject', @@ -1345,7 +1345,7 @@ const url = origin + post.path + post.queryString; [#url-base] === UrlBase -image:xp-810.svg[XP 8.1.0,opts=inline] The site or project URLs belong to, resolved by <>. Pass it as is to <> and <>; a `base` that `urlBase()` did not return raises an error. +image:xp-810.svg[XP 8.1.0,opts=inline] The site or project URLs belong to, resolved by <>. It stands in for a site request: what request-following URLs take from the request, the URL parts take from the base. Pass it as is to <> and <>; a `base` that `urlBase()` did not return raises an error. [%header,cols="1%,1%,98%a"] [frame="none"] From e7faefd5caf609c350196f61dfe90e4b9990f528 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 19:04:48 +0000 Subject: [PATCH 07/12] Rename the URL base to the portal scope Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 64 +++++++++++++++++----------------- docs/upgrade.adoc | 12 +++---- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 31c07ee5..1de78994 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -40,10 +40,10 @@ image:xp-810.svg[XP 8.1.0,opts=inline] The URL functions follow the request: the | <> | <> | <> | <> | <> | <> -| <> | <> +| <> | <> |=== -Page URLs and rich text belong to a site, or to a project. The request-following functions take it from the request: its project, branch, site and the site's configuration. <> resolves the same from configuration alone, as a stand-in for a site request, and its result is passed as `base` to every `pageUrlParts()` and `processHtmlParts()` call for that site. Its `baseUrl` is the Base URL configured there, or `null`. A page URL is `baseUrl + path + queryString`, where the caller supplies the origin the site is served from when `baseUrl` is `null`, and the path is relative to the site or project. +Page URLs and rich text belong to a site, or to a project. The request-following functions take it from the request: its project, branch, site and the site's configuration. <> resolves the same from configuration alone, as a stand-in for a site request, and its result is passed as `scope` to every `pageUrlParts()` and `processHtmlParts()` call for that site. Its `baseUrl` is the Base URL configured there, or `null`. A page URL is `baseUrl + path + queryString`, where the caller supplies the origin the site is served from when `baseUrl` is `null`, and the path is relative to the site or project. `imageUrlParts()` and `attachmentUrlParts()` take a `project` and `branch`. A media URL is `mediaBaseUrl + path + queryString`, where the caller supplies the root of the media APIs as it serves them; `+media.defaultBaseUrl+` and the mounts of the media APIs do not apply. @@ -240,7 +240,7 @@ const url = 'https://cdn.example.com/api' + parts.path + parts.queryString; Generates the base URL of the current site request: the address of the site - or project - the matched virtual host mapping points at, rewritten for that host. Page URLs following the request are relative to it, and so are routes the site serves outside the content tree, such as <<../web/sites/mappings#, mappings>>. Without a site request, it is the Base URL configured for the project of the current context, or else the address the site engine serves the project at. -For the Base URL configured for a site or project, independent of the request, use <>. +For the Base URL configured for a site or project, independent of the request, use <>. [.lead] Parameters @@ -253,10 +253,10 @@ Parameters |=== | Name | Type | Description | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute` or `websocket`. Default is `server`. Ignored when the site has a configured Base URL — see <>. -| id | string | *Deprecated.* Use <> with `key` instead. -| path | string | *Deprecated.* Use <> with `key` instead. -| project | string | *Deprecated.* Use <> with `project` instead. -| branch | string | *Deprecated.* Use <> with `branch` instead. +| id | string | *Deprecated.* Use <> with `key` instead. +| path | string | *Deprecated.* Use <> with `key` instead. +| project | string | *Deprecated.* Use <> with `project` instead. +| branch | string | *Deprecated.* Use <> with `branch` instead. |=== [.lead] @@ -973,8 +973,8 @@ Parameters | id | string | *Optional.* Id of a content. If id is set, then path is not used. | path | string | *Optional.* Path of a content. Relative paths are resolved based on the current context. | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute` or `websocket`. Default is `server`. Ignored when the site has a configured Base URL — see <>. -| project | string | *Deprecated.* Use <> with a `base` from <> with `project` instead. -| branch | string | *Deprecated.* Use <> with a `base` from <> with `branch` instead. +| project | string | *Deprecated.* Use <> with a `scope` from <> with `project` instead. +| branch | string | *Deprecated.* Use <> with a `scope` from <> with `branch` instead. | params | object | *Optional.* Custom query parameters to append to the URL. |=== @@ -1002,9 +1002,9 @@ const url = pageUrl({ === pageUrlParts -image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the parts of a page URL from configuration alone, for the site or project `base` stands for: `baseUrl + path + queryString`. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the parts of a page URL from configuration alone, for the site or project `scope` stands for: `baseUrl + path + queryString`. See <>. -The page has to be inside the site or project `base` stands for, or be it; for a page elsewhere an error is raised. +The page has to be inside the site or project `scope` stands for, or be it; for a page elsewhere an error is raised. [.lead] Parameters @@ -1018,7 +1018,7 @@ Parameters | Name | Type | Description | id | string | *Optional.* Id of the page. Either `id` or `path` is required. | path | string | *Optional.* Path of the page within the project. -| base | <> | *Optional.* The site or project the URL belongs to, resolved by <>; the page is looked up in its project and branch. Defaults to the project of the current context. +| scope | <> | *Optional.* The site or project the URL belongs to, resolved by <>; the page is looked up in its project and branch. Defaults to the project of the current context. | params | object | *Optional.* Custom query parameters of the URL. |=== @@ -1032,10 +1032,10 @@ Example [source,typescript] ---- -import {pageUrlParts, urlBase} from '/lib/xp/portal'; +import {pageUrlParts, portalScope} from '/lib/xp/portal'; // The site the URLs belong to, in place of a site request: resolve it once, and pass it to every call for that site -const base = urlBase({ +const scope = portalScope({ key: '/my-site', project: 'myproject', branch: 'master' @@ -1044,7 +1044,7 @@ const base = urlBase({ // Parts of the URL of a page, relative to the site it belongs to const parts = pageUrlParts({ path: '/my-site/posts/first-post', - base, + scope, params: { a: 1 } @@ -1108,14 +1108,14 @@ const html = processHtml({ === processHtmlParts -image:xp-810.svg[XP 8.1.0,opts=inline] Processes an HTML text like <>, from configuration alone, for the site or project `base` stands for, and returns the parts of every internal link, image and macro in it. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Processes an HTML text like <>, from configuration alone, for the site or project `scope` stands for, and returns the parts of every internal link, image and macro in it. See <>. Everything is resolved for the base: the Base URL, the path each content link is relative to, the project and branch contents and media are looked up in, and the applications image styles and macros come from. Internal links, images and macros in the returned HTML are placeholders, which the caller renders from their entries: * a link carries a `data-link-ref` attribute, and an image a `data-image-ref` attribute, naming its entry in `links` or `images`; -* a macro an application of the base provides becomes an `editor-macro` element holding its body, with a `data-macro-name` attribute holding the name of its descriptor and a `data-macro-ref` attribute naming its entry in `macros`. Other macros stay as written. +* a macro an application of the scope provides becomes an `editor-macro` element holding its body, with a `data-macro-name` attribute holding the name of its descriptor and a `data-macro-ref` attribute naming its entry in `macros`. Other macros stay as written. A link or image that does not resolve keeps its ref, and its entry has no parts, so the caller decides how to render it. @@ -1130,7 +1130,7 @@ Parameters |=== | Name | Type | Description | value | string | Html value string to process. -| base | <> | *Optional.* The site or project the HTML belongs to, resolved by <>. Defaults to the project of the current context. +| scope | <> | *Optional.* The site or project the HTML belongs to, resolved by <>. Defaults to the project of the current context. | imageWidths | number[] | *Optional.* Image widths for the `srcset` attribute of `++` tags, for images the image API scales. | imageSizes | string | *Optional.* Value of the `sizes` attribute of `++` tags. Written only along with the `srcset` that `imageWidths` adds. |=== @@ -1145,9 +1145,9 @@ Example [source,typescript] ---- -import {processHtmlParts, urlBase} from '/lib/xp/portal'; +import {processHtmlParts, portalScope} from '/lib/xp/portal'; -const base = urlBase({ +const scope = portalScope({ key: '/my-site', project: 'myproject', branch: 'master' @@ -1155,7 +1155,7 @@ const base = urlBase({ const result = processHtmlParts({ value: 'Post[youtube videoid="abc"/]', - base + scope }); // The site's configured Base URL, or the origin the frontend serves the site from @@ -1295,14 +1295,14 @@ const url = buildUrl({ }); ---- -=== urlBase +=== portalScope -image:xp-810.svg[XP 8.1.0,opts=inline] Resolves the site - or the project - URLs belong to, from configuration alone, for <> and <>: what a site request would provide them - the project, the branch, the site and its configuration - without one. Resolve it once and pass it as `base` to every call for that site. See <>. +image:xp-810.svg[XP 8.1.0,opts=inline] Resolves a portal scope: an immutable, request-independent context for resolving page URLs and processing rich text for a selected site or project, from configuration alone. It provides <> and <> what a site request would - the project, the branch, the site and its configuration - without one. Resolve it once and pass it as `scope` to every call for that site. See <>. [.lead] Parameters -`urlBase()` takes a single, optional `UrlBaseParams` object with these properties: +`portalScope()` takes a single, optional `PortalScopeParams` object with these properties: [%header,cols="1%,1%,98%a"] [frame="none"] @@ -1317,35 +1317,35 @@ Parameters [.lead] Returns -*object* : (<>) The resolved site or project. +*object* : (<>) The resolved scope. [.lead] Example [source,typescript] ---- -import {pageUrlParts, urlBase} from '/lib/xp/portal'; +import {pageUrlParts, portalScope} from '/lib/xp/portal'; // The site URLs belong to, in place of a site request: resolved once for every URL of that site -const base = urlBase({ +const scope = portalScope({ key: '/my-site', project: 'myproject', branch: 'master' }); // The site's configured Base URL, or the origin the frontend serves the site from -const origin = base.baseUrl ?? 'https://www.example.com'; +const origin = scope.baseUrl ?? 'https://www.example.com'; -const post = pageUrlParts({path: '/my-site/posts/first-post', base}); +const post = pageUrlParts({path: '/my-site/posts/first-post', scope}); const url = origin + post.path + post.queryString; ---- == Type Definitions -[#url-base] -=== UrlBase +[#portal-scope] +=== PortalScope -image:xp-810.svg[XP 8.1.0,opts=inline] The site or project URLs belong to, resolved by <>. It stands in for a site request: what request-following URLs take from the request, the URL parts take from the base. Pass it as is to <> and <>; a `base` that `urlBase()` did not return raises an error. +image:xp-810.svg[XP 8.1.0,opts=inline] An immutable, request-independent context for resolving page URLs and processing rich text for a selected site or project, resolved by <>. It stands in for a site request: what request-following URLs take from the request, the URL parts take from the scope. Pass it as is to <> and <>; a `scope` that `portalScope()` did not return raises an error. [%header,cols="1%,1%,98%a"] [frame="none"] diff --git a/docs/upgrade.adoc b/docs/upgrade.adoc index 0ac6be7f..4d70aeef 100644 --- a/docs/upgrade.adoc +++ b/docs/upgrade.adoc @@ -221,8 +221,8 @@ Both are read at URL generation, so the same code produces the right URL in ever Setting a project or branch on a request-following URL function switched it from the request to configuration, even for the request's own project, so the URL left the virtual host the request came through. These parameters are deprecated in favour of the <> functions, which resolve URLs from configuration alone: -* `project` and `branch` on <>: use <> with a `base` from <>. -* `id`, `path`, `project` and `branch` on <>: use <>. `baseUrl()` keeps `type`, and without parameters it is the base URL of the current site request. +* `project` and `branch` on <>: use <> with a `scope` from <>. +* `id`, `path`, `project` and `branch` on <>: use <>. `baseUrl()` keeps `type`, and without parameters it is the base URL of the current site request. .Before [source,typescript] @@ -240,10 +240,10 @@ const url = pageUrl({ .After [source,typescript] ---- -import {pageUrlParts, urlBase} from '/lib/xp/portal'; +import {pageUrlParts, portalScope} from '/lib/xp/portal'; -const base = urlBase({key: '/my-site', project: 'myproject', branch: 'master'}); -const parts = pageUrlParts({path: '/my-site/posts/first-post', base}); +const scope = portalScope({key: '/my-site', project: 'myproject', branch: 'master'}); +const parts = pageUrlParts({path: '/my-site/posts/first-post', scope}); const url = (parts.baseUrl ?? 'https://www.example.com') + parts.path + parts.queryString; ---- @@ -309,7 +309,7 @@ Disposer registration:: `+__.disposer()+` registrations are remembered when they are made while the application is starting - from `main.ts`, or a module it loads during start-up. Registering one later is not dependable; move such registrations into start-up. URLs for a frontend of your own:: -A frontend that serves content from a host of its own builds its URLs from the <> functions: <>, <>, <> and <>, with the site or project resolved once by <>. +A frontend that serves content from a host of its own builds its URLs from the <> functions: <>, <>, <> and <>, with the site or project resolved once by <>. Image styles of a given application:: An image in rich text can name its style as `:`, where several applications define styles of the same name. From b1875cc9934da696ff828d463e349962d062cd5c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 19:59:41 +0000 Subject: [PATCH 08/12] Give the fragment of a link with its # Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 1de78994..0c25f467 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -1164,7 +1164,7 @@ const origin = result.baseUrl ?? 'https://www.example.com'; for (const link of result.links) { // a link that does not resolve has no parts if (link.type === 'content' && link.page) { - const href = origin + link.page.path + link.page.queryString + (link.fragment ? `#${link.fragment}` : ''); + const href = origin + link.page.path + link.page.queryString + link.fragment; // set href on the element whose data-link-ref is link.ref } } @@ -1439,7 +1439,7 @@ image:xp-810.svg[XP 8.1.0,opts=inline] A link to a content page, written as `con | uri | string | The link as written in the HTML. | contentId | string | Id of the linked content. | page | <> \| null | Parts of the page URL, with the query string the link carries; `null` when the link does not resolve. -| fragment | string \| null | Fragment of the link, without `#`; `null` when it has none. +| fragment | string | Fragment of the link, with its leading `#`; empty when it has none. The href of the link is `baseUrl + path + queryString + fragment`. |=== [#processed-html-attachment-link] From dc95fccd4c3ad91aa1567fbf284765d265094524 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 12:52:12 +0000 Subject: [PATCH 09/12] Align attachmentUrl lookup text with 404 URLs, describe imageWidths as an array Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 0c25f467..7c409c71 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -144,7 +144,7 @@ const url = assetUrl({ Generates URL to an attachment. -If `name` is provided, XP will try to find an attachment by the unique name and give an exception if it's not found. If `name` is empty but `label` is provided, XP will try to find an attachment with the provided label (will return the first one if several were found, or give an exception if none were found). If both `name` and `label` are empty, XP will generate attachment url only for a binary content with an attachment. +If `name` is provided, the URL points to the attachment with that unique name. If `name` is empty but `label` is provided, it points to the first attachment with that label. If both `name` and `label` are empty, it points to the attachment of a binary content. When no attachment matches, the URL is answered with 404 — see <>. [.lead] Parameters @@ -1078,9 +1078,9 @@ Parameters [grid="none"] |=== | Name | Type | Description -| value | string | Html value string to process. +| value | string | HTML to process. | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute`. Default is `server`. -| imageWidths | number[] | *Optional.* A comma-separated list of image widths. If this parameter is provided, the `++` tags of images the image API scales will have an additional `srcset` attribute with image URLs generated for specified widths. +| imageWidths | number[] | *Optional.* Image widths, such as `[400, 800]`. If provided, the `++` tags of images the image API scales get a `srcset` attribute with an image URL for each width. | imageSizes | string | *Optional.* Specifies the width for an image depending on browser dimensions. The value has the following format: `(media-condition) width`. Multiple sizes are comma-separated. Written only along with the `srcset` that `imageWidths` adds. |=== @@ -1129,7 +1129,7 @@ Parameters [grid="none"] |=== | Name | Type | Description -| value | string | Html value string to process. +| value | string | HTML to process. | scope | <> | *Optional.* The site or project the HTML belongs to, resolved by <>. Defaults to the project of the current context. | imageWidths | number[] | *Optional.* Image widths for the `srcset` attribute of `++` tags, for images the image API scales. | imageSizes | string | *Optional.* Value of the `sizes` attribute of `++` tags. Written only along with the `srcset` that `imageWidths` adds. From d1c42c4ed4dba4b7b31873eec283000dcba52f27 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 15:24:35 +0000 Subject: [PATCH 10/12] Describe the config of processHtmlParts macros Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 7c409c71..fbdeba9b 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -1171,7 +1171,7 @@ for (const link of result.links) { for (const macro of result.macros) { if (macro.descriptor === 'com.example.myapp:youtube') { - const embedUrl = 'https://www.youtube.com/embed/' + macro.params.videoId[0]; + const embedUrl = 'https://www.youtube.com/embed/' + macro.config.videoId; // render the editor-macro element whose data-macro-ref is macro.ref } } @@ -1519,7 +1519,7 @@ image:xp-810.svg[XP 8.1.0,opts=inline] A macro, resolved among the applications | Name | Type | Description | ref | string | Value of the `data-macro-ref` attribute of the `editor-macro` element. | descriptor | string | Key of the macro descriptor, such as `system:embed`. -| params | object | Parameters of the macro, each a list of its values in the order written. A parameter matching an input of the descriptor's form, ignoring case, is named as that input. +| config | object | Parameters of the macro. A parameter matching an input of the descriptor's form, ignoring case, is named as that input. An input taking several values holds a list of them in the order written; any other parameter holds its first value. | body | string | Body of the macro as written; empty for a macro without one. |=== From 428e72362d9aa9a3b5c8c90085a4c3c9d03e38db Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 12:22:00 +0000 Subject: [PATCH 11/12] Describe imageSrcWidth and the rich text image changes Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 6 +++++- docs/upgrade.adoc | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index fbdeba9b..241cc3ae 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -1080,6 +1080,7 @@ Parameters | Name | Type | Description | value | string | HTML to process. | type | string | *Optional.* URL type. Either `server` (server-relative URL) or `absolute`. Default is `server`. +| imageSrcWidth | number | image:xp-810.svg[XP 8.1.0,opts=inline] *Optional.* Width of the `src` of `++` tags, for images the image API scales. The height follows the aspect ratio of the image's style or scale. Default is `768`. | imageWidths | number[] | *Optional.* Image widths, such as `[400, 800]`. If provided, the `++` tags of images the image API scales get a `srcset` attribute with an image URL for each width. | imageSizes | string | *Optional.* Specifies the width for an image depending on browser dimensions. The value has the following format: `(media-condition) width`. Multiple sizes are comma-separated. Written only along with the `srcset` that `imageWidths` adds. |=== @@ -1102,7 +1103,9 @@ const html = processHtml({ 'Inline' + 'Download' + '', - imageWidths: [32, 480, 800] + imageSrcWidth: 800, + imageWidths: [480, 800, 1200], + imageSizes: '(max-width: 800px) 100vw, 800px' }); ---- @@ -1131,6 +1134,7 @@ Parameters | Name | Type | Description | value | string | HTML to process. | scope | <> | *Optional.* The site or project the HTML belongs to, resolved by <>. Defaults to the project of the current context. +| imageSrcWidth | number | *Optional.* Width of the `src` of `++` tags, for images the image API scales. The height follows the aspect ratio of the image's style or scale. Default is `768`. | imageWidths | number[] | *Optional.* Image widths for the `srcset` attribute of `++` tags, for images the image API scales. | imageSizes | string | *Optional.* Value of the `sizes` attribute of `++` tags. Written only along with the `srcset` that `imageWidths` adds. |=== diff --git a/docs/upgrade.adoc b/docs/upgrade.adoc index 4d70aeef..742288e1 100644 --- a/docs/upgrade.adoc +++ b/docs/upgrade.adoc @@ -287,7 +287,7 @@ URLs that do not resolve:: <>, <>, <> and <> generate a URL answered with 404, rather than one answered with 500, when the content it names does not resolve. See <>. Rich text images:: -`processHtml()` writes `sizes` only along with a `srcset` of at least one width, and an image the image API serves as stored gets no `srcset`. +`processHtml()` writes `sizes` only along with a `srcset` of at least one width, and an image the image API serves as stored gets no `srcset`. `srcset` and `sizes` come right after `src` in the `++` tag. The height of an image with an aspect ratio is rounded to the nearest pixel: a `21:9` image is now 329 pixels high at the default width of 768, rather than 324. Rich text macros:: `processHtml()` now looks up each macro itself, so macros render on any page, also outside a site. A macro no application provides is shown as written. HTML comments in the rich text stay plain comments: they are never run as macros. @@ -311,5 +311,8 @@ Disposer registration:: URLs for a frontend of your own:: A frontend that serves content from a host of its own builds its URLs from the <> functions: <>, <>, <> and <>, with the site or project resolved once by <>. +Width of rich text images:: +The `src` of every image in rich text was 768 pixels wide. `imageSrcWidth` of <> sets that width; the height still follows the image's style. + Image styles of a given application:: An image in rich text can name its style as `:`, where several applications define styles of the same name. From a5a0013a4f5126bd46f5ba7ac5078a51583373ac Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 12:28:23 +0000 Subject: [PATCH 12/12] Warn against editing the scale segment of image URLs Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8TCeZWgJjKTDhmntR9JtB --- docs/libraries/lib-portal.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/libraries/lib-portal.adoc b/docs/libraries/lib-portal.adoc index 241cc3ae..b3fd0ea4 100644 --- a/docs/libraries/lib-portal.adoc +++ b/docs/libraries/lib-portal.adoc @@ -49,6 +49,8 @@ Page URLs and rich text belong to a site, or to a project. The request-following Parts are URL-escaped as they appear in the URL: assemble them without encoding them again. +WARNING: Use each image URL as it is returned, and don't make other sizes by editing its `scale` segment. The fingerprint of an image URL can depend on its whole transformation, so an edited URL may no longer be served. The image's style also contributes to the scale. Ask XP for each size instead, with `scale` of <>, or `imageSrcWidth` and `imageWidths` of <>. + [#unresolved_urls] === URLs that do not resolve