maxmilton/new-tab

View on GitHub
src/components/SearchResult.ts

Summary

Maintainability
A
0 mins
Test Coverage
F
35%
import { append, clone, collect, h } from 'stage1';
import { compile } from 'stage1/macro' with { type: 'macro' };
import { DEFAULT_SECTION_ORDER } from '../utils';
import { Link, type LinkComponent, type LinkProps } from './Link';

const DEFAULT_RESULTS_AMOUNT = 12; // chrome.topSites.get returns 12 items
const MORE_RESULTS_AMOUNT = 50;

interface TabItem {
  /** Tab ID. */
  id: number;
  windowId: number;
}

interface OpenTabLink extends LinkComponent {
  $$data: TabItem;
}

// eslint-disable-next-line func-names
const handleTabClick = function (this: OpenTabLink) {
  chrome.tabs.getCurrent((currentTab) => {
    if (currentTab!.id === this.$$data.id) return;

    // Switch to the clicked tab
    void chrome.windows.update(this.$$data.windowId, { focused: true });
    void chrome.tabs.update(this.$$data.id, { active: true });

    // Close current "new-tab" page
    void chrome.tabs.remove(currentTab!.id!);
  });

  // Prevent default behaviour; shorter than `event.preventDefault()`
  return false;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type SearchResultComponent<T = any> = HTMLDivElement & {
  $$update: (newData: T[]) => void;
  $$filter: (text: string) => void;
};

interface Refs {
  t: Text;
  l: HTMLDivElement;
  m: HTMLButtonElement;
}

const meta = compile(`
  <div hidden>
    <h2>@t</h2>

    <div @l></div>

    <button @m>Show more</button>
  </div>
`);
const view = h<SearchResultComponent>(meta.html);

export const SearchResult = <T extends LinkProps & TabItem>(
  sectionName: (typeof DEFAULT_SECTION_ORDER)[number],
): SearchResultComponent<T> => {
  const root = clone<SearchResultComponent<T>>(view);
  const refs = collect<Refs>(root, meta.k, meta.d);
  const isOpenTabs = sectionName === DEFAULT_SECTION_ORDER[0];
  let rawData: T[];
  let renderedLength: number;

  const renderList = (listData: T[], showCount = DEFAULT_RESULTS_AMOUNT) => {
    performance.mark(sectionName);

    const list = isOpenTabs ? listData : listData.slice(0, showCount);
    let index = 0;
    let link: LinkComponent;

    renderedLength = list.length;
    root.hidden = !renderedLength;
    refs.m.hidden = renderedLength === listData.length;
    refs.l.textContent = '';

    for (; index < renderedLength; index++) {
      link = append(Link(list[index]), refs.l);
      if (isOpenTabs) {
        (link as OpenTabLink).$$data = list[index];
        link.__click = handleTabClick;
      }
    }

    performance.measure(sectionName, sectionName);
  };

  // eslint-disable-next-line no-return-assign
  root.$$update = (newData) => renderList((rawData = newData));

  root.$$filter = (text) =>
    renderList(
      rawData.filter((item) =>
        (item.title + '[' + item.url)
          .toLowerCase()
          .includes(text.toLowerCase()),
      ),
    );

  refs.t.nodeValue = sectionName;

  refs.m.__click = () =>
    renderList(rawData, renderedLength + MORE_RESULTS_AMOUNT);

  return root;
};