helpers.tsx raw
1 // Helper functions and constants for the ORLY dashboard
2
3 // Comprehensive kind names mapping
4 export const KIND_NAMES = {
5 0: "Profile Metadata",
6 1: "Text Note",
7 2: "Recommend Relay",
8 3: "Contacts",
9 4: "Encrypted DM",
10 5: "Delete Request",
11 6: "Repost",
12 7: "Reaction",
13 8: "Badge Award",
14 16: "Generic Repost",
15 40: "Channel Creation",
16 41: "Channel Metadata",
17 42: "Channel Message",
18 43: "Channel Hide Message",
19 44: "Channel Mute User",
20 1063: "File Metadata",
21 1311: "Live Chat Message",
22 1984: "Reporting",
23 1985: "Label",
24 9734: "Zap Request",
25 9735: "Zap Receipt",
26 10000: "Mute List",
27 10001: "Pin List",
28 10002: "Relay List Metadata",
29 10003: "Bookmark List",
30 10004: "Communities List",
31 10005: "Public Chats List",
32 10006: "Blocked Relays List",
33 10007: "Search Relays List",
34 10009: "User Groups",
35 10015: "Interests List",
36 10030: "User Emoji List",
37 13194: "Wallet Info",
38 22242: "Client Auth",
39 23194: "Wallet Request",
40 23195: "Wallet Response",
41 24133: "Nostr Connect",
42 27235: "HTTP Auth",
43 30000: "Categorized People List",
44 30001: "Categorized Bookmarks",
45 30002: "Categorized Relay List",
46 30003: "Bookmark Sets",
47 30004: "Curation Sets",
48 30005: "Video Sets",
49 30008: "Profile Badges",
50 30009: "Badge Definition",
51 30015: "Interest Sets",
52 30017: "Create/Update Stall",
53 30018: "Create/Update Product",
54 30019: "Marketplace UI/UX",
55 30020: "Product Sold As Auction",
56 30023: "Long-form Content",
57 30024: "Draft Long-form Content",
58 30030: "Emoji Sets",
59 30063: "Release Artifact Sets",
60 30078: "Application-specific Data",
61 30311: "Live Event",
62 30315: "User Statuses",
63 30388: "Slide Set",
64 30402: "Classified Listing",
65 30403: "Draft Classified Listing",
66 30617: "Repository Announcement",
67 30618: "Repository State Announcement",
68 30818: "Wiki Article",
69 30819: "Redirects",
70 31922: "Date-Based Calendar Event",
71 31923: "Time-Based Calendar Event",
72 31924: "Calendar",
73 31925: "Calendar Event RSVP",
74 31989: "Handler Recommendation",
75 31990: "Handler Information",
76 34550: "Community Definition",
77 34551: "Community Post Approval",
78 };
79
80 // Get human-readable kind name
81 export function getKindName(kind) {
82 return KIND_NAMES[kind] || `Kind ${kind}`;
83 }
84
85 // Validate hex string (for pubkeys and event IDs)
86 export function isValidHex(str, length = null) {
87 if (!str || typeof str !== "string") return false;
88 const hexRegex = /^[0-9a-fA-F]+$/;
89 if (!hexRegex.test(str)) return false;
90 if (length && str.length !== length) return false;
91 return true;
92 }
93
94 // Validate pubkey (64 character hex)
95 export function isValidPubkey(pubkey) {
96 return isValidHex(pubkey, 64);
97 }
98
99 // Validate event ID (64 character hex)
100 export function isValidEventId(eventId) {
101 return isValidHex(eventId, 64);
102 }
103
104 // Validate tag name (single letter a-zA-Z)
105 export function isValidTagName(tagName) {
106 return /^[a-zA-Z]$/.test(tagName);
107 }
108
109 // Format timestamp to localized string
110 export function formatTimestamp(timestamp) {
111 return new Date(timestamp * 1000).toLocaleString();
112 }
113
114 // Format timestamp for datetime-local input
115 export function formatDateTimeLocal(timestamp) {
116 const date = new Date(timestamp * 1000);
117 const year = date.getFullYear();
118 const month = String(date.getMonth() + 1).padStart(2, '0');
119 const day = String(date.getDate()).padStart(2, '0');
120 const hours = String(date.getHours()).padStart(2, '0');
121 const minutes = String(date.getMinutes()).padStart(2, '0');
122 return `${year}-${month}-${day}T${hours}:${minutes}`;
123 }
124
125 // Parse datetime-local input to unix timestamp
126 export function parseDateTimeLocal(dateTimeString) {
127 return Math.floor(new Date(dateTimeString).getTime() / 1000);
128 }
129
130 // Truncate pubkey for display
131 export function truncatePubkey(pubkey) {
132 if (!pubkey) return "";
133 return pubkey.slice(0, 8) + "..." + pubkey.slice(-8);
134 }
135
136 // Truncate content for display
137 export function truncateContent(content, maxLength = 100) {
138 if (!content) return "";
139 return content.length > maxLength ? content.slice(0, maxLength) + "..." : content;
140 }
141
142 // Build Nostr filter from form data
143 export function buildFilter({
144 searchText = null,
145 kinds = [],
146 authors = [],
147 ids = [],
148 tags = [],
149 since = null,
150 until = null,
151 limit = null,
152 }) {
153 const filter = {};
154
155 if (searchText && searchText.trim()) {
156 filter.search = searchText.trim();
157 }
158
159 if (kinds && kinds.length > 0) {
160 filter.kinds = kinds;
161 }
162
163 if (authors && authors.length > 0) {
164 filter.authors = authors;
165 }
166
167 if (ids && ids.length > 0) {
168 filter.ids = ids;
169 }
170
171 // Add tag filters (e.g., #e, #p, #a)
172 if (tags && tags.length > 0) {
173 tags.forEach(tag => {
174 if (tag.name && tag.value) {
175 const tagKey = `#${tag.name}`;
176 if (!filter[tagKey]) {
177 filter[tagKey] = [];
178 }
179 filter[tagKey].push(tag.value);
180 }
181 });
182 }
183
184 if (since) {
185 filter.since = since;
186 }
187
188 if (until) {
189 filter.until = until;
190 }
191
192 if (limit && limit > 0) {
193 filter.limit = limit;
194 }
195
196 return filter;
197 }
198
199 // Pretty print JSON with word breaking for long strings
200 export function prettyPrintFilter(filter) {
201 return JSON.stringify(filter, null, 2);
202 }
203
204