packages feed

espial-0.0.40: frontend/src/globals.ts

import type { Bookmark, Filter, Note, SharedP } from './types';

/** Server-rendered page data. */
export type AppData = {
  /** The list of bookmarks for the current page. */
  bmarks: Bookmark[];
  /** A single bookmark, used when viewing or editing one entry. */
  bmark: Bookmark;
  /** The list of notes for the current page. */
  notes?: Note[];
  /** Whether the current user owns the displayed profile. */
  isowner: boolean;
  /** Whether tag suggestions are enabled. */
  suggestTags?: boolean;
  /** Whether the Return key applies a tag suggestion (Tab always does). */
  suggestTagsUseReturnKey?: boolean;
  /** Whether note listings show a text preview, or just the title. */
  previewNotes?: boolean;
  /** Whether the archive backend is enabled. */
  archiveBackendEnabled?: boolean;
  /** The current filter applied to the bookmarks. */
  filter?: Filter;
  /** The current shared/private filter. */
  sharedp?: SharedP;
  /** The path tags currently filtering bookmarks. */
  tags?: string[];
  /** The current search query string, or null if none. */
  query?: string | null;
};

/** Server-rendered global data. */
export type App = {
  /** The HTTP header name used to send the CSRF token. */
  csrfHeaderName: string;
  /** The cookie name that holds the CSRF token. */
  csrfCookieName: string;
  /** The form parameter name used to submit the CSRF token. */
  csrfParamName: string;
  /** The current CSRF token value. */
  csrfToken: string;
  /** URL for the home route. */
  homeR: string;
  /** URL for the logout route. */
  authRlogoutR: string;
  /** URL for the current user's profile, or null if not logged in. */
  userR: string | null;
  /** Endpoint URL for fetching tag cloud data. */
  tagCloudR?: string;
  /** URL for the current note, or null if not viewing a note. */
  noteR: string | null;
  /** URL for the i18n backend */
  i18nR: string;
  /** The current language code (e.g. 'en', 'fr'). */
  lang: string;
  /** Server-rendered page data. */
  dat: AppData;
};

declare global {
  // Provided by the server in a <script> tag.

  var app: App;
}

export function app(): App {
  return globalThis.app;
}

export function fromNow(locale = navigator.language, date: Date | number | string): string {
  const target = date instanceof Date ? date.getTime() : new Date(date).getTime();

  const diff = target - Date.now();
  const abs = Math.abs(diff);

  const units: readonly [Intl.RelativeTimeFormatUnit, number][] = [
    ['year', 365 * 24 * 60 * 60 * 1000],
    ['month', 30 * 24 * 60 * 60 * 1000],
    ['day', 24 * 60 * 60 * 1000],
    ['hour', 60 * 60 * 1000],
    ['minute', 60 * 1000],
    ['second', 1000],
  ];

  const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });

  for (const [unit, ms] of units) {
    if (abs >= ms || unit === 'second') {
      return rtf.format(Math.round(diff / ms), unit);
    }
  }

  // Unreachable, but satisfies TypeScript.
  return '';
}

export function shdatetime(lang: string, dateString: string): string {
  return new Date(dateString).toLocaleString(lang, {
    dateStyle: 'full',
    timeStyle: 'long',
  });
}

export function toLocaleDateString(lang: string, dateString: string): string {
  return new Date(dateString).toLocaleDateString(lang, {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  });
}

export function closeWindow(win: Window): void {
  win.close();
}

export type RawHTML = string;

export function setFocus(elemId: string): void {
  document.getElementById(elemId)?.focus();
}