@mostfeatured/dbi 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts +33 -1
  2. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
  3. package/dist/src/types/Components/HTMLComponentsV2/index.js +408 -82
  4. package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
  5. package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts +52 -0
  6. package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts.map +1 -1
  7. package/dist/src/types/Components/HTMLComponentsV2/parser.js +275 -0
  8. package/dist/src/types/Components/HTMLComponentsV2/parser.js.map +1 -1
  9. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts +26 -0
  10. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts.map +1 -1
  11. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js +509 -34
  12. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js.map +1 -1
  13. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts +10 -0
  14. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts.map +1 -1
  15. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js +76 -11
  16. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js.map +1 -1
  17. package/dist/test/index.js +76 -3
  18. package/dist/test/index.js.map +1 -1
  19. package/docs/ADVANCED_FEATURES.md +4 -0
  20. package/docs/API_REFERENCE.md +4 -0
  21. package/docs/CHAT_INPUT.md +4 -0
  22. package/docs/COMPONENTS.md +4 -0
  23. package/docs/EVENTS.md +4 -0
  24. package/docs/GETTING_STARTED.md +4 -0
  25. package/docs/LOCALIZATION.md +4 -0
  26. package/docs/README.md +4 -0
  27. package/docs/SVELTE_COMPONENTS.md +162 -6
  28. package/docs/llm/ADVANCED_FEATURES.txt +521 -0
  29. package/docs/llm/API_REFERENCE.txt +659 -0
  30. package/docs/llm/CHAT_INPUT.txt +514 -0
  31. package/docs/llm/COMPONENTS.txt +595 -0
  32. package/docs/llm/EVENTS.txt +449 -0
  33. package/docs/llm/GETTING_STARTED.txt +296 -0
  34. package/docs/llm/LOCALIZATION.txt +501 -0
  35. package/docs/llm/README.txt +193 -0
  36. package/docs/llm/SVELTE_COMPONENTS.txt +566 -0
  37. package/generated/svelte-dbi.d.ts +122 -0
  38. package/package.json +1 -1
  39. package/src/types/Components/HTMLComponentsV2/index.ts +466 -94
  40. package/src/types/Components/HTMLComponentsV2/parser.ts +317 -0
  41. package/src/types/Components/HTMLComponentsV2/svelteParser.ts +567 -35
  42. package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +91 -13
  43. package/test/index.ts +76 -3
  44. package/test/product-showcase.svelte +380 -24
  45. package/llm.txt +0 -1088
@@ -247,6 +247,259 @@ function parseTextInput(dbi: DBI<NamespaceEnums>, dbiName: string, textInputSele
247
247
  }
248
248
  }
249
249
 
