astro-typst icon indicating copy to clipboard operation
astro-typst copied to clipboard

Links to sections in text

Open syrkis opened this issue 8 months ago • 3 comments

Example: in .pdf, the document outline is clickable (clicking a given section navigates to it on the page). In astro-typst, the links remain clickable, but do not scroll down to the appropriate area. Would adding this to astro-typst be appropriate?

syrkis avatar Mar 22 '25 09:03 syrkis

Workaround: add this JS snippet to your page.

/**
Copyright 2025 Myriad-Dreamin

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
function findAncestor(el, cls) {
  while ((el = el.parentElement) && !el.classList.contains(cls));
  return el;
}

window.handleTypstLocation = function (elem, page, x, y) {
  const docRoot = findAncestor(elem, 'typst-doc');
  const children = docRoot.children;
  let nthPage = 0;
  for (let i = 0; i < children.length; i++) {
    if (children[i].tagName === 'g') {
      nthPage++;
    }
    if (nthPage == page) {
      const page = children[i];
      const dataWidth = page.getAttribute('data-page-width');
      const dataHeight = page.getAttribute('data-page-height');
      const rect = page.getBoundingClientRect();
      const xOffsetInner = Math.max(0, x / dataWidth - 0.05) * rect.width;
      const yOffsetInner = Math.max(0, y / dataHeight - 0.05) * rect.height;
      const xOffsetInnerFix = (x / dataWidth) * rect.width - xOffsetInner;
      const yOffsetInnerFix = (y / dataHeight) * rect.height - yOffsetInner;

      const docRoot = document.body || document.firstElementChild;
      const basePos = docRoot.getBoundingClientRect();

      const xOffset = rect.left - basePos.left + xOffsetInner;
      const yOffset = rect.top - basePos.top + yOffsetInner;
      const left = xOffset + xOffsetInnerFix;
      const top = yOffset + yOffsetInnerFix;

      console.log('scrolling to', xOffset, yOffset, left, top);

      window.scrollTo(xOffset, yOffset);
      const ripple = document.createElement('div');
      ripple.className = 'typst-ripple';
      docRoot.appendChild(ripple);

      ripple.style.left = left.toString() + 'px';
      ripple.style.top = top.toString() + 'px';

      ripple.style.animation = 'typst-ripple-effect .4s linear';
      ripple.onanimationend = () => {
        docRoot.removeChild(ripple);
      };
      return;
    }
  }
};

I will think about how to handle this part later.

OverflowCat avatar Mar 25 '25 07:03 OverflowCat

Thank you so much. Really appreciate your work with astro-typst 🙏

syrkis avatar Mar 26 '25 09:03 syrkis

Thanks! While I built the Astro wrapper, the heavy lifting of rendering comes from typst.ts. Happy to hear it's working well for you!

OverflowCat avatar Mar 31 '25 17:03 OverflowCat

The typst compiler outputs tagged elements with data-typst-label rather than id, as there may be duplicate ids. Also, with JS there can be cross-doc jump. What about I wrap the above JS code in an Astro component and you can just import it in every page? Pinging @jenningsloy318 @syrkis

OverflowCat avatar May 30 '25 18:05 OverflowCat

sounds cleaner indeed. but i personally don't mind the way it is now. i sat it up once and it works.

syrkis avatar Jun 01 '25 20:06 syrkis