diff --git a/frontend/src/app/features/admin/pages/admin-shop.component.ts b/frontend/src/app/features/admin/pages/admin-shop.component.ts index d53cee2..1e368eb 100644 --- a/frontend/src/app/features/admin/pages/admin-shop.component.ts +++ b/frontend/src/app/features/admin/pages/admin-shop.component.ts @@ -1836,17 +1836,18 @@ export class AdminShopComponent implements OnInit, OnDestroy { if (!editor) { return; } + const currentHtml = this.serializeNodeChildren(editor); const currentLanguage = this.activeContentLanguage; if (sanitize) { - const normalized = this.normalizeRichTextStorageValue(editor.innerHTML); + const normalized = this.normalizeRichTextStorageValue(currentHtml); const safeHtml = normalized ?? ''; this.productForm.descriptions[currentLanguage] = safeHtml; - if (editor.innerHTML !== safeHtml) { - editor.innerHTML = safeHtml; + if (currentHtml !== safeHtml) { + this.replaceElementContentFromHtml(editor, safeHtml); } return; } - this.productForm.descriptions[currentLanguage] = editor.innerHTML; + this.productForm.descriptions[currentLanguage] = currentHtml; } private renderActiveDescriptionInEditor(): void { @@ -1856,8 +1857,8 @@ export class AdminShopComponent implements OnInit, OnDestroy { } const html = this.productForm.descriptions[this.activeContentLanguage] ?? ''; - if (editor.innerHTML !== html) { - editor.innerHTML = html; + if (this.serializeNodeChildren(editor) !== html) { + this.replaceElementContentFromHtml(editor, html); } } @@ -1926,7 +1927,7 @@ export class AdminShopComponent implements OnInit, OnDestroy { } } - return outputBody.innerHTML; + return this.serializeNodeChildren(outputBody); } private sanitizeRichTextNode( @@ -2016,9 +2017,35 @@ export class AdminShopComponent implements OnInit, OnDestroy { } private extractTextFromHtml(value: string): string { - const container = document.createElement('div'); - container.innerHTML = value; - return container.textContent ?? ''; + const parser = new DOMParser(); + const parsed = parser.parseFromString(`${value}`, 'text/html'); + return parsed.body.textContent ?? ''; + } + + private serializeNodeChildren(node: Node): string { + const serializer = new XMLSerializer(); + let html = ''; + for (const child of Array.from(node.childNodes)) { + html += serializer.serializeToString(child); + } + return html; + } + + private replaceElementContentFromHtml( + element: HTMLElement, + html: string, + ): void { + if (!html) { + element.replaceChildren(); + return; + } + + const parser = new DOMParser(); + const parsed = parser.parseFromString(`${html}`, 'text/html'); + const nodes = Array.from(parsed.body.childNodes).map((child) => + document.importNode(child, true), + ); + element.replaceChildren(...nodes); } private escapeHtml(value: string): string { diff --git a/frontend/src/app/features/shop/product-detail.component.ts b/frontend/src/app/features/shop/product-detail.component.ts index ae2fb3f..94da4a6 100644 --- a/frontend/src/app/features/shop/product-detail.component.ts +++ b/frontend/src/app/features/shop/product-detail.component.ts @@ -671,9 +671,12 @@ export class ProductDetailComponent { if (!this.containsHtmlMarkup(normalized)) { return normalized; } - const container = document.createElement('div'); - container.innerHTML = normalized; - const text = (container.textContent ?? '').replace(/\u00a0/g, ' ').trim(); + const parser = new DOMParser(); + const parsed = parser.parseFromString( + `${normalized}`, + 'text/html', + ); + const text = (parsed.body.textContent ?? '').replace(/\u00a0/g, ' ').trim(); return text || null; }