Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for image links #199

Merged
merged 4 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions blocks/edit/da-editor/da-editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,10 @@ table tr:first-of-type p:first-child {
pointer-events: none;
}

.ProseMirror img[href] {
border: #00ee 3px solid;
}

/* Palette */
.da-palettes {
position: fixed;
Expand Down
34 changes: 33 additions & 1 deletion blocks/edit/prose/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,45 @@ function addCustomMarks(marks) {
.addToEnd('contextHighlightingMark', contextHighlight);
}

function getImageNodeWithHref() {
// due to bug in y-prosemirror, add href to image node
// which will be converted to a wrapping <a> tag
return {
inline: true,
attrs: {
src: { validate: 'string' },
alt: { default: null, validate: 'string|null' },
title: { default: null, validate: 'string|null' },
href: { default: null, validate: 'string|null' },
},
group: 'inline',
draggable: true,
parseDOM: [{
tag: 'img[src]',
getAttrs(dom) {
return {
src: dom.getAttribute('src'),
title: dom.getAttribute('title'),
alt: dom.getAttribute('alt'),
href: dom.getAttribute('href'),
};
},
}],
toDOM(node) {
const { src, alt, title, href } = node.attrs;
return ['img', { src, alt, title, href }];
},
};
}

// Note: until getSchema() is separated in its own module, this function needs to be kept in-sync
// with the getSchema() function in da-collab src/collab.js
export function getSchema() {
const { marks, nodes: baseNodes } = baseSchema.spec;
const withLocNodes = addLocNodes(baseNodes);
const withListnodes = addListNodes(withLocNodes, 'block+', 'block');
const nodes = withListnodes.append(tableNodes({ tableGroup: 'block', cellContent: 'block+' }));
const withTableNodes = withListnodes.append(tableNodes({ tableGroup: 'block', cellContent: 'block+' }));
const nodes = withTableNodes.update('image', getImageNodeWithHref());
return new Schema({ nodes, marks: addCustomMarks(marks) });
}

Expand Down
82 changes: 68 additions & 14 deletions blocks/edit/prose/plugins/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ function calculateLinkPosition(state, link, offset) {
};
}

function hasImageNode(contentArr) {
if (!contentArr) return false;
return contentArr.some((node) => node.type.name === 'image');
}

function linkItem(linkMarkType) {
const label = 'Link';

Expand All @@ -120,8 +125,10 @@ function linkItem(linkMarkType) {
class: 'edit-link',
active(state) { return markActive(state, linkMarkType); },
enable(state) {
return state.selection.content().content.childCount <= 1
&& (!state.selection.empty || this.active(state));
const selContent = state.selection.content();
return selContent.content.childCount <= 1
&& (!state.selection.empty || this.active(state))
&& !hasImageNode(selContent.content?.content[0]?.content?.content);
},
run(initialState, dispatch, view) {
if (lastPrompt.isOpen()) {
Expand Down Expand Up @@ -158,17 +165,35 @@ function linkItem(linkMarkType) {
end = $to.pos;
}

let isImage = false;
view.state.doc.nodesBetween($from.pos, $to.pos, (node) => {
if (node.type === view.state.schema.nodes.image) {
isImage = true;
fields.href.value = node.attrs.href;
fields.title.value = node.attrs.title;
}
});

dispatch(view.state.tr
.addMark(start, end, view.state.schema.marks.contextHighlightingMark.create({}))
.setMeta('addToHistory', false));

const callback = (attrs) => {
const tr = view.state.tr
.setSelection(TextSelection.create(view.state.doc, start, end));
if (fields.href.value) {
dispatch(tr.addMark(start, end, linkMarkType.create(attrs)));
} else if (this.active(view.state)) {
dispatch(tr.removeMark(start, end, linkMarkType));
if (isImage) {
if (fields.href.value) {
dispatch(view.state.tr.setNodeAttribute(start, 'href', fields.href.value.trim()));
}
if (fields.title.value) {
dispatch(view.state.tr.setNodeAttribute(start, 'title', fields.title.value.trim()));
}
} else {
const tr = view.state.tr
.setSelection(TextSelection.create(view.state.doc, start, end));
if (fields.href.value) {
dispatch(tr.addMark(start, end, linkMarkType.create(attrs)));
} else if (this.active(view.state)) {
dispatch(tr.removeMark(start, end, linkMarkType));
}
}

view.focus();
Expand All @@ -187,15 +212,44 @@ function removeLinkItem(linkMarkType) {
title: 'Remove link',
label: 'Remove Link',
class: 'edit-unlink',
active(state) { return markActive(state, linkMarkType); },
isImage: false,
active(state) {
this.isImage = false;
const { $from, $to } = state.selection;
let imgHasAttrs = false;
state.doc.nodesBetween($from.pos, $to.pos, (node) => {
if (node.type === state.schema.nodes.image) {
this.isImage = true;
if (node.attrs.href || node.attrs.title) {
imgHasAttrs = true;
} else {
imgHasAttrs = false;
}
}
});
if (this.isImage) {
if (($to.pos - $from.pos) <= 1) {
return imgHasAttrs;
}
// selection is more than just an image
return false;
}

return markActive(state, linkMarkType);
},
enable(state) { return this.active(state); },
// eslint-disable-next-line no-unused-vars
run(state, dispatch, _view) {
const { link, offset } = findExistingLink(state, linkMarkType);
const { start, end } = calculateLinkPosition(state, link, offset);
const tr = state.tr.setSelection(TextSelection.create(state.doc, start, end))
.removeMark(start, end, linkMarkType);
dispatch(tr);
if (this.isImage) {
const { $from } = state.selection;
dispatch(state.tr.setNodeAttribute($from.pos, 'href', null).setNodeAttribute($from.pos, 'title', null));
} else {
const { link, offset } = findExistingLink(state, linkMarkType);
const { start, end } = calculateLinkPosition(state, link, offset);
const tr = state.tr.setSelection(TextSelection.create(state.doc, start, end))
.removeMark(start, end, linkMarkType);
dispatch(tr);
}
},
});
}
Expand Down
14 changes: 13 additions & 1 deletion blocks/shared/prose2aem.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function makePictures(editor) {
const clone = img.cloneNode(true);
clone.setAttribute('loading', 'lazy');

const pic = document.createElement('picture');
let pic = document.createElement('picture');

const srcMobile = document.createElement('source');
srcMobile.srcset = clone.src;
Expand All @@ -66,6 +66,18 @@ function makePictures(editor) {

pic.append(srcMobile, srcTablet, clone);

const hrefAttr = img.getAttribute('href');
if (hrefAttr) {
const a = document.createElement('a');
a.href = hrefAttr;
const titleAttr = img.getAttribute('title');
if (titleAttr) {
a.title = titleAttr;
}
a.append(pic);
pic = a;
}

// Determine what to replace
const imgParent = img.parentElement;
const imgGrandparent = imgParent.parentElement;
Expand Down
5 changes: 2 additions & 3 deletions test/e2e/tests/browse.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import ENV from '../utils/env.js';

test('Get Main Page', async ({ page }) => {
await page.goto(ENV);

const html = await page.content();
expect(html).toContain('Dark Alley');

expect(html).toContain('Browse - DA');
await expect(page.locator('a.nx-nav-brand')).toBeVisible();
await expect(page.locator('a.nx-nav-brand')).toContainText('Project Dark Alley');
await expect(page.locator('a.nx-nav-brand')).toContainText('Document Authoring');
});
4 changes: 4 additions & 0 deletions test/unit/blocks/shared/mocks/prose2aem.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@
</tr>
</table>
</div>
<!-- Linked Images -->
<div>
<img src="http://www.foo.com/myimg.jpg" href="https://my.image.link">
</div>
6 changes: 6 additions & 0 deletions test/unit/blocks/shared/prose2aem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ describe('aem2prose', () => {
const meta = document.querySelector('.metadata');
expect(meta).to.not.exist;
});

it('Wraps imgs with href attrs in a link tag', () => {
const pictureEl = document.querySelector('a > picture');
const parent = pictureEl.parentElement;
expect(parent.href).to.equal('https://my.image.link/');
});
});
Loading