Files
eranos/src/lib/extraKinds.ts
T
Mary Kate Fain e65485a44b Rename Feed settings to Home Feed, clarify labels, conditionally show community
- Rename 'Feed' to 'Home Feed' in settings hub, page header, and SEO title
- Rename 'Notes' section to 'Basic Home Feed Options'
- Rename 'Other Stuff' to 'Show More Content Types in Home Feed'
- Rename 'Reposts' to 'Reposted Notes', 'Generic Reposts' to 'Reposted Other Content'
- Only show community domain management when community feed toggle is on
2026-03-07 18:43:00 -06:00

474 lines
14 KiB
TypeScript

import type { FeedSettings } from '@/contexts/AppContext';
/** A sub-kind that lives under a parent ExtraKindDef. */
export interface SubKindDef {
kind: number;
/** Key in FeedSettings that controls visibility within the parent page. */
showKey: keyof FeedSettings;
/** Key in FeedSettings that controls inclusion in mixed feeds. */
feedKey: keyof FeedSettings;
/** Human-readable label. */
label: string;
/** Short description. */
description: string;
/** Whether this kind is addressable (30000-39999). */
addressable: boolean;
}
/** An external site where users can create or participate in a specific kind. */
export interface ExtraKindSite {
/** Full URL to the site. */
url: string;
/** Display name override. Defaults to the first segment of the hostname, capitalized. */
name?: string;
}
/** Section labels for grouping extra kinds in settings UI. */
export type ExtraKindSection = 'feed' | 'media' | 'social' | 'whimsy';
/** Display labels for each section. */
export const SECTION_LABELS: Record<ExtraKindSection, string> = {
feed: 'Feed',
media: 'Media',
social: 'Social',
whimsy: 'Whimsy',
};
/** Ordered list of sections for the "Other Stuff" settings UI. */
export const SECTION_ORDER: ExtraKindSection[] = ['media', 'social', 'whimsy'];
/** Metadata for an extra (non-kind-1) content type. */
export interface ExtraKindDef {
kind: number;
/** Unique identifier for this content type. Used in sidebar ordering and icon maps. */
id: string;
/** Key in FeedSettings that controls sidebar visibility (omit for feed-only items). */
showKey?: keyof FeedSettings;
/** Key in FeedSettings that controls inclusion in mixed feeds (only for entries without subKinds). */
feedKey?: keyof FeedSettings;
/** Additional kind numbers to include alongside `kind` when the feed toggle is on. */
extraFeedKinds?: number[];
/** Human-readable label. */
label: string;
/** Short description. */
description: string;
/** Route path (without leading slash). Omit for feed-only items with no dedicated page. */
route?: string;
/** Whether this kind is addressable (30000-39999). */
addressable: boolean;
/** Optional sub-kinds that break this entry into granular options. */
subKinds?: SubKindDef[];
/** Section grouping for the settings UI. */
section: ExtraKindSection;
/** If true, this entry only has a feed toggle (no sidebar toggle). */
feedOnly?: boolean;
/** Longer, whimsical blurb shown in the info modal. */
blurb?: string;
/** External sites where users can create or participate in this kind of content. */
sites?: ExtraKindSite[];
}
/** All supported extra content kinds, ordered by section (feed → media → social → whimsy). */
export const EXTRA_KINDS: ExtraKindDef[] = [
// Feed (core content types — feed toggle only, no sidebar page)
{
kind: 1,
id: 'posts',
feedKey: 'feedIncludePosts',
label: 'Posts',
description: 'Short text notes',
addressable: false,
section: 'feed',
feedOnly: true,
},
{
kind: 1111,
id: 'comments',
feedKey: 'feedIncludePosts',
label: 'Comments',
description: 'NIP-22 comments on posts and external content',
addressable: false,
section: 'feed',
feedOnly: true,
},
{
kind: 6,
id: 'reposts',
feedKey: 'feedIncludeReposts',
label: 'Reposted Notes',
description: 'Shared posts from others',
addressable: false,
section: 'feed',
feedOnly: true,
},
{
kind: 16,
id: 'generic-reposts',
feedKey: 'feedIncludeReposts',
label: 'Reposted Other Content',
description: 'Shared non-text-note posts from others',
addressable: false,
section: 'feed',
feedOnly: true,
},
{
kind: 30023,
id: 'articles',
showKey: 'showArticles',
feedKey: 'feedIncludeArticles',
label: 'Articles',
description: 'Long-form blog posts',
route: 'articles',
addressable: true,
section: 'feed',
blurb: 'Blog posts, essays, and guides. Write and publish from a dedicated editor.',
sites: [{ url: 'https://inkwell.shakespeare.wtf' }],
},
// Media
{
kind: 20,
id: 'photos',
showKey: 'showPhotos',
feedKey: 'feedIncludePhotos',
label: 'Photos',
description: 'Picture-first posts (NIP-68)',
route: 'photos',
addressable: false,
section: 'media',
blurb: 'Instagram-style photo posts. Share images with captions and tags.',
sites: [{ url: 'https://nostr.build', name: 'nostr.build' }],
},
{
kind: 21,
id: 'videos',
showKey: 'showVideos',
label: 'Videos',
description: 'Video posts (NIP-71 kinds 21 & 22) and live streams',
route: 'videos',
addressable: false,
section: 'media',
blurb: 'Watch and discover videos and live streams in a YouTube/Twitch-style interface.',
sites: [{ url: 'https://zap.stream', name: 'zap.stream' }],
subKinds: [
{
kind: 21,
showKey: 'showVideos',
feedKey: 'feedIncludeNormalVideos',
label: 'Videos',
description: 'Normal videos (NIP-71 kind 21)',
addressable: false,
},
{
kind: 22,
showKey: 'showVideos',
feedKey: 'feedIncludeShortVideos',
label: 'Short Videos',
description: 'Short vertical videos (NIP-71 kind 22)',
addressable: false,
},
],
},
{
kind: 1222,
id: 'voice',
feedKey: 'feedIncludeVoiceMessages',
label: 'Voice Messages',
description: 'Short audio voice messages (NIP-A0)',
addressable: false,
section: 'media',
feedOnly: true,
blurb: 'Record and share short voice messages, up to 60 seconds long.',
},
{
kind: 34236,
id: 'vines',
showKey: 'showVines',
feedKey: 'feedIncludeVines',
label: 'Vines',
description: 'Short-form videos',
route: 'vines',
addressable: true,
section: 'media',
blurb: 'Short video clips. Record and share from a dedicated app.',
sites: [{ url: 'https://divine.video' }],
},
{
kind: 36787,
id: 'music',
showKey: 'showMusic',
label: 'Music',
description: 'Music tracks and playlists',
route: 'music',
addressable: true,
section: 'media',
blurb: 'Discover and listen to music tracks and playlists shared on Nostr. Upload music and create playlists from a dedicated music app.',
sites: [{ url: 'https://nodecast.xyz' }],
subKinds: [
{
kind: 36787,
showKey: 'showMusic',
feedKey: 'feedIncludeMusicTracks',
label: 'Tracks',
description: 'Music tracks (kind 36787)',
addressable: true,
},
{
kind: 34139,
showKey: 'showMusic',
feedKey: 'feedIncludeMusicPlaylists',
label: 'Playlists',
description: 'Music playlists (kind 34139)',
addressable: true,
},
],
},
{
kind: 30054,
id: 'podcasts',
showKey: 'showPodcasts',
label: 'Podcasts',
description: 'Podcast episodes and trailers',
route: 'podcasts',
addressable: true,
section: 'media',
blurb: 'Listen to podcast episodes and trailers shared on Nostr.',
subKinds: [
{
kind: 30054,
showKey: 'showPodcasts',
feedKey: 'feedIncludePodcastEpisodes',
label: 'Episodes',
description: 'Podcast episodes (kind 30054)',
addressable: true,
},
{
kind: 30055,
showKey: 'showPodcasts',
feedKey: 'feedIncludePodcastTrailers',
label: 'Trailers',
description: 'Podcast trailers (kind 30055)',
addressable: true,
},
],
},
// Social
{
kind: 30315,
id: 'statuses',
showKey: 'showUserStatuses',
label: 'User Statuses',
description: 'Live statuses on profiles and posts (NIP-38)',
addressable: true,
section: 'social',
feedOnly: true,
blurb: 'See what people are up to — statuses appear next to names on posts and on profile pages.',
},
{
kind: 31923,
id: 'events',
showKey: 'showEvents',
feedKey: 'feedIncludeEvents',
extraFeedKinds: [31922],
label: 'Events',
description: 'Calendar events and meetups (NIP-52)',
route: 'events',
addressable: true,
section: 'social',
blurb: 'Events and meetups on Nostr. RSVP and see who else is going. Create and manage events on Plektos.',
sites: [{ url: 'https://plektos.app', name: 'Plektos' }],
},
{
kind: 1063,
id: 'webxdc',
showKey: 'showWebxdc',
feedKey: 'feedIncludeWebxdc',
label: 'Webxdc',
description: 'Sandboxed HTML5 apps shared over Nostr',
route: 'webxdc',
addressable: false,
section: 'social',
blurb: 'Webxdc apps are sandboxed HTML5 mini-apps (.xdc files) shared over Nostr. Play games, run tools, and collaborate with others.',
sites: [{ url: 'https://webxdc.org', name: 'webxdc.org' }],
},
{
kind: 36767,
id: 'themes',
showKey: 'showProfileThemes',
label: 'Themes',
description: 'Custom UI themes & updates',
route: 'themes',
addressable: true,
section: 'social',
blurb: 'Shareable custom UI themes. Create your own theme or browse themes shared by others.',
subKinds: [
{
kind: 36767,
showKey: 'showThemeDefinitions',
feedKey: 'feedIncludeThemeDefinitions',
label: 'Theme Definitions',
description: 'Shareable named themes',
addressable: true,
},
{
kind: 16767,
showKey: 'showProfileThemeUpdates',
feedKey: 'feedIncludeProfileThemeUpdates',
label: 'Profile Themes',
description: 'Profile theme updates',
addressable: false,
},
],
},
{
kind: 1068,
id: 'polls',
showKey: 'showPolls',
feedKey: 'feedIncludePolls',
label: 'Polls',
description: 'Community polls and votes',
route: 'polls',
addressable: false,
section: 'social',
blurb: 'Ask a question, let people vote. Create polls from a polling app.',
sites: [{ url: 'https://pollerama.fun' }],
},
{
kind: 39089,
id: 'packs',
showKey: 'showPacks',
feedKey: 'feedIncludePacks',
label: 'Follow Packs',
description: 'Curated follow recommendations',
route: 'packs',
addressable: true,
section: 'social',
blurb: 'Curated lists of people to follow. Browse or create your own.',
sites: [{ url: 'https://following.space', name: 'following.space' }, { url: 'https://following.party', name: 'following.party' }],
},
// Whimsy
{
kind: 3367,
id: 'colors',
showKey: 'showColors',
feedKey: 'feedIncludeColors',
label: 'Color Moments',
description: 'Color moment palettes',
route: 'colors',
addressable: false,
section: 'whimsy',
blurb: 'Share your mood as a color palette. Pick colors that match the moment.',
sites: [{ url: 'https://espy.you' }],
},
{
kind: 37381,
id: 'decks',
showKey: 'showDecks',
feedKey: 'feedIncludeDecks',
label: 'Magic Decks',
description: 'Magic: The Gathering deck lists',
route: 'decks',
addressable: true,
section: 'whimsy',
blurb: 'Magic: The Gathering deck lists. Build and share decks with other players.',
sites: [{ url: 'https://surveil.cards' }],
},
{
kind: 30030,
id: 'emojis',
showKey: 'showEmojiPacks',
feedKey: 'feedIncludeEmojiPacks',
label: 'Emoji Packs',
description: 'Custom emoji collections (NIP-30)',
route: 'emojis',
addressable: true,
section: 'social',
blurb: 'Custom emoji packs for reactions, posts, and profiles. Browse, collect, and use custom emojis from the Nostr community.',
},
{
kind: 37516,
id: 'treasures',
showKey: 'showTreasures',
label: 'Treasures',
description: 'Geocaches & found logs',
route: 'treasures',
addressable: true,
section: 'whimsy',
blurb: 'Real-world geocaching. Hide treasures outside, find others, and log your discoveries.',
sites: [{ url: 'https://treasures.to' }],
subKinds: [
{
kind: 37516,
showKey: 'showTreasureGeocaches',
feedKey: 'feedIncludeTreasureGeocaches',
label: 'Geocaches',
description: 'Geocache listings',
addressable: true,
},
{
kind: 7516,
showKey: 'showTreasureFoundLogs',
feedKey: 'feedIncludeTreasureFoundLogs',
label: 'Found Logs',
description: 'Geocache found logs',
addressable: false,
},
],
},
];
/** Lookup an ExtraKindDef by its `id` field. */
export function getExtraKindDef(id: string): ExtraKindDef | undefined {
return EXTRA_KINDS.find((d) => d.id === id);
}
/** Entries rendered in the "Notes" section (Posts, Reposts, Articles). */
export const FEED_KINDS: ExtraKindDef[] = EXTRA_KINDS.filter((def) => def.section === 'feed');
/** Return the kind numbers the user has opted to include in mixed feeds. */
export function getEnabledFeedKinds(feedSettings: FeedSettings): number[] {
const kinds: number[] = [];
for (const def of EXTRA_KINDS) {
if (def.subKinds) {
for (const sub of def.subKinds) {
if (feedSettings[sub.feedKey]) {
kinds.push(sub.kind);
}
}
} else if (def.feedKey && feedSettings[def.feedKey]) {
kinds.push(def.kind);
if (def.extraFeedKinds) {
kinds.push(...def.extraFeedKinds);
}
}
}
return kinds;
}
/** Return the kinds enabled for a specific extra-kind page (respecting sub-kind toggles). */
export function getPageKinds(def: ExtraKindDef, feedSettings: FeedSettings): number[] {
if (!def.subKinds) return [def.kind];
return def.subKinds
.filter((sub) => feedSettings[sub.showKey])
.map((sub) => sub.kind);
}
/** Return all extra kind numbers (regardless of settings). */
export function getAllExtraKindNumbers(): number[] {
const kinds: number[] = [];
for (const def of EXTRA_KINDS) {
if (def.subKinds) {
for (const sub of def.subKinds) {
if (!kinds.includes(sub.kind)) kinds.push(sub.kind);
}
} else {
kinds.push(def.kind);
}
}
return kinds;
}