Repository: go-gitea/gitea
File: web_src/js/features/repo-code.ts (TypeScript)

Code before:
  td.prepend(btn);

  // put a copy of the menu back into DOM for the next click
  btn.closest('.code-view').append(menu.cloneNode(true));

  createTippy(btn, {
    theme: 'menu',
    trigger: 'click',
    hideOnClick: true,
    content: menu,
    placement: 'right-start',
    interactive: true,
    onShow: (tippy) => {
      tippy.popper.addEventListener('click', () => {
        tippy.hide();
      }, {once: true});
    },
  });
}

export function initRepoCodeView() {
  registerGlobalInitFunc('initRepoCodeView', (el: HTMLElement) => {
    if (!el.querySelector('.lines-num')) return;

    let selRangeStart: string;
    addDelegatedEventListener(el, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
      if (!selRangeStart || !e.shiftKey) {
        selRangeStart = el.getAttribute('id');
        selectRange(selRangeStart);
      } else {
        const selRangeStop = el.getAttribute('id');
        selectRange(`${selRangeStart}-${selRangeStop}`);
      }
      window.getSelection().removeAllRanges();
      showLineButton();
    });
  });

  const onHashChange = () => {
    if (!window.location.hash) return;
    const range = window.location.hash.substring(1);
    const first = selectRange(range);
    if (first) {
      // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
      if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
      first.scrollIntoView({block: 'start'});
      showLineButton();
    }
  };
  onHashChange();
  window.addEventListener('hashchange', onHashChange);

Review comment (refactor):
~~I do not think the fix is right. You will have duplicate "window.addEventListener" calls and add duplicate "hashchange" event listeners.~~

Hmm, just a moment, will check again by checking out the code. Maybe I misread.

Code after:

  // put a copy of the menu back into DOM for the next click
  btn.closest('.code-view').append(menu.cloneNode(true));

  createTippy(btn, {
    theme: 'menu',
    trigger: 'click',
    hideOnClick: true,
    content: menu,
    placement: 'right-start',
    interactive: true,
    onShow: (tippy) => {
      tippy.popper.addEventListener('click', () => {
        tippy.hide();
      }, {once: true});
    },
  });
}

export function initRepoCodeView() {
  // When viewing a file or blame, there is always a ".file-view" element,
  // but the ".code-view" class is only present when viewing the "code" of a file; it is not present when viewing a PDF file.
  // Since the ".file-view" will be dynamically reloaded when navigating via the left file tree (eg: view a PDF file, then view a source code file, etc.)
  // the "code-view" related event listeners should always be added when the current page contains ".file-view" element.
  if (!document.querySelector('.repo-view-container .file-view')) return;

  // "file code view" and "blame" pages need this "line number button" feature
  let selRangeStart: string;
  addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
    if (!selRangeStart || !e.shiftKey) {
      selRangeStart = el.getAttribute('id');
      selectRange(selRangeStart);
    } else {
      const selRangeStop = el.getAttribute('id');
      selectRange(`${selRangeStart}-${selRangeStop}`);
    }
    window.getSelection().removeAllRanges();
    showLineButton();
  });

  // apply the selected range from the URL hash
  const onHashChange = () => {
    if (!window.location.hash) return;
    if (!document.querySelector('.code-view .lines-num')) return;
    const range = window.location.hash.substring(1);
    const first = selectRange(range);
    if (first) {
      // set scrollRestoration to 'manual' when there is a hash in the URL, so that the scroll position will not be remembered after refreshing
      if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
      first.scrollIntoView({block: 'start'});
      showLineButton();