Skip to content
Closed
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
44 changes: 29 additions & 15 deletions blocks/edit/da-editor/da-editor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { DOMParser as proseDOMParser } from 'da-y-wrapper';
import { DOMParser as proseDOMParser, DOMSerializer } from 'da-y-wrapper';
import { LitElement, html, nothing } from 'da-lit';
import getSheet from '../../shared/sheet.js';
import { initIms, daFetch } from '../../shared/utils.js';
import { getMetadata, parse, aem2prose, setDaMetadata } from '../utils/helpers.js';
import { setDaMetadata } from '../utils/helpers.js';
import convertHtmlToProsemirror from '../../shared/convertHtml.js';

const sheet = await getSheet('/blocks/edit/da-editor/da-editor.css');

Expand All @@ -29,19 +30,32 @@ export default class DaEditor extends LitElement {
this._versionDom = null;
const resp = await daFetch(this.version);
if (!resp.ok) return;
const text = await resp.text();
const doc = parse(text);
this._daMetadata = getMetadata(doc.querySelector('body > .da-metadata'));
const proseDom = aem2prose(doc);
const flattedDom = document.createElement('div');
flattedDom.append(...proseDom);
flattedDom.querySelectorAll('table').forEach((table) => {
const div = document.createElement('div');
div.className = 'tableWrapper';
table.insertAdjacentElement('afterend', div);
div.append(table);
});
this._versionDom = flattedDom;
const htmlContent = await resp.text();

try {
// Use da-collab's convert API for consistent HTML-to-ProseMirror conversion
const { prosemirror, daMetadata } = await convertHtmlToProsemirror(htmlContent);
this._daMetadata = daMetadata;

// Reconstruct DOM from ProseMirror JSON for preview
const { schema } = window.view.state;
const doc = schema.nodeFromJSON(prosemirror);
const serializer = DOMSerializer.fromSchema(schema);
const fragment = serializer.serializeFragment(doc.content);

const flattedDom = document.createElement('div');
flattedDom.append(fragment);
flattedDom.querySelectorAll('table').forEach((table) => {
const div = document.createElement('div');
div.className = 'tableWrapper';
table.insertAdjacentElement('afterend', div);
div.append(table);
});
this._versionDom = flattedDom;
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to fetch version:', err);
}
}

