npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@standard-reader/renderer-angular

v0.4.0

Published

Headless, unstyled Angular renderer for Standard Site documents.

Readme

@standard-reader/renderer-angular

A headless, unstyled Angular renderer for Standard Site documents, built on the framework-agnostic @standard-reader/renderer-core.

  • A standalone <sr-standard-document> component — bind the document input.
  • Renders unstyled semantic HTML you style with your own CSS.
  • Override the media and data-backed blocks with <ng-template> refs via the components input — shared (image, code, table, embeds, …) and platform (a Leaflet poll, a pckt gallery, an Offprint component, …).

This is the same renderer that powers the Standard Reader app, factored out so you can drop richly-formatted cross-platform posts into your own UI.

Install

npm install @standard-reader/renderer-angular @angular/core @angular/common
# to fetch documents to render (optional):
npm install @standard-reader/lexicons @atproto/lex-client

Quick start

Fetch a document with the typed Standard Reader API client (@standard-reader/lexicons and @atproto/lex-client) — a single getDocument call returns the card metadata and the renderable body:

import { Client } from "@atproto/lex-client";
import {
  standardReader,
  STANDARD_READER_SERVICE,
} from "@standard-reader/lexicons";

const client = new Client(STANDARD_READER_SERVICE);

const doc = await client.call(standardReader.getDocument, {
  document: "at://did:plc:…/site.standard.document/…",
});

doc lines up field-for-field with the renderer's document input:

import { Component } from "@angular/core";
import { StandardDocumentComponent } from "@standard-reader/renderer-angular";

@Component({
  selector: "app-article",
  standalone: true,
  imports: [StandardDocumentComponent],
  template: `<sr-standard-document
    [document]="document"
    [options]="{ dropCap: true }"
  />`,
})
export class ArticleComponent {
  document = {
    content: doc.content,
    contentFormat: doc.contentFormat,
    authorDid: doc.did,
    description: doc.description,
  };
}

With no components input, the document renders as unstyled semantic HTML (<p>, <h2>, <figure><img>, <pre><code>, <ul>, …) that you style with ordinary CSS. Data-backed platform blocks (polls, galleries, note embeds, Offprint components) render nothing until you provide a template for them.

The document input

Every renderer takes one input for the document — the content payload of a site.standard.document. The format is detected from the payload's $type.

interface StandardSiteDocument {
  /** The content-union payload — the `contentJson` of a document record. */
  content: unknown;
  /** Explicit format `$type`, used only when `content.$type` is absent. */
  contentFormat?: string | null;
  /** DID of the repo hosting image blobs (required for blob-backed images). */
  authorDid?: string;
  /** Header description; a leading heading matching it is dropped as a dupe. */
  description?: string | null;
}

Supported content formats:

| Family | $type | Vocabulary | | ------------------ | --------------------------------------------- | ----------------- | | Leaflet | pub.leaflet.content, pub.leaflet.document | Leaflet blocks | | pckt | blog.pckt.content | pckt blocks | | Offprint | app.offprint.content | shared structured | | BlockNote | org.blocknote.document#content | shared structured | | Fables | ca.justexe.fables.blocks | shared structured | | OXA | pub.oxa.document.document | shared structured | | WSS rich-text | com.wss.content.rich-text | shared structured | | item-block formats | (several) | shared structured |

Customizing components

Overrides are <ng-template> refs keyed by node type, passed via components. Each template receives the node's props as its $implicit context — bind them with let-p.

@Component({
  standalone: true,
  imports: [StandardDocumentComponent, LivePollComponent, PcktGalleryComponent],
  template: `
    <ng-template #poll let-p><live-poll [uri]="p.pollUri" /></ng-template>
    <ng-template #gallery let-p><pckt-gallery [record]="p.ref" /></ng-template>
    <ng-template #image let-p>
      <my-image [src]="p.src" [alt]="p.alt" [ratio]="p.aspectRatio" />
    </ng-template>

    <sr-standard-document
      [document]="doc"
      [components]="{
        shared: { image },
        leaflet: { poll },
        pckt: { gallery },
      }"
    />
  `,
})
export class ArticleComponent {
  @ViewChild("poll", { static: true }) poll!: TemplateRef<any>;
  /* … Angular binds the #poll/#gallery/#image templates automatically … */
}

The components input is a partial map with four groups — shared, leaflet, pckt, offprint. Anything you omit falls back to the unstyled default (or, for data-backed platform blocks, to nothing).

Shared components

Override these once and they apply to Leaflet, pckt, Offprint and every third-party format. Only the media and data-backed blocks are template- overridable:

Blocks: image, code, iframe, website, table, math, button, blueskyEmbed, imageGrid, imageCarousel, imageDiff, unknown.

Structural blocks (paragraphs, headings, blockquotes, lists, callouts, footnotes) and inline marks are not template-overridable — they render as unstyled semantic HTML you target with CSS. Inline formatting comes through as <strong>, <em>, <a>, <code>, mention/link anchors, and so on, so a stylesheet scoped to sr-standard-document is all you need to theme them.

