尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Manifest-driven UI engine for complex structured Nostr content (Reviews, Articles, Events, Venues) with automatic event coordination, field-level visibility controls, plugin composition, and NIP-78 manifest storage.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗🚧 ALPHA - API WILL CHANGE 🚧
This project is in active development. Expect breaking changes to the API, manifest format, and plugin system before v1.0.
An easy-to-use library for showing and creating Nostr posts on any web page, with a deeper plugin + manifest system for complex structured content.
Use it in plain HTML through the CDN bundle, drop in Web Components, use the React wrappers, or run it headless. Start with simple post creation and viewing, then scale up to manifest-driven structured content like reviews, articles, events, venues, and multi-event workflows.
nostr-post enables you to:
✅ Use it fast - Add Nostr post creation, viewing, and feeds to almost any site with Web Components or the CDN bundle
✅ Stay simple when you want - Works for basic kind 1 posts without needing manifests or custom plugins
✅ Define content once via JSON manifest (what fields, which plugin, where data lives)
✅ Works everywhere - Web Components, React, Next.js, or headless
✅ Automatic coordination - Forms split into multi-event bundles with NIP-78 linking
✅ Composable plugins - Media arrays, geohash locations, markdown editors, hashtags, ratings
✅ Field-level control - Visibility, read-only modes, default values, prefill, exclude fields
✅ NIP support - NIP-01, NIP-07 (signing), NIP-23 (articles), NIP-52 (geohash filtering), NIP-73 (identity tags), NIP-78 (manifest storage), NIP-92 (media metadata), NIP-98 (file upload auth), Blossom (media servers)
┌────────────────────────────────────┐
│ Your App (React, Vue, Next.js) │
└────────────────┬───────────────────┘
│
┌────────────▼──────────────┐
│ @nostr-post/react │ (Hooks + React Components)
│ + Plugin Packages │ (plugin-geo, plugin-media, etc.)
└────────────┬──────────────┘
│
┌────────────▼──────────────────┐
│ @nostr-post/web │ (Lit Web Components)
│ <nostr-post-composer> │
│ <nostr-post-view> │
│ <nostr-post-feed> │
└────────────┬──────────────────┘
│
┌────────────▼──────────────────┐
│ @nostr-post/posts │ (Publishing and reading posts,
│ publish, threads, feeds │ no UI: for any UI)
└────────────┬──────────────────┘
│
┌────────────▼──────────────────┐
│ @nostr-post/signer │ (NIP-07, Relay Publish/Fetch)
│ @nostr-post/plugins (Registry)│
└────────────┬──────────────────┘
│
┌────────────▼──────────────────┐
│ @nostr-post/core │ (Zero Dependencies)
│ Manifest validation │ Pure TypeScript Logic
│ Event coordination │
│ NIP-78 storage │
└───────────────────────────────┘
### Manifest fetching
- Manifest resolution and relay fetch logic lives in `@nostr-post/signer`.
- There is a single centralized, cached helper `fetchManifestByATag` that consumer
components (`view`, `composer`, `feed`) use to resolve an `a` tag (NIP-01 format
for NIP-78 manifests). The helper deduplicates in-flight requests and optionally
falls back to querying by `d` tag across authors (configurable via `fallbackToD`).
- `fetchManifestForEvent` finds the manifest a post or wiki page was made with: its `a` address,
else by its manifest label (see [MANIFEST.md](./MANIFEST.md#which-manifest-an-event-was-made-with)).
This keeps relay I/O in one place and avoids duplicate network requests.
| Package | Status | Purpose |
|---|---|---|
| @nostr-post/core | ✅ Complete | Manifest validation, event coordination, NIP-78 storage |
| @nostr-post/signer | ✅ Complete | NIP-07 signing, relay publish/fetch |
| @nostr-post/plugins | ✅ Complete | Plugin registry + types (extraTags, resolveFromTags) |
| @nostr-post/posts | ✅ Complete | Publishing and reading posts without a UI (bundles, threads, feeds) — README |
| @nostr-post/outbox | 🚧 In progress | Posts published later: drafts, timed posts, upload later — README |
| @nostr-post/web | ✅ Complete | Lit web components (composer, view, feed) |
| @nostr-post/react | ✅ Complete | React hooks + wrapper components |
| @nostr-post/cdn | ✅ Complete | Single-file CDN bundle (ESM + IIFE, no npm required) |
| plugin-stars | ✅ Complete | Star rating (1-5 scale, customizable) |
| plugin-geo | ✅ Complete | Geohash location picker with NIP-52 prefix tags |
| plugin-embed | ✅ Complete | Players for links: YouTube, Spotify, SoundCloud, Vimeo, Bandcamp |
| plugin-profile | ✅ Complete | People picker: profile search or npub, p tags with roles |
| plugin-date | ✅ Complete | Date / date-time input; NIP-52 date and day tags |
Define content structure once, deploy everywhere (all keys: MANIFEST.md):
const manifest: NostrPostManifest = {
id: "blog-v1",
version: "1.0.0",
publishFormats: [
{ id: "article", label: "Article", kinds: [30023], default: true },
], // Long-form articles
fields: [
{
id: "title",
type: "string",
uiPlugin: "textarea",
mapTo: { kind: 30023, target: "title" },
defaultValue: "Untitled Article",
},
{
id: "content",
type: "string",
uiPlugin: "markdown",
mapTo: { kind: 30023, target: "content" },
required: true,
},
],
};
Automatically splits data across event kinds and links them via NIP-78:
const result = coordinateEvents(manifest, formData, { pubkey });
// returns: { events: [Kind 30023, Kind 30078 (manifest)] }
Manifests can offer multiple user-facing publish formats, each activating a different set of kinds. This lets a composer keep one form while publishing, for example, either as a public Kind 1 note or as a structured NIP-78 event.
const manifest: NostrPostManifest = {
id: "review-v2",
version: "2.0.0",
publishFormats: [
{ id: "kind1", label: "Public note", kinds: [1], default: true },
{ id: "nip78", label: "Structured review", kinds: [30078] },
],
fields: [
{
id: "review",
type: "string",
uiPlugin: "textarea",
mapTo: [
{ kind: 1, target: "content" },
{ kind: 30078, target: "content", path: "review" },
],
},
],
};
If a field has multiple targets, mapBehavior controls how it publishes:
first-active (default): publish to the first mapping whose kind is activeall-active: publish to every active mapping for that fieldReady-to-use UI components via Web Components or React:
attachToattachToVisibility, defaults, prefill, readonly, exclusion, and field attachment:
{
id: "private-notes",
type: "string",
visibility: {
edit: "visible", // Author can edit
view: "hidden" // Viewers cannot see
},
defaultValue: "My notes...",
}
// attachTo: attach a plugin field to a target field's toolbar
// — auto-extracts data (hashtags, URLs) from the target field's content
// — renders the plugin's toggle button in the target field's toolbar
{
id: "tags",
type: "string",
uiPlugin: "hashtag",
attachTo: "content", // auto-extract #hashtags from 'content', show # button in its toolbar
mapTo: { kind: 1, target: "tag", tagName: "t" },
}
Plugins emit custom tags and reconstruct from tags:
// plugin-venue wraps plugin-geo, adds OSM search
// Emits: ["g", geohash], ["i", "osm:node:123"], ["location", address]
// Reconstructs from all 3 tags back into VenueData object
Built on standardized Nostr Improvement Proposals:
| NIP | Feature | Example |
|---|---|---|
| NIP-01 | Base protocol | All events build on this |
| NIP-07 | Browser extension signing | window.nostr.signEvent() |
| NIP-22 | Comments (kind 1111) | Comments on events other than notes |
| NIP-23 | Kind 30023 (articles) | Long-form content |
| NIP-52 | Geohash prefix tags | Relay-side location filtering |
| NIP-73 | External identity tags | Countries, profile URLs; OSM IDs in our own osm: namespace (conventions) |
| NIP-78 | Manifest storage | Kind 30078 structured data |
| NIP-98 | HTTP file server auth | nostr.build upload |
| NIP-92 | Media metadata (imeta) | Media plugin |
| Blossom | Media servers (BUD-01/02/03) | Media plugin upload |
The fastest way to get started — add one script tag:
<!DOCTYPE html>
<html>
<head>
<script
type="module"
src="https://saintego.github.io/nostr-post/nostr-post.js"
></script>
</head>
<body>
<h1>Create a Post</h1>
<nostr-post-composer auto-publish></nostr-post-composer>
</body>
</html>
Or use the IIFE build for older browsers:
<script src="https://saintego.github.io/nostr-post/nostr-post.iife.js"></script>
📦 What's included: All web components (<nostr-post-composer>, <nostr-post-view>, <nostr-post-feed>) + all 6 plugins are automatically registered. Full CDN docs →
<!DOCTYPE html>
<html>
<head>
<script type="module">
import "@nostr-post/web";
</script>
</head>
<body>
<nostr-post-composer></nostr-post-composer>
</body>
</html>
import { NostrPostComposer, useNostrAuth } from "@nostr-post/react";
export default function App() {
const { pubkey, login } = useNostrAuth();
return !pubkey ? (
<button onClick={login}>Login with Nostr</button>
) : (
<NostrPostComposer pubkey={pubkey} />
);
}
Detailed setup: QUICKSTART.md
import { coordinateEvents } from "@nostr-post/core/coordinator";
import { validateManifest, getFieldsByKind } from "@nostr-post/core/manifest";
import type { NostrPostManifest, PostField } from "@nostr-post/core/types";
Status: Complete
NIP-07 browser extension integration and relay communication.
import { NostrSigner } from "@nostr-post/signer";
const signer = new NostrSigner();
await signer.signEvent(event);
Status: Complete
Framework-agnostic plugin definitions and registry. Includes plugins for:
import { getPlugin } from "@nostr-post/plugins/registry";
const starsPlugin = getPlugin("stars");
Status: Complete
Universal Web Components that work in any framework or vanilla HTML.
<nostr-post-composer></nostr-post-composer>
<nostr-post-view></nostr-post-view>
<nostr-post-feed></nostr-post-feed>
Built-in plugin loading:
@nostr-post/web lazy-loads built-in plugin UI modules when a manifest uses them.media, hashtag, stars, geo, date, profile, embed, venue, markdown, list.Default behavior when no manifest is provided:
nostr-post-composer uses STANDARD_KIND1_POST_MANIFESTnostr-post-view uses STANDARD_KIND1_POST_MANIFESTThis means text, media (r tags), and hashtags (t tags) render out of the box for standard kind 1 posts.
<nostr-post-view> shows a note's text the way other Nostr clients do: links, images and videos
(by file extension or the event's imeta tags), #hashtags and nostr: references in place;
nostr:npub… / nostr:nprofile… mentions show the person's name once their profile loads.
Tag values the text already shows (r URLs, t hashtags) aren't repeated below it.
nostr-link-base (default https://njump.me/) sets where nostr: references link to.
<nostr-post-composer> extended reply context API:
reply-to-event-id, reply-to-pubkey, root-event-id, root-pubkey, root-address (an addressable root: the reply is a NIP-22 comment, kind 1111, naming it by address)show-reply-target to show current reply targeteditable-reply-target to allow editing reply target directly in the composer<nostr-post-composer> submit feedback: next to the submit button it shows the running step
(publishing linked pages such as a venue's wiki page, then signing and publishing the post, and
"waiting for your signer to approve" while a signature is pending), the
fields that failed validation (the first one is scrolled into view) and why a submit failed (also
dispatched as nostr-post-error). Publish hooks that take longer than 90 s fail the submit. After the
post is out, plugins' after-publish steps run (e.g. saving its venue to lists); one that fails is shown
next to the button but doesn't undo the post. A publish format with kinds: [] publishes no post, only
the plugins' steps ("Done!"). The text
comes from messages (ComposerMessages, defaults in DEFAULT_COMPOSER_MESSAGES), which apps can
override, e.g. for translations.
<nostr-post-view> metadata visibility defaults:
show-kind=false, show-tags=false)show-kind and/or show-tags<nostr-post-feed> interaction API:
comments-enabled (default true)reactions-enabled (default true)commentManifest (defaults to standard kind 1 manifest)reactionOptions (defaults to ['+', '❤️', '🔥', '😂'])<nostr-post-feed> supports direct Nostr filtering (authors, kinds, ids, since, until, limit, search) plus universal tag filters via tagFilters / filter-tags (for example #g geohash and #i OSM identity tags). It also supports advanced multi-filter REQ queries through the filters property, a layout hook (how posts are laid out) and trustedEditors. NIP-52 calendars are @nostr-post/calendar: <nostr-calendar-agenda> (a date window or a month, by day), RSVP, calendars and iCalendar.
Detailed examples: packages/cdn/README.md
Status: Complete
React hooks and components for Next.js/React apps.
import {
NostrPostComposer,
NostrPostFeed,
useNostrAuth,
} from "@nostr-post/react";
function App() {
const { pubkey, login } = useNostrAuth();
return <NostrPostComposer pubkey={pubkey} />;
}
@nostr-post/react wrappers expose the same interaction capabilities as web components:
NostrPostFeed: commentsEnabled, reactionsEnabled, commentManifest, reactionOptionsNostrPostView: showKind, showTags (technical metadata off by default in view)NostrPostComposer: reply-target properties via web-component attributes/propertiesuseNostrPublish() defaults to STANDARD_KIND1_POST_MANIFEST when manifest is omitted.
NostrPostFeed supports the same advanced filtering options as the web component:
import { NostrPostFeed } from "@nostr-post/react";
export function VenueFeed({ placeId }: { placeId: string }) {
return (
<NostrPostFeed
kinds={[1]}
limit={20}
filterTags={`#i:osm:node:${placeId},#g:u09tvw`}
/>
);
}
You can also use object/advanced forms:
<NostrPostFeed
tagFilters={{ "#i": [`osm:node:${placeId}`], "#g": ["u09tvw"] }}
since={Math.floor(Date.now() / 1000) - 60 * 60 * 24 * 7}
search="review"
/>
<NostrPostFeed
filters={[
{ kinds: [1], "#g": ["u09tvw"], limit: 20 },
{ kinds: [30023], "#t": ["review"], limit: 20 },
]}
/>
Status: Complete
Single-file CDN bundle with all components and plugins — no npm required.
Deployed at: https://saintego.github.io/nostr-post/
<!-- ESM (recommended) -->
<script
type="module"
src="https://saintego.github.io/nostr-post/nostr-post.js"
></script>
<!-- IIFE (classic) -->
<script src="https://saintego.github.io/nostr-post/nostr-post.iife.js"></script>
<!-- Use components -->
<nostr-post-composer auto-publish></nostr-post-composer>
Bundle size: ~267 KB raw / ~72 KB gzipped
Define your content structure once, use it everywhere.
Automatically split complex data across multiple Nostr event kinds:
Full TypeScript support with strict mode enabled.
No classes, no mutations, just pure functions and immutable data.
Core logic works in any JavaScript environment. UI layers available for Web Components and React.
# Clone the repository
git clone https://github.com/yourusername/nostr-post.git
cd nostr-post
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run type checking
pnpm typecheck
# Lint and format
pnpm lint:fix
pnpm format
nostr-post/
├── packages/
│ └── core/ ✅ Headless logic
│ ├── src/
│ │ ├── types.ts # Core type definitions
│ │ ├── manifest.ts # Manifest validation
│ │ └── coordinator.ts # Event coordination
│ └── package.json
├── DEVELOPMENT_GUIDE.md # Comprehensive dev guide
├── EXAMPLES.md # Usage examples
└── package.json # Monorepo root
metadata keyPlanned and finished work is tracked in GitHub Issues.
# Install dependencies
pnpm install
# Build all 15 packages (11 libraries + 3 examples + manifest creator)
pnpm -r build
# Watch mode for development
pnpm -r dev
# Type checking
pnpm typecheck
# Linting + formatting with Biome
pnpm lint
pnpm format
| Guide | Purpose |
|---|---|
| QUICKSTART.md | Setup for Web Components, React, Next.js |
| USAGE_GUIDE.md | Complete API reference for all packages |
| MANIFEST.md | Manifest reference: fields, plugin metadata, wiki entity manifests |
| ARCHITECTURE.md | Design decisions, NIP support, patterns |
| DEVELOPMENT_GUIDE.md | Contributing, technical standards |
| EXAMPLES.md | Real-world code examples |
| PLUGINS.md | Creating custom UI plugins |
| INTEGRATION.md | Using in other projects, Vercel deployment, venue review app example |
| PROJECT_SUMMARY.md | Technical implementation summary |
| NOSTR_CONVENTIONS.md | Our tags and formats beyond the NIPs, and what other clients see |
We follow strict coding standards:
See DEVELOPMENT_GUIDE.md for complete standards.
MIT
Built with ❤️ for the Nostr ecosystem
D| calendar | ✅ Complete | NIP-52 calendars: agenda/month, RSVP, calendars, iCalendar |
| plugin-venue | ✅ Complete | OSM venue search + NIP-73 identity tags, wraps geo |
| plugin-media | ✅ Complete | Multi-file upload: the user's Blossom servers, then nostr.build |
| plugin-markdown | ✅ Complete | Markdown editor with live preview |
| plugin-hashtag | ✅ Complete | Hashtag array input with optional auto-extraction via attachTo |
| plugin-reference | ✅ Complete | URL/nostr reference list with optional auto-extraction via attachTo |