kindCategories.js raw

   1  /**
   2   * Kind categories for curating mode.
   3   * These define predefined groups of event kinds that can be enabled/disabled together.
   4   * The categories match the server-side definitions in pkg/database/curating-acl.go.
   5   */
   6  
   7  export const curationKindCategories = [
   8    {
   9      id: "social",
  10      name: "Social/Notes",
  11      description: "User profiles, notes, follows, reposts, reactions, and relay lists",
  12      kinds: [0, 1, 3, 6, 7, 10002],
  13    },
  14    {
  15      id: "dm",
  16      name: "Direct Messages",
  17      description: "Encrypted direct messages (legacy and NIP-17 gift-wrapped)",
  18      kinds: [4, 14, 1059],
  19    },
  20    {
  21      id: "longform",
  22      name: "Long-form Content",
  23      description: "Blog posts and article drafts",
  24      kinds: [30023, 30024],
  25    },
  26    {
  27      id: "media",
  28      name: "Media",
  29      description: "File metadata and media attachments",
  30      kinds: [1063, 20, 21, 22],
  31    },
  32    {
  33      id: "marketplace_nip15",
  34      name: "Marketplace (NIP-15)",
  35      description: "Legacy NIP-15 stalls and products",
  36      kinds: [30017, 30018, 30019, 30020],
  37    },
  38    {
  39      id: "marketplace_nip99",
  40      name: "Marketplace (NIP-99/Gamma)",
  41      description: "NIP-99 classified listings, collections, shipping, reviews (Plebeian Market)",
  42      kinds: [30402, 30403, 30405, 30406, 31555],
  43    },
  44    {
  45      id: "order_communication",
  46      name: "Order Communication",
  47      description: "Gamma Markets order messages and payment receipts (kinds 16, 17)",
  48      kinds: [16, 17],
  49    },
  50    {
  51      id: "groups_nip29",
  52      name: "Group Messaging (NIP-29)",
  53      description: "Simple relay-based group chat messages",
  54      kinds: [9, 10, 11, 12],
  55    },
  56    {
  57      id: "groups_nip72",
  58      name: "Communities (NIP-72)",
  59      description: "Community definitions and threaded discussions",
  60      kinds: [34550, 1111, 4550],
  61    },
  62    {
  63      id: "lists",
  64      name: "Lists/Bookmarks",
  65      description: "Mute lists, pin lists, and parameterized list events",
  66      kinds: [10000, 10001, 30000, 30001],
  67    },
  68  ];
  69  
  70  /**
  71   * Get all kinds from selected categories.
  72   * @param {string[]} categoryIds - Array of category IDs
  73   * @returns {number[]} - Array of unique kind numbers
  74   */
  75  export function getKindsFromCategories(categoryIds) {
  76    const kinds = new Set();
  77    for (const id of categoryIds) {
  78      const category = curationKindCategories.find((c) => c.id === id);
  79      if (category) {
  80        category.kinds.forEach((k) => kinds.add(k));
  81      }
  82    }
  83    return Array.from(kinds).sort((a, b) => a - b);
  84  }
  85  
  86  /**
  87   * Get category IDs that contain a given kind.
  88   * @param {number} kind - The kind number to look up
  89   * @returns {string[]} - Array of category IDs containing this kind
  90   */
  91  export function getCategoriesForKind(kind) {
  92    return curationKindCategories
  93      .filter((c) => c.kinds.includes(kind))
  94      .map((c) => c.id);
  95  }
  96  
  97  /**
  98   * Parse a custom kinds string (e.g., "100, 200-300, 500") into an array of kinds.
  99   * @param {string} customKinds - Comma-separated list of kinds and ranges
 100   * @returns {number[]} - Array of individual kind numbers
 101   */
 102  export function parseCustomKinds(customKinds) {
 103    if (!customKinds || !customKinds.trim()) return [];
 104  
 105    const kinds = new Set();
 106    const parts = customKinds.split(",").map((p) => p.trim());
 107  
 108    for (const part of parts) {
 109      if (!part) continue;
 110  
 111      // Check if it's a range (e.g., "100-200")
 112      if (part.includes("-")) {
 113        const [start, end] = part.split("-").map((n) => parseInt(n.trim(), 10));
 114        if (!isNaN(start) && !isNaN(end) && start <= end) {
 115          // Don't expand ranges > 1000 to avoid memory issues
 116          if (end - start <= 1000) {
 117            for (let i = start; i <= end; i++) {
 118              kinds.add(i);
 119            }
 120          }
 121        }
 122      } else {
 123        const num = parseInt(part, 10);
 124        if (!isNaN(num)) {
 125          kinds.add(num);
 126        }
 127      }
 128    }
 129  
 130    return Array.from(kinds).sort((a, b) => a - b);
 131  }
 132  
 133  /**
 134   * Format a list of kinds into a compact string with ranges.
 135   * @param {number[]} kinds - Array of kind numbers
 136   * @returns {string} - Formatted string like "1, 3, 5-10, 15"
 137   */
 138  export function formatKindsCompact(kinds) {
 139    if (!kinds || kinds.length === 0) return "";
 140  
 141    const sorted = [...kinds].sort((a, b) => a - b);
 142    const ranges = [];
 143    let rangeStart = sorted[0];
 144    let rangeEnd = sorted[0];
 145  
 146    for (let i = 1; i < sorted.length; i++) {
 147      if (sorted[i] === rangeEnd + 1) {
 148        rangeEnd = sorted[i];
 149      } else {
 150        if (rangeEnd > rangeStart + 1) {
 151          ranges.push(`${rangeStart}-${rangeEnd}`);
 152        } else if (rangeEnd === rangeStart + 1) {
 153          ranges.push(`${rangeStart}, ${rangeEnd}`);
 154        } else {
 155          ranges.push(`${rangeStart}`);
 156        }
 157        rangeStart = sorted[i];
 158        rangeEnd = sorted[i];
 159      }
 160    }
 161  
 162    // Push the last range
 163    if (rangeEnd > rangeStart + 1) {
 164      ranges.push(`${rangeStart}-${rangeEnd}`);
 165    } else if (rangeEnd === rangeStart + 1) {
 166      ranges.push(`${rangeStart}, ${rangeEnd}`);
 167    } else {
 168      ranges.push(`${rangeStart}`);
 169    }
 170  
 171    return ranges.join(", ");
 172  }
 173