gitea源码

anchors.ts 2.8KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import {svg} from '../svg.ts';
  2. const addPrefix = (str: string): string => `user-content-${str}`;
  3. const removePrefix = (str: string): string => str.replace(/^user-content-/, '');
  4. const hasPrefix = (str: string): boolean => str.startsWith('user-content-');
  5. // scroll to anchor while respecting the `user-content` prefix that exists on the target
  6. function scrollToAnchor(encodedId?: string): void {
  7. // FIXME: need to rewrite this function with new a better markup anchor generation logic, too many tricks here
  8. let elemId: string;
  9. try {
  10. elemId = decodeURIComponent(encodedId ?? '');
  11. } catch {} // ignore the errors, since the "encodedId" is from user's input
  12. if (!elemId) return;
  13. const prefixedId = addPrefix(elemId);
  14. // eslint-disable-next-line unicorn/prefer-query-selector
  15. let el = document.getElementById(prefixedId);
  16. // check for matching user-generated `a[name]`
  17. el = el ?? document.querySelector(`a[name="${CSS.escape(prefixedId)}"]`);
  18. // compat for links with old 'user-content-' prefixed hashes
  19. // eslint-disable-next-line unicorn/prefer-query-selector
  20. el = (!el && hasPrefix(elemId)) ? document.getElementById(elemId) : el;
  21. el?.scrollIntoView();
  22. }
  23. export function initMarkupAnchors(): void {
  24. const markupEls = document.querySelectorAll('.markup');
  25. if (!markupEls.length) return;
  26. for (const markupEl of markupEls) {
  27. // create link icons for markup headings, the resulting link href will remove `user-content-`
  28. for (const heading of markupEl.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
  29. const a = document.createElement('a');
  30. a.classList.add('anchor');
  31. a.setAttribute('href', `#${encodeURIComponent(removePrefix(heading.id))}`);
  32. a.innerHTML = svg('octicon-link');
  33. heading.prepend(a);
  34. }
  35. // remove `user-content-` prefix from links so they don't show in url bar when clicked
  36. for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')) {
  37. const href = a.getAttribute('href');
  38. if (!href.startsWith('#user-content-')) continue;
  39. a.setAttribute('href', `#${removePrefix(href.substring(1))}`);
  40. }
  41. // add `user-content-` prefix to user-generated `a[name]` link targets
  42. // TODO: this prefix should be added in backend instead
  43. for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[name]')) {
  44. const name = a.getAttribute('name');
  45. if (!name) continue;
  46. a.setAttribute('name', addPrefix(name));
  47. }
  48. for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')) {
  49. a.addEventListener('click', (e) => {
  50. scrollToAnchor((e.currentTarget as HTMLAnchorElement).getAttribute('href')?.substring(1));
  51. });
  52. }
  53. }
  54. // scroll to anchor unless the browser has already scrolled somewhere during page load
  55. if (!document.querySelector(':target')) {
  56. scrollToAnchor(window.location.hash?.substring(1));
  57. }
  58. }