first commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export function isNull<Type>(data: Type | null, returnValue: Type): Type {
|
||||
return data ? data : returnValue;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import currency from "currency.js";
|
||||
|
||||
export const formatPrice = (price: number) => {
|
||||
return currency(price, { symbol: "$", separator: ".", decimal: "," }).format();
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
export const getSlug = (name: string): string => {
|
||||
return name
|
||||
.replaceAll(".", "")
|
||||
.replaceAll(",", "")
|
||||
.replaceAll(":", "")
|
||||
.replaceAll(";", "")
|
||||
.replaceAll("!", "")
|
||||
.replaceAll("<", "")
|
||||
.replaceAll(">", "")
|
||||
.replaceAll("'", "")
|
||||
.replaceAll('"', "")
|
||||
.replaceAll("#", "")
|
||||
.replaceAll("$", "")
|
||||
.replaceAll("%", "")
|
||||
.replaceAll("&", "")
|
||||
.replaceAll("/", "")
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.replaceAll("=", "")
|
||||
.replaceAll("¿", "")
|
||||
.replaceAll("?", "")
|
||||
.replaceAll("*", "")
|
||||
.replaceAll("+", "")
|
||||
.replaceAll("-", "")
|
||||
.replaceAll("|", "")
|
||||
.replaceAll("{", "")
|
||||
.replaceAll("}", "")
|
||||
.replaceAll("[", "")
|
||||
.replaceAll("]", "")
|
||||
.split(" ")
|
||||
.filter((item) => item.length > 0)
|
||||
.reduce((acc, item) => acc + "-" + item.toLowerCase(), "")
|
||||
.substring(1);
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
/**
|
||||
* Preprocesa HTML generado por Quill antes de enviarlo al backend
|
||||
* o renderizarlo en React.
|
||||
* - Sanitiza el HTML para prevenir XSS
|
||||
* - Convierte listas bullets (<ol data-list="bullet">) a <ul>
|
||||
* - Mantiene colores, background-color y clases de Quill
|
||||
*
|
||||
* @param rawHtml HTML crudo de Quill
|
||||
* @returns HTML limpio y con listas correctamente formateadas
|
||||
*/
|
||||
export function preprocessQuillHtml(rawHtml: string): string {
|
||||
// 1️⃣ Sanitización con DOMPurify
|
||||
const cleanHtml = DOMPurify.sanitize(rawHtml, {
|
||||
ALLOWED_TAGS: [
|
||||
"p",
|
||||
"ul",
|
||||
"ol",
|
||||
"li",
|
||||
"strong",
|
||||
"b",
|
||||
"i",
|
||||
"em",
|
||||
"u",
|
||||
"s",
|
||||
"span",
|
||||
"a",
|
||||
"img",
|
||||
"br",
|
||||
"div",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"iframe", // Permitir iframes
|
||||
],
|
||||
// ✅ Incluimos data-list para que no sea eliminada
|
||||
ALLOWED_ATTR: [
|
||||
"style",
|
||||
"class",
|
||||
"contenteditable",
|
||||
"data-list",
|
||||
"href",
|
||||
"target",
|
||||
"rel",
|
||||
"title",
|
||||
"src",
|
||||
"alt",
|
||||
"width",
|
||||
"height",
|
||||
"allowfullscreen", // Permitir para videos
|
||||
"frameborder", // Permitir para videos
|
||||
],
|
||||
ALLOWED_URI_REGEXP: /^(https?|mailto):/,
|
||||
});
|
||||
|
||||
// 2️⃣ Parsear como DOM
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(cleanHtml, "text/html");
|
||||
|
||||
// 📹 Filtrar iframes para permitir solo videos de YouTube
|
||||
doc.querySelectorAll("iframe").forEach((iframe) => {
|
||||
const src = iframe.getAttribute("src") || "";
|
||||
const isYoutubeVideo =
|
||||
src.startsWith("https://www.youtube.com/embed/") ||
|
||||
src.startsWith("https://youtube.com/embed/");
|
||||
|
||||
if (!isYoutubeVideo) {
|
||||
iframe.remove(); // Eliminar iframes que no sean de YouTube
|
||||
}
|
||||
});
|
||||
|
||||
// 3️⃣ Convertir <ol data-list="bullet"> a <ul> y manejar <li> con data-list="bullet"
|
||||
doc.querySelectorAll("ol").forEach((ol) => {
|
||||
const olEl = ol as HTMLElement;
|
||||
// Si la OL tiene data-list="bullet" o si contiene LI con data-list="bullet"
|
||||
const hasBulletLi = Array.from(olEl.children).some(
|
||||
(child) => (child as HTMLElement).dataset.list === "bullet"
|
||||
);
|
||||
|
||||
if (olEl.dataset.list === "bullet" || hasBulletLi) {
|
||||
const ul = doc.createElement("ul");
|
||||
// Mover los hijos de ol a ul
|
||||
while (olEl.firstChild) {
|
||||
const child = olEl.firstChild as HTMLElement;
|
||||
// Eliminar spans ql-ui vacíos y el atributo data-list de los li
|
||||
if (child.tagName === "LI") {
|
||||
child.querySelectorAll("span.ql-ui").forEach((span) => span.remove());
|
||||
delete child.dataset.list; // Eliminar el atributo data-list
|
||||
}
|
||||
ul.appendChild(child);
|
||||
}
|
||||
olEl.replaceWith(ul);
|
||||
}
|
||||
});
|
||||
|
||||
// Asegurarse de que cualquier <li> con data-list="bullet" que no esté dentro de una OL/UL sea manejado
|
||||
doc.querySelectorAll("li[data-list='bullet']").forEach((li) => {
|
||||
const liEl = li as HTMLElement;
|
||||
// Eliminar spans ql-ui vacíos
|
||||
liEl.querySelectorAll("span.ql-ui").forEach((span) => span.remove());
|
||||
delete liEl.dataset.list; // Eliminar el atributo data-list
|
||||
// Si el padre no es una UL, envolverlo en una UL (esto es un caso extremo, pero para robustez)
|
||||
if (liEl.parentElement && liEl.parentElement.tagName !== "UL") {
|
||||
const ul = doc.createElement("ul");
|
||||
liEl.replaceWith(ul);
|
||||
ul.appendChild(liEl);
|
||||
}
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
}
|
||||
|
||||
export const transformQuillContentForDisplay = (htmlString: string): string => {
|
||||
if (!htmlString) return '';
|
||||
|
||||
// Usar DOMParser para manipulación segura del HTML
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// Transformar imágenes
|
||||
const images = doc.querySelectorAll('img');
|
||||
images.forEach((img) => {
|
||||
// Crear wrapper para imagen
|
||||
const wrapper = doc.createElement('div');
|
||||
wrapper.className = 'image-wrapper';
|
||||
|
||||
// Clonar la imagen con sus atributos
|
||||
const clonedImg = img.cloneNode(true) as HTMLImageElement;
|
||||
|
||||
// Insertar en wrapper y reemplazar
|
||||
wrapper.appendChild(clonedImg);
|
||||
if (img.parentNode) {
|
||||
img.parentNode.replaceChild(wrapper, img);
|
||||
}
|
||||
});
|
||||
|
||||
// Transformar videos (Quill usa iframes para videos)
|
||||
const videos = doc.querySelectorAll('iframe.ql-video, iframe[class*="ql-video"]');
|
||||
videos.forEach((iframe) => {
|
||||
// Crear wrapper para video
|
||||
const wrapper = doc.createElement('div');
|
||||
wrapper.className = 'video-wrapper';
|
||||
|
||||
// Clonar el iframe y ajustar estilos
|
||||
const clonedIframe = iframe.cloneNode(true) as HTMLIFrameElement;
|
||||
|
||||
|
||||
// Insertar en wrapper y reemplazar
|
||||
wrapper.appendChild(clonedIframe);
|
||||
if (iframe.parentNode) {
|
||||
iframe.parentNode.replaceChild(wrapper, iframe);
|
||||
}
|
||||
});
|
||||
|
||||
// También buscar divs que ya puedan ser videos de Quill
|
||||
const videoDivs = doc.querySelectorAll('div.ql-video, div[class*="ql-video"]');
|
||||
videoDivs.forEach((div) => {
|
||||
const wrapper = doc.createElement('div');
|
||||
wrapper.className = 'video-wrapper';
|
||||
|
||||
const clonedContent = div.cloneNode(true);
|
||||
wrapper.appendChild(clonedContent);
|
||||
|
||||
if (div.parentNode) {
|
||||
div.parentNode.replaceChild(wrapper, div);
|
||||
}
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
};
|
||||
Reference in New Issue
Block a user