cordo 2.0.0 → 2.0.1
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/package.json +2 -7
- package/src/README.md +3 -0
- package/src/components/builtin/button.ts +64 -0
- package/src/components/builtin/collection.ts +6 -0
- package/src/components/builtin/container.ts +31 -0
- package/src/components/builtin/divider.ts +25 -0
- package/src/components/builtin/gallery.ts +13 -0
- package/src/components/builtin/image.ts +31 -0
- package/src/components/builtin/link-button.ts +42 -0
- package/src/components/builtin/row.ts +19 -0
- package/src/components/builtin/section.ts +24 -0
- package/src/components/builtin/select-string.ts +92 -0
- package/src/components/builtin/spacer.ts +25 -0
- package/src/components/builtin/text.ts +94 -0
- package/src/components/component.ts +170 -0
- package/src/components/modifier.ts +23 -0
- package/src/components/mods/debug-id-to-label.ts +28 -0
- package/src/components/mods/debug-print.ts +14 -0
- package/src/components/mods/debug-route.ts +17 -0
- package/src/components/mods/disable-all-components.ts +38 -0
- package/src/core/dynamic-types.ts +5 -0
- package/src/core/files/config.ts +161 -0
- package/src/core/files/error-boundary.ts +41 -0
- package/src/core/files/lockfile.ts +150 -0
- package/src/core/files/route.ts +175 -0
- package/src/core/gateway.ts +165 -0
- package/src/core/hooks.ts +41 -0
- package/src/core/interaction.ts +37 -0
- package/src/core/magic.ts +65 -0
- package/src/core/routing/filesystem.ts +115 -0
- package/src/core/routing/resolve.ts +145 -0
- package/src/core/routing/respond.ts +208 -0
- package/src/errors/builtin/route-assumption-failed.ts +20 -0
- package/src/errors/builtin/route-not-found.ts +13 -0
- package/src/errors/cordo-error.ts +20 -0
- package/src/errors/handle.ts +74 -0
- package/src/functions/compiler.ts +206 -0
- package/src/functions/funct.ts +56 -0
- package/src/functions/impl/goto.ts +82 -0
- package/src/functions/impl/run.ts +104 -0
- package/src/functions/impl/value.ts +15 -0
- package/src/http/express.ts +28 -0
- package/src/lib/emoji.ts +28 -0
- package/src/lib/ids.test.ts +47 -0
- package/src/lib/ids.ts +37 -0
- package/src/lib/utils.ts +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cordo",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "A framework for handling complex discord api interactions",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
@@ -11,12 +11,7 @@
|
|
|
11
11
|
"./http": "./src/http/index.ts"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"src
|
|
15
|
-
"src/core/index.ts",
|
|
16
|
-
"src/components/index.ts",
|
|
17
|
-
"src/errors/index.ts",
|
|
18
|
-
"src/functions/index.ts",
|
|
19
|
-
"src/http/index.ts"
|
|
14
|
+
"src/*"
|
|
20
15
|
],
|
|
21
16
|
"scripts": {
|
|
22
17
|
"build": "tsc",
|
package/src/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ButtonStyle, type APIEmoji } from "discord-api-types/v10"
|
|
2
|
+
import { ComponentType, createComponent } from "../component"
|
|
3
|
+
import { LibEmoji } from "../../lib/emoji"
|
|
4
|
+
import { Hooks } from "../../core/hooks"
|
|
5
|
+
import type { CordoFunct, CordoFunctRun } from "../../functions"
|
|
6
|
+
import { FunctCompiler } from "../../functions/compiler"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export function button() {
|
|
10
|
+
let labelVal: string | undefined = undefined
|
|
11
|
+
let emojiVal: APIEmoji | undefined = undefined
|
|
12
|
+
let disabledVal: boolean | undefined = undefined
|
|
13
|
+
let styleVal = ButtonStyle.Secondary
|
|
14
|
+
const functVal: CordoFunct[] = []
|
|
15
|
+
|
|
16
|
+
function getLabel() {
|
|
17
|
+
if (!labelVal)
|
|
18
|
+
return emojiVal ? '' : 'Click'
|
|
19
|
+
|
|
20
|
+
return Hooks.callHook(
|
|
21
|
+
'transformUserFacingText',
|
|
22
|
+
labelVal,
|
|
23
|
+
{ component: 'Button', position: 'label' }
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const out = {
|
|
28
|
+
...createComponent('Button', () => ({
|
|
29
|
+
type: ComponentType.Button,
|
|
30
|
+
label: getLabel(),
|
|
31
|
+
emoji: emojiVal,
|
|
32
|
+
style: styleVal,
|
|
33
|
+
disabled: disabledVal,
|
|
34
|
+
custom_id: FunctCompiler.toCustomId(disabledVal ? [] : functVal)
|
|
35
|
+
})),
|
|
36
|
+
|
|
37
|
+
label: (text: string) => {
|
|
38
|
+
labelVal = text
|
|
39
|
+
return out
|
|
40
|
+
},
|
|
41
|
+
emoji: (emoji: LibEmoji.Input) => {
|
|
42
|
+
emojiVal = LibEmoji.read(emoji)
|
|
43
|
+
return out
|
|
44
|
+
},
|
|
45
|
+
style: (style: 'primary' | 'secondary' | 'success' | 'danger') => {
|
|
46
|
+
if (style === 'primary') styleVal = ButtonStyle.Primary
|
|
47
|
+
else if (style === 'secondary') styleVal = ButtonStyle.Secondary
|
|
48
|
+
else if (style === 'success') styleVal = ButtonStyle.Success
|
|
49
|
+
else if (style === 'danger') styleVal = ButtonStyle.Danger
|
|
50
|
+
return out
|
|
51
|
+
},
|
|
52
|
+
disabled(disabled = true, opts?: { greyOut?: boolean }) {
|
|
53
|
+
disabledVal = disabled
|
|
54
|
+
if (opts?.greyOut) styleVal = ButtonStyle.Secondary
|
|
55
|
+
return out
|
|
56
|
+
},
|
|
57
|
+
onClick: (...funct: CordoFunctRun) => {
|
|
58
|
+
functVal.push(...funct)
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ComponentType, createComponent, renderComponentList, type CordoComponent } from "../component"
|
|
2
|
+
import type { CordoModifier } from "../modifier"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
type AllowedComponents = CordoComponent<'ActionRow' | 'Button' | 'TextDisplay' | 'Section' | 'MediaGallery' | 'Seperator' | 'File' | 'StringSelect'> | CordoModifier
|
|
6
|
+
type AllowedComponentArray = Array<AllowedComponents | AllowedComponents[]>
|
|
7
|
+
|
|
8
|
+
export function container(...components: AllowedComponentArray) {
|
|
9
|
+
let accentColor: string | number | undefined = undefined
|
|
10
|
+
let spoiler: boolean | undefined = undefined
|
|
11
|
+
|
|
12
|
+
const out = {
|
|
13
|
+
...createComponent('Container', ({ hirarchy, attributes }) => ({
|
|
14
|
+
type: ComponentType.Container,
|
|
15
|
+
components: renderComponentList(components.flat(), 'Container', hirarchy, attributes),
|
|
16
|
+
accent_color: typeof accentColor === 'string' ? parseInt(accentColor.slice(1), 16) : accentColor,
|
|
17
|
+
spoiler
|
|
18
|
+
})),
|
|
19
|
+
|
|
20
|
+
accentColor: (color: string | number) => {
|
|
21
|
+
accentColor = color
|
|
22
|
+
return out
|
|
23
|
+
},
|
|
24
|
+
spoiler: (value: boolean = true) => {
|
|
25
|
+
spoiler = value
|
|
26
|
+
return out
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ComponentType, createComponent } from "../component"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function divider() {
|
|
5
|
+
let spacing: number | undefined = undefined
|
|
6
|
+
|
|
7
|
+
const out = {
|
|
8
|
+
...createComponent('Seperator', () => ({
|
|
9
|
+
type: ComponentType.Seperator,
|
|
10
|
+
spacing,
|
|
11
|
+
divider: true
|
|
12
|
+
})),
|
|
13
|
+
|
|
14
|
+
size: (size: 'small' | 'large') => {
|
|
15
|
+
spacing = (size === 'small')
|
|
16
|
+
? 1
|
|
17
|
+
: (size === 'large')
|
|
18
|
+
? 2
|
|
19
|
+
: undefined
|
|
20
|
+
return out
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return out
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ComponentType, createComponent, renderComponentList, type CordoComponent } from "../component"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function gallery(...items: CordoComponent<'Thumbnail'>[]) {
|
|
5
|
+
const out = {
|
|
6
|
+
...createComponent('MediaGallery', ({ }) => ({
|
|
7
|
+
type: ComponentType.MediaGallery,
|
|
8
|
+
items: renderComponentList(items, 'MediaGallery')
|
|
9
|
+
})),
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return out
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ComponentType, createComponent } from "../component"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function image(url: string) {
|
|
5
|
+
let description: string | undefined = undefined
|
|
6
|
+
let spoiler: boolean | undefined = undefined
|
|
7
|
+
|
|
8
|
+
const out = {
|
|
9
|
+
...createComponent('Thumbnail', ({ hirarchy }) => ({
|
|
10
|
+
type: hirarchy[0] === 'MediaGallery' // if placed inside a media gallery we act as a media gallery item instead
|
|
11
|
+
? undefined
|
|
12
|
+
: ComponentType.Thumbnail,
|
|
13
|
+
media: {
|
|
14
|
+
url
|
|
15
|
+
},
|
|
16
|
+
spoiler,
|
|
17
|
+
description
|
|
18
|
+
})),
|
|
19
|
+
|
|
20
|
+
description: (value: string) => {
|
|
21
|
+
description = value
|
|
22
|
+
return out
|
|
23
|
+
},
|
|
24
|
+
spoiler: (value: boolean = true) => {
|
|
25
|
+
spoiler = value
|
|
26
|
+
return out
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ButtonStyle } from "discord-api-types/v10"
|
|
2
|
+
import { ComponentType, createComponent } from "../component"
|
|
3
|
+
import { Hooks } from "../../core/hooks"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function linkButton(url: string) {
|
|
7
|
+
let labelVal: string | undefined = undefined
|
|
8
|
+
let emojiVal: string | undefined = undefined
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function getLabel() {
|
|
12
|
+
if (!labelVal)
|
|
13
|
+
return emojiVal ? '' : new URL(url).hostname
|
|
14
|
+
|
|
15
|
+
return Hooks.callHook(
|
|
16
|
+
'transformUserFacingText',
|
|
17
|
+
labelVal,
|
|
18
|
+
{ component: 'Button', position: 'label' }
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const out = {
|
|
23
|
+
...createComponent('Button', () => ({
|
|
24
|
+
type: ComponentType.Button,
|
|
25
|
+
label: getLabel(),
|
|
26
|
+
emoji: emojiVal,
|
|
27
|
+
style: ButtonStyle.Link,
|
|
28
|
+
url
|
|
29
|
+
})),
|
|
30
|
+
|
|
31
|
+
label: (text: string) => {
|
|
32
|
+
labelVal = text
|
|
33
|
+
return out
|
|
34
|
+
},
|
|
35
|
+
emoji: (emoji: string) => {
|
|
36
|
+
emojiVal = emoji
|
|
37
|
+
return out
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ComponentType, createComponent, renderComponentList, type CordoComponent } from "../component"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
type AllowedComponents = CordoComponent<'Button' | 'StringSelect'>
|
|
5
|
+
type AllowedComponentArray
|
|
6
|
+
= [ AllowedComponents ]
|
|
7
|
+
| [ AllowedComponents, AllowedComponents ]
|
|
8
|
+
| [ AllowedComponents, AllowedComponents, AllowedComponents ]
|
|
9
|
+
| [ AllowedComponents, AllowedComponents, AllowedComponents, AllowedComponents ]
|
|
10
|
+
| [ AllowedComponents, AllowedComponents, AllowedComponents, AllowedComponents, AllowedComponents ]
|
|
11
|
+
|
|
12
|
+
export function row(...components: AllowedComponentArray | [ AllowedComponentArray ]) {
|
|
13
|
+
if (Array.isArray(components[0]))
|
|
14
|
+
components = components[0]
|
|
15
|
+
return createComponent('ActionRow', ({ hirarchy, attributes }) => ({
|
|
16
|
+
type: ComponentType.ActionRow,
|
|
17
|
+
components: renderComponentList(components as AllowedComponentArray, 'ActionRow', hirarchy, attributes)
|
|
18
|
+
}))
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ComponentType, createComponent, renderComponent, renderComponentList, type CordoComponent } from "../component"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
type AllowedComponentsContent = CordoComponent<'TextDisplay'>
|
|
5
|
+
type AllowedComponentsAccessory = CordoComponent<'Thumbnail' | 'Button'>
|
|
6
|
+
|
|
7
|
+
export function section(...components: AllowedComponentsContent[]) {
|
|
8
|
+
let accessory: AllowedComponentsAccessory | undefined = undefined
|
|
9
|
+
|
|
10
|
+
const out = {
|
|
11
|
+
...createComponent('Section', ({ hirarchy, attributes }) => ({
|
|
12
|
+
type: ComponentType.Section,
|
|
13
|
+
components: renderComponentList(components, 'Section', hirarchy, attributes),
|
|
14
|
+
accessory: accessory ? renderComponent(accessory, 'Section', hirarchy, attributes) : undefined
|
|
15
|
+
})),
|
|
16
|
+
|
|
17
|
+
decorate(component: AllowedComponentsAccessory) {
|
|
18
|
+
accessory = component
|
|
19
|
+
return out
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return out
|
|
24
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type APISelectMenuOption } from "discord-api-types/v10"
|
|
2
|
+
import { ComponentType, createComponent } from "../component"
|
|
3
|
+
import { Hooks } from "../../core/hooks"
|
|
4
|
+
import { value, type CordoFunct, type CordoFunctRun } from "../../functions"
|
|
5
|
+
import { FunctCompiler } from "../../functions/compiler"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
type SelectMenuOption<Value extends string = string> = Omit<APISelectMenuOption, 'value'> & ({
|
|
9
|
+
value?: Value
|
|
10
|
+
onClick?: CordoFunct | CordoFunctRun
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export function selectString<Values extends string = string>() {
|
|
14
|
+
let placeholderVal: string | undefined = undefined
|
|
15
|
+
let minValues: number | undefined = undefined
|
|
16
|
+
let maxValues: number | undefined = undefined
|
|
17
|
+
let optionsVal: SelectMenuOption[] = []
|
|
18
|
+
let disabledVal: boolean | undefined = undefined
|
|
19
|
+
const functVal: CordoFunct[] = []
|
|
20
|
+
|
|
21
|
+
function getPlaceholder() {
|
|
22
|
+
if (!placeholderVal)
|
|
23
|
+
return undefined
|
|
24
|
+
return Hooks.callHook(
|
|
25
|
+
'transformUserFacingText',
|
|
26
|
+
placeholderVal,
|
|
27
|
+
{ component: 'StringSelect', position: 'placeholder' }
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getOptions(): SelectMenuOption[] {
|
|
32
|
+
return optionsVal.slice(0, 25).map(o => ({
|
|
33
|
+
...o,
|
|
34
|
+
label: Hooks.callHook('transformUserFacingText', o.label, { component: 'StringSelect', position: 'option.label' }),
|
|
35
|
+
description: o.description
|
|
36
|
+
? Hooks.callHook('transformUserFacingText', o.description, { component: 'StringSelect', position: 'option.description' })
|
|
37
|
+
: undefined,
|
|
38
|
+
value: FunctCompiler.toCustomId([
|
|
39
|
+
...(o.onClick
|
|
40
|
+
? Array.isArray(o.onClick)
|
|
41
|
+
? o.onClick
|
|
42
|
+
: [ o.onClick ]
|
|
43
|
+
: []
|
|
44
|
+
),
|
|
45
|
+
o.value ? value(o.value) : null
|
|
46
|
+
])
|
|
47
|
+
}))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const out = {
|
|
51
|
+
...createComponent('StringSelect', () => ({
|
|
52
|
+
type: ComponentType.StringSelect,
|
|
53
|
+
placeholder: getPlaceholder(),
|
|
54
|
+
min_values: minValues,
|
|
55
|
+
max_values: maxValues ? Math.min(optionsVal.length, maxValues) : undefined,
|
|
56
|
+
disabled: disabledVal,
|
|
57
|
+
options: getOptions(),
|
|
58
|
+
custom_id: FunctCompiler.toCustomId(disabledVal ? [] : functVal)
|
|
59
|
+
})),
|
|
60
|
+
|
|
61
|
+
placeholder: (text: string) => {
|
|
62
|
+
placeholderVal = text
|
|
63
|
+
return out
|
|
64
|
+
},
|
|
65
|
+
min: (num: number = 1) => {
|
|
66
|
+
minValues = num
|
|
67
|
+
return out
|
|
68
|
+
},
|
|
69
|
+
max: (num: number = 25) => {
|
|
70
|
+
maxValues = num
|
|
71
|
+
return out
|
|
72
|
+
},
|
|
73
|
+
disabled(disabled = true) {
|
|
74
|
+
disabledVal = disabled
|
|
75
|
+
return out
|
|
76
|
+
},
|
|
77
|
+
onSubmit: (...funct: CordoFunctRun) => {
|
|
78
|
+
functVal.push(...funct)
|
|
79
|
+
return out
|
|
80
|
+
},
|
|
81
|
+
setOptions(options: Array<SelectMenuOption<Values>>) {
|
|
82
|
+
optionsVal = options
|
|
83
|
+
return out
|
|
84
|
+
},
|
|
85
|
+
addOption(o: SelectMenuOption<Values>) {
|
|
86
|
+
optionsVal.push(o)
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return out
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ComponentType, createComponent } from "../component"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function spacer() {
|
|
5
|
+
let spacing: number | undefined = undefined
|
|
6
|
+
|
|
7
|
+
const out = {
|
|
8
|
+
...createComponent('Seperator', () => ({
|
|
9
|
+
type: ComponentType.Seperator,
|
|
10
|
+
spacing,
|
|
11
|
+
divider: false
|
|
12
|
+
})),
|
|
13
|
+
|
|
14
|
+
size: (size: 'small' | 'large') => {
|
|
15
|
+
spacing = (size === 'small')
|
|
16
|
+
? 1
|
|
17
|
+
: (size === 'large')
|
|
18
|
+
? 2
|
|
19
|
+
: undefined
|
|
20
|
+
return out
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return out
|
|
25
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Hooks } from "../../core/hooks"
|
|
2
|
+
import { ComponentType, createComponent } from "../component"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function text(...content: Array<string | { toString: () => string }>) {
|
|
6
|
+
let outerPrefix: string = ''
|
|
7
|
+
let innerPrefix: string = ''
|
|
8
|
+
let innerSuffix: string = ''
|
|
9
|
+
let linkUrl: string | null = null
|
|
10
|
+
|
|
11
|
+
function toString(attributes: Record<string, any> = {}): string {
|
|
12
|
+
const stringContent = content
|
|
13
|
+
.map(c => typeof c === 'string' ? c : c.toString())
|
|
14
|
+
.map(c => Hooks.callHook('transformUserFacingText', c, { ...attributes, component: 'TextDisplay', position: null }))
|
|
15
|
+
const innerContent = (innerPrefix ?? '') + stringContent.join(' ').replace(/^ +| +$/mg, '') + (innerSuffix ?? '')
|
|
16
|
+
const outerContent = linkUrl
|
|
17
|
+
? `[${innerContent}](${linkUrl})`
|
|
18
|
+
: innerContent
|
|
19
|
+
return outerPrefix + outerContent as string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const out = {
|
|
23
|
+
...createComponent('TextDisplay', ({ attributes }) => ({
|
|
24
|
+
type: ComponentType.TextDisplay,
|
|
25
|
+
content: toString(attributes)
|
|
26
|
+
})),
|
|
27
|
+
|
|
28
|
+
toString,
|
|
29
|
+
|
|
30
|
+
size: (size: 'h1' | 'h2' | 'h3' | 'small' | 'default') => {
|
|
31
|
+
if (size === 'h1') outerPrefix = '# '
|
|
32
|
+
else if (size === 'h2') outerPrefix = '## '
|
|
33
|
+
else if (size === 'h3') outerPrefix = '### '
|
|
34
|
+
else if (size === 'small') outerPrefix = '-# '
|
|
35
|
+
else outerPrefix = ''
|
|
36
|
+
return out
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
link: (url: string) => {
|
|
40
|
+
linkUrl = url
|
|
41
|
+
return out
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
bold: (val = true) => {
|
|
45
|
+
if (val) {
|
|
46
|
+
innerPrefix = innerPrefix + '**'
|
|
47
|
+
innerSuffix = '**' + innerSuffix
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
},
|
|
51
|
+
italic: (val = true) => {
|
|
52
|
+
if (val) {
|
|
53
|
+
innerPrefix = innerPrefix + '*'
|
|
54
|
+
innerSuffix = '*' + innerSuffix
|
|
55
|
+
}
|
|
56
|
+
return out
|
|
57
|
+
},
|
|
58
|
+
underline: (val = true) => {
|
|
59
|
+
if (val) {
|
|
60
|
+
innerPrefix = innerPrefix + '__'
|
|
61
|
+
innerSuffix = '__' + innerSuffix
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
},
|
|
65
|
+
strike: (val = true) => {
|
|
66
|
+
if (val) {
|
|
67
|
+
innerPrefix = innerPrefix + '~~'
|
|
68
|
+
innerSuffix = '~~' + innerSuffix
|
|
69
|
+
}
|
|
70
|
+
return out
|
|
71
|
+
},
|
|
72
|
+
quote: (val = true) => {
|
|
73
|
+
if (val)
|
|
74
|
+
outerPrefix = '> '
|
|
75
|
+
return out
|
|
76
|
+
},
|
|
77
|
+
code: (val = true) => {
|
|
78
|
+
if (val) {
|
|
79
|
+
innerPrefix = innerPrefix + '`'
|
|
80
|
+
innerSuffix = '`' + innerSuffix
|
|
81
|
+
}
|
|
82
|
+
return out
|
|
83
|
+
},
|
|
84
|
+
codeBlock: (language = '', val = true) => {
|
|
85
|
+
if (val) {
|
|
86
|
+
innerPrefix = innerPrefix + '```' + (language ? language + '\n' : '')
|
|
87
|
+
innerSuffix = '```' + innerSuffix
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return out
|
|
94
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import defu from "defu"
|
|
2
|
+
import { row } from "./builtin/row"
|
|
3
|
+
import { readModifier, type CordoModifier } from "./modifier"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const CordoComponentSymbol = Symbol('CordoComponent')
|
|
7
|
+
|
|
8
|
+
export const ComponentType = {
|
|
9
|
+
ActionRow: 1,
|
|
10
|
+
Button: 2,
|
|
11
|
+
StringSelect: 3,
|
|
12
|
+
TextInput: 4,
|
|
13
|
+
UserSelect: 5,
|
|
14
|
+
RoleSelect: 6,
|
|
15
|
+
MentionableSelect: 7,
|
|
16
|
+
ChannelSelect: 8,
|
|
17
|
+
Section: 9,
|
|
18
|
+
TextDisplay: 10,
|
|
19
|
+
Thumbnail: 11,
|
|
20
|
+
MediaGallery: 12,
|
|
21
|
+
File: 13,
|
|
22
|
+
Seperator: 14,
|
|
23
|
+
Container: 17
|
|
24
|
+
} as const
|
|
25
|
+
export type StringComponentType = keyof typeof ComponentType
|
|
26
|
+
export type ComponentIdFromName<Name extends StringComponentType> = typeof ComponentType[Name]
|
|
27
|
+
|
|
28
|
+
export type CordoComponent<Type extends StringComponentType = StringComponentType> = {
|
|
29
|
+
[CordoComponentSymbol]: {
|
|
30
|
+
nativeName: Type
|
|
31
|
+
nativeType: typeof ComponentType[Type]
|
|
32
|
+
visible: boolean
|
|
33
|
+
attributes: Record<string, any>
|
|
34
|
+
render: (meta: {
|
|
35
|
+
hirarchy: Array<StringComponentType>
|
|
36
|
+
attributes: Record<string, any>
|
|
37
|
+
}) => Record<string, any> | null
|
|
38
|
+
}
|
|
39
|
+
visible: (value: boolean) => CordoComponent<Type>
|
|
40
|
+
attributes: (attrs: Record<string, any>) => CordoComponent<Type>
|
|
41
|
+
}
|
|
42
|
+
export type CordoComponentPayload<Type extends StringComponentType> = CordoComponent<Type>[typeof CordoComponentSymbol]
|
|
43
|
+
|
|
44
|
+
export function createComponent<Type extends StringComponentType>(
|
|
45
|
+
type: Type,
|
|
46
|
+
render: CordoComponentPayload<Type>['render']
|
|
47
|
+
): CordoComponent<Type> {
|
|
48
|
+
const comp = {
|
|
49
|
+
nativeName: type,
|
|
50
|
+
nativeType: ComponentType[type],
|
|
51
|
+
visible: true,
|
|
52
|
+
attributes: {},
|
|
53
|
+
render
|
|
54
|
+
}
|
|
55
|
+
const out = {
|
|
56
|
+
[CordoComponentSymbol]: comp,
|
|
57
|
+
visible(value: boolean) {
|
|
58
|
+
comp.visible = value
|
|
59
|
+
return this
|
|
60
|
+
},
|
|
61
|
+
attributes(attrs: Record<string, any>) {
|
|
62
|
+
comp.attributes = defu(attrs, comp.attributes)
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function readComponent<T extends CordoComponent<StringComponentType>>(comp: T): T[typeof CordoComponentSymbol] {
|
|
70
|
+
return comp[CordoComponentSymbol]!
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isComponent(t: Record<string, any>): t is CordoComponent<StringComponentType> {
|
|
74
|
+
return !!t && CordoComponentSymbol in t
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function renderComponent(
|
|
78
|
+
c: CordoComponent<StringComponentType> | CordoComponentPayload<StringComponentType>,
|
|
79
|
+
parent: StringComponentType | null,
|
|
80
|
+
hirarchy: Array<StringComponentType> = [],
|
|
81
|
+
inheritAttributes: Record<string, any> = {}
|
|
82
|
+
) {
|
|
83
|
+
const extracted = CordoComponentSymbol in c ? readComponent(c) : c
|
|
84
|
+
if (!extracted.visible)
|
|
85
|
+
return null
|
|
86
|
+
return extracted.render({
|
|
87
|
+
hirarchy: parent
|
|
88
|
+
? [ parent, ...hirarchy ]
|
|
89
|
+
: hirarchy,
|
|
90
|
+
attributes: defu(extracted.attributes, inheritAttributes)
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function renderComponentList(
|
|
95
|
+
list: Array<CordoComponent<StringComponentType> | CordoModifier>,
|
|
96
|
+
parent: StringComponentType | null,
|
|
97
|
+
hirarchy: Array<StringComponentType> = [],
|
|
98
|
+
inheritAttributes: Record<string, any> = {}
|
|
99
|
+
) {
|
|
100
|
+
let pipeline: Array<CordoComponentPayload<StringComponentType>> = []
|
|
101
|
+
const rowBuilder: Array<CordoComponent<StringComponentType>> = []
|
|
102
|
+
const modifiers: Array<ReturnType<typeof readModifier>> = []
|
|
103
|
+
|
|
104
|
+
for (const item of list) {
|
|
105
|
+
if (!item)
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if (!isComponent(item)) {
|
|
109
|
+
const mod = readModifier(item)
|
|
110
|
+
if (!modifiers.some(m => m.name === mod.name))
|
|
111
|
+
modifiers.push(mod)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let parsed = readComponent(item)
|
|
116
|
+
for (const mod of modifiers) {
|
|
117
|
+
if (mod.hooks?.onRender)
|
|
118
|
+
parsed = mod.hooks.onRender(parsed)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (parent !== 'ActionRow') {
|
|
122
|
+
if (parsed.nativeName === 'Button') {
|
|
123
|
+
rowBuilder.push(item)
|
|
124
|
+
if (rowBuilder.length === 5) {
|
|
125
|
+
pipeline.push(readComponent(row(...rowBuilder as any)))
|
|
126
|
+
rowBuilder.splice(0)
|
|
127
|
+
}
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (parsed.nativeName === 'StringSelect') {
|
|
132
|
+
if (rowBuilder.length > 0) {
|
|
133
|
+
pipeline.push(readComponent(row(...rowBuilder as any)))
|
|
134
|
+
rowBuilder.splice(0)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pipeline.push(readComponent(row(item as any)))
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (rowBuilder.length > 0) {
|
|
143
|
+
pipeline.push(readComponent(row(...rowBuilder as any)))
|
|
144
|
+
rowBuilder.splice(0)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
pipeline.push(parsed)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (rowBuilder.length > 0) {
|
|
151
|
+
pipeline.push(readComponent(row(...rowBuilder as any)))
|
|
152
|
+
rowBuilder.splice(0)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const mod of modifiers) {
|
|
156
|
+
if (mod.hooks?.preRender)
|
|
157
|
+
pipeline = mod.hooks.preRender(pipeline)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let output = pipeline
|
|
161
|
+
.map(c => renderComponent(c, parent, hirarchy, inheritAttributes))
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
|
|
164
|
+
for (const mod of modifiers) {
|
|
165
|
+
if (mod.hooks?.postRender)
|
|
166
|
+
output = mod.hooks.postRender(output)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return output
|
|
170
|
+
}
|