design-comuni-plone-theme
Version:
Volto Theme for Italia design guidelines
254 lines (218 loc) • 8.59 kB
JavaScript
import { jsx } from 'slate-hyperscript';
import { deserializeChildren } from '@plone/volto-slate/editor/deserialize';
import { TD, TH, TEXT_NODE } from '@plone/volto-slate/constants';
/*rispetto a quello di volto-slate:
- aggiunge anche la classe (styleName) se era stata impostata
*/
export const blockTagDeserializer = (tagname) => (editor, el, options) => {
// if (tagname === 'h2') debugger;
let children = deserializeChildren(el, editor, options).filter(
(n) => n !== null,
);
if (
[TD, TH].includes(tagname) &&
children.length > 0 &&
typeof children[0] === 'string'
) {
// TODO: should here be handled the cases when there are more strings in
// `children` or when there are besides strings other types of nodes too?
const p = { type: 'div', children: [{ text: '' }] };
p.children[0].text = children[0];
children = [p];
}
// normalizes block elements so that they're never empty
// Avoids a hard crash from the Slate editor
const hasValidChildren = children.length && children.find((c) => !!c);
if (!hasValidChildren) {
children = [{ text: '' }];
}
return jsx(
'element',
{ type: tagname, styleName: el.getAttribute('class') },
children,
);
};
/*rispetto a quello di volto-slate:
- se il tag body contiene direttamente dei children <li> allora crea un <ul> wrapper.
- se il tag body contiene una lista da Word Online, la normalizza.
- se il tag body contiene una lista da Word Desktop, la normalizza.
*/
export const bodyTagDeserializer = () => (editor, el, options) => {
if (
el?.firstElementChild?.nodeName === 'LI' &&
el?.lastElementChild?.nodeName === 'LI'
) {
return jsx(
'element',
{ type: 'ul' },
deserializeChildren(el, editor, options),
);
}
const children = Array.from(el.children);
// START: Gestione Word Online
const hasWordOnlineList = children.some(
(child) =>
child.nodeName === 'DIV' &&
child.classList?.contains('ListContainerWrapper'),
);
if (hasWordOnlineList) {
const newBody = document.createElement('body');
let currentList = null;
for (const child of children) {
const isWordListWrapper =
child.nodeName === 'DIV' &&
child.classList?.contains('ListContainerWrapper') &&
(child.firstElementChild?.nodeName === 'UL' ||
child.firstElementChild?.nodeName === 'OL');
if (isWordListWrapper) {
const listNode = child.firstElementChild;
const listType = listNode.nodeName.toLowerCase();
const listItem = listNode.querySelector('li');
if (listItem) {
const paragraph = listItem.querySelector('p.Paragraph');
if (paragraph) {
while (paragraph.firstChild) {
listItem.appendChild(paragraph.firstChild);
}
listItem.removeChild(paragraph);
}
if (currentList && currentList.nodeName.toLowerCase() === listType) {
currentList.appendChild(listItem);
} else {
currentList = document.createElement(listType);
currentList.appendChild(listItem);
newBody.appendChild(currentList);
}
}
} else {
currentList = null;
newBody.appendChild(child.cloneNode(true));
}
}
return jsx('fragment', {}, deserializeChildren(newBody, editor, options));
}
// END: Gestione Word Online
// START: NUOVA GESTIONE Word Desktop
const hasWordDesktopList = children.some((child) =>
child.matches('p[class*="MsoListParagraph"]'),
);
if (hasWordDesktopList) {
const newBody = document.createElement('body');
let currentList = null;
for (const child of children) {
// Verifica se l'elemento è un paragrafo di lista di Word
if (child.matches('p[class*="MsoListParagraph"]')) {
let listType = 'ul'; // Default a lista non ordinata
// Rimuove gli elementi specifici di Word e determina il tipo di lista
const bulletSpan = child.querySelector(
'span[style*="mso-list:Ignore"]',
);
if (bulletSpan) {
// Se il bullet non è il classico pallino, la lista è ordinata (ol)
if (bulletSpan.textContent.trim() !== '·') {
listType = 'ol';
}
// Rimuove l'intero blocco del bullet, che è un commento condizionale o un span
bulletSpan.closest('span')?.remove();
}
// Rimuove i tag <o:p> vuoti di Office
child.querySelectorAll('o\\:p').forEach((el) => el.remove());
// Se la lista corrente non esiste o è di tipo diverso, ne crea una nuova
if (!currentList || currentList.nodeName.toLowerCase() !== listType) {
currentList = document.createElement(listType);
newBody.appendChild(currentList);
}
// Crea il nuovo elemento <li> e ci sposta dentro tutto il contenuto del <p>
const listItem = document.createElement('li');
while (child.firstChild) {
listItem.appendChild(child.firstChild);
}
currentList.appendChild(listItem);
} else {
// Se non è un elemento di lista, interrompe il raggruppamento
currentList = null;
// Aggiunge gli altri elementi (es. paragrafi normali) così come sono
newBody.appendChild(child.cloneNode(true));
}
}
// Deserializza il nuovo body pulito e ristrutturato
return jsx('fragment', {}, deserializeChildren(newBody, editor, options));
}
// END: NUOVA GESTIONE Word Desktop
return jsx('fragment', {}, deserializeChildren(el, editor, options));
};
export const spanTagDeserializer = (editor, el, options) => {
const style = el.getAttribute('style') || '';
let children = el.childNodes;
if (
// handle formatting from OpenOffice
children.length === 1 &&
children[0].nodeType === TEXT_NODE &&
children[0].textContent === '\n'
) {
return jsx('text', {}, ' ');
}
children = deserializeChildren(el, editor, options);
// whitespace is replaced by deserialize() with null;
children = children.map((c) => (c === null ? '' : c));
// THIS IS THE PATCH FOR THE DEFAULT DESERIALIZER:
// Text pasted from Word Online has some "strange" html and styles
// like bold, underline and italic are set in css inline styles and not as markup
const stylesMapping = {
'font-weight:bold': 'strong',
'font-style:italic': 'em',
'text-decoration:underline': 'u',
};
function parseStyles(style) {
if (!style) return [];
const cleaned = style.replace(/\s/g, '').toLowerCase();
return Object.entries(stylesMapping)
.filter(([cssProp]) => cleaned.includes(cssProp))
.map(([, type]) => type);
}
function nestStyles(children, styleString) {
const activeStyles = parseStyles(styleString);
if (activeStyles.length === 0) {
return children;
}
// Riduci da destra a sinistra creando la struttura annidata con jsx
return activeStyles.reduceRight((acc, styleType) => {
const attrs = { type: styleType };
// acc può essere array di children, ma jsx vuole children singolo o array
return [jsx('element', attrs, acc)];
}, children);
}
if (parseStyles(style).length > 0) {
const styled = nestStyles(children, style);
return Array.isArray(styled) && styled.length === 1 ? styled[0] : styled;
}
// TODO: handle sub/sup as <sub> and <sup>
// Handle Google Docs' <sub> formatting
if (style.replace(/\s/g, '').indexOf('vertical-align:sub') > -1) {
const attrs = { sub: true };
return children.map((child) => {
return jsx('text', attrs, child);
});
}
// Handle Google Docs' <sup> formatting
if (style.replace(/\s/g, '').indexOf('vertical-align:super') > -1) {
const attrs = { sup: true };
return children.map((child) => {
return jsx('text', attrs, child);
});
}
const res = children.find((c) => typeof c !== 'string')
? children
: jsx('text', {}, children);
return res;
};
export default function install(config) {
config.settings.slate.htmlTagsToSlate.BODY = bodyTagDeserializer();
config.settings.slate.htmlTagsToSlate.P = blockTagDeserializer('p');
config.settings.slate.htmlTagsToSlate.OL = blockTagDeserializer('ol');
config.settings.slate.htmlTagsToSlate.UL = blockTagDeserializer('ul');
config.settings.slate.htmlTagsToSlate.BLOCKQUOTE =
blockTagDeserializer('blockquote');
config.settings.slate.htmlTagsToSlate.SPAN = spanTagDeserializer;
//A (link) deserializer is defined in ./Link/deserializer.js
}