import type { DisplayFile } from "./file_thumbnail"; /** * The networks a post can be previewed FOR — the ones whose feed shape and * publishing rules this module knows. A surface with no API (a personal Facebook * profile, a group, a Zalo OA) has no entry: what it accepts is decided by a * person pasting, not by a request the platform can refuse. * * RN-free on purpose: the publish gate in an app reads this without rendering * anything, and the locale packs type their labels off it. */ export declare const SOCIAL_NETWORKS: readonly ["facebook", "instagram", "threads", "x", "linkedin"]; export type SocialNetwork = (typeof SOCIAL_NETWORKS)[number]; /** * What one network's feed does with a post, and what its API refuses. * * Two kinds of fact, deliberately in one place: the FOLD (where the feed hides * the rest behind "more") is a display fact, the rest are publishing rules the * API enforces after the bytes have travelled. Both are what a person needs to * see BEFORE pressing publish, so the preview and the gate read one table. */ export interface SocialNetworkRules { /** The characters the API accepts. Counted in code points — X weights URLs * and CJK differently, so the count is a guide there, not the network's own. */ maxChars: number; /** Where the feed folds the text behind "more", or null when it never does. */ foldChars: number | null; /** The most images one post carries, or null when the API sets no count. */ maxImages: number | null; /** Whether a video has to be the only attachment. */ video: "alone" | "mixed"; /** Whether a GIF is its own kind that goes alone (X) or just an image. */ gif: "alone" | "image"; /** What a link becomes: a card, or plain text the feed does not open. */ link: "card" | "text"; /** Whether the API takes a link beside media, or refuses the pair. */ linkBesideMedia: boolean; /** Whether a post with no media is refused. */ requiresMedia: boolean; } export declare const SOCIAL_NETWORK_RULES: Record; /** The network's own name. Proper nouns, so not in the locale packs. */ export declare const SOCIAL_NETWORK_LABELS: Record; export interface SocialPostLink { url: string; title?: string; } /** What a post IS before it goes out — the three things every network takes. */ export interface SocialPostDraft { text: string; media?: readonly DisplayFile[]; link?: SocialPostLink | null; } export type SocialPostIssue = { kind: "over_limit"; over: number; max: number; } | { kind: "needs_media"; } | { kind: "too_many_images"; max: number; } | { kind: "video_alone"; } | { kind: "gif_alone"; } | { kind: "link_or_media"; }; /** Code points, so a Vietnamese vowel with its tone is one character. */ export declare function countSocialChars(text: string): number; /** * Everything the network would refuse, from the draft alone — so the gate can * close before the bytes travel, and say why in the same words the preview does. * An empty list is not a promise the post lands (a token can have expired, a * container can fail to process); it is the promise the REQUEST is well-formed. */ export declare function socialPostIssues(network: SocialNetwork, draft: SocialPostDraft): SocialPostIssue[]; export interface SocialPostPreviewLabels { more: string; justNow: string; overLimit: (over: number) => string; needsMedia: string; tooManyImages: (max: number) => string; videoAlone: string; gifAlone: string; linkOrMedia: string; } /** One issue, in the pack's words — the same sentence beside the preview and on * the gate that reads the same list. */ export declare function socialPostIssueText(issue: SocialPostIssue, labels: SocialPostPreviewLabels): string;