Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
/**
|
|
* Closed rich-text AST for provider content (proposal §4.5 "rich content
|
|
* contract"). Nothing outside these node types can be rendered: there is no
|
|
* node that carries raw HTML, attributes, styles or arbitrary URLs.
|
|
*
|
|
* URL-bearing fields are only ever produced by `parseSafeHttpUrl` /
|
|
* `toProxiedMediaUrl`, and the renderer re-validates them, so an AST that was
|
|
* round-tripped through storage or the network cannot smuggle a bad scheme.
|
|
*/
|
|
|
|
export const RICH_TEXT_VERSION = 1 as const;
|
|
|
|
export type TextNode = { readonly type: "text"; readonly text: string };
|
|
export type LineBreakNode = { readonly type: "linebreak" };
|
|
/** Custom emoji / emote. `src` is always a media-proxy URL, never the origin URL. */
|
|
export type EmojiNode = { readonly type: "emoji"; readonly shortcode: string; readonly src: string };
|
|
export type LinkNode = {
|
|
readonly type: "link";
|
|
/** Absolute http(s) URL, credential-free. */
|
|
readonly href: string;
|
|
/** ASCII (punycode) host of `href`, shown to the operator next to the label. */
|
|
readonly host: string;
|
|
readonly children: readonly (TextNode | EmojiNode)[];
|
|
};
|
|
export type MentionNode = {
|
|
readonly type: "mention";
|
|
/** Handle without the leading "@", e.g. `alice` or `alice@example.social`. */
|
|
readonly handle: string;
|
|
readonly href?: string;
|
|
};
|
|
export type HashtagNode = {
|
|
readonly type: "hashtag";
|
|
/** Tag without the leading "#". */
|
|
readonly tag: string;
|
|
readonly href?: string;
|
|
};
|
|
|
|
export type InlineNode = TextNode | LineBreakNode | EmojiNode | LinkNode | MentionNode | HashtagNode;
|
|
|
|
export type ParagraphNode = { readonly type: "paragraph"; readonly children: readonly InlineNode[] };
|
|
|
|
export type RichTextDocument = {
|
|
readonly type: "doc";
|
|
readonly version: typeof RICH_TEXT_VERSION;
|
|
readonly children: readonly ParagraphNode[];
|
|
/** True when input exceeded a length/node/depth limit and was cut. */
|
|
readonly truncated: boolean;
|
|
};
|
|
|
|
export type LinkPreview = {
|
|
readonly type: "linkPreview";
|
|
readonly href: string;
|
|
readonly host: string;
|
|
readonly title?: string;
|
|
readonly description?: string;
|
|
readonly siteName?: string;
|
|
/** Media-proxy URL for the preview image. */
|
|
readonly image?: string;
|
|
};
|