← Files WixARCHIVED FILE

skills/wix-headless-templates/blog/project/src/wix/blog/types.ts

6.27 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

// Blog DTOs — the serializable shapes every hook, component, and page consumes.
// Plain JSON: safe as Astro island props or across server/client boundaries. Cover images are
// resolved https URLs; dates are pre-formatted display strings plus an ISO value for <time>.
// A field that is "" or undefined means "not known" — render nothing for it, never a placeholder.

/** A post as a feed/grid tile needs it. */
export interface PostSummary {
  id: string;
  slug: string;
  title: string;
  /** Short summary (≤500 chars) — the card body. May be "". */
  excerpt: string;
  /** Display-ready publish date, e.g. "Aug 26, 2026" ("" when missing). */
  dateLabel: string;
  /** ISO publish date for <time datetime> ("" when missing). */
  dateISO: string;
  /** Estimated reading time in minutes (0 when unknown — render nothing, never "0 min read"). */
  minutesToRead: number;
  featured: boolean;
  /** Pinned posts lead the default feed order. */
  pinned: boolean;
  /**
   * Resolved https cover URL, 16:9 ("" when the post has no cover, or its author hid it). A video
   * cover resolves to its poster, an external embed (YouTube/Vimeo) to its thumbnail.
   */
  coverUrl: string;
  /** Alt text for the cover: the author's alt text, else the title ("" when there is no cover). */
  coverAlt: string;
  categoryIds: string[];
  tagIds: string[];
  /** The author's member id ("" when none). */
  authorId: string;
  /** The author's display name ("" when unresolved — render no byline then). */
  authorName: string;
  /** Resolved https avatar URL ("" when none). */
  authorAvatarUrl: string;
  /** Counters (the METRICS fieldset). undefined = unknown → render nothing. */
  viewCount?: number;
  likeCount?: number;
  commentCount?: number;
}

/** A post as the post page needs it. */
export interface PostDetail extends PostSummary {
  /**
   * Ricos rich-content document (plain JSON) — the real post body. Render it ONLY through
   * the shipped RichContent component (@wix/ricos viewer); it is not HTML and not text.
   */
  richContent: Record<string, unknown> | null;
  /** Plain-text body split into paragraphs — the honest fallback when richContent is null. */
  paragraphs: string[];
  /** Display-ready last-published date ("" when the post was never republished). */
  updatedLabel: string;
  updatedISO: string;
  /** The owner's SEO title override, else the title — the <title> on stacks without the SEO service. */
  seoTitle: string;
  /** The owner's meta-description override, else the excerpt, else the body text cut to 500 chars. */
  seoDescription: string;
  /** Writer-curated related posts, in the writer's order (may be empty). */
  relatedPostIds: string[];
  /** Comments are open on this post (the dashboard's per-post switch). */
  commentingEnabled: boolean;
  /** The comments thread id (the REFERENCE_ID fieldset); "" when the API answered without it. */
  referenceId: string;
}

/** A post author — a site member's public profile. */
export interface BlogAuthor {
  id: string;
  /** Display name ("" when the member set none). */
  name: string;
  /** Resolved https avatar URL ("" when none). */
  avatarUrl: string;
}

/** The post's counters as the API's dedicated metrics read answers them. */
export interface PostMetrics {
  views?: number;
  likes?: number;
  comments?: number;
}

/** The viewer's like state for one post, plus the fresh counters read alongside it. */
export interface LikeState {
  /** Whether THIS viewer (member or anonymous visitor) has liked the post. */
  liked: boolean;
  /** null when the metrics read failed — keep the count you had. */
  metrics: PostMetrics | null;
}

export interface BlogCategory {
  id: string;
  slug: string;
  /** Display name (the API calls it `label`, never `name`). */
  label: string;
  /** The category's SEO title ("" when the owner set none) — the <title> of a category page. */
  title: string;
  description: string;
  /** Number of posts in the category (hide empty categories with it). */
  postCount: number;
  /** Resolved https cover URL ("" when none). */
  coverUrl: string;
}

export interface BlogTag {
  id: string;
  slug: string;
  label: string;
  /** Number of PUBLISHED posts with this tag. */
  postCount: number;
}

/** One feed page; pass `nextCursor` back to fetch the next (null → no more). */
export interface PostPage {
  posts: PostSummary[];
  nextCursor: string | null;
}

/** PUBLISHED is the normal state; PENDING awaits the owner's approval; DELETED is a placeholder kept for its replies. */
export type CommentStatus = "PUBLISHED" | "PENDING" | "DELETED" | "HIDDEN" | "UNKNOWN";

/** One comment — a top-level comment or a reply. Threads are two levels deep (Wix's rule). */
export interface BlogComment {
  id: string;
  /** The top-level comment this reply hangs under; null for a top-level comment. */
  topLevelId: string | null;
  /** The comment this one replies to (may itself be a reply — show "replying to"); null for a top-level comment. */
  parentId: string | null;
  /** The parent's author name ("" when unknown or a top-level comment). */
  parentAuthorName: string;
  /** The author's member id ("" for a guest or a deleted comment). */
  authorId: string;
  /** The author's display name ("" when unresolved). */
  authorName: string;
  authorAvatarUrl: string;
  /** True when the current member wrote it — show the delete control only then. */
  isOwn: boolean;
  dateLabel: string;
  dateISO: string;
  status: CommentStatus;
  /** Ricos document (plain JSON) — render through RichContent where it deploys; a PENDING comment carries its draft. */
  content: Record<string, unknown> | null;
  /** The comment's plain text — the body on stacks without the Ricos viewer ("" for a DELETED placeholder). */
  text: string;
  /** Number of replies (any depth) under a top-level comment. */
  replyCount: number;
}

/** The replies of one top-level comment; `nextCursor` continues them (null → none or all loaded). */
export interface CommentThread {
  comments: BlogComment[];
  nextCursor: string | null;
}

/** One page of top-level comments plus the reply seams the API answered for them. */
export interface CommentPage {
  comments: BlogComment[];
  nextCursor: string | null;
  /** Keyed by top-level comment id — its replies (empty until loaded) and their cursor. */
  replies: Record<string, CommentThread>;
}

SHA-256: eca7103b1ec4a283a27fe67665d75246900949c847d476aa6b31a536ab6ab9a1