@devvit/client 0.11.14-next-2025-05-05-765f3688c.0
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/LICENSE +26 -0
- package/README.md +3 -0
- package/client-meta.min.json +175 -0
- package/client.min.js +2 -0
- package/client.min.js.map +7 -0
- package/effects/contants.d.ts +3 -0
- package/effects/contants.d.ts.map +1 -0
- package/effects/contants.js +3 -0
- package/effects/emit-effect.d.ts +17 -0
- package/effects/emit-effect.d.ts.map +1 -0
- package/effects/emit-effect.js +42 -0
- package/effects/emit-effect.test.d.ts.map +1 -0
- package/effects/helpers/assert-valid-form-fields.d.ts +11 -0
- package/effects/helpers/assert-valid-form-fields.d.ts.map +1 -0
- package/effects/helpers/assert-valid-form-fields.js +29 -0
- package/effects/helpers/assert-valid-form-fields.test.d.ts.map +1 -0
- package/effects/helpers/form-types.d.ts +149 -0
- package/effects/helpers/form-types.d.ts.map +1 -0
- package/effects/helpers/form-types.js +5 -0
- package/effects/helpers/get-form-values.d.ts +6 -0
- package/effects/helpers/get-form-values.d.ts.map +1 -0
- package/effects/helpers/get-form-values.js +30 -0
- package/effects/helpers/get-form-values.test.d.ts.map +1 -0
- package/effects/helpers/test-helpers.d.ts +10 -0
- package/effects/helpers/test-helpers.d.ts.map +1 -0
- package/effects/helpers/test-helpers.js +254 -0
- package/effects/helpers/transform-form.d.ts +4 -0
- package/effects/helpers/transform-form.d.ts.map +1 -0
- package/effects/helpers/transform-form.js +140 -0
- package/effects/helpers/transform-form.test.d.ts.map +1 -0
- package/effects/index.d.ts +4 -0
- package/effects/index.d.ts.map +1 -0
- package/effects/index.js +3 -0
- package/effects/navigate-to.d.ts +12 -0
- package/effects/navigate-to.d.ts.map +1 -0
- package/effects/navigate-to.js +17 -0
- package/effects/navigate-to.test.d.ts.map +1 -0
- package/effects/show-form.d.ts +16 -0
- package/effects/show-form.d.ts.map +1 -0
- package/effects/show-form.js +49 -0
- package/effects/show-form.test.d.ts.map +1 -0
- package/effects/show-toast.d.ts +9 -0
- package/effects/show-toast.d.ts.map +1 -0
- package/effects/show-toast.js +23 -0
- package/effects/show-toast.test.d.ts.map +1 -0
- package/effects/types.d.ts +19 -0
- package/effects/types.d.ts.map +1 -0
- package/effects/types.js +6 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -0
- package/index.js +1 -0
- package/index.test.d.ts.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2023 Reddit Inc.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions
|
|
5
|
+
are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
documentation and/or other materials provided with the distribution.
|
|
12
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
13
|
+
contributors may be used to endorse or promote products derived from
|
|
14
|
+
this software without specific prior written permission.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
22
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
23
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
24
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
25
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
26
|
+
SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
{
|
|
2
|
+
"inputs": {
|
|
3
|
+
"src/effects/contants.ts": {
|
|
4
|
+
"bytes": 201,
|
|
5
|
+
"imports": [],
|
|
6
|
+
"format": "esm"
|
|
7
|
+
},
|
|
8
|
+
"src/effects/emit-effect.ts": {
|
|
9
|
+
"bytes": 2213,
|
|
10
|
+
"imports": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/effects/contants.ts",
|
|
13
|
+
"kind": "import-statement",
|
|
14
|
+
"original": "./contants.js"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"format": "esm"
|
|
18
|
+
},
|
|
19
|
+
"src/effects/navigate-to.ts": {
|
|
20
|
+
"bytes": 1043,
|
|
21
|
+
"imports": [
|
|
22
|
+
{
|
|
23
|
+
"path": "src/effects/emit-effect.ts",
|
|
24
|
+
"kind": "import-statement",
|
|
25
|
+
"original": "./emit-effect.js"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"format": "esm"
|
|
29
|
+
},
|
|
30
|
+
"src/effects/helpers/assert-valid-form-fields.ts": {
|
|
31
|
+
"bytes": 1271,
|
|
32
|
+
"imports": [],
|
|
33
|
+
"format": "esm"
|
|
34
|
+
},
|
|
35
|
+
"src/effects/helpers/get-form-values.ts": {
|
|
36
|
+
"bytes": 1337,
|
|
37
|
+
"imports": [],
|
|
38
|
+
"format": "esm"
|
|
39
|
+
},
|
|
40
|
+
"src/effects/helpers/transform-form.ts": {
|
|
41
|
+
"bytes": 4194,
|
|
42
|
+
"imports": [],
|
|
43
|
+
"format": "esm"
|
|
44
|
+
},
|
|
45
|
+
"src/effects/types.ts": {
|
|
46
|
+
"bytes": 506,
|
|
47
|
+
"imports": [],
|
|
48
|
+
"format": "esm"
|
|
49
|
+
},
|
|
50
|
+
"src/effects/show-form.ts": {
|
|
51
|
+
"bytes": 2074,
|
|
52
|
+
"imports": [
|
|
53
|
+
{
|
|
54
|
+
"path": "src/effects/emit-effect.ts",
|
|
55
|
+
"kind": "import-statement",
|
|
56
|
+
"original": "./emit-effect.js"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "src/effects/helpers/assert-valid-form-fields.ts",
|
|
60
|
+
"kind": "import-statement",
|
|
61
|
+
"original": "./helpers/assert-valid-form-fields.js"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"path": "src/effects/helpers/get-form-values.ts",
|
|
65
|
+
"kind": "import-statement",
|
|
66
|
+
"original": "./helpers/get-form-values.js"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"path": "src/effects/helpers/transform-form.ts",
|
|
70
|
+
"kind": "import-statement",
|
|
71
|
+
"original": "./helpers/transform-form.js"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"path": "src/effects/types.ts",
|
|
75
|
+
"kind": "import-statement",
|
|
76
|
+
"original": "./types.js"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"format": "esm"
|
|
80
|
+
},
|
|
81
|
+
"src/effects/show-toast.ts": {
|
|
82
|
+
"bytes": 1015,
|
|
83
|
+
"imports": [
|
|
84
|
+
{
|
|
85
|
+
"path": "src/effects/emit-effect.ts",
|
|
86
|
+
"kind": "import-statement",
|
|
87
|
+
"original": "./emit-effect.js"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"format": "esm"
|
|
91
|
+
},
|
|
92
|
+
"src/effects/index.ts": {
|
|
93
|
+
"bytes": 135,
|
|
94
|
+
"imports": [
|
|
95
|
+
{
|
|
96
|
+
"path": "src/effects/navigate-to.ts",
|
|
97
|
+
"kind": "import-statement",
|
|
98
|
+
"original": "./navigate-to.js"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"path": "src/effects/show-form.ts",
|
|
102
|
+
"kind": "import-statement",
|
|
103
|
+
"original": "./show-form.js"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"path": "src/effects/show-toast.ts",
|
|
107
|
+
"kind": "import-statement",
|
|
108
|
+
"original": "./show-toast.js"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"format": "esm"
|
|
112
|
+
},
|
|
113
|
+
"src/index.ts": {
|
|
114
|
+
"bytes": 70,
|
|
115
|
+
"imports": [
|
|
116
|
+
{
|
|
117
|
+
"path": "src/effects/index.ts",
|
|
118
|
+
"kind": "import-statement",
|
|
119
|
+
"original": "./effects/index.js"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"format": "esm"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"outputs": {
|
|
126
|
+
"dist/client.min.js.map": {
|
|
127
|
+
"imports": [],
|
|
128
|
+
"exports": [],
|
|
129
|
+
"inputs": {},
|
|
130
|
+
"bytes": 18965
|
|
131
|
+
},
|
|
132
|
+
"dist/client.min.js": {
|
|
133
|
+
"imports": [],
|
|
134
|
+
"exports": [
|
|
135
|
+
"navigateTo",
|
|
136
|
+
"showForm",
|
|
137
|
+
"showToast"
|
|
138
|
+
],
|
|
139
|
+
"entryPoint": "src/index.ts",
|
|
140
|
+
"inputs": {
|
|
141
|
+
"src/effects/contants.ts": {
|
|
142
|
+
"bytesInOutput": 10
|
|
143
|
+
},
|
|
144
|
+
"src/effects/emit-effect.ts": {
|
|
145
|
+
"bytesInOutput": 377
|
|
146
|
+
},
|
|
147
|
+
"src/effects/navigate-to.ts": {
|
|
148
|
+
"bytesInOutput": 156
|
|
149
|
+
},
|
|
150
|
+
"src/effects/index.ts": {
|
|
151
|
+
"bytesInOutput": 0
|
|
152
|
+
},
|
|
153
|
+
"src/effects/helpers/assert-valid-form-fields.ts": {
|
|
154
|
+
"bytesInOutput": 357
|
|
155
|
+
},
|
|
156
|
+
"src/effects/helpers/get-form-values.ts": {
|
|
157
|
+
"bytesInOutput": 331
|
|
158
|
+
},
|
|
159
|
+
"src/effects/helpers/transform-form.ts": {
|
|
160
|
+
"bytesInOutput": 1664
|
|
161
|
+
},
|
|
162
|
+
"src/effects/show-form.ts": {
|
|
163
|
+
"bytesInOutput": 361
|
|
164
|
+
},
|
|
165
|
+
"src/effects/show-toast.ts": {
|
|
166
|
+
"bytesInOutput": 139
|
|
167
|
+
},
|
|
168
|
+
"src/index.ts": {
|
|
169
|
+
"bytesInOutput": 0
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"bytes": 3488
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
package/client.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var l=[3];var i=e=>new Promise(t=>{let o={scope:0,type:"devvit-internal",effect:e};if(l.includes(e.type)){let r=self.crypto.randomUUID();o.id=r;let n=s=>{s.data?.type==="devvit-message"&&s.data?.data?.id===r&&(t(s.data.data),window.removeEventListener("message",n))};window.addEventListener("message",n),window.parent.postMessage(o,"*")}else window.parent.postMessage(o,"*"),t(void 0)});function d(e){let t;typeof e=="string"?t=new URL(e).toString():t=new URL(e.permalink,"https://www.reddit.com").toString(),i({navigateToUrl:{url:t},type:5})}function a(e,t=new Set){for(let o of e){if(o.type==="group"){a(o.fields,t);continue}let r=o.name;if(t.has(r))throw new Error(`Duplicate field name: ${r}`);t.add(r)}F(e)}function F(e){for(let t of e)if(t.type==="string"&&t.isSecret&&t.scope!=="app")throw`Invalid setting: only app settings can be secrets. Add "scope: SettingScope.App" to field "${t.name}"`}function T(e){switch(e.fieldType){case 0:return e.stringValue;case 7:return e.stringValue;case 1:return e.stringValue;case 2:return e.numberValue;case 3:return e.boolValue;case 5:return e.selectionValue?.values??[];default:return}}function m(e){return Object.keys(e).reduce((t,o)=>{let r=T(e[o]);return r!==void 0&&(t[o]=r),t},{})}function p(e){return e.map(t=>{switch(t.type){case"string":return y(t);case"image":return v(t);case"paragraph":return g(t);case"number":return E(t);case"select":return b(t);case"boolean":return S(t);case"group":return h(t);default:throw new Error("Unknown field type.")}})}function y(e){return{defaultValue:{fieldType:0,stringValue:e.defaultValue},disabled:e.disabled,fieldConfig:{stringConfig:{placeholder:e.placeholder}},fieldId:e.name,fieldType:0,helpText:e.helpText,label:e.label,required:e.required,isSecret:e.isSecret}}function v(e){return{disabled:e.disabled,fieldId:e.name,fieldType:7,helpText:e.helpText,label:e.label,required:e.required}}function g(e){return{defaultValue:{fieldType:1,stringValue:e.defaultValue},disabled:e.disabled,fieldConfig:{paragraphConfig:{lineHeight:e.lineHeight,placeholder:e.placeholder}},fieldId:e.name,fieldType:1,helpText:e.helpText,label:e.label,required:e.required}}function E(e){return{defaultValue:{fieldType:2,numberValue:e.defaultValue},disabled:e.disabled,fieldConfig:{numberConfig:{}},fieldId:e.name,fieldType:2,helpText:e.helpText,label:e.label,required:e.required}}function b(e){return{defaultValue:{fieldType:5,selectionValue:{values:e.defaultValue??[]}},disabled:e.disabled,fieldConfig:{selectionConfig:{choices:e.options,multiSelect:e.multiSelect}},fieldId:e.name,fieldType:5,helpText:e.helpText,label:e.label,required:e.required}}function S(e){return{defaultValue:{fieldType:3,boolValue:e.defaultValue},disabled:e.disabled,fieldId:e.name,fieldType:3,helpText:e.helpText,label:e.label}}function h(e){return{fieldId:"",fieldType:6,fieldConfig:{groupConfig:{fields:p(e.fields)}},label:e.label,helpText:e.helpText}}var f=1,w=()=>(f++,`form.${f}`),c=async e=>{let t={fields:[],id:w(),title:e.title,acceptLabel:e.acceptLabel,cancelLabel:e.cancelLabel,shortDescription:e.description};a(e.fields),t.fields=p(e.fields);let o=await i({showForm:{form:t},type:3});if(!o||!o.formSubmitted)return{action:"CANCELED"};let r=m(o.formSubmitted.results);return{action:"SUBMITTED",values:r}};function u(e){let t;e instanceof Object?t={text:e.text,appearance:e.appearance==="success"?1:0}:t={text:e},i({showToast:{toast:t},type:4})}export{d as navigateTo,c as showForm,u as showToast};
|
|
2
|
+
//# sourceMappingURL=client.min.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/effects/contants.ts", "../src/effects/emit-effect.ts", "../src/effects/navigate-to.ts", "../src/effects/helpers/assert-valid-form-fields.ts", "../src/effects/helpers/get-form-values.ts", "../src/effects/helpers/transform-form.ts", "../src/effects/show-form.ts", "../src/effects/show-toast.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EffectType } from '@devvit/protos/types/devvit/ui/effects/v1alpha/effect.js';\n\nexport const EFFECTS_WITH_RESPONSE: readonly EffectType[] = [\n 3 satisfies EffectType.EFFECT_SHOW_FORM,\n];\n", "import type { Effect } from '@devvit/protos';\nimport type { WebViewInternalMessageScope } from '@devvit/protos/types/devvit/ui/effects/web_view/v1alpha/post_message.js';\nimport type { WebViewInternalEventMessage } from '@devvit/protos/types/devvit/ui/events/v1alpha/web_view.js';\n\nimport { EFFECTS_WITH_RESPONSE } from './contants.js';\nimport type { EmitEffectPayload } from './types.js';\n\n/**\n * Emits an effect to the parent window and handles the response if required.\n *\n * @param effect - The effect to be emitted to the parent window\n * @returns A promise that resolves with the response message for effects that require\n * a response, or resolves immediately with undefined for effects that don't\n *\n * @description\n * This function handles two types of effects:\n * 1. Effects that require a response: Creates a unique ID, sets up a message listener,\n * and resolves the promise when a matching response is received\n * 2. Effects that don't require a response: Posts the message and resolves immediately\n */\nexport const emitEffect = (effect: Effect): Promise<WebViewInternalEventMessage | undefined> => {\n return new Promise<WebViewInternalEventMessage | undefined>((resolve) => {\n const message: EmitEffectPayload = {\n scope: 0 satisfies WebViewInternalMessageScope.CLIENT,\n type: 'devvit-internal',\n effect,\n };\n\n // Only set message id and add a listener for effects which require a response\n if (EFFECTS_WITH_RESPONSE.includes(effect.type)) {\n const id = self.crypto.randomUUID();\n message.id = id;\n\n const handleEffect = (event: MessageEvent): void => {\n if (event.data?.type === 'devvit-message' && event.data?.data?.id === id) {\n resolve(event.data.data as WebViewInternalEventMessage);\n window.removeEventListener('message', handleEffect);\n }\n };\n\n window.addEventListener('message', handleEffect);\n\n // Post message to the parent window, handled by client web view component\n window.parent.postMessage(message, '*');\n } else {\n window.parent.postMessage(message, '*');\n // Resolve immediately for effects that don't expect a response.\n resolve(undefined);\n }\n });\n};\n", "import type { EffectType } from '@devvit/protos/types/devvit/ui/effects/v1alpha/effect.js';\nimport type { Comment, Post, Subreddit, User } from '@devvit/public-api';\n\nimport { emitEffect } from './emit-effect.js';\n\n/**\n * Navigates to a URL, subreddit, post, comment, or user.\n *\n * @param thingOrUrl - The URL, subreddit, post, comment, or user to navigate to\n */\nexport function navigateTo(url: string): void;\nexport function navigateTo(subreddit: Subreddit): void;\nexport function navigateTo(post: Post): void;\nexport function navigateTo(comment: Comment): void;\nexport function navigateTo(user: User): void;\nexport function navigateTo(thingOrUrl: string | Subreddit | Post | Comment | User): void {\n let url: string;\n\n if (typeof thingOrUrl === 'string') {\n // Validate URL\n url = new URL(thingOrUrl).toString();\n } else {\n url = new URL(thingOrUrl.permalink, 'https://www.reddit.com').toString();\n }\n\n void emitEffect({\n navigateToUrl: {\n url,\n },\n type: 5 satisfies EffectType.EFFECT_NAVIGATE_TO_URL,\n });\n}\n", "import type { FormField, SettingScope } from './form-types.js';\n\n/**\n * Make sure that the form fields have unique names.\n *\n * This is a carbon copy of the assertValidFormFields function in the public-api package\n * We copy it here so that @devvit/client does not need to depend on public-api\n * Any changes to this function should be reflected in the public-api version\n */\nexport function assertValidFormFields(\n fields: readonly FormField[],\n seenNames: Set<string> = new Set()\n): void {\n for (const field of fields) {\n if (field.type === 'group') {\n assertValidFormFields(field.fields, seenNames);\n continue;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldName = (field as any).name as string;\n\n if (seenNames.has(fieldName)) {\n throw new Error(`Duplicate field name: ${fieldName}`);\n }\n\n seenNames.add(fieldName);\n }\n assertAppSecretsOnly(fields);\n}\n\nexport function assertAppSecretsOnly(fields: readonly FormField[]): void {\n for (const field of fields) {\n if (field.type === 'string' && field.isSecret && field.scope !== ('app' as SettingScope.App)) {\n throw `Invalid setting: only app settings can be secrets. Add \"scope: SettingScope.App\" to field \"${field.name}\"`;\n }\n }\n}\n", "import type { FormFieldType, FormFieldValue } from '@devvit/protos';\n\nimport type { FormValues } from './form-types.js';\n\nfunction flattenFormFieldValue(\n value: FormFieldValue\n): undefined | string | string[] | number | boolean {\n switch (value.fieldType) {\n case 0 satisfies FormFieldType.STRING:\n return value.stringValue;\n case 7 satisfies FormFieldType.IMAGE:\n // the string value is the URL\n return value.stringValue;\n case 1 satisfies FormFieldType.PARAGRAPH:\n return value.stringValue;\n case 2 satisfies FormFieldType.NUMBER:\n return value.numberValue;\n case 3 satisfies FormFieldType.BOOLEAN:\n return value.boolValue;\n case 5 satisfies FormFieldType.SELECTION:\n return value.selectionValue?.values ?? [];\n default:\n return undefined;\n }\n}\n\n// This is a carbon copy of the transformFormFields function in the public-api package\n// We copy it here so that @devvit/client does not need to depend on public-api\n// Any changes to this function should be reflected in the public-api version\nexport function getFormValues(results: { [key: string]: FormFieldValue }): FormValues {\n return Object.keys(results).reduce((acc, key) => {\n const val = flattenFormFieldValue(results[key]);\n if (val !== undefined) acc[key] = val;\n return acc;\n }, {} as FormValues);\n}\n", "import type { FormField as FormFieldProto, FormFieldType } from '@devvit/protos';\n\nimport type {\n BooleanField,\n FormField,\n FormFieldGroup,\n ImageField,\n NumberField,\n ParagraphField,\n SelectField,\n StringField,\n} from './form-types.js';\n\n// This is a carbon copy of the transformFormFields function in the public-api package\n// We copy it here so that @devvit/client does not need to depend on public-api\n// Any changes to this function should be reflected in the public-api version\nexport function transformFormFields(fields: readonly FormField[]): FormFieldProto[] {\n return fields.map((field) => {\n switch (field.type) {\n case 'string':\n return transformStringField(field);\n case 'image':\n return transformImageField(field);\n case 'paragraph':\n return transformParagraphField(field);\n case 'number':\n return transformNumberField(field);\n case 'select':\n return transformSelectField(field);\n case 'boolean':\n return transformBooleanField(field);\n case 'group':\n return transformGroupField(field);\n default:\n throw new Error('Unknown field type.');\n }\n });\n}\n\nfunction transformStringField(field: StringField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: 0 satisfies FormFieldType.STRING,\n stringValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n stringConfig: {\n placeholder: field.placeholder,\n },\n },\n fieldId: field.name,\n fieldType: 0 satisfies FormFieldType.STRING,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n isSecret: field.isSecret,\n };\n}\n\nfunction transformImageField(field: ImageField): FormFieldProto {\n return {\n disabled: field.disabled,\n fieldId: field.name,\n fieldType: 7 satisfies FormFieldType.IMAGE,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformParagraphField(field: ParagraphField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: 1 satisfies FormFieldType.PARAGRAPH,\n stringValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n paragraphConfig: {\n lineHeight: field.lineHeight,\n placeholder: field.placeholder,\n },\n },\n fieldId: field.name,\n fieldType: 1 satisfies FormFieldType.PARAGRAPH,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformNumberField(field: NumberField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: 2 satisfies FormFieldType.NUMBER,\n numberValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n numberConfig: {},\n },\n fieldId: field.name,\n fieldType: 2 satisfies FormFieldType.NUMBER,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformSelectField(field: SelectField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: 5 satisfies FormFieldType.SELECTION,\n selectionValue: {\n values: field.defaultValue ?? [],\n },\n },\n disabled: field.disabled,\n fieldConfig: {\n selectionConfig: {\n choices: field.options,\n multiSelect: field.multiSelect,\n },\n },\n fieldId: field.name,\n fieldType: 5 satisfies FormFieldType.SELECTION,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformBooleanField(field: BooleanField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: 3 satisfies FormFieldType.BOOLEAN,\n boolValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldId: field.name,\n fieldType: 3 satisfies FormFieldType.BOOLEAN,\n helpText: field.helpText,\n label: field.label,\n };\n}\n\nfunction transformGroupField(field: FormFieldGroup): FormFieldProto {\n return {\n fieldId: '',\n fieldType: 6 satisfies FormFieldType.GROUP,\n fieldConfig: {\n groupConfig: {\n fields: transformFormFields(field.fields),\n },\n },\n label: field.label,\n helpText: field.helpText,\n };\n}\n", "import type { Form as FormProto } from '@devvit/protos';\nimport type { EffectType } from '@devvit/protos/types/devvit/ui/effects/v1alpha/effect.js';\nimport type { Form, FormKey } from '@devvit/public-api';\n\nimport { emitEffect } from './emit-effect.js';\nimport { assertValidFormFields } from './helpers/assert-valid-form-fields.js';\nimport type { FormToFormValues } from './helpers/form-types.js';\nimport { getFormValues } from './helpers/get-form-values.js';\nimport { transformFormFields } from './helpers/transform-form.js';\nimport { FormAction, type FormEffectResponse } from './types.js';\n\nlet _formKey = 1;\n\nconst getNextFormKey = (): FormKey => {\n _formKey++;\n return `form.${_formKey}`;\n};\n\n/**\n * Opens a form in a modal.\n * Returns a promise that resolves with the form submission results.\n * The form can be submitted or canceled by the user.\n *\n * @param formDefinition - The form configuration\n * @returns A promise that resolves to either:\n * - An object with `action: FormAction.SUBMITTED` and the submitted form values\n * - An object with `action: FormAction.CANCELED` if the user canceled the form\n * @throws Will throw if the form fields are invalid\n */\nexport const showForm = async <const T extends Form>(\n formDefinition: T\n): Promise<FormEffectResponse<FormToFormValues<T>>> => {\n const form: FormProto = {\n fields: [],\n id: getNextFormKey(),\n title: formDefinition.title,\n acceptLabel: formDefinition.acceptLabel,\n cancelLabel: formDefinition.cancelLabel,\n shortDescription: formDefinition.description,\n };\n\n assertValidFormFields(formDefinition.fields);\n form.fields = transformFormFields(formDefinition.fields);\n\n const response = await emitEffect({\n showForm: {\n form,\n },\n type: 3 satisfies EffectType.EFFECT_SHOW_FORM,\n });\n\n if (!response || !response.formSubmitted) {\n return {\n action: FormAction.CANCELED,\n };\n }\n\n const formResults = getFormValues(response.formSubmitted.results);\n\n return {\n action: FormAction.SUBMITTED,\n values: formResults as FormToFormValues<T>,\n };\n};\n", "import type { EffectType } from '@devvit/protos/types/devvit/ui/effects/v1alpha/effect.js';\nimport type {\n Toast as ToastProto,\n ToastAppearance,\n} from '@devvit/protos/types/devvit/ui/toast/toast.js';\nimport type { Toast } from '@devvit/public-api';\n\nimport { emitEffect } from './emit-effect.js';\n\n/**\n * Shows a toast message.\n *\n * @param textOrToast - The text or toast object to display\n */\nexport function showToast(text: string): void;\nexport function showToast(toast: Toast): void;\nexport function showToast(textOrToast: string | Toast): void {\n let toast: ToastProto;\n\n if (textOrToast instanceof Object) {\n toast = {\n text: textOrToast.text,\n appearance:\n textOrToast.appearance === 'success'\n ? (1 satisfies ToastAppearance.SUCCESS)\n : (0 satisfies ToastAppearance.NEUTRAL),\n };\n } else {\n toast = {\n text: textOrToast,\n };\n }\n\n void emitEffect({\n showToast: {\n toast,\n },\n type: 4 satisfies EffectType.EFFECT_SHOW_TOAST,\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["EFFECTS_WITH_RESPONSE", "emitEffect", "effect", "resolve", "message", "EFFECTS_WITH_RESPONSE", "id", "handleEffect", "event", "navigateTo", "thingOrUrl", "url", "emitEffect", "assertValidFormFields", "fields", "seenNames", "field", "fieldName", "assertAppSecretsOnly", "flattenFormFieldValue", "value", "getFormValues", "results", "acc", "key", "val", "transformFormFields", "fields", "field", "transformStringField", "transformImageField", "transformParagraphField", "transformNumberField", "transformSelectField", "transformBooleanField", "transformGroupField", "_formKey", "getNextFormKey", "showForm", "formDefinition", "form", "assertValidFormFields", "transformFormFields", "response", "emitEffect", "formResults", "getFormValues", "showToast", "textOrToast", "toast", "emitEffect"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contants.d.ts","sourceRoot":"","sources":["../../src/effects/contants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0DAA0D,CAAC;AAE3F,eAAO,MAAM,qBAAqB,EAAE,SAAS,UAAU,EAEtD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Effect } from '@devvit/protos';
|
|
2
|
+
import type { WebViewInternalEventMessage } from '@devvit/protos/types/devvit/ui/events/v1alpha/web_view.js';
|
|
3
|
+
/**
|
|
4
|
+
* Emits an effect to the parent window and handles the response if required.
|
|
5
|
+
*
|
|
6
|
+
* @param effect - The effect to be emitted to the parent window
|
|
7
|
+
* @returns A promise that resolves with the response message for effects that require
|
|
8
|
+
* a response, or resolves immediately with undefined for effects that don't
|
|
9
|
+
*
|
|
10
|
+
* @description
|
|
11
|
+
* This function handles two types of effects:
|
|
12
|
+
* 1. Effects that require a response: Creates a unique ID, sets up a message listener,
|
|
13
|
+
* and resolves the promise when a matching response is received
|
|
14
|
+
* 2. Effects that don't require a response: Posts the message and resolves immediately
|
|
15
|
+
*/
|
|
16
|
+
export declare const emitEffect: (effect: Effect) => Promise<WebViewInternalEventMessage | undefined>;
|
|
17
|
+
//# sourceMappingURL=emit-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-effect.d.ts","sourceRoot":"","sources":["../../src/effects/emit-effect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2DAA2D,CAAC;AAK7G;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,WAAY,MAAM,KAAG,QAAQ,2BAA2B,GAAG,SAAS,CA8B1F,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EFFECTS_WITH_RESPONSE } from './contants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Emits an effect to the parent window and handles the response if required.
|
|
4
|
+
*
|
|
5
|
+
* @param effect - The effect to be emitted to the parent window
|
|
6
|
+
* @returns A promise that resolves with the response message for effects that require
|
|
7
|
+
* a response, or resolves immediately with undefined for effects that don't
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* This function handles two types of effects:
|
|
11
|
+
* 1. Effects that require a response: Creates a unique ID, sets up a message listener,
|
|
12
|
+
* and resolves the promise when a matching response is received
|
|
13
|
+
* 2. Effects that don't require a response: Posts the message and resolves immediately
|
|
14
|
+
*/
|
|
15
|
+
export const emitEffect = (effect) => {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const message = {
|
|
18
|
+
scope: 0,
|
|
19
|
+
type: 'devvit-internal',
|
|
20
|
+
effect,
|
|
21
|
+
};
|
|
22
|
+
// Only set message id and add a listener for effects which require a response
|
|
23
|
+
if (EFFECTS_WITH_RESPONSE.includes(effect.type)) {
|
|
24
|
+
const id = self.crypto.randomUUID();
|
|
25
|
+
message.id = id;
|
|
26
|
+
const handleEffect = (event) => {
|
|
27
|
+
if (event.data?.type === 'devvit-message' && event.data?.data?.id === id) {
|
|
28
|
+
resolve(event.data.data);
|
|
29
|
+
window.removeEventListener('message', handleEffect);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener('message', handleEffect);
|
|
33
|
+
// Post message to the parent window, handled by client web view component
|
|
34
|
+
window.parent.postMessage(message, '*');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
window.parent.postMessage(message, '*');
|
|
38
|
+
// Resolve immediately for effects that don't expect a response.
|
|
39
|
+
resolve(undefined);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-effect.test.d.ts","sourceRoot":"","sources":["../../src/effects/emit-effect.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FormField } from './form-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Make sure that the form fields have unique names.
|
|
4
|
+
*
|
|
5
|
+
* This is a carbon copy of the assertValidFormFields function in the public-api package
|
|
6
|
+
* We copy it here so that @devvit/client does not need to depend on public-api
|
|
7
|
+
* Any changes to this function should be reflected in the public-api version
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertValidFormFields(fields: readonly FormField[], seenNames?: Set<string>): void;
|
|
10
|
+
export declare function assertAppSecretsOnly(fields: readonly FormField[]): void;
|
|
11
|
+
//# sourceMappingURL=assert-valid-form-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-valid-form-fields.d.ts","sourceRoot":"","sources":["../../../src/effects/helpers/assert-valid-form-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,iBAAiB,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,SAAS,GAAE,GAAG,CAAC,MAAM,CAAa,GACjC,IAAI,CAiBN;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAMvE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make sure that the form fields have unique names.
|
|
3
|
+
*
|
|
4
|
+
* This is a carbon copy of the assertValidFormFields function in the public-api package
|
|
5
|
+
* We copy it here so that @devvit/client does not need to depend on public-api
|
|
6
|
+
* Any changes to this function should be reflected in the public-api version
|
|
7
|
+
*/
|
|
8
|
+
export function assertValidFormFields(fields, seenNames = new Set()) {
|
|
9
|
+
for (const field of fields) {
|
|
10
|
+
if (field.type === 'group') {
|
|
11
|
+
assertValidFormFields(field.fields, seenNames);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
const fieldName = field.name;
|
|
16
|
+
if (seenNames.has(fieldName)) {
|
|
17
|
+
throw new Error(`Duplicate field name: ${fieldName}`);
|
|
18
|
+
}
|
|
19
|
+
seenNames.add(fieldName);
|
|
20
|
+
}
|
|
21
|
+
assertAppSecretsOnly(fields);
|
|
22
|
+
}
|
|
23
|
+
export function assertAppSecretsOnly(fields) {
|
|
24
|
+
for (const field of fields) {
|
|
25
|
+
if (field.type === 'string' && field.isSecret && field.scope !== 'app') {
|
|
26
|
+
throw `Invalid setting: only app settings can be secrets. Add "scope: SettingScope.App" to field "${field.name}"`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-valid-form-fields.test.d.ts","sourceRoot":"","sources":["../../../src/effects/helpers/assert-valid-form-fields.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { FieldConfig_Boolean, FieldConfig_Number, FieldConfig_Paragraph, FieldConfig_Selection, FieldConfig_Selection_Item, FieldConfig_String } from '@devvit/protos';
|
|
2
|
+
type Prettify<T> = {
|
|
3
|
+
[K in keyof T]: T[K];
|
|
4
|
+
};
|
|
5
|
+
type JSONValue = JSONPrimitive | JSONArray | JSONObject;
|
|
6
|
+
type JSONObject = {
|
|
7
|
+
[key: string]: JSONValue;
|
|
8
|
+
};
|
|
9
|
+
type JSONArray = JSONValue[];
|
|
10
|
+
type JSONPrimitive = boolean | null | number | string;
|
|
11
|
+
export type FormValues = JSONObject;
|
|
12
|
+
export type BaseField<ValueType> = {
|
|
13
|
+
/**
|
|
14
|
+
* The name of the field. This will be used as the key in the `values` object
|
|
15
|
+
* when the form is submitted.
|
|
16
|
+
*/
|
|
17
|
+
name: string;
|
|
18
|
+
/** The label of the field. This will be displayed to the user */
|
|
19
|
+
label: string;
|
|
20
|
+
/** An optional help text that will be displayed below the field */
|
|
21
|
+
helpText?: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* If true the field will be required and the user will not be able to submit
|
|
24
|
+
* the form without filling it in.
|
|
25
|
+
*/
|
|
26
|
+
required?: boolean | undefined;
|
|
27
|
+
/** If true the field will be disabled */
|
|
28
|
+
disabled?: boolean | undefined;
|
|
29
|
+
/** The default value of the field */
|
|
30
|
+
defaultValue?: ValueType | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* This indicates whether the field (setting) is an app level or install level
|
|
33
|
+
* setting. App setting values can be used by any installation.
|
|
34
|
+
*/
|
|
35
|
+
scope?: SettingScopeType | undefined;
|
|
36
|
+
};
|
|
37
|
+
export type SettingScopeType = 'installation' | 'app';
|
|
38
|
+
export declare enum SettingScope {
|
|
39
|
+
Installation = "installation",
|
|
40
|
+
App = "app"
|
|
41
|
+
}
|
|
42
|
+
/** A text field */
|
|
43
|
+
export type StringField = Prettify<BaseField<string> & Omit<FieldConfig_String, 'minLength' | 'maxLength'> & {
|
|
44
|
+
type: 'string';
|
|
45
|
+
isSecret?: boolean;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Allows a user to upload an image as part of submitting the form. The string value that's
|
|
49
|
+
* given back is the URL of the image.
|
|
50
|
+
* @experimental
|
|
51
|
+
*/
|
|
52
|
+
export type ImageField = Omit<BaseField<string>, 'defaultValue'> & {
|
|
53
|
+
type: 'image';
|
|
54
|
+
};
|
|
55
|
+
/** A paragraph or textarea field */
|
|
56
|
+
export type ParagraphField = Prettify<BaseField<string> & Omit<FieldConfig_Paragraph, 'maxCharacters'> & {
|
|
57
|
+
type: 'paragraph';
|
|
58
|
+
}>;
|
|
59
|
+
/** A number field */
|
|
60
|
+
export type NumberField = Prettify<BaseField<number> & Omit<FieldConfig_Number, 'min' | 'max' | 'step'> & {
|
|
61
|
+
type: 'number';
|
|
62
|
+
}>;
|
|
63
|
+
/** A boolean field displayed as a toggle */
|
|
64
|
+
export type BooleanField = Prettify<Omit<BaseField<boolean>, 'required'> & FieldConfig_Boolean & {
|
|
65
|
+
type: 'boolean';
|
|
66
|
+
}>;
|
|
67
|
+
/** A dropdown field that allows users to pick from a list of options */
|
|
68
|
+
export type SelectField = Prettify<BaseField<string[]> & Omit<FieldConfig_Selection, 'choices' | 'renderAsList' | 'minSelections' | 'maxSelections'> & {
|
|
69
|
+
type: 'select';
|
|
70
|
+
options: FieldConfig_Selection_Item[];
|
|
71
|
+
}>;
|
|
72
|
+
/** A grouping of fields */
|
|
73
|
+
export type FormFieldGroup = {
|
|
74
|
+
type: 'group';
|
|
75
|
+
/** The label of the group that will be displayed to the user */
|
|
76
|
+
label: string;
|
|
77
|
+
/** The fields that will be displayed in the group */
|
|
78
|
+
fields: readonly FormField[];
|
|
79
|
+
/** An optional help text that will be displayed below the group */
|
|
80
|
+
helpText?: string | undefined;
|
|
81
|
+
required?: never;
|
|
82
|
+
};
|
|
83
|
+
export type FormField = StringField | ImageField | ParagraphField | NumberField | BooleanField | SelectField | FormFieldGroup;
|
|
84
|
+
export type Form = {
|
|
85
|
+
/** The fields that will be displayed in the form */
|
|
86
|
+
fields: readonly FormField[];
|
|
87
|
+
/** An optional title for the form */
|
|
88
|
+
title?: string;
|
|
89
|
+
/** An optional description for the form */
|
|
90
|
+
description?: string;
|
|
91
|
+
/** An optional label for the submit button */
|
|
92
|
+
acceptLabel?: string;
|
|
93
|
+
/** An optional label for the cancel button */
|
|
94
|
+
cancelLabel?: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* A function that returns a form. You can use this to dynamically generate a form.
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const formKey = Devvit.createForm((data) => ({
|
|
101
|
+
* fields: data.fields,
|
|
102
|
+
* title: data.title,
|
|
103
|
+
* }), callback);
|
|
104
|
+
*
|
|
105
|
+
* ...
|
|
106
|
+
*
|
|
107
|
+
* ui.showForm(formKey, {
|
|
108
|
+
* fields: [{ type: 'string', name: 'title', label: 'Title' }]
|
|
109
|
+
* title: 'My dynamic form'
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
* */
|
|
113
|
+
export type FormFunction<T extends {
|
|
114
|
+
[key: string]: any;
|
|
115
|
+
} = {
|
|
116
|
+
[key: string]: any;
|
|
117
|
+
}> = (data: T) => Form;
|
|
118
|
+
export type FormToFormValues<T extends Form | FormFunction = Form | FormFunction> = FormFieldsToFormValues<(T extends FormFunction ? ReturnType<T> : T)['fields']>;
|
|
119
|
+
/**
|
|
120
|
+
* Input is a FormField[], output is a
|
|
121
|
+
* {fieldNameA: fieldTypeA, fieldNameB: fieldTypeB}.
|
|
122
|
+
*/
|
|
123
|
+
type FormFieldsToFormValues<T extends readonly FormField[]> = T extends readonly [
|
|
124
|
+
infer Field extends FormField,
|
|
125
|
+
...infer Rest extends FormField[]
|
|
126
|
+
] ? FormFieldToFormValue<Field> & FormFieldsToFormValues<Rest> : {
|
|
127
|
+
[key: string]: any;
|
|
128
|
+
};
|
|
129
|
+
/** Input is a FormField, output is a {fieldName: fieldType}. */
|
|
130
|
+
type FormFieldToFormValue<T extends FormField> = T extends BooleanField ? {
|
|
131
|
+
[_ in T['name']]: boolean;
|
|
132
|
+
} : T extends ImageField | ParagraphField | StringField ? FormFieldToRequiredFormValue<T, string> : T extends NumberField ? FormFieldToRequiredFormValue<T, number> : T extends SelectField ? {
|
|
133
|
+
[_ in T['name']]: string[];
|
|
134
|
+
} : T extends FormFieldGroup ? FormFieldsToFormValues<T['fields']> : never;
|
|
135
|
+
/**
|
|
136
|
+
* Input is a FormField, output is a {fieldName: fieldType} or
|
|
137
|
+
* {fieldName?: fieldType}.
|
|
138
|
+
*/
|
|
139
|
+
type FormFieldToRequiredFormValue<T extends ImageField | ParagraphField | StringField | NumberField, V> = T extends {
|
|
140
|
+
required: true;
|
|
141
|
+
} | {
|
|
142
|
+
defaultValue: boolean | number | string;
|
|
143
|
+
} ? {
|
|
144
|
+
[_ in T['name']]: V;
|
|
145
|
+
} : {
|
|
146
|
+
[_ in T['name']]?: V;
|
|
147
|
+
};
|
|
148
|
+
export {};
|
|
149
|
+
//# sourceMappingURL=form-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-types.d.ts","sourceRoot":"","sources":["../../../src/effects/helpers/form-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-form-values.d.ts","sourceRoot":"","sources":["../../../src/effects/helpers/get-form-values.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA2BlD,wBAAgB,aAAa,CAAC,OAAO,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAAA;CAAE,GAAG,UAAU,CAMpF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function flattenFormFieldValue(value) {
|
|
2
|
+
switch (value.fieldType) {
|
|
3
|
+
case 0:
|
|
4
|
+
return value.stringValue;
|
|
5
|
+
case 7:
|
|
6
|
+
// the string value is the URL
|
|
7
|
+
return value.stringValue;
|
|
8
|
+
case 1:
|
|
9
|
+
return value.stringValue;
|
|
10
|
+
case 2:
|
|
11
|
+
return value.numberValue;
|
|
12
|
+
case 3:
|
|
13
|
+
return value.boolValue;
|
|
14
|
+
case 5:
|
|
15
|
+
return value.selectionValue?.values ?? [];
|
|
16
|
+
default:
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// This is a carbon copy of the transformFormFields function in the public-api package
|
|
21
|
+
// We copy it here so that @devvit/client does not need to depend on public-api
|
|
22
|
+
// Any changes to this function should be reflected in the public-api version
|
|
23
|
+
export function getFormValues(results) {
|
|
24
|
+
return Object.keys(results).reduce((acc, key) => {
|
|
25
|
+
const val = flattenFormFieldValue(results[key]);
|
|
26
|
+
if (val !== undefined)
|
|
27
|
+
acc[key] = val;
|
|
28
|
+
return acc;
|
|
29
|
+
}, {});
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-form-values.test.d.ts","sourceRoot":"","sources":["../../../src/effects/helpers/get-form-values.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Effect } from '@devvit/protos/types/devvit/ui/effects/v1alpha/effect.js';
|
|
2
|
+
import type { Comment, Form, Post, Subreddit, User } from '@devvit/public-api';
|
|
3
|
+
export declare const mockSubreddit: Readonly<Subreddit>;
|
|
4
|
+
export declare const mockPost: Readonly<Post>;
|
|
5
|
+
export declare const mockComment: Readonly<Comment>;
|
|
6
|
+
export declare const mockUser: Readonly<User>;
|
|
7
|
+
export declare const basicFormDefinition: Readonly<Form>;
|
|
8
|
+
export declare const complexFormDefinition: Readonly<Form>;
|
|
9
|
+
export declare const expectedShowFormMessage: (form: Readonly<Form>) => Effect;
|
|
10
|
+
//# sourceMappingURL=test-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../../src/effects/helpers/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,0DAA0D,CAAC;AACnG,OAAO,KAAK,EACV,OAAO,EACP,IAAI,EAEJ,IAAI,EAGJ,SAAS,EACT,IAAI,EACL,MAAM,oBAAoB,CAAC;AAI5B,eAAO,MAAM,aAAa,qBAmDkB,CAAC;AAE7C,eAAO,MAAM,QAAQ,gBAkCS,CAAC;AAE/B,eAAO,MAAM,WAAW,mBAyBS,CAAC;AAElC,eAAO,MAAM,QAAQ,gBAYS,CAAC;AAI/B,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,IAAI,CAY9C,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,IAAI,CA2BhD,CAAC;AAEF,eAAO,MAAM,uBAAuB,SAAU,SAAS,IAAI,CAAC,KAAG,MAyF7D,CAAC"}
|