cautpartener / client / src / app / views / post-view.ts
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * #/p/<shard>/<cid> โ€” public post permalink (M5). Resolves the post from the
 * shard's feed rooms by plain mailbox replay (owner offline is fine, like
 * every public view) and renders the same card + comments the feed shows.
 * Honest terminal states: invalid link, mailbox unreachable, or not found
 * within the TTL window (ยง7 โ€” the link may simply have expired).
 */

import { RuntimeLimits } from "../../config";
import { openFeed, type FeedHandle } from "../../public/feed-service";
import { isShard } from "../../public/public-doc";
import { t } from "../i18n";
import { h } from "../ui-kit";
import { postCard } from "./feed-tab";

const CID_RE = /^[a-z0-9]{10,128}$/;

function frame(...children: (HTMLElement | null)[]): HTMLElement {
  return h(
    "div",
    { class: "profile-view", "data-testid": "pv-view" },
    h(
      "div",
      { class: "profile-view-inner" },
      h("a", { class: "btn ghost small", href: "#/" }, t("backHome")),
      ...children.filter((c): c is HTMLElement => c !== null),
    ),
  );
}

function terminal(root: HTMLElement, glyph: string, msg: string, testid: string): void {
  root.replaceChildren(
    frame(
      h(
        "section",
        { class: "card placeholder" },
        h("div", { class: "glyph" }, glyph),
        h("p", { class: "add-msg", "data-testid": testid }, msg),
      ),
    ),
  );
}

let openHandle: FeedHandle | null = null;

/** Run `cleanup` the moment `el` leaves the document (route change). */
function onDisconnect(el: HTMLElement, cleanup: () => void): void {
  const obs = new MutationObserver(() => {
    if (!el.isConnected) {
      obs.disconnect();
      cleanup();
    }
  });
  obs.observe(document.body, { childList: true, subtree: true });
}

export function renderPostView(root: HTMLElement, shard: string, cid: string): void {
  openHandle?.close();
  openHandle = null;
  // a STABLE host owns every draw: internal redraws replace the host's children
  // (host stays connected), while leaving the route replaces the host itself in
  // `root` โ€” that disconnect fires the cleanup and closes the feed handle.
  const host = h("div", { class: "pv-host" });
  root.replaceChildren(host);
  if (!isShard(shard) || !CID_RE.test(cid)) {
    terminal(host, "๐Ÿšซ", t("upInvalid"), "pv-invalid");
    return;
  }
  host.replaceChildren(
    frame(
      h(
        "section",
        { class: "card placeholder" },
        h("div", { class: "glyph" }, "๐Ÿ”"),
        h("p", { class: "muted", "data-testid": "pv-loading" }, t("pvLoading")),
      ),
    ),
  );
  void (async () => {
    const handle = await openFeed(shard);
    if (!host.isConnected) {
      handle.close();
      return;
    }
    openHandle = handle;
    // leaving the permalink route drops `host` from the DOM: close the handle so
    // the gossipsub feedTopic is left and background refresh stops (no leak).
    onDisconnect(host, () => {
      handle.close();
      if (openHandle === handle) openHandle = null;
    });
    const draw = (): void => {
      const post = handle.post(cid);
      if (!post) {
        terminal(host, "โณ", t("pvNotFound", { hours: RuntimeLimits.ttlHours }), "pv-notfound");
        return;
      }
      host.replaceChildren(frame(h("div", { class: "feed-list pv-single" }, postCard(handle, post, { refresh: draw }))));
    };
    const post = handle.post(cid);
    if (!post) {
      // the initial page budget may not have reached it โ€” page further back
      let more = true;
      while (more && !handle.post(cid)) more = await handle.loadOlder();
    }
    if (!host.isConnected) return;
    draw();
  })().catch(() => {
    if (host.isConnected) terminal(host, "๐Ÿ“ก", t("upNet"), "pv-net");
  });
}

static mirror of HEAD ยท about ยท clone: git clone https://cod-sursa.cautpartener.ro/cautpartener.git