@Component({
  standalone: true,
  imports: [StandardDocumentComponent, MyImageComponent, MyCodeComponent],
  template: `
    <ng-template #image let-p>
      <my-image [src]="p.src" [alt]="p.alt" [ratio]="p.aspectRatio" />
    </ng-template>
    <ng-template #code let-p>
      <my-code [code]="p.code" [language]="p.language" />
    </ng-template>

    <sr-standard-document
      [document]="doc"
      [components]="{ shared: { image, code } }"
    />
  `,
})
export class ArticleComponent {}

Platform components

Platform components render blocks unique to one publishing platform — usually interactive or data-backed embeds the headless defaults can't fetch. Supply a template to make them live:

@Component({
  standalone: true,
  imports: [StandardDocumentComponent /* + your embed components */],
  template: `
    <ng-template #poll let-p><live-poll [uri]="p.pollUri" /></ng-template>
    <ng-template #publication let-p>
      <publication-card [uri]="p.uri" />
    </ng-template>
    <ng-template #gallery let-p><pckt-gallery [record]="p.ref" /></ng-template>
    <ng-template #note let-p><note-card [uri]="p.uri" /></ng-template>
    <ng-template #component let-p>
      <offprint-component [uri]="p.componentUri" />
    </ng-template>

    <sr-standard-document
      [document]="doc"
      [components]="{
        leaflet: { poll, standardSitePublication: publication },
        pckt: { gallery, noteEmbed: note },
        offprint: { component },
      }"
    />
  `,
})
export class ArticleComponent {}

The platform keys are leaflet.poll / signup / separator / standardSitePost / standardSitePublication, pckt.gallery / noteEmbed, and offprint.component.

Resolving the data these components need. The platform components — and the inline mention / link components — hand you AT-URIs and DIDs (pollUri, ref, componentUri, uri, did, …) that you resolve to records and identities yourself. A hosted AT Protocol data service like microcosm works great for many of them, so you don't have to stand up your own AppView:

  • Slingshot — an edge record + identity cache. Resolve a record by AT-URI (the poll, gallery blob, note post, Offprint component, embedded publication/document) or resolve a handle/DID to build mention chips and smart links.
  • Constellation — a network-wide backlink index, handy for the interaction data these embeds show (poll tallies, who-embedded-this, reply counts).

Both are free and hosted, and pair naturally with a data-fetching layer (TanStack Query, SWR, or your framework's own data layer) inside your platform components.

Options

interface RendererOptions {
  /** Drop cap on the first paragraph (rendered as `data-drop-cap`). */
  dropCap?: boolean;
  /** Drop a leading image block (e.g. when a hero is shown above the body). */
  skipLeadingImage?: boolean;
  /** Override how blob refs become image URLs; defaults to the Bluesky CDN. */
  resolveImageUrl?: ImageUrlResolver;
}

Images

By default, blob-backed images resolve to a Bluesky CDN URL built from the blob CID and authorDid (the CDN serves any PDS blob by (did, cid)); absolute https sources pass through. Override resolveImageUrl in options to route through your own image proxy:

@Component({
  standalone: true,
  imports: [StandardDocumentComponent],
  template: `
    <ng-template #image let-p>
      <img [src]="p.src" [alt]="p.alt" loading="lazy" />
    </ng-template>

    <sr-standard-document
      [document]="doc"
      [components]="{ shared: { image } }"
      [options]="{
        resolveImageUrl: resolveImageUrl,
      }"
    />
  `,
})
export class ArticleComponent {
  resolveImageUrl = ({ blob, externalSrc, authorDid }) =>
    externalSrc ?? myCdn(authorDid, blob);
}

The resolved URL reaches your image template as src.

Parsing without rendering

The pure parsers, the render tree, and the block-vocabulary types are not in this package — they live in the framework-agnostic core. Install @standard-reader/renderer-core directly when you want to pre-process a document, index it, or build your own renderer:

import {
  buildRenderTree,
  segmentInline,
  leafletBlocks,
  pcktBlocks,
  offprintBlocks,
  structuredFormatBlocks,
  type LeafletRenderableBlock,
  type StructuredRenderableBlock,
} from "@standard-reader/renderer-core";

const blocks = leafletBlocks(content); // Array<LeafletRenderableBlock>

TypeScript

Every component contract is exported, so your override templates are fully typed. Type a TemplateRef with the Tpl<…> helper, or type the whole components input with AngularComponents:

import type {
  AngularComponents,
  AngularSharedComponents,
  Tpl,
} from "@standard-reader/renderer-angular";

// One template's `$implicit` context:
@ViewChild("image", { static: true })
image!: Tpl<{ src: string; alt: string }>;

// Or the whole input:
const components: AngularComponents = {
  shared: { image: this.image },
};

License

MIT