Share via

Template line break issue

stsi appqadev1 20 Reputation points
2026-03-18T09:58:12.7233333+00:00

I am facing multiple issues with my email template. While editing a draft email, the content automatically loses its position and gets shuffled from one place to another.

I have tried several approaches to resolve this issue, but it still persists.

Do you have any idea what might be causing this? If you know the root cause or a reliable solution, please share your recommendations.

Also, if there is any API or implementation approach that can ensure the content remains stable while editing, please share a working example.

private ensureHtmlFormat(content: string): string {

    if (!content) return '';

    const parser = new DOMParser();

    const doc = parser.parseFromString(content, 'text/html');

    /* =========================================================

       1️⃣ REMOVE INVALID "#" LINKS

    ========================================================== */

    const hashLinks = doc.querySelectorAll('a[href="#"]');

    hashLinks.forEach(link => link.remove());

    const allLinks = doc.querySelectorAll('a');

    allLinks.forEach(link => {

        if (link.textContent?.trim() === '#') {

            link.remove();

        }

    });

    /* =========================================================

       2️⃣ FIX ORDERED LIST WITH NESTED UL STRUCTURE

    ========================================================== */

    const listItems = doc.querySelectorAll('ol > li');

    listItems.forEach((li, index) => {

        const nestedUl = li.querySelector('ul');

        if (nestedUl) {

            const ulParent = nestedUl.parentElement;

            if (!ulParent || !li.contains(nestedUl)) return;

            const nodes = Array.from(ulParent.childNodes);

            const ulIndex = nodes.indexOf(nestedUl);

            if (ulIndex !== -1 && ulIndex < nodes.length - 1) {

                const trailingNodes = nodes.slice(ulIndex + 1);

                if (trailingNodes.length > 0) {

                    trailingNodes.forEach(node => ulParent.removeChild(node));

                    const fragment = doc.createDocumentFragment();

                    const isInsideFont = ulParent.tagName === 'FONT';

                    let fontWrapper: HTMLElement | null = null;

                    if (isInsideFont) {

                        fontWrapper = doc.createElement('font');

                        Array.from(ulParent.attributes).forEach(attr => {

                            fontWrapper?.setAttribute(attr.name, attr.value);

                        });

                    }

                    trailingNodes.forEach(node => {

                        let contentNode: Node;

                        if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {

                            const p = doc.createElement('p');

                            p.textContent = node.textContent.trim();

                            p.style.marginLeft = '40px';

                            p.style.marginTop = '0';

                            p.style.marginBottom = '8px';

                            p.style.lineHeight = '1.4';

                            contentNode = p;

                        } else if (node.nodeType === Node.ELEMENT_NODE) {

                            const el = node as HTMLElement;

                            el.style.marginLeft = '40px';

                            contentNode = el;

                        } else {

                            return;

                        }

                        if (isInsideFont && fontWrapper) {

                            fontWrapper.appendChild(contentNode);

                        } else {

                            fragment.appendChild(contentNode);

                        }

                    });

                    if (isInsideFont && fontWrapper && fontWrapper.childNodes.length > 0) {

                        fragment.appendChild(fontWrapper);

                    }

                    const nextLi = listItems[index + 1];

                    if (nextLi) {

                        nextLi.before(fragment);

                    } else {

                        li.parentElement?.appendChild(fragment);

                    }

                }

            }

        }

    });

    /* =========================================================

       3️⃣ ADD SPACING FOR UL ELEMENTS (wrap in div with margin)

    ========================================================== */

    const allUls = doc.querySelectorAll('ul, ol');

    // allUls.forEach(ul => {

    //     // Create wrapper div with margin for spacing

    //     const wrapper = doc.createElement('div');

    //     wrapper.style.marginTop = '16px';

    //     wrapper.style.marginBottom = '16px';

    //     // Insert wrapper before ul, then move ul into wrapper

    //     ul.parentNode?.insertBefore(wrapper, ul);

    //     wrapper.appendChild(ul);

    // });

    // const allUls = doc.querySelectorAll('ul');

    allUls.forEach(ul => {

        // ✅ Force bullet styling

        if ((ul as HTMLElement).tagName === 'UL') {

            (ul as HTMLElement).style.listStyleType = 'disc';

        }

        //(ul as HTMLElement).style.marginLeft = '30px';

        //(ul as HTMLElement).style.padding = '0';

        (ul as HTMLElement).style.setProperty('text-indent', '-2px', 'important');

        //Ensure each child is wrapped in <li>

        Array.from(ul.childNodes).forEach(node => {

            if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {

                const li = doc.createElement('li');

                li.textContent = node.textContent.trim();

                ul.replaceChild(li, node);

            }

        });

        ul.querySelectorAll('li').forEach((liItem) => {

            (liItem as HTMLElement).style.setProperty('text-indent', '-2px', 'important');

            //(liItem as HTMLElement).style.marginTop = '0';

            //(liItem as HTMLElement).style.marginBottom = '0';

            //(liItem as HTMLElement).style.paddingTop = '0';

            //(liItem as HTMLElement).style.paddingBottom = '0';

        });

    });

    /* =========================================================

       4️⃣ CLEAN HTML OUTPUT

    ========================================================== */

    let cleaned = doc.body.innerHTML;

    // Add <br> after </b> when followed by <ul> wrapper

    cleaned = cleaned.replace(/<\/b>\s*(<div[^>]*>\s*<ul)/gi, '</b><br>$1');

    // Add <br> after </div> when followed by <p> (for COI case)

    cleaned = cleaned.replace(/<\/div>\s*(?=<p)/gi, '</div><br>');

    // Remove excessive <br> between paragraphs

    cleaned = cleaned.replace(/<\/p>\s*(<br[^>]*>\s*)+(?=<p)/gi, '</p><br>');

    // Remove multiple SPACES only

    cleaned = cleaned.replace(/ {2,}/g, ' ');

    // Remove extra &nbsp;

    cleaned = cleaned.replace(/(&nbsp;){2,}/gi, '&nbsp;');

    // if hide the ul element 

    cleaned = cleaned.replace(/<(b|font)[^>]*>\s*(<ul)/gi, '$2');

    return cleaned.trim();

}
Outlook | Web | Outlook on the web for business | Email
0 comments No comments

Answer accepted by question author

AI answer

2026-03-18T09:58:35.0466667+00:00

The behavior is most likely caused by how Outlook/Word parses and “cleans up” HTML for email, especially around lists, inline styles, and post‑processing of the HtmlBody.

Key points from the provided information:

  1. Outlook uses Word as the HTML editor
    • When an email is displayed or sent, Word’s HTML engine validates and rewrites the HTML.
    • This can cause:
      • Loss of CSS‑based formatting
      • Changes to paragraph and list structure
      • Reassignment of styles to MSONormal and similar classes
    • Any complex or non‑Word‑friendly HTML that is injected and then re‑edited in Outlook can be reordered or reformatted.
  2. Editing HtmlBody directly is fragile
    • When HTML is set programmatically and then the item is displayed/sent, Outlook/Word may strip or rearrange formatting.
    • The recommended approach is to work with the underlying Word editor object instead of repeatedly manipulating HtmlBody.
  3. Lists and line breaks are especially vulnerable
    • Bullet and numbered lists, nested lists, and extra line breaks are common points where Outlook/Word “fixes” the HTML.
    • Cleanup logic like:
      • Moving nodes around ul/ol and li
      • Injecting <br> after certain tags
      • Collapsing spaces and &nbsp; can easily create HTML that Word normalizes differently, leading to content appearing out of order when the draft is reopened or edited.

How this relates to the current implementation

  • The ensureHtmlFormat function:
    • Parses and rewrites the entire HTML tree.
    • Moves nodes around list items (ol > li), wraps text nodes into <p> or <li>, and injects inline styles.
    • Performs regex replacements on the final HTML string (adding <br>, collapsing spaces, removing extra &nbsp;).
  • After this, Outlook/Word applies its own normalization, which can:
    • Merge or split paragraphs and list items.
    • Reorder or unwrap elements that don’t match its expected structure.
    • Remove or change inline styles.
  • The combination of aggressive DOM manipulation plus Outlook’s own HTML rewrite is what causes content to “lose its position” or get shuffled when editing the draft.

Recommended direction for a more stable solution

  1. Avoid heavy post‑processing of HtmlBody
    • Minimize structural changes to lists and paragraphs.
    • Avoid regex‑based manipulation of the final HTML where possible, especially around <p>, <br>, <ul>, <ol>, and <li>.
  2. Use the Word editor object instead of repeatedly editing HtmlBody
    • For Outlook items created via the Outlook Object Model, use the underlying WordEditor of the inspector to edit HTML/RTF content.
    • This aligns the content with Word’s internal model from the start, reducing the chance that Word will later “fix” or reorder it.
  3. Keep list markup simple and Word‑friendly
    • Use standard <ul>, <ol>, and <li> without deep nesting or complex inline styles.
    • Avoid wrapping lists in multiple nested <div>/<font> elements or adding non‑standard spacing constructs that Word may normalize.
  4. Test with minimal transformations
    • Start from a simple, static HTML template that renders correctly in Outlook.
    • Gradually add only the transformations that are strictly necessary, checking after each change whether Outlook still preserves order when the draft is edited.

There is no single API call that guarantees Outlook will never reorder HTML, but using the Word editor object instead of repeatedly rewriting HtmlBody, and keeping the HTML structure simple and Word‑compatible, is the documented workaround to prevent formatting and layout from being lost or shuffled.


References:

AI-generated content may be incorrect. Read our transparency notes for more information.

Was this answer helpful?

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.