handleCancel() {
Expand Down
36 changes: 25 additions & 11 deletions blocks/edit/da-library/da-library.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable indent */
import { DOMParser as proseDOMParser, TextSelection } from 'da-y-wrapper';
import { DOMParser as proseDOMParser, DOMSerializer, TextSelection } from 'da-y-wrapper';
import {
LitElement,
html,
Expand All @@ -13,10 +13,10 @@ import { getNx, sanitizePathParts } from '../../../scripts/utils.js';
import { getBlocks, getBlockVariants } from './helpers/index.js';
import getSheet from '../../shared/sheet.js';
import inlinesvg from '../../shared/inlinesvg.js';
import { aem2prose } from '../utils/helpers.js';
import { daFetch, aemAdmin } from '../../shared/utils.js';
import searchFor from './helpers/search.js';
import { delay, getItems, getLibraryList, getPreviewUrl, getEdsUrlVars } from './helpers/helpers.js';
import convertHtmlToProsemirror from '../../shared/convertHtml.js';

const sheet = await getSheet('/blocks/edit/da-library/da-library.css');
const buttons = await getSheet(`${getNx()}/styles/buttons.css`);
Expand Down Expand Up @@ -278,15 +278,29 @@ class DaLibrary extends LitElement {
async handleTemplateClick(item) {
const resp = await daFetch(`${item.value}`);
if (!resp.ok) return;
const text = await resp.text();
const doc = new DOMParser().parseFromString(text, 'text/html');
// Convert to a metadata block so it can be copied
doc.querySelector('.template-metadata')?.classList.replace('template-metadata', 'metadata');
const proseDom = aem2prose(doc);
const flattedDom = document.createElement('div');
flattedDom.append(...proseDom);
const newNodes = proseDOMParser.fromSchema(window.view.state.schema).parse(flattedDom);
window.view.dispatch(window.view.state.tr.replaceSelectionWith(newNodes));
let htmlContent = await resp.text();

// Convert template-metadata to metadata block before conversion
htmlContent = htmlContent.replace(/class="template-metadata"/g, 'class="metadata"');

try {
// Use da-collab's convert API for consistent HTML-to-ProseMirror conversion
const { prosemirror } = await convertHtmlToProsemirror(htmlContent);
const { schema } = window.view.state;
const doc = schema.nodeFromJSON(prosemirror);

// Serialize back to DOM for ProseMirror to parse
const serializer = DOMSerializer.fromSchema(schema);
const fragment = serializer.serializeFragment(doc.content);
const flattedDom = document.createElement('div');
flattedDom.append(fragment);

const newNodes = proseDOMParser.fromSchema(schema).parse(flattedDom);
window.view.dispatch(window.view.state.tr.replaceSelectionWith(newNodes));
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to insert template:', err);
}
}

getParts() {
Expand Down
86 changes: 0 additions & 86 deletions blocks/edit/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,92 +43,6 @@ export function getTable(block) {
return table;
}

function para() {
return document.createElement('p');
}

export function aem2prose(doc) {
// Fix BRs
const brs = doc.querySelectorAll('p br');
brs.forEach((br) => br.remove());

// Els with da-diff-added property get wrapped in the da-diff-added element
const diffAddedEls = doc.querySelectorAll('[da-diff-added]');
diffAddedEls.forEach((el) => {
const div = document.createElement('da-diff-added');
div.setAttribute('da-diff-added', '');
if (el.classList.contains('block-group-start')) {
div.className = 'da-group';
}
el.parentElement.insertBefore(div, el);
div.appendChild(el);
});

// Fix blocks
const blocks = doc.querySelectorAll('main > div > div, da-diff-deleted > div, da-diff-added > div, da-diff-deleted.da-group > div > div, da-diff-added.da-group > div > div');
blocks.forEach((block) => {
if (block.className?.includes('loc-')) return;
const table = getTable(block);
block.parentElement.replaceChild(table, block);
table.insertAdjacentElement('beforebegin', para());
table.insertAdjacentElement('afterend', para());
});

// Fix pictures
const imgs = doc.querySelectorAll('picture img');
imgs.forEach((img) => {
const pic = img.closest('picture');
pic.parentElement.replaceChild(img, pic);
});

// Fix three dashes
const paras = doc.querySelectorAll('p');
paras.forEach((p) => {
if (p.textContent.trim() === '---') {
const hr = document.createElement('hr');
p.parentElement.replaceChild(hr, p);
}
});

// Fix da-diff-* list items
const lis = doc.querySelectorAll('main > div > :is(ul, ol) > :is(da-diff-added, da-diff-deleted)');
lis.forEach((li) => {
const isDiffDeleted = li.nodeName === 'DA-DIFF-DELETED';
const isDiffAdded = li.nodeName === 'DA-DIFF-ADDED';

if (!isDiffDeleted && !isDiffAdded) return;

if (isDiffDeleted && li.firstChild?.nodeName === 'LI' && li.firstChild.children.length === 0) {
li.firstChild.remove();
}

if (li.firstChild?.nodeName === 'LI') {
const innerLi = li.firstChild;
const newLi = document.createElement('li');
const diffElement = document.createElement(isDiffDeleted ? 'da-diff-deleted' : 'da-diff-added');

while (innerLi.firstChild) {
diffElement.appendChild(innerLi.firstChild);
}

newLi.appendChild(diffElement);
li.parentElement.replaceChild(newLi, li);
}
});

// Fix sections
const sections = doc.body.querySelectorAll('main > div');
return [...sections].map((section, idx) => {
const fragment = new DocumentFragment();
if (idx > 0) {
const hr = document.createElement('hr');
fragment.append(para(), hr, para());
}
fragment.append(...section.querySelectorAll(':scope > *'));
return fragment;
});
}

/* eslint-disable max-len */
/**
* [admin] Unable to preview '.../page.md': source contains large image: error fetching resource at http.../hello: Image 1 exceeds allowed limit of 10.00MB
Expand Down
8 changes: 8 additions & 0 deletions blocks/shared/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ const DA_COLLAB_ENVS = {
prod: 'wss://collab.da.live',
};

// HTTP versions of collab endpoints for REST API calls (convert API)
const DA_COLLAB_HTTP_ENVS = {
local: 'http://localhost:4711',
stage: 'https://stage-collab.da.live',
prod: 'https://collab.da.live',
};

function getDaEnv(location, key, envs) {
const { href } = location;
const query = new URL(href).searchParams.get(key);
Expand All @@ -50,3 +57,4 @@ export const getDaAdmin = (() => {

export const DA_ORIGIN = (() => getDaEnv(window.location, 'da-admin', DA_ADMIN_ENVS))();
export const COLLAB_ORIGIN = (() => getDaEnv(window.location, 'da-collab', DA_COLLAB_ENVS))();
export const COLLAB_HTTP_ORIGIN = (() => getDaEnv(window.location, 'da-collab', DA_COLLAB_HTTP_ENVS))();
25 changes: 25 additions & 0 deletions blocks/shared/convertHtml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { COLLAB_HTTP_ORIGIN } from './constants.js';

/**
* Convert AEM HTML to ProseMirror JSON using the da-collab convert API.
* This provides a single source of truth for HTML-to-ProseMirror conversion,
* ensuring consistency between live editing and version preview/restore.
*
* @param {string} html - The AEM HTML string to convert
* @returns {Promise<{prosemirror: Object, daMetadata: Object}>} - The converted document
* @throws {Error} - If the conversion fails
*/
export default async function convertHtmlToProsemirror(html) {
const response = await fetch(`${COLLAB_HTTP_ORIGIN}/api/v1/convert`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html }),
});

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `Conversion failed: ${response.status}`);
}

return response.json();
}
36 changes: 0 additions & 36 deletions test/unit/blocks/edit/utils/helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { readFile } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';

import {
aem2prose,
saveToDa,
convertSheets,
parse,
Expand All @@ -17,41 +16,6 @@ import {

document.body.innerHTML = await readFile({ path: './mocks/body.html' });

describe('aem2prose', () => {
before('parse everything', () => {
const docFragments = aem2prose(document);
const main = document.body.querySelector('main');
main.innerHTML = '';
main.append(...docFragments);
});

it('Decorates block', () => {
const threeTwo = document.querySelector('table');
// Title
const title = threeTwo.querySelector('td').textContent;
expect(title).to.equal('marquee (dark, large)');

// Last cell
const lastCell = [...threeTwo.querySelectorAll('td')].slice(-1)[0];
expect(lastCell.colSpan).to.equal(2);
});

it('Decorates picture', () => {
const pic = document.querySelector('picture');
expect(pic).to.not.exist;
});

it('Decorates sections', () => {
const hr = document.querySelector('hr');
expect(hr).to.exist;
});

it('Decorates HRs', () => {
const hr = document.querySelector('table hr');
expect(hr).to.exist;
});
});

function createSheet(name, data, columnWidths) {
return {
name,
Expand Down
Loading
Loading