From 176d3be5ffb7c84338c9ad3ea071d7851aa0ec41 Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 10:16:47 +0200 Subject: [PATCH 01/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/index.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index ef2b74199..de3817061 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -337,12 +337,27 @@ function restoreCursorPosition(view) { } } +function rewriteImageSrcs(pm) { + pm.querySelectorAll('img[src]').forEach((img) => { + try { + const url = new URL(img.src); + if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { + url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); + img.src = url.toString(); + } + } catch { + // relative or malformed src — skip + } + }); +} + function addSyncedListener(wsProvider, canWrite) { onWsSync(wsProvider, () => { - if (canWrite) { - const pm = document.querySelector('da-content')?.shadowRoot - .querySelector('da-editor')?.shadowRoot.querySelector('.ProseMirror'); - if (pm) pm.contentEditable = 'true'; + const pm = document.querySelector('da-content')?.shadowRoot + .querySelector('da-editor')?.shadowRoot.querySelector('.ProseMirror'); + if (pm) { + if (canWrite) pm.contentEditable = 'true'; + rewriteImageSrcs(pm); } }); } From 6685bd95fb1ee243e102a23bc0b6eb3fe9fa71ca Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 10:40:36 +0200 Subject: [PATCH 02/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/index.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index de3817061..4fd49fee0 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -337,17 +337,25 @@ function restoreCursorPosition(view) { } } +function rewriteAemHost(urlStr) { + try { + const url = new URL(urlStr); + if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { + url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); + return url.toString(); + } + } catch { + // relative or malformed — leave unchanged + } + return urlStr; +} + function rewriteImageSrcs(pm) { pm.querySelectorAll('img[src]').forEach((img) => { - try { - const url = new URL(img.src); - if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { - url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); - img.src = url.toString(); - } - } catch { - // relative or malformed src — skip - } + img.src = rewriteAemHost(img.src); + }); + pm.querySelectorAll('source[srcset]').forEach((source) => { + source.srcset = rewriteAemHost(source.srcset); }); } From c22aa1eb6248341e3ce4bc541e5d73afcb170cf4 Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 10:55:49 +0200 Subject: [PATCH 03/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index 4fd49fee0..03293fb61 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -361,12 +361,12 @@ function rewriteImageSrcs(pm) { function addSyncedListener(wsProvider, canWrite) { onWsSync(wsProvider, () => { - const pm = document.querySelector('da-content')?.shadowRoot - .querySelector('da-editor')?.shadowRoot.querySelector('.ProseMirror'); - if (pm) { - if (canWrite) pm.contentEditable = 'true'; - rewriteImageSrcs(pm); + if (canWrite) { + const pm = document.querySelector('da-content')?.shadowRoot + .querySelector('da-editor')?.shadowRoot.querySelector('.ProseMirror'); + if (pm) pm.contentEditable = 'true'; } + rewriteImageSrcs(window.view.dom); }); } @@ -437,6 +437,7 @@ function applyDelayedPlugins(pluginsPromise, schema, canWrite, basePlugins) { // Reconfigure the view with the full plugin list const newState = window.view.state.reconfigure({ plugins: pluginList }); window.view.updateState(newState); + rewriteImageSrcs(window.view.dom); }); } From 836289a8aca243e9c30f74424ad58d7c60abe9ff Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 11:12:32 +0200 Subject: [PATCH 04/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/plugins/imageFocalPoint.js | 26 ++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/blocks/edit/prose/plugins/imageFocalPoint.js b/blocks/edit/prose/plugins/imageFocalPoint.js index b7c046263..1132cbea1 100644 --- a/blocks/edit/prose/plugins/imageFocalPoint.js +++ b/blocks/edit/prose/plugins/imageFocalPoint.js @@ -6,6 +6,19 @@ import { getTableInfo, isInTableCell } from './tableUtils.js'; const imageFocalPointKey = new PluginKey('imageFocalPoint'); +function rewriteAemHost(urlStr) { + try { + const url = new URL(urlStr); + if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { + url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); + return url.toString(); + } + } catch { + // relative or malformed — leave unchanged + } + return urlStr; +} + // Cache blocks data at module level let blocksDataPromise = null; async function getBlocksData() { @@ -40,7 +53,7 @@ function shouldShowFocalPoint(tableName, blocks) { } function updateImageAttributes(img, attrs) { - img.src = attrs.src; + img.src = rewriteAemHost(attrs.src); ['alt', 'title', 'width', 'height'].forEach((attr) => { if (attrs[attr]) { img[attr] = attrs[attr]; @@ -150,7 +163,16 @@ export default function imageFocalPoint() { if (isInTableCell(view.state, getPos())) { return new ImageWithFocalPointView(node, view, getPos); } - return null; + const img = document.createElement('img'); + updateImageAttributes(img, node.attrs); + return { + dom: img, + update(updated) { + if (updated.type.name !== 'image') return false; + updateImageAttributes(img, updated.attrs); + return true; + }, + }; }, }, }, From b08d1d9a999335e64ebf70840e7e0b9f19a63cbe Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 11:16:14 +0200 Subject: [PATCH 05/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/index.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index 03293fb61..5123d80b4 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -350,15 +350,6 @@ function rewriteAemHost(urlStr) { return urlStr; } -function rewriteImageSrcs(pm) { - pm.querySelectorAll('img[src]').forEach((img) => { - img.src = rewriteAemHost(img.src); - }); - pm.querySelectorAll('source[srcset]').forEach((source) => { - source.srcset = rewriteAemHost(source.srcset); - }); -} - function addSyncedListener(wsProvider, canWrite) { onWsSync(wsProvider, () => { if (canWrite) { @@ -366,7 +357,6 @@ function addSyncedListener(wsProvider, canWrite) { .querySelector('da-editor')?.shadowRoot.querySelector('.ProseMirror'); if (pm) pm.contentEditable = 'true'; } - rewriteImageSrcs(window.view.dom); }); } @@ -437,7 +427,6 @@ function applyDelayedPlugins(pluginsPromise, schema, canWrite, basePlugins) { // Reconfigure the view with the full plugin list const newState = window.view.state.reconfigure({ plugins: pluginList }); window.view.updateState(newState); - rewriteImageSrcs(window.view.dom); }); } @@ -493,6 +482,20 @@ export default async function initProse({ path, permissions, doc, daContent, wsP state, dispatchTransaction, nodeViews: { + image(node) { + const img = document.createElement('img'); + img.src = rewriteAemHost(node.attrs.src); + if (node.attrs.alt) img.alt = node.attrs.alt; + return { + dom: img, + update(updated) { + if (updated.type.name !== 'image') return false; + img.src = rewriteAemHost(updated.attrs.src); + if (updated.attrs.alt) img.alt = updated.attrs.alt; + return true; + }, + }; + }, diff_added(node, view, getPos) { const LocAddedView = getDiffClass('da-diff-added', getSchema, dispatchTransaction, { isUpstream: false }); return new LocAddedView(node, view, getPos); From 59104748f491f798e1e769f4aff8dc4ac1b568fd Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 11:28:35 +0200 Subject: [PATCH 06/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/image-utils.js | 23 ++++++++++++++++++++ blocks/edit/prose/index.js | 17 +++------------ blocks/edit/prose/plugins/imageFocalPoint.js | 16 ++------------ 3 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 blocks/edit/prose/image-utils.js diff --git a/blocks/edit/prose/image-utils.js b/blocks/edit/prose/image-utils.js new file mode 100644 index 000000000..c8a3da2cd --- /dev/null +++ b/blocks/edit/prose/image-utils.js @@ -0,0 +1,23 @@ +/** + * Rewrites an image src from an AEM preview/publish host to the DA preview host. + * + * Images stored in DA content may reference *.aem.page (preview) or *.aem.live + * (publish) URLs. These hosts require AEM authentication that the DA editor does + * not carry, causing 401 errors when the browser fetches them. The equivalent + * *.preview.da.live URL serves the same content and is accessible from the editor. + * + * @param {string} src - The original image src URL. + * @returns {string} The rewritten src, or the original if no rewrite was needed. + */ +export function rewriteImageSrcForEditor(src) { + try { + const url = new URL(src); + if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { + url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); + return url.toString(); + } + } catch { + // relative or malformed src — leave unchanged + } + return src; +} diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index 5123d80b4..d933f216b 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -27,6 +27,7 @@ import { COLLAB_ORIGIN, DA_ORIGIN } from '../../shared/constants.js'; import { daFetch, getAuthToken } from '../../shared/utils.js'; import { getDiffClass, checkForLocNodes, addActiveView } from './diff/diff-utils.js'; import { debounce, initDaMetadata } from '../utils/helpers.js'; +import { rewriteImageSrcForEditor } from './image-utils.js'; async function checkDoc(path) { return daFetch(path, { method: 'HEAD' }); @@ -337,18 +338,6 @@ function restoreCursorPosition(view) { } } -function rewriteAemHost(urlStr) { - try { - const url = new URL(urlStr); - if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { - url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); - return url.toString(); - } - } catch { - // relative or malformed — leave unchanged - } - return urlStr; -} function addSyncedListener(wsProvider, canWrite) { onWsSync(wsProvider, () => { @@ -484,13 +473,13 @@ export default async function initProse({ path, permissions, doc, daContent, wsP nodeViews: { image(node) { const img = document.createElement('img'); - img.src = rewriteAemHost(node.attrs.src); + img.src = rewriteImageSrcForEditor(node.attrs.src); if (node.attrs.alt) img.alt = node.attrs.alt; return { dom: img, update(updated) { if (updated.type.name !== 'image') return false; - img.src = rewriteAemHost(updated.attrs.src); + img.src = rewriteImageSrcForEditor(updated.attrs.src); if (updated.attrs.alt) img.alt = updated.attrs.alt; return true; }, diff --git a/blocks/edit/prose/plugins/imageFocalPoint.js b/blocks/edit/prose/plugins/imageFocalPoint.js index 1132cbea1..547a2d5a4 100644 --- a/blocks/edit/prose/plugins/imageFocalPoint.js +++ b/blocks/edit/prose/plugins/imageFocalPoint.js @@ -1,4 +1,5 @@ import { Plugin, PluginKey } from 'da-y-wrapper'; +import { rewriteImageSrcForEditor } from '../image-utils.js'; import inlinesvg from '../../../shared/inlinesvg.js'; import { openFocalPointDialog } from './focalPointDialog.js'; import { loadLibrary } from '../../da-library/helpers/helpers.js'; @@ -6,19 +7,6 @@ import { getTableInfo, isInTableCell } from './tableUtils.js'; const imageFocalPointKey = new PluginKey('imageFocalPoint'); -function rewriteAemHost(urlStr) { - try { - const url = new URL(urlStr); - if (url.host.endsWith('.aem.page') || url.host.endsWith('.aem.live')) { - url.host = url.host.replace(/\.aem\.(page|live)$/, '.preview.da.live'); - return url.toString(); - } - } catch { - // relative or malformed — leave unchanged - } - return urlStr; -} - // Cache blocks data at module level let blocksDataPromise = null; async function getBlocksData() { @@ -53,7 +41,7 @@ function shouldShowFocalPoint(tableName, blocks) { } function updateImageAttributes(img, attrs) { - img.src = rewriteAemHost(attrs.src); + img.src = rewriteImageSrcForEditor(attrs.src); ['alt', 'title', 'width', 'height'].forEach((attr) => { if (attrs[attr]) { img[attr] = attrs[attr]; From aee0536c4a546c42729fb57ace40d21315d7318c Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 11:47:56 +0200 Subject: [PATCH 07/10] fix: Images stored in media bus with auth enabled not rendering --- blocks/edit/prose/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index d933f216b..466bdf12d 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -338,7 +338,6 @@ function restoreCursorPosition(view) { } } - function addSyncedListener(wsProvider, canWrite) { onWsSync(wsProvider, () => { if (canWrite) { From 7e714accc26cba25aa303f714d2f928d5a6e5dcd Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Wed, 13 May 2026 11:52:42 +0200 Subject: [PATCH 08/10] fix: Images stored in media bus with auth enabled not rendering --- .../unit/blocks/edit/prose/plugins/imageFocalPoint.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js b/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js index a72d5af4d..00e3d9c9a 100644 --- a/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js +++ b/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js @@ -128,7 +128,7 @@ describe('imageFocalPoint Plugin', () => { expect(icon.classList.contains('focal-point-icon-active')).to.be.true; }); - it('does not create node view for images outside table cells', () => { + it('creates a plain image node view for images outside table cells', () => { const plugin = imageFocalPoint(); const createNodeView = plugin.props.nodeViews.image; @@ -144,8 +144,9 @@ describe('imageFocalPoint Plugin', () => { const mockView = { state: mockState, dom: document.createElement('div') }; const getPos = () => 10; - const nodeView = createNodeView({}, mockView, getPos); - expect(nodeView).to.be.null; + const nodeView = createNodeView({ attrs: { src: 'https://example.com/img.jpg' } }, mockView, getPos); + expect(nodeView).to.not.be.null; + expect(nodeView.dom.tagName).to.equal('IMG'); }); it('updates node view correctly', async () => { From 91afe27fbbb3850f900d6a2df2a19bba814e94d6 Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Tue, 19 May 2026 15:29:13 +0200 Subject: [PATCH 09/10] fix: Images stored in media bus with auth enabled not rendering --- test/unit/blocks/edit/prose/index.test.js | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/unit/blocks/edit/prose/index.test.js b/test/unit/blocks/edit/prose/index.test.js index abecf61e4..dcf5f95b1 100644 --- a/test/unit/blocks/edit/prose/index.test.js +++ b/test/unit/blocks/edit/prose/index.test.js @@ -311,6 +311,32 @@ describe('prose/index initProse default export', () => { } }); + it('Image nodeView rewrites aem.page and aem.live srcs to preview.da.live', async () => { + const ydoc = new Y.Doc(); + const provider = buildFakeWsProvider({ withSynced: false }); + const wsPromise = Promise.resolve({ wsProvider: provider, ydoc }); + Object.defineProperty(fakeContent, 'proseEl', { + configurable: true, + set(v) { + v.getRootNode = () => ({ host: document.createElement('div') }); + this._proseEl = v; + }, + get() { return this._proseEl; }, + }); + await initProse({ path: 'https://admin.da.live/source/o/r/p.html', permissions: ['read', 'write'], doc: null, daContent: fakeContent, wsPromise }); + + const { image: imageNodeView } = window.view.props.nodeViews; + + const aemPage = imageNodeView({ attrs: { src: 'https://main--site--org.aem.page/img.jpg' } }); + expect(aemPage.dom.src).to.equal('https://main--site--org.preview.da.live/img.jpg'); + + const aemLive = imageNodeView({ attrs: { src: 'https://main--site--org.aem.live/img.jpg' } }); + expect(aemLive.dom.src).to.equal('https://main--site--org.preview.da.live/img.jpg'); + + const other = imageNodeView({ attrs: { src: 'https://example.com/img.jpg' } }); + expect(other.dom.src).to.equal('https://example.com/img.jpg'); + }); + it('Destroys an existing window.view before creating a new one', async () => { let destroyed = 0; window.view = { destroy: () => { destroyed += 1; } }; From dfe691ec621b28e37286b8965bebe2b0f892cc4e Mon Sep 17 00:00:00 2001 From: Andrei Tuicu Date: Tue, 19 May 2026 18:49:49 +0200 Subject: [PATCH 10/10] fix: Images stored in media bus with auth enabled not rendering --- .../blocks/edit/prose/image-utils.test.js | 37 +++++++++++++++++++ .../prose/plugins/imageFocalPoint.test.js | 28 ++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test/unit/blocks/edit/prose/image-utils.test.js diff --git a/test/unit/blocks/edit/prose/image-utils.test.js b/test/unit/blocks/edit/prose/image-utils.test.js new file mode 100644 index 000000000..ad7054190 --- /dev/null +++ b/test/unit/blocks/edit/prose/image-utils.test.js @@ -0,0 +1,37 @@ +import { expect } from '@esm-bundle/chai'; +import { rewriteImageSrcForEditor } from '../../../../../blocks/edit/prose/image-utils.js'; + +describe('rewriteImageSrcForEditor', () => { + it('rewrites aem.page host to preview.da.live', () => { + expect(rewriteImageSrcForEditor('https://main--site--org.aem.page/img.jpg')) + .to.equal('https://main--site--org.preview.da.live/img.jpg'); + }); + + it('rewrites aem.live host to preview.da.live', () => { + expect(rewriteImageSrcForEditor('https://main--site--org.aem.live/img.jpg')) + .to.equal('https://main--site--org.preview.da.live/img.jpg'); + }); + + it('preserves the path and query string when rewriting', () => { + expect(rewriteImageSrcForEditor('https://main--site--org.aem.page/media_abc.jpg?width=2000&format=webply')) + .to.equal('https://main--site--org.preview.da.live/media_abc.jpg?width=2000&format=webply'); + }); + + it('leaves unrelated URLs unchanged', () => { + expect(rewriteImageSrcForEditor('https://example.com/img.jpg')) + .to.equal('https://example.com/img.jpg'); + }); + + it('leaves content.da.live URLs unchanged', () => { + expect(rewriteImageSrcForEditor('https://content.da.live/org/repo/img.jpg')) + .to.equal('https://content.da.live/org/repo/img.jpg'); + }); + + it('leaves relative URLs unchanged', () => { + expect(rewriteImageSrcForEditor('/img/photo.jpg')).to.equal('/img/photo.jpg'); + }); + + it('leaves malformed URLs unchanged', () => { + expect(rewriteImageSrcForEditor('not a url')).to.equal('not a url'); + }); +}); diff --git a/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js b/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js index 00e3d9c9a..29281628e 100644 --- a/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js +++ b/test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js @@ -128,6 +128,34 @@ describe('imageFocalPoint Plugin', () => { expect(icon.classList.contains('focal-point-icon-active')).to.be.true; }); + it('rewrites aem.page and aem.live srcs to preview.da.live for table cell images', () => { + const plugin = imageFocalPoint(); + const createNodeView = plugin.props.nodeViews.image; + + const mockState = { + doc: { + resolve: () => ({ + depth: 3, + node: (d) => { + if (d === 3) return { type: { name: 'table_cell' } }; + if (d === 2) return { childCount: 1 }; + if (d === 1) return { child: () => ({ child: () => ({ textContent: 'hero' }) }) }; + return { type: { name: 'doc' } }; + }, + index: () => 0, + }), + }, + }; + const mockView = { state: mockState, dom: document.createElement('div') }; + const getPos = () => 10; + + const nodeView = createNodeView({ type: { name: 'image' }, attrs: { src: 'https://main--site--org.aem.page/img.jpg' } }, mockView, getPos); + expect(nodeView.dom.querySelector('img').src).to.equal('https://main--site--org.preview.da.live/img.jpg'); + + const nodeView2 = createNodeView({ type: { name: 'image' }, attrs: { src: 'https://main--site--org.aem.live/img.jpg' } }, mockView, getPos); + expect(nodeView2.dom.querySelector('img').src).to.equal('https://main--site--org.preview.da.live/img.jpg'); + }); + it('creates a plain image node view for images outside table cells', () => { const plugin = imageFocalPoint(); const createNodeView = plugin.props.nodeViews.image;