@kevlid/discordmenus 0.1.5 → 0.1.7
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/README.md +7 -0
- package/index.d.ts +1 -1
- package/package.json +2 -9
- package/src/handle/component.js +9 -0
- package/src/render/selectMenu.js +49 -15
- package/src/utils/formatKey.js +4 -16
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @kevlid/discordmenus
|
|
2
2
|
|
|
3
|
+
> ⚠️ Usage is __**NOT**__ recommended
|
|
4
|
+
|
|
3
5
|
```js
|
|
4
6
|
const fs = require("fs");
|
|
5
7
|
const path = require("path");
|
|
@@ -58,3 +60,8 @@ client.on("interactionCreate", async (interaction) => {
|
|
|
58
60
|
|
|
59
61
|
client.login(process.env.BOT_TOKEN);
|
|
60
62
|
```
|
|
63
|
+
|
|
64
|
+
### Role / user / channel selects
|
|
65
|
+
|
|
66
|
+
- Persist IDs as **strings** (Discord snowflakes are too large for JS numbers; Mongo/JSON numbers will corrupt them and `default_values` won’t show on rerender).
|
|
67
|
+
- Select `custom_id`s include **category + page** so paging doesn’t reuse the same component identity in the same slot across pages.
|
package/index.d.ts
CHANGED
|
@@ -265,5 +265,5 @@ export declare function fromDiscordJS(interaction: DiscordInteraction): DiscordI
|
|
|
265
265
|
|
|
266
266
|
/** Encode option/menu keys for custom IDs (uppercase → _lowercase, spaces → _). */
|
|
267
267
|
export declare function formatKey(key: string): string;
|
|
268
|
-
/**
|
|
268
|
+
/** No-op: casing is not reconstructed anywhere. */
|
|
269
269
|
export declare function decodeKey(encoded: string): string;
|
package/package.json
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevlid/discordmenus",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "some menu stuff for discord stuff",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"author": "kevlid",
|
|
8
8
|
"license": "ISC",
|
|
9
|
-
"keywords": [
|
|
10
|
-
"discord",
|
|
11
|
-
"discord.js",
|
|
12
|
-
"menu",
|
|
13
|
-
"typescript",
|
|
14
|
-
"builder"
|
|
15
|
-
],
|
|
16
9
|
"files": [
|
|
17
10
|
"src",
|
|
18
11
|
"index.d.ts",
|
package/src/handle/component.js
CHANGED
|
@@ -398,6 +398,15 @@ async function handleComponent(interaction, menu, menu_key, renderMenu) {
|
|
|
398
398
|
const maxSel = Number(opt?.maxValues);
|
|
399
399
|
const multi = Number.isFinite(maxSel) && maxSel > 1;
|
|
400
400
|
let saveValue = multi ? [...selected] : selected[0] ?? null;
|
|
401
|
+
const snowflakeSelectTypes = new Set([
|
|
402
|
+
OptionTypes.UserSelect,
|
|
403
|
+
OptionTypes.RoleSelect,
|
|
404
|
+
OptionTypes.ChannelSelect,
|
|
405
|
+
OptionTypes.MentionableSelect,
|
|
406
|
+
]);
|
|
407
|
+
if (opt && snowflakeSelectTypes.has(opt.type)) {
|
|
408
|
+
saveValue = multi ? selected.map(v => String(v)) : selected[0] != null ? String(selected[0]) : null;
|
|
409
|
+
}
|
|
401
410
|
if (
|
|
402
411
|
!multi &&
|
|
403
412
|
saveValue == null &&
|
package/src/render/selectMenu.js
CHANGED
|
@@ -2,20 +2,48 @@ const { OptionTypes } = require("../utils/types");
|
|
|
2
2
|
const { renderTextDisplay, renderActionRow, renderSeparator } = require("./utils");
|
|
3
3
|
const { encodeId } = require("../utils/customIds");
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Discord snowflakes as JS numbers lose precision; DBs should store string IDs.
|
|
7
|
+
* Coerce to string and keep plausible snowflake digit strings for default_values.
|
|
8
|
+
*/
|
|
9
|
+
function collectEntityIds(value, maxCount) {
|
|
10
|
+
const max = Math.max(0, Math.min(25, Number(maxCount) || 25));
|
|
11
|
+
const out = [];
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const push = v => {
|
|
14
|
+
if (out.length >= max) return;
|
|
15
|
+
if (v == null) return;
|
|
16
|
+
let s;
|
|
17
|
+
if (typeof v === "bigint") {
|
|
18
|
+
s = v.toString();
|
|
19
|
+
} else if (typeof v === "number" && Number.isFinite(v)) {
|
|
20
|
+
s = String(Math.trunc(v));
|
|
21
|
+
} else if (typeof v === "string") {
|
|
22
|
+
s = v.trim();
|
|
23
|
+
} else if (typeof v === "object" && v !== null && v.id != null) {
|
|
24
|
+
s = String(v.id).trim();
|
|
25
|
+
} else {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!/^\d{17,22}$/.test(s)) return;
|
|
29
|
+
if (seen.has(s)) return;
|
|
30
|
+
seen.add(s);
|
|
31
|
+
out.push(s);
|
|
32
|
+
};
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
for (const item of value) {
|
|
35
|
+
push(item);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
push(value);
|
|
11
39
|
}
|
|
12
|
-
return
|
|
40
|
+
return out;
|
|
13
41
|
}
|
|
14
42
|
|
|
15
43
|
function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category, page) {
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
const ctx = { u: userId };
|
|
44
|
+
// Include cat/page so each physical select is unique when paging; avoids client mix-ups
|
|
45
|
+
// when the same row position shows a different option on another page.
|
|
46
|
+
const ctx = { u: userId, cat: category, page };
|
|
19
47
|
const customId = encodeId(menuKey, [opt.key, opt.type], ctx);
|
|
20
48
|
|
|
21
49
|
const component = {
|
|
@@ -29,8 +57,14 @@ function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category,
|
|
|
29
57
|
component.placeholder = opt.placeholder;
|
|
30
58
|
}
|
|
31
59
|
|
|
60
|
+
const maxDefaults = Math.min(25, Number(opt.maxValues) || 1);
|
|
61
|
+
|
|
32
62
|
if (opt.type === OptionTypes.StringSelect) {
|
|
33
|
-
const selectedValues =
|
|
63
|
+
const selectedValues = Array.isArray(currentValue)
|
|
64
|
+
? currentValue.map(String)
|
|
65
|
+
: currentValue != null
|
|
66
|
+
? [String(currentValue)]
|
|
67
|
+
: [];
|
|
34
68
|
component.options = opt.choices.map(c => ({
|
|
35
69
|
label: c.label,
|
|
36
70
|
value: c.value,
|
|
@@ -40,14 +74,14 @@ function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category,
|
|
|
40
74
|
}
|
|
41
75
|
|
|
42
76
|
if (opt.type === OptionTypes.UserSelect) {
|
|
43
|
-
const ids =
|
|
77
|
+
const ids = collectEntityIds(currentValue, maxDefaults);
|
|
44
78
|
if (ids.length > 0) {
|
|
45
79
|
component.default_values = ids.map(id => ({ id, type: "user" }));
|
|
46
80
|
}
|
|
47
81
|
}
|
|
48
82
|
|
|
49
83
|
if (opt.type === OptionTypes.RoleSelect) {
|
|
50
|
-
const ids =
|
|
84
|
+
const ids = collectEntityIds(currentValue, maxDefaults);
|
|
51
85
|
if (ids.length > 0) {
|
|
52
86
|
component.default_values = ids.map(id => ({ id, type: "role" }));
|
|
53
87
|
}
|
|
@@ -57,7 +91,7 @@ function renderSelectMenuComponent(menuKey, opt, currentValue, userId, category,
|
|
|
57
91
|
if (opt.channelTypes && opt.channelTypes.length > 0) {
|
|
58
92
|
component.channel_types = opt.channelTypes;
|
|
59
93
|
}
|
|
60
|
-
const ids =
|
|
94
|
+
const ids = collectEntityIds(currentValue, maxDefaults);
|
|
61
95
|
if (ids.length > 0) {
|
|
62
96
|
component.default_values = ids.map(id => ({ id, type: "channel" }));
|
|
63
97
|
}
|
|
@@ -80,4 +114,4 @@ function renderSelectMenuOption(menuKey, opt, currentValue, userId, category, pa
|
|
|
80
114
|
return [textDisplay, actionRow, renderSeparator()];
|
|
81
115
|
}
|
|
82
116
|
|
|
83
|
-
module.exports = { renderSelectMenuOption };
|
|
117
|
+
module.exports = { renderSelectMenuOption, collectEntityIds };
|
package/src/utils/formatKey.js
CHANGED
|
@@ -19,25 +19,13 @@ function formatKey(key) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* NOTE: decodeKey is intentionally a no-op.
|
|
23
|
+
* Keys are encoded with `formatKey()` for stability in custom IDs, and we do not attempt to
|
|
24
|
+
* reconstruct original casing anywhere (including get/save contexts).
|
|
24
25
|
*/
|
|
25
26
|
function decodeKey(encoded) {
|
|
26
27
|
if (typeof encoded !== "string") throw new TypeError("Key must be a string");
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < encoded.length; i++) {
|
|
29
|
-
const c = encoded[i];
|
|
30
|
-
if (c === "_" && i + 1 < encoded.length) {
|
|
31
|
-
const next = encoded[i + 1];
|
|
32
|
-
if (next >= "a" && next <= "z") {
|
|
33
|
-
out += next.toUpperCase();
|
|
34
|
-
i++;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
out += c;
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
28
|
+
return String(encoded);
|
|
41
29
|
}
|
|
42
30
|
|
|
43
31
|
module.exports = {
|