@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.
- package/dist/src/types/Components/HTMLComponentsV2/index.d.ts +33 -1
- package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/index.js +408 -82
- package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts +52 -0
- package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/parser.js +275 -0
- package/dist/src/types/Components/HTMLComponentsV2/parser.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts +26 -0
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js +509 -34
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts +10 -0
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js +76 -11
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js.map +1 -1
- package/dist/test/index.js +76 -3
- package/dist/test/index.js.map +1 -1
- package/docs/ADVANCED_FEATURES.md +4 -0
- package/docs/API_REFERENCE.md +4 -0
- package/docs/CHAT_INPUT.md +4 -0
- package/docs/COMPONENTS.md +4 -0
- package/docs/EVENTS.md +4 -0
- package/docs/GETTING_STARTED.md +4 -0
- package/docs/LOCALIZATION.md +4 -0
- package/docs/README.md +4 -0
- package/docs/SVELTE_COMPONENTS.md +162 -6
- package/docs/llm/ADVANCED_FEATURES.txt +521 -0
- package/docs/llm/API_REFERENCE.txt +659 -0
- package/docs/llm/CHAT_INPUT.txt +514 -0
- package/docs/llm/COMPONENTS.txt +595 -0
- package/docs/llm/EVENTS.txt +449 -0
- package/docs/llm/GETTING_STARTED.txt +296 -0
- package/docs/llm/LOCALIZATION.txt +501 -0
- package/docs/llm/README.txt +193 -0
- package/docs/llm/SVELTE_COMPONENTS.txt +566 -0
- package/generated/svelte-dbi.d.ts +122 -0
- package/package.json +1 -1
- package/src/types/Components/HTMLComponentsV2/index.ts +466 -94
- package/src/types/Components/HTMLComponentsV2/parser.ts +317 -0
- package/src/types/Components/HTMLComponentsV2/svelteParser.ts +567 -35
- package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +91 -13
- package/test/index.ts +76 -3
- package/test/product-showcase.svelte +380 -24
- 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
|
}
|