Skip to content

Commit

Permalink
feat: in-context editable blocks (#5)
Browse files Browse the repository at this point in the history
* feat: support in-context authoring for blocks

* fix: preserve rte instrumentation

* fix: decorate richtext after decorating blocks on reload

* fix: move editor-support-rte.js scripts to boilerplate

* chore: short hand for moving instrumentation

* fix: remove moved attribute
  • Loading branch information
buuhuu authored Mar 3, 2024
1 parent 04a637a commit f5c5274
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 19 deletions.
11 changes: 7 additions & 4 deletions blocks/cards/cards.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { createOptimizedPicture } from '../../scripts/aem.js';
import { moveInstrumentation } from '../../scripts/scripts.js';

export default function decorate(block) {
/* change to ul, li */
const ul = document.createElement('ul');
[...block.children].forEach((row) => {
const li = document.createElement('li');
[...row.attributes].forEach(({ nodeName, nodeValue }) => {
li.setAttribute(nodeName, nodeValue);
});
moveInstrumentation(row, li);
while (row.firstElementChild) li.append(row.firstElementChild);
[...li.children].forEach((div) => {
if (div.children.length === 1 && div.querySelector('picture')) div.className = 'cards-card-image';
else div.className = 'cards-card-body';
});
ul.append(li);
});
ul.querySelectorAll('img').forEach((img) => img.closest('picture').replaceWith(createOptimizedPicture(img.src, img.alt, false, [{ width: '750' }])));
ul.querySelectorAll('img').forEach((img) => {
const optimizedPic = createOptimizedPicture(img.src, img.alt, false, [{ width: '750' }]);
moveInstrumentation(img, optimizedPic.querySelector('img'));
img.closest('picture').replaceWith(optimizedPic);
});
block.textContent = '';
block.append(ul);
}
12 changes: 12 additions & 0 deletions scripts/aem.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,10 +634,22 @@ function decorateBlock(block) {
if ((!firstChild && cellText)
|| (firstChild && !firstChild.tagName.match(/^(P(RE)?|H[1-6]|(U|O)L|TABLE)$/))) {
const paragraph = document.createElement('p');
[...cell.attributes]
// move the instrumentation from the cell to the new paragraph, also keep the class
// in case the content is a buttton and the cell the button-container
.filter(({ nodeName }) => nodeName === 'class'
|| nodeName.startsWith('data-aue')
|| nodeName.startsWith('data-richtext'))
.forEach(({ nodeName, nodeValue }) => {
paragraph.setAttribute(nodeName, nodeValue);
cell.removeAttribute(nodeName);
});
paragraph.append(...cell.childNodes);
cell.replaceChildren(paragraph);
}
});
// eslint-disable-next-line no-use-before-define
decorateButtons(block);
}
}

Expand Down
47 changes: 33 additions & 14 deletions scripts/editor-support-rte.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,59 @@
// group editable texts in single wrappers if applicable
//
// this script should execute after script.js by editor-support.js
/* eslint-disable no-cond-assign */
/* eslint-disable import/prefer-default-export */

// group editable texts in single wrappers if applicable.
// this script should execute after script.js but before the the universal editor cors script
// and any block being loaded

