Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions blocks/edit/prose/image-utils.js
Original file line number Diff line number Diff line change
@@ -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) {
Comment thread
andreituicu marked this conversation as resolved.
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;
}
15 changes: 15 additions & 0 deletions blocks/edit/prose/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
Expand Down Expand Up @@ -469,6 +470,20 @@ export default async function initProse({ path, permissions, doc, daContent, wsP
state,
dispatchTransaction,
nodeViews: {
image(node) {
const img = document.createElement('img');
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 = rewriteImageSrcForEditor(updated.attrs.src);
if (updated.attrs.alt) img.alt = updated.attrs.alt;
return true;
},
};
},
Comment thread
andreituicu marked this conversation as resolved.
diff_added(node, view, getPos) {
const LocAddedView = getDiffClass('da-diff-added', getSchema, dispatchTransaction, { isUpstream: false });
return new LocAddedView(node, view, getPos);
Expand Down
14 changes: 12 additions & 2 deletions blocks/edit/prose/plugins/imageFocalPoint.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -40,7 +41,7 @@ function shouldShowFocalPoint(tableName, blocks) {
}

function updateImageAttributes(img, attrs) {
img.src = attrs.src;
img.src = rewriteImageSrcForEditor(attrs.src);
['alt', 'title', 'width', 'height'].forEach((attr) => {
if (attrs[attr]) {
img[attr] = attrs[attr];
Expand Down Expand Up @@ -150,7 +151,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;
Comment thread
kptdobe marked this conversation as resolved.
},
};
},
},
},
Expand Down
37 changes: 37 additions & 0 deletions test/unit/blocks/edit/prose/image-utils.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
26 changes: 26 additions & 0 deletions test/unit/blocks/edit/prose/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; } };
Expand Down
35 changes: 32 additions & 3 deletions test/unit/blocks/edit/prose/plugins/imageFocalPoint.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,35 @@ 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('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;

Expand All @@ -144,8 +172,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 () => {
Expand Down
Loading