mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	fix: External links in widget not opening in new tab (#11608)
This commit is contained in:
		@@ -38,16 +38,9 @@ export const openExternalLinksInNewTab = () => {
 | 
				
			|||||||
  document.addEventListener('click', event => {
 | 
					  document.addEventListener('click', event => {
 | 
				
			||||||
    if (!isOnArticlePage) return;
 | 
					    if (!isOnArticlePage) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Some of the links come wrapped in strong tag through prosemirror
 | 
					    const link = event.target.closest('a');
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const isTagAnchor = event.target.tagName === 'A';
 | 
					 | 
				
			||||||
    const isParentTagAnchor =
 | 
					 | 
				
			||||||
      event.target.tagName === 'STRONG' &&
 | 
					 | 
				
			||||||
      event.target.parentNode.tagName === 'A';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isTagAnchor || isParentTagAnchor) {
 | 
					 | 
				
			||||||
      const link = isTagAnchor ? event.target : event.target.parentNode;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (link) {
 | 
				
			||||||
      const isInternalLink =
 | 
					      const isInternalLink =
 | 
				
			||||||
        link.hostname === window.location.hostname ||
 | 
					        link.hostname === window.location.hostname ||
 | 
				
			||||||
        link.href.includes(customDomain) ||
 | 
					        link.href.includes(customDomain) ||
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,9 @@
 | 
				
			|||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
 | 
					import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
 | 
				
			||||||
import { JSDOM } from 'jsdom';
 | 
					import { JSDOM } from 'jsdom';
 | 
				
			||||||
import { InitializationHelpers } from '../portalHelpers';
 | 
					import {
 | 
				
			||||||
 | 
					  InitializationHelpers,
 | 
				
			||||||
 | 
					  openExternalLinksInNewTab,
 | 
				
			||||||
 | 
					} from '../portalHelpers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('InitializationHelpers.navigateToLocalePage', () => {
 | 
					describe('InitializationHelpers.navigateToLocalePage', () => {
 | 
				
			||||||
  let dom;
 | 
					  let dom;
 | 
				
			||||||
@@ -44,3 +47,96 @@ describe('InitializationHelpers.navigateToLocalePage', () => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('openExternalLinksInNewTab', () => {
 | 
				
			||||||
 | 
					  let dom;
 | 
				
			||||||
 | 
					  let document;
 | 
				
			||||||
 | 
					  let window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    dom = new JSDOM(
 | 
				
			||||||
 | 
					      `<!DOCTYPE html>
 | 
				
			||||||
 | 
					      <html>
 | 
				
			||||||
 | 
					        <body>
 | 
				
			||||||
 | 
					          <div id="cw-article-content">
 | 
				
			||||||
 | 
					            <a href="https://external.com" id="external">External</a>
 | 
				
			||||||
 | 
					            <a href="https://app.chatwoot.com/page" id="internal">Internal</a>
 | 
				
			||||||
 | 
					            <a href="https://custom.domain.com/page" id="custom">Custom</a>
 | 
				
			||||||
 | 
					            <a href="https://example.com" id="nested"><code>Code</code><strong>Bold</strong></a>
 | 
				
			||||||
 | 
					            <ul>
 | 
				
			||||||
 | 
					              <li>Visit the preferences centre here > <a href="https://external.com" id="list-link"><strong>https://external.com</strong></a></li>
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </body>
 | 
				
			||||||
 | 
					      </html>`,
 | 
				
			||||||
 | 
					      { url: 'https://app.chatwoot.com/hc/article' }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document = dom.window.document;
 | 
				
			||||||
 | 
					    window = dom.window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.portalConfig = {
 | 
				
			||||||
 | 
					      customDomain: 'custom.domain.com',
 | 
				
			||||||
 | 
					      hostURL: 'app.chatwoot.com',
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    global.document = document;
 | 
				
			||||||
 | 
					    global.window = window;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(() => {
 | 
				
			||||||
 | 
					    dom = null;
 | 
				
			||||||
 | 
					    document = null;
 | 
				
			||||||
 | 
					    window = null;
 | 
				
			||||||
 | 
					    delete global.document;
 | 
				
			||||||
 | 
					    delete global.window;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const simulateClick = selector => {
 | 
				
			||||||
 | 
					    const element = document.querySelector(selector);
 | 
				
			||||||
 | 
					    const event = new window.MouseEvent('click', { bubbles: true });
 | 
				
			||||||
 | 
					    element.dispatchEvent(event);
 | 
				
			||||||
 | 
					    return element.closest('a') || element;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('opens external links in new tab', () => {
 | 
				
			||||||
 | 
					    openExternalLinksInNewTab();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const link = simulateClick('#external');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(link.target).toBe('_blank');
 | 
				
			||||||
 | 
					    expect(link.rel).toBe('noopener noreferrer');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('preserves internal links', () => {
 | 
				
			||||||
 | 
					    openExternalLinksInNewTab();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const internal = simulateClick('#internal');
 | 
				
			||||||
 | 
					    const custom = simulateClick('#custom');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(internal.target).not.toBe('_blank');
 | 
				
			||||||
 | 
					    expect(custom.target).not.toBe('_blank');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('handles clicks on nested elements', () => {
 | 
				
			||||||
 | 
					    openExternalLinksInNewTab();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    simulateClick('#nested code');
 | 
				
			||||||
 | 
					    simulateClick('#nested strong');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const link = document.getElementById('nested');
 | 
				
			||||||
 | 
					    expect(link.target).toBe('_blank');
 | 
				
			||||||
 | 
					    expect(link.rel).toBe('noopener noreferrer');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('handles links inside list items with strong tags', () => {
 | 
				
			||||||
 | 
					    openExternalLinksInNewTab();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Click on the strong element inside the link in the list
 | 
				
			||||||
 | 
					    simulateClick('#list-link strong');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const link = document.getElementById('list-link');
 | 
				
			||||||
 | 
					    expect(link.target).toBe('_blank');
 | 
				
			||||||
 | 
					    expect(link.rel).toBe('noopener noreferrer');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user