// eslint-disable-next-line import/prefer-default-export
export function decorateRichtext(container = document) {
function deleteInstrumentation(element) {
delete element.dataset.richtextResource;
delete element.dataset.richtextProp;
delete element.dataset.richtextFilter;
delete element.dataset.richtextLabel;
}

let element;
// eslint-disable-next-line no-cond-assign
while (element = container.querySelector('[data-richtext-resource]')) {
const { richtextResource, richtextProp, richtextFilter } = element.dataset;
while (element = container.querySelector('[data-richtext-prop]:not(div)')) {
const {
richtextResource,
richtextProp,
richtextFilter,
richtextLabel,
} = element.dataset;
deleteInstrumentation(element);
const siblings = [];
let sibling = element;
// eslint-disable-next-line no-cond-assign
while (sibling = sibling.nextElementSibling) {
if (sibling.dataset.richtextResource === richtextResource
&& sibling.dataset.richtextProp === richtextProp) {
deleteInstrumentation(sibling);
siblings.push(sibling);
} else break;
}
const orphanElements = document.querySelectorAll(`[data-richtext-id="${richtextResource}"][data-richtext-prop="${richtextProp}"]`);

let orphanElements;
if (richtextResource && richtextProp) {
orphanElements = document.querySelectorAll(`[data-richtext-id="${richtextResource}"][data-richtext-prop="${richtextProp}"]`);
} else {
const editable = element.closest('[data-aue-resource]');
if (editable) {
orphanElements = editable.querySelectorAll(`[data-richtext-prop="${richtextProp}"]`);
} else {
console.warn(`Editable parent not found or richtext property ${richtextProp}`);

Check warning on line 43 in scripts/editor-support-rte.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement

Check warning on line 43 in scripts/editor-support-rte.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement

Check warning on line 43 in scripts/editor-support-rte.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
return;
}
}

if (orphanElements.length) {
// eslint-disable-next-line no-console
console.warn('Found orphan elements of a richtext, that were not consecutive siblings of '

Check warning on line 49 in scripts/editor-support-rte.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement

Check warning on line 49 in scripts/editor-support-rte.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement

Check warning on line 49 in scripts/editor-support-rte.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
+ 'the first paragraph.', orphanElements);
orphanElements.forEach((el) => deleteInstrumentation(el));
+ 'the first paragraph', orphanElements);
orphanElements.forEach((orphanElement) => deleteInstrumentation(orphanElement));
} else {
const group = document.createElement('div');
group.dataset.aueResource = richtextResource;
group.dataset.aueProp = richtextProp;
if (richtextResource) group.dataset.aueResource = richtextResource;
if (richtextProp) group.dataset.aueProp = richtextProp;
if (richtextLabel) group.dataset.aueLabel = richtextLabel;
if (richtextFilter) group.dataset.aueFilter = richtextFilter;
group.dataset.aueBehavior = 'component';
group.dataset.aueType = 'richtext';
Expand Down
2 changes: 1 addition & 1 deletion scripts/editor-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ async function applyChanges(event) {
block.insertAdjacentElement('afterend', newBlock);
decorateButtons(newBlock);
decorateIcons(newBlock);
decorateRichtext(newBlock);
decorateBlock(newBlock);
decorateRichtext(newBlock);
await loadBlock(newBlock);
block.remove();
newBlock.style.display = null;
Expand Down
34 changes: 34 additions & 0 deletions scripts/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,40 @@ import {

const LCP_BLOCKS = []; // add your LCP blocks to the list

/**
* Moves all the attributes from a given elmenet to another given element.
* @param {Element} from the element to copy attributes from
* @param {Element} to the element to copy attributes to
*/
export function moveAttributes(from, to, attributes) {
if (!attributes) {
// eslint-disable-next-line no-param-reassign
attributes = [...from.attributes].map(({ nodeName }) => nodeName);
}
attributes.forEach((attr) => {
const value = from.getAttribute(attr);
if (value) {
to.setAttribute(attr, value);
from.removeAttribute(attr);
}
});
}

/**
* Move instrumentation attributes from a given element to another given element.
* @param {Element} from the element to copy attributes from
* @param {Element} to the element to copy attributes to
*/
export function moveInstrumentation(from, to) {
moveAttributes(
from,
to,
[...from.attributes]
.map(({ nodeName }) => nodeName)
.filter((attr) => attr.startsWith('data-aue-') || attr.startsWith('data-richtext-')),
);
}

/**
* load fonts.css and set a session storage flag
*/
Expand Down

0 comments on commit f5c5274

Please sign in to comment.