250
+ /**
251
+ * Parse a modal element into Discord Modal format
252
+ * Now supports the new Label component structure with various child components:
253
+ * - text-input (type 4)
254
+ * - string-select (type 3)
255
+ * - user-select (type 5)
256
+ * - role-select (type 6)
257
+ * - mentionable-select (type 7)
258
+ * - channel-select (type 8)
259
+ * - file-upload (type 19)
260
+ * - text-display (type 10)
261
+ *
262
+ * Example with Label wrapper (recommended):
263
+ * <components type="modal" id="my-modal" title="My Modal">
264
+ * <label label="Your Name" description="Enter your full name">
265
+ * <text-input id="name" style="Short" />
266
+ * </label>
267
+ * <label label="Select Bug Type">
268
+ * <string-select id="bug-type">
269
+ * <option value="ant">🐜 Ant</option>
270
+ * </string-select>
271
+ * </label>
272
+ * </components>
273
+ *
274
+ * Legacy format (still supported but deprecated):
275
+ * <components type="modal" id="my-modal" title="My Modal">
276
+ * <text-input id="name" label="Name" />
277
+ * </components>
278
+ */
279
+ export function parseModal(dbi: DBI<NamespaceEnums>, dbiName: string, modalElement: Element, { data = {}, ttl = 0 }: any = {}) {
280
+ const title = modalElement.getAttribute("title") || "Modal";
281
+ const modalId = modalElement.getAttribute("id") || modalElement.getAttribute("name");
282
+
283
+ if (!modalId) {
284
+ throw new Error("Modal must have an id or name attribute");
285
+ }
286
+
287
+ // Parse modal children - supports Label, action-row (legacy), and direct text-input (legacy)
288
+ const children = Array.from(modalElement.children);
289
+ const components: any[] = [];
290
+
291
+ for (const element of children) {
292
+ const tagName = element.tagName.toUpperCase();
293
+
294
+ if (tagName === "FIELD") {
295
+ // New Label component (type 18) - wraps other modal components
296
+ components.push(parseModalField(dbi, dbiName, element));
297
+ } else if (tagName === "ACTION-ROW") {
298
+ // Legacy: Action row with text inputs (deprecated but still supported)
299
+ components.push({
300
+ type: ComponentType.ActionRow,
301
+ components: Array.from(element.children).map((child) => {
302
+ return parseModalComponent(dbi, dbiName, child);
303
+ })
304
+ });
305
+ } else if (tagName === "TEXT-INPUT") {
306
+ // Legacy: Direct text-input auto-wrapped in action row
307
+ components.push({
308
+ type: ComponentType.ActionRow,
309
+ components: [parseTextInput(dbi, dbiName, element)]
310
+ });
311
+ } else if (tagName === "TEXT-DISPLAY") {
312
+ // Text display directly in modal (type 10)
313
+ components.push({
314
+ type: 10, // ComponentType.TextDisplay
315
+ content: getCleanTextContent(element)
316
+ });
317
+ } else {
318
+ // Try to parse as a modal-supported component and auto-wrap in Label
319
+ const supportedTags = ['STRING-SELECT', 'USER-SELECT', 'ROLE-SELECT', 'MENTIONABLE-SELECT', 'CHANNEL-SELECT', 'FILE-UPLOAD'];
320
+ if (supportedTags.includes(tagName)) {
321
+ // Auto-wrap in a label for convenience
322
+ components.push({
323
+ type: 18, // Label
324
+ label: element.getAttribute("label") || element.getAttribute("placeholder") || tagName.toLowerCase(),
325
+ description: element.getAttribute("description"),
326
+ component: parseModalComponent(dbi, dbiName, element)
327
+ });
328
+ } else {
329
+ console.warn(`[DBI-Modal] Unsupported element in modal: ${tagName}. Supported: field, text-input, text-display, string-select, user-select, role-select, mentionable-select, channel-select, file-upload`);
330
+ }
331
+ }
332
+ }
333
+
334
+ // Build custom_id for the modal - include ref for state persistence
335
+ const customIdParts: any[] = [modalId];
336
+
337
+ // Add ref if data has one (for state persistence across modal submit)
338
+ // Use ¤ prefix so it gets resolved to actual state object when interaction is received
339
+ if (data?.$ref) {
340
+ customIdParts.push(`¤${data.$ref}`);
341
+ }
342
+
343
+ const customId = buildCustomId(dbi, dbiName, customIdParts, ttl, true);
344
+
345
+ return {
346
+ title,
347
+ customId,
348
+ components,
349
+ modalId, // Store original ID for handler lookup
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Parse a Field/Label component for modals (type 18)
355
+ * <field label="Field Name" description="Optional description">
356
+ * <text-input id="field" />
357
+ * </field>
358
+ */
359
+ function parseModalField(dbi: DBI<NamespaceEnums>, dbiName: string, fieldElement: Element) {
360
+ const label = fieldElement.getAttribute("label") || "Label";
361
+ const description = fieldElement.getAttribute("description");
362
+
363
+ // Get the child component
364
+ const child = fieldElement.children[0];
365
+ if (!child) {
366
+ throw new Error("Field component must have a child component (text-input, string-select, etc.);");
367
+ }
368
+
369
+ return {
370
+ type: 18, // Label component type
371
+ label,
372
+ description,
373
+ component: parseModalComponent(dbi, dbiName, child)
374
+ };
375
+ }
376
+
377
+ /**
378
+ * Parse a component that can be inside a modal Label
379
+ */
380
+ function parseModalComponent(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element): any {
381
+ const tagName = element.tagName.toUpperCase();
382
+
383
+ switch (tagName) {
384
+ case "TEXT-INPUT":
385
+ return parseTextInput(dbi, dbiName, element);
386
+
387
+ case "STRING-SELECT":
388
+ case "STRING-SELECT-MENU":
389
+ return parseStringSelectForModal(dbi, dbiName, element);
390
+
391
+ case "USER-SELECT":
392
+ case "USER-SELECT-MENU":
393
+ return parseAutoSelectForModal(dbi, dbiName, element, 5); // type 5
394
+
395
+ case "ROLE-SELECT":
396
+ case "ROLE-SELECT-MENU":
397
+ return parseAutoSelectForModal(dbi, dbiName, element, 6); // type 6
398
+
399
+ case "MENTIONABLE-SELECT":
400
+ case "MENTIONABLE-SELECT-MENU":
401
+ return parseAutoSelectForModal(dbi, dbiName, element, 7); // type 7
402
+
403
+ case "CHANNEL-SELECT":
404
+ case "CHANNEL-SELECT-MENU":
405
+ return parseAutoSelectForModal(dbi, dbiName, element, 8); // type 8
406
+
407
+ case "FILE-UPLOAD":
408
+ return parseFileUpload(dbi, dbiName, element);
409
+
410
+ default:
411
+ throw new Error(`Unsupported modal component: ${tagName}. Supported: text-input, string-select, user-select, role-select, mentionable-select, channel-select, file-upload`);
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Parse a string select for modal (similar to message but with required field)
417
+ */
418
+ function parseStringSelectForModal(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element) {
419
+ const customId = element.getAttribute("id") || element.getAttribute("custom-id") || element.getAttribute("name");
420
+ if (!customId) {
421
+ throw new Error("String select in modal must have an id, custom-id, or name attribute");
422
+ }
423
+
424
+ const minValues = parseInt(element.getAttribute("min-values") || "1");
425
+ const maxValues = parseInt(element.getAttribute("max-values") || "1");
426
+ const required = element.getAttribute("required") !== "false";
427
+
428
+ const options = Array.from(element.querySelectorAll("option")).map((option) => ({
429
+ label: getCleanTextContent(option) || option.getAttribute("label") || "Option",
430
+ value: option.getAttribute("value") || getCleanTextContent(option),
431
+ description: option.getAttribute("description"),
432
+ emoji: option.getAttribute("emoji") ? { name: option.getAttribute("emoji") } : undefined,
433
+ default: getAttributeBoolean(option, "default")
434
+ }));
435
+
436
+ return {
437
+ type: 3, // String Select
438
+ custom_id: customId,
439
+ placeholder: element.getAttribute("placeholder"),
440
+ min_values: isNaN(minValues) ? 1 : minValues,
441
+ max_values: isNaN(maxValues) ? 1 : maxValues,
442
+ required,
443
+ options
444
+ };
445
+ }
446
+
447
+ /**
448
+ * Parse auto-populated select menus for modal (user, role, mentionable, channel)
449
+ */
450
+ function parseAutoSelectForModal(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element, componentType: number) {
451
+ const customId = element.getAttribute("id") || element.getAttribute("custom-id") || element.getAttribute("name");
452
+ if (!customId) {
453
+ throw new Error(`Select menu in modal must have an id, custom-id, or name attribute`);
454
+ }
455
+
456
+ const minValues = parseInt(element.getAttribute("min-values") || "1");
457
+ const maxValues = parseInt(element.getAttribute("max-values") || "1");
458
+ const required = element.getAttribute("required") !== "false";
459
+
460
+ const result: any = {
461
+ type: componentType,
462
+ custom_id: customId,
463
+ placeholder: element.getAttribute("placeholder"),
464
+ min_values: isNaN(minValues) ? 1 : minValues,
465
+ max_values: isNaN(maxValues) ? 1 : maxValues,
466
+ required
467
+ };
468
+
469
+ // Channel select can have channel_types filter
470
+ if (componentType === 8) {
471
+ const channelTypes = element.getAttribute("channel-types");
472
+ if (channelTypes) {
473
+ result.channel_types = channelTypes.split(",").map(t => parseInt(t.trim()));
474
+ }
475
+ }
476
+
477
+ return result;
478
+ }
479
+
480
+ /**
481
+ * Parse file upload component for modals (type 19)
482
+ * <file-upload id="attachment" min-values="1" max-values="5" />
483
+ */
484
+ function parseFileUpload(dbi: DBI<NamespaceEnums>, dbiName: string, element: Element) {
485
+ const customId = element.getAttribute("id") || element.getAttribute("custom-id") || element.getAttribute("name");
486
+ if (!customId) {
487
+ throw new Error("File upload must have an id, custom-id, or name attribute");
488
+ }
489
+
490
+ const minValues = parseInt(element.getAttribute("min-values") || "1");
491
+ const maxValues = parseInt(element.getAttribute("max-values") || "1");
492
+ const required = element.getAttribute("required") !== "false";
493
+
494
+ return {
495
+ type: 19, // File Upload
496
+ custom_id: customId,
497
+ min_values: isNaN(minValues) ? 1 : minValues,
498
+ max_values: isNaN(maxValues) ? 1 : maxValues,
499
+ required
500
+ };
501
+ }
502
+
250
503
  function parseColor(color: string) {
251
504
  if (!color) return;
252
505
  if (/\d{3,6}/.test(color)) return parseInt(color, 10);
@@ -329,4 +582,68 @@ export function parseHTMLComponentsV2(dbi: DBI<NamespaceEnums>, template: string
329
582
  return children.map((element) => {
330
583
  return parseElement(dbi, dbiName, element);
331
584
  });
585
+ }
586
+
587
+ export interface ParsedComponentsResult {
588
+ /** Main components to display (id="main" or no id/type) */
589
+ components: any[];
590
+ /** Modal definitions keyed by their id */
591
+ modals: Map<string, { title: string; customId: string; components: any[]; modalId: string }>;
592
+ }
593
+
594
+ /**
595
+ * Parse HTML with support for multiple <components> elements
596
+ * - <components> or <components id="main"> - Main display components
597
+ * - <components type="modal" id="xxx"> - Modal definitions
598
+ */
599
+ export function parseHTMLComponentsV2Multi(dbi: DBI<NamespaceEnums>, template: string, dbiName: string, { data = {}, ttl = 0 }: any = { data: {}, ttl: 0 }): ParsedComponentsResult {
600
+ const { window: { document } } = new JSDOM(
601
+ eta.renderString(
602
+ template,
603
+ {
604
+ it: data,
605
+ $refId(obj: any) {
606
+ if (obj?.$ref) return `¤${obj.$ref}`;
607
+ let id = stuffs.randomString(8);
608
+ Object.assign(obj, {
609
+ $ref: id,
610
+ $unRef() { return dbi.data.refs.delete(id); },
611
+ });
612
+ dbi.data.refs.set(id, { at: Date.now(), value: obj, ttl });
613
+ return `¤${id}`;
614
+ }
615
+ }
616
+ )
617
+ );
618
+
619
+ // Only select top-level <components> elements (direct children of body)
620
+ // This avoids selecting nested <components> inside <container>, <section>, etc.
621
+ const allComponents = [...document.body.children].filter(el => el.tagName === "COMPONENTS");
622
+ const modals = new Map<string, { title: string; customId: string; components: any[]; modalId: string }>();
623
+ let mainComponents: any[] = [];
624
+
625
+ for (const componentsEl of allComponents) {
626
+ const type = componentsEl.getAttribute("type");
627
+ const id = componentsEl.getAttribute("id");
628
+
629
+ if (type === "modal") {
630
+ // This is a modal definition
631
+ const modalData = parseModal(dbi, dbiName, componentsEl, { data, ttl });
632
+ modals.set(modalData.modalId, modalData);
633
+ } else if (!id || id === "main") {
634
+ // This is the main components (no id, or id="main")
635
+ const children = Array.from(componentsEl.children);
636
+ mainComponents = children.map((element) => parseElement(dbi, dbiName, element));
637
+ }
638
+ // Components with other ids are ignored (could be used for other purposes)
639
+ }
640
+
641
+ if (!mainComponents.length && modals.size === 0) {
642
+ throw new Error("No components found in the provided HTML template.");
643
+ }
644
+
645
+ return {
646
+ components: mainComponents,
647
+ modals
648
+ };
332
649
  }