Skip to content

Commit

Permalink
Merge branch 'aem-assets-plugin' of github.com:hlxsites/franklin-asse…
Browse files Browse the repository at this point in the history
…ts-selector into aem-assets-plugin
  • Loading branch information
Satya Deep Maheshwari committed Oct 10, 2024
2 parents 3f714e8 + f623a63 commit 8de2e72
Show file tree
Hide file tree
Showing 20 changed files with 1,526 additions and 4 deletions.
45 changes: 45 additions & 0 deletions EXTERNAL_IMAGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Using images from external URLs in AEM Franklin pages

## Introduction
This document explains a mechanism for getting images served from external URLs on AEM Franklin pages. You may find this useful if you want to have your images served from an external assets repository.

## Process
During the page authoring process, the author has to specify the external URL from which the image is served. This is done by placing external image links containing the hyperlinked publicly accessible image URLs on the Word/Google Document. The image links are then replaced with the actual images during the page rendering process.

### Note for site authors
Here's [an example page and document](https://ext-images--franklin-assets-selector--hlxsites.hlx.page/external-images-example?view-doc-source=true) that shows how to use external images in AEM Franklin pages.
You can specify external images by just copying and pasting the image URL in the Word/Google Document. The image URL must be hyperlinked. If the hyperlink has an image file extension, it will be treated as an external image. If the hyperlink does not have an image file extension, it will be treated as a regular hyperlink.
Alternatively, you can also explicitly specify an external image marker and adding the external image url as a hyperlink in it.
The above [example page](https://ext-images--franklin-assets-selector--hlxsites.hlx.page/external-images-example?view-doc-source=true) demonstrates both the approaches.

### Note for site developers
Anchor tags get treated as external images if their `textContent` is same as their `href` attribute and the URL specified in `href` is of an image file extension. For e.g. `'jpg', 'jpeg', 'png', 'gif', 'webp'`. Web optimized image formats such as `webp` should be preferred. You can find the implementation of this [here](https://github.com/hlxsites/franklin-assets-selector/blob/9145aeac55512ec199152065b16db6c24cea3421/scripts/scripts.js#L105-L110)

Alternatively, an *image marker* text can be used to explicitly indicate external images. This is a pre-configured value. You can configure it [here](https://github.com/hlxsites/franklin-assets-selector/blob/9145aeac55512ec199152065b16db6c24cea3421/scripts/scripts.js#L227).


Also note that for creating optimized `picture` tags for external images, you must override `createOptimizedPicture` function. You can find a sample overidden implementation of `createOptimizedPicture` [here](https://github.com/hlxsites/franklin-assets-selector/blob/9145aeac55512ec199152065b16db6c24cea3421/scripts/scripts.js#L142-L182).

To summarize, most of the logic for this [is here](https://github.com/hlxsites/franklin-assets-selector/blob/9145aeac55512ec199152065b16db6c24cea3421/scripts/scripts.js#L69-L218) and trigger point for it starts with `decorateExternalImages`. For e.g. [here with an implict external image decoration](https://github.com/hlxsites/franklin-assets-selector/blob/9145aeac55512ec199152065b16db6c24cea3421/scripts/scripts.js#L229-L230).

```
export function decorateMain(main) {
// decorate external images with implicit external image detection
decorateExternalImages(main);
...
}
```

And [here with an explicit external image marker](https://github.com/hlxsites/franklin-assets-selector/blob/9145aeac55512ec199152065b16db6c24cea3421/scripts/scripts.js#L226-L227).
```
export function decorateMain(main) {
// decorate external images with explicit external image marker
decorateExternalImages(main, '//External Image//');
...
}
```

## How does this work?
During the page rendering process, the frontend code replaces the anchor tags identified as exteernal images on the page with the `picture` tags with `src`/`srcset` attributes set as the external image's url as specified in the external image link placed on the Word / Google Document during the page authoring process.

Authors can optionally specify query paramaters in the hyperlinked external url and they would be retained in the `picture` tag's `src`/`srcset` attributes. These are useful for specifying image delivery parameters such as image width, height, format, etc. as understood by the external image delivery service.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Your Project's Title...
Your project's description...
# AEM Asset Selector for Franklin Authoring
Integration between AEM Asset Selector and AEM Franklin to make AEM assets available in Franklin site authoring.

# High level flow

[Link to Diagram Source](https://lucid.app/lucidchart/d6db1b7d-144f-4ac9-94a2-fce760ed2ca4/edit?viewport_loc=-368%2C-403%2C1899%2C1069%2C0_0&invitationId=inv_cd6848d0-dfc0-4be9-b0cb-3cae5a1ba757)

![High Level Flow](/resources/using-asset-selector-with-franklin.jpeg)

## Environments
- Preview: https://main--{repo}--{owner}.aem.page/
Expand All @@ -24,3 +30,18 @@ npm run lint
1. Install the [AEM CLI](https://github.com/adobe/helix-cli): `npm install -g @adobe/aem-cli`
1. Start AEM Proxy: `aem up` (opens your browser at `http://localhost:3000`)
1. Open the `{repo}` directory in your favorite IDE and start coding :)

## Extending the Assets Selector Capabilities

To extend the capabilities of the Assets Selector, you can look at the following sample configurations and GitHub repository as a reference. These configurations are hosted on Adobe IO Runtime through App Builder and can be adapted to fit your project needs and extension requirements.

- [Sample Configuration Hosted on Adobe IO Runtime][0]
- [Sample Configuration with Web Path][1]
- [GitHub Reference Implementation][2]

This configuration is particularly useful for extending the Assets Selector capabilities documented [here][3]. Feel free to integrate and customize it as per your project’s use cases.

[0]: https://245265-extensionconfig.adobeioruntime.net/api/v1/web/extension-config/extension-config
[1]: https://245265-extensionconfig.adobeioruntime.net/api/v1/web/extension-config/extension-config?webPath=snorkling
[2]: https://github.com/Adobe-Marketing-Cloud/assets-selector-extension
[3]: https://www.aem.live/developer/configuring-aem-assets-sidekick-plugin#extend-aem-assets-sidekick-plugin
62 changes: 62 additions & 0 deletions blocks/embed/embed.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
main .embed {
width: unset;
text-align: center;
max-width: 800px;
margin: 32px auto;
}

main .embed > div {
display: flex;
justify-content: center;
}

main .embed.embed-twitter .twitter-tweet-rendered {
margin-left: auto;
margin-right: auto;
}

main .embed .embed-placeholder {
width: 100%;
aspect-ratio: 16 / 9;
position: relative;
}

main .embed .embed-placeholder > * {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
inset: 0;
}

main .embed .embed-placeholder picture img {
width: 100%;
height: 100%;
object-fit: cover;
}

main .embed .embed-placeholder-play button {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(3);
width: 22px;
height: 22px;
border: 2px solid;
border-radius: 20px;
padding: 0;
}

main .embed .embed-placeholder-play button::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 0;
height: 10px;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid;
top: 4px;
left: 7px;
}
113 changes: 113 additions & 0 deletions blocks/embed/embed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Embed Block
* Show videos and social posts directly on your page
* https://www.hlx.live/developer/block-collection/embed
*/

const loadScript = (url, callback, type) => {
const head = document.querySelector('head');
const script = document.createElement('script');
script.src = url;
if (type) {
script.setAttribute('type', type);
}
script.onload = callback;
head.append(script);
return script;
};

const getDefaultEmbed = (url) => `<div style="left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.25%;">
<iframe src="${url.href}" style="border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;" allowfullscreen=""
scrolling="no" allow="encrypted-media" title="Content from ${url.hostname}" loading="lazy">
</iframe>
</div>`;

const embedYoutube = (url, autoplay) => {
const usp = new URLSearchParams(url.search);
const suffix = autoplay ? '&muted=1&autoplay=1' : '';
let vid = usp.get('v') ? encodeURIComponent(usp.get('v')) : '';
const embed = url.pathname;
if (url.origin.includes('youtu.be')) {
[, vid] = url.pathname.split('/');
}
const embedHTML = `<div style="left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.25%;">
<iframe src="https://www.youtube.com${vid ? `/embed/${vid}?rel=0&v=${vid}${suffix}` : embed}" style="border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; accelerometer; gyroscope; picture-in-picture" allowfullscreen="" scrolling="no" title="Content from Youtube" loading="lazy"></iframe>
</div>`;
return embedHTML;
};

const embedVimeo = (url, autoplay) => {
const [, video] = url.pathname.split('/');
const suffix = autoplay ? '?muted=1&autoplay=1' : '';
const embedHTML = `<div style="left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.25%;">
<iframe src="https://player.vimeo.com/video/${video}${suffix}"
style="border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;"
frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen
title="Content from Vimeo" loading="lazy"></iframe>
</div>`;
return embedHTML;
};

const embedTwitter = (url) => {
const embedHTML = `<blockquote class="twitter-tweet"><a href="${url.href}"></a></blockquote>`;
loadScript('https://platform.twitter.com/widgets.js');
return embedHTML;
};

const loadEmbed = (block, link, autoplay) => {
if (block.classList.contains('embed-is-loaded')) {
return;
}

const EMBEDS_CONFIG = [
{
match: ['youtube', 'youtu.be'],
embed: embedYoutube,
},
{
match: ['vimeo'],
embed: embedVimeo,
},
{
match: ['twitter'],
embed: embedTwitter,
},
];

const config = EMBEDS_CONFIG.find((e) => e.match.some((match) => link.includes(match)));
const url = new URL(link);
if (config) {
block.innerHTML = config.embed(url, autoplay);
block.classList = `block embed embed-${config.match[0]}`;
} else {
block.innerHTML = getDefaultEmbed(url);
block.classList = 'block embed';
}
block.classList.add('embed-is-loaded');
};

export default function decorate(block) {
const placeholder = block.querySelector('picture');
const link = block.querySelector('a').href;
block.textContent = '';

if (placeholder) {
const wrapper = document.createElement('div');
wrapper.className = 'embed-placeholder';
wrapper.innerHTML = '<div class="embed-placeholder-play"><button title="Play"></button></div>';
wrapper.prepend(placeholder);
wrapper.addEventListener('click', () => {
loadEmbed(block, link, true);
});
block.append(wrapper);
} else {
const observer = new IntersectionObserver((entries) => {
if (entries.some((e) => e.isIntersecting)) {
observer.disconnect();
loadEmbed(block, link);
}
});
observer.observe(block);
}
}
74 changes: 74 additions & 0 deletions blocks/hotspot/hotspot.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* CSS: styles.css */
.block.hotspot {
position: relative;
background: none;
}

.block.hotspot img {
max-width: 100%;
width: auto;
height: auto;
}

.block.hotspot > div.hotspot {
background: red; /* Temporary for visibility */
z-index: 10; /* Ensure it is above the image */
width: 20px;
height: 20px;
border-radius: 100%;
border: 3px solid white;
position: absolute;
cursor: pointer;
}

.block.hotspot img,
.block.hotspot video {
max-width: 100%;
width: auto;
height: auto;
}

.block.hotspot > div.hotspot .hotspot-content {
display: none; /* Hidden by default */
position: absolute;
top: -10px; /* Adjust positioning as needed */
left: 50%;
transform: translateX(-50%);
padding: 10px;
border-radius: 5px;
z-index: 15;
width: 200px; /* Adjust size as needed */
}

.block.hotspot > div.hotspot .hotspot-content.bgborder {
background: white;
border: 1px solid white;
}

.block.hotspot > div.hotspot.onclick .hotspot-content,
.block.hotspot > div.hotspot:hover .hotspot-content {
display: block; /* Show on click or hover */
}

.block.hotspot > div.hotspot .hotspot-content img,
.block.hotspot > div.hotspot .hotspot-content video {
width: 400px;
height: auto;
max-width: unset;
}

.block.hotspot > div.hotspot .hotspot-content video {
max-height: 150px; /* Adjust as needed */
}

.block.hotspot > div.hotspot:hover::after,
.block.hotspot > div.hotspot.onclick::after {
opacity: 1;
}

.block.hotspot svg {
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
Loading

0 comments on commit 8de2e72

Please sign in to comment.