stoplightio / elements

Build beautiful, interactive API Docs with embeddable React or Web Components, powered by OpenAPI and Markdown.
https://stoplight.io/open-source/elements/
Apache License 2.0
1.74k stars 201 forks source link

Anchor links from articles not working in Elements Dev Portal #2595

Open dylangarrett opened 3 months ago

dylangarrett commented 3 months ago

The anchor links from any articles written in the Stoplight platform do not work correctly in Angular. The basePath value from the elements component is not considered in the anchor link.

Similar to https://github.com/stoplightio/elements/issues/1699 and https://github.com/stoplightio/elements/issues/2133

Context

Current Behavior

    <elements-stoplight-project
    projectId="..."
    basePath="/api/docs" // this is not reflected in anchor links 
   ></elements-stoplight-project>

Expected Behavior

Possible Workaround/Solution

As a workaround I directly modify anchor tags in my component, it works but shouldn't be necessary

export class ApiPortalComponent implements AfterViewInit {
  private mutationObserver: MutationObserver;
  public basePath = '/docs/api';
  private elementsQuerySelector = 'elements-stoplight-project';

  constructor(private router: Router, private renderer: Renderer2, private el: ElementRef) {}

  ngAfterViewInit() {
    this.updateAnchorLinks();

    this.mutationObserver = new MutationObserver(() => {
      this.updateAnchorLinks();
    });

    const targetNode = this.el.nativeElement.querySelector(this.elementsQuerySelector);
    if (targetNode) {
      this.mutationObserver.observe(targetNode, {
        childList: true,
        subtree: true,
      });
    }
  }

  ngOnDestroy() {
    if (this.mutationObserver) {
      this.mutationObserver.disconnect();
    }
  }

  /**
   * Fixes an issue where anchor links are not working as expected in Stoplight Elements Dev Portal
   * https://github.com/stoplightio/elements/issues/1699
   * https://github.com/stoplightio/elements/issues/2133
   * 
   * The anchor links do not contain the base path of the API portal, so we need to update them.
   * Loop through all anchor links in the API portal and update the href attribute to include the base path.
   * Also fixes the issue where the page refreshes when clicking on an anchor link.
   */
  public updateAnchorLinks() {
    const anchorLinks = this.el.nativeElement.querySelectorAll(`${this.elementsQuerySelector} a[href^="#"]`);
    anchorLinks.forEach((link: HTMLAnchorElement) => {
      const anchor = link.getAttribute('href')?.split('#')[1];
      if (anchor && link.href.indexOf(this.basePath) === -1) { 

        this.renderer.setAttribute(link, 'href', `${this.basePath}#${anchor}`);

        // Add click event listener to prevent full page refresh and scroll to the anchor
        this.renderer.listen(link, 'click', (event) => {
          event.preventDefault();
          void this.router.navigate([this.basePath], { fragment: anchor }).then(() => {
            this.scrollToAnchor(anchor);
          });
        });
      }
    });
  }

  private scrollToAnchor(anchor: string) {
    const element = document.getElementById(anchor);
    if (element) {
      element.scrollIntoView({ behavior: 'smooth' });
    }
  }
}

Steps to Reproduce

  1. Run elements dev portal locally, ensure there is a basePath specified in the component
  2. Write an article which includes headings
  3. Navigate to the article in the dev portal, click a link on the right side image